summaryrefslogtreecommitdiff
path: root/src/ir.rs
blob: eb087754be89be60ba0bf103d44dc48c3bdc3486 (plain)
use rust_decimal::Decimal;

use crate::{
	ast::{Command, Expression, ExpressionSuffix, List, ListItem, Program, Statement},
	tokens::{Token, TokenType},
};

#[derive(Debug, Clone, PartialEq)]
pub enum Ty {
	Any,
	Union(Vec<Ty>),
	Pair(Box<Ty>, Box<Ty>),
	Function {
		args: Vec<Ty>,
		return_ty: Box<Ty>,
	},
	String {
		value: Option<String>,
	},
	Number {
		minimum: Option<Decimal>,
		maximum: Option<Decimal>,
	},
	Atom {
		value: Option<String>,
	},
}

#[derive(Debug, Clone, PartialEq)]
pub struct Register {
	id: usize,
	ty: Ty,
}

#[derive(Debug, Clone, PartialEq)]
pub enum IrStatement {
	LoadImmediateNumber {
		destination: Register,
		immediate: Decimal,
	},
	LoadImmediateString {
		destination: Register,
		immediate: String,
	},
	LoadImmediateAtom {
		destination: Register,
		immediate: String,
	},
	CreateConsPair {
		destination: Register,
		left: Register,
		right: Register,
	},
	Copy {
		destination: Register,
		source: Register,
	},
	EvaluateAtom {
		destination: Register,
		atom: Register,
	},
	CallFunction {
		destination: Register,
		function: Register,
		args: Vec<Register>,
	},
}

pub struct Scope {
	registers: Vec<Register>,
	statements: Vec<IrStatement>,
}

fn new_register(scope: &mut Scope, ty: Ty) -> Register {
	let id = scope.registers.len();
	let register = Register { id, ty };
	scope.registers.push(register.clone());
	register
}

pub fn compile_program(program: &Program) -> Scope {
	let mut scope = Scope {
		registers: Vec::new(),
		statements: Vec::new(),
	};

	for command in &*program.commands {
		compile_command(&mut scope, command);
	}

	scope
}

fn compile_command(scope: &mut Scope, command: &Command) {
	match command {
		Command::Statement(statement) => compile_statement(scope, statement),
		Command::Expression(_) => unimplemented!("top-level lists are not currently supported"),
	};
}

fn compile_statement(scope: &mut Scope, statement: &Statement) {
	let args = statement
		.args
		.iter()
		.map(|expr| compile_expression(scope, expr))
		.collect();

	let function_atom = new_register(scope, Ty::Atom { value: None });
	let function = new_register(scope, Ty::Any);
	let destination = new_register(scope, Ty::Any);

	let TokenType::Identifier(function_name) = &statement.function_token.ty else {
		panic!("expected a function name")
	};

	scope.statements.push(IrStatement::LoadImmediateAtom {
		destination: function_atom.clone(),
		immediate: function_name.to_string(),
	});

	scope.statements.push(IrStatement::EvaluateAtom {
		destination: function.clone(),
		atom: function_atom,
	});

	scope.statements.push(IrStatement::CallFunction {
		destination,
		function,
		args,
	});
}

fn compile_expression(scope: &mut Scope, expression: &Expression) -> Register {
	let expression_suffix = compile_expression_suffix(scope, &expression.suffix);

	if expression.is_quoted() {
		let head = new_register(
			scope,
			Ty::Pair(
				Box::new(Ty::Function {
					args: vec![Ty::Any],
					return_ty: Box::new(Ty::Any),
				}),
				Box::new(Ty::Any),
			),
		);
		let tail = new_register(scope, Ty::Any);
		let quote = new_register(
			scope,
			Ty::Atom {
				value: Some("QUOTE".to_string()),
			},
		);
		let null = new_register(
			scope,
			Ty::Atom {
				value: Some("NIL".to_string()),
			},
		);

		scope.statements.push(IrStatement::LoadImmediateAtom {
			destination: quote.clone(),
			immediate: "QUOTE".to_string(),
		});
		scope.statements.push(IrStatement::LoadImmediateAtom {
			destination: null.clone(),
			immediate: "NIL".to_string(),
		});
		scope.statements.push(IrStatement::CreateConsPair {
			destination: tail.clone(),
			left: expression_suffix,
			right: null,
		});
		scope.statements.push(IrStatement::CreateConsPair {
			destination: head.clone(),
			left: quote,
			right: tail,
		});

		head
	} else {
		expression_suffix
	}
}

fn compile_expression_suffix(scope: &mut Scope, suffix: &ExpressionSuffix) -> Register {
	match suffix {
		ExpressionSuffix::Nothing => panic!("invalid value"),
		ExpressionSuffix::Atom(atom) => compile_atom(scope, atom),
		ExpressionSuffix::List(list) => compile_list(scope, list),
	}
}

fn compile_atom(scope: &mut Scope, suffix: &Token) -> Register {
	match &suffix.ty {
		TokenType::Identifier(atom) => {
			let register = new_register(
				scope,
				Ty::Atom {
					value: Some(atom.to_string()),
				},
			);

			scope.statements.push(IrStatement::LoadImmediateAtom {
				destination: register.clone(),
				immediate: atom.to_string(),
			});

			register
		}
		TokenType::String {
			content,
			terminated: _,
		} => {
			let register = new_register(
				scope,
				Ty::String {
					value: Some(content.to_string()),
				},
			);

			scope.statements.push(IrStatement::LoadImmediateString {
				destination: register.clone(),
				immediate: content.to_string(),
			});

			register
		}
		TokenType::Number(number) => {
			let register = new_register(
				scope,
				Ty::Number {
					minimum: Some(*number),
					maximum: Some(*number),
				},
			);

			scope.statements.push(IrStatement::LoadImmediateNumber {
				destination: register.clone(),
				immediate: *number,
			});

			register
		}
		_ => {
			panic!("expected a string, number, or identifier");
		}
	}
}

fn compile_list(scope: &mut Scope, list: &List) -> Register {
	let contains_dot = list
		.items
		.iter()
		.any(|item| matches!(item, ListItem::Dot(_)));
	let mut head = (!contains_dot).then(|| {
		let register = new_register(
			scope,
			Ty::Atom {
				value: Some("NIL".to_string()),
			},
		);
		scope.statements.push(IrStatement::LoadImmediateAtom {
			destination: register.clone(),
			immediate: "NIL".to_string(),
		});
		register
	});

	for item in list.items.iter().rev() {
		let ListItem::Expression(item) = item else {
			continue;
		};
		let Some(head) = head.as_mut() else {
			head = Some(compile_expression(scope, item));
			continue;
		};

		let expression = compile_expression(scope, item);
		let register = new_register(
			scope,
			Ty::Pair(Box::new(expression.ty.clone()), Box::new(head.ty.clone())),
		);

		scope.statements.push(IrStatement::CreateConsPair {
			destination: register.clone(),
			left: expression,
			right: head.clone(),
		});
		*head = register;
	}

	head.expect("list should be non-empty")
}