summaryrefslogtreecommitdiff
path: root/matrix-lang/src/compiler.rs
diff options
context:
space:
mode:
Diffstat (limited to 'matrix-lang/src/compiler.rs')
-rw-r--r--matrix-lang/src/compiler.rs628
1 files changed, 628 insertions, 0 deletions
diff --git a/matrix-lang/src/compiler.rs b/matrix-lang/src/compiler.rs
new file mode 100644
index 0000000..95c6ccf
--- /dev/null
+++ b/matrix-lang/src/compiler.rs
@@ -0,0 +1,628 @@
+use crate::prelude::*;
+
+use Instruction as I;
+use Value as V;
+use ExprData as E;
+
+pub type NamesTable = Rc<RefCell<Vec<Rc<str>>>>;
+pub type GlobalsTable = Rc<RefCell<Vec<Global>>>;
+
+pub struct CompilerBuilder<'c> {
+ globals: GlobalsTable,
+ names: NamesTable,
+ repl: bool,
+ debug: bool,
+ name: Rc<str>,
+ parent: Option<&'c Compiler<'c>>,
+}
+
+impl<'c> CompilerBuilder<'c> {
+
+ pub fn new() -> Self {
+ Self {
+ globals: Rc::new(RefCell::new(Vec::new())),
+ repl: false,
+ debug: false,
+ name: "<root>".into(),
+ parent: None,
+ names: Rc::new(RefCell::new(Vec::new()))
+ }
+ }
+
+ pub fn repl(mut self, repl: bool) -> Self {
+ self.repl = repl;
+ self
+ }
+
+ pub fn debug(mut self, debug: bool) -> Self {
+ self.debug = debug;
+ self
+ }
+
+ pub fn globals(mut self, globals: GlobalsTable) -> Self {
+ self.globals = globals;
+ self
+ }
+
+ pub fn names(mut self, names: NamesTable) -> Self {
+ self.names = names;
+ self
+ }
+
+ pub fn parent(mut self, parent: &'c Compiler) -> Self {
+ self.parent = Some(parent);
+ self
+ }
+
+ pub fn name(mut self, name: Rc<str>) -> Self {
+ self.name = name;
+ self
+ }
+
+ pub fn build(self) -> Compiler<'c> {
+ Compiler {
+ name: self.name,
+ names: self.names,
+ parent: self.parent,
+ globals: self.globals,
+ repl: self.repl,
+ debug: self.debug,
+ scopes: Vec::new(),
+ locals: Vec::new(),
+ chunk: Chunk::new(),
+ loop_top: Vec::new(),
+ loop_bot: Vec::new(),
+ root_is_block: false,
+ }
+ }
+}
+
+pub struct Compiler<'c> {
+ name: Rc<str>,
+ parent: Option<&'c Compiler<'c>>,
+
+ locals: Vec<Local>,
+ globals: GlobalsTable,
+ names: NamesTable,
+
+ root_is_block: bool,
+
+ scopes: Vec<usize>,
+ chunk: Chunk,
+
+ loop_top: Vec<(usize, usize)>,
+ loop_bot: Vec<usize>,
+
+ repl: bool,
+ debug: bool,
+}
+
+#[derive(Clone)]
+struct Local {
+ name: Rc<str>,
+ idx: usize,
+ scope: usize,
+ is_const: bool,
+}
+
+#[derive(Clone)]
+pub struct Global {
+ pub name: Rc<str>,
+ pub idx: usize,
+ pub is_const: bool,
+}
+
+macro_rules! error {
+ ($($arg:tt)*) => {
+ exception!(COMPILE_EXCEPTION, $($arg)*)
+ };
+}
+
+impl<'c> Compiler<'c> {
+
+ fn child(&'c self, name: Rc<str>) -> Self {
+ CompilerBuilder::new()
+ .name(name)
+ .debug(self.debug)
+ .globals(self.globals.clone())
+ .names(self.names.clone())
+ .repl(false)
+ .parent(self)
+ .build()
+ }
+
+ fn begin_scope(&mut self) {
+ if self.root_is_block {
+ self.root_is_block = false;
+ } else {
+ self.scopes.push(self.locals.len())
+ }
+ }
+
+ fn collapse_scopes(&mut self, top_scope: usize) {
+ let mut cutoff = usize::MAX;
+ while self.scopes.len() > top_scope {
+ cutoff = self.scopes.pop().unwrap()
+ }
+ if cutoff < self.locals.len() {
+ self.emit(Instruction::DiscardLocals((self.locals.len() - cutoff) as u16), self.last_pos());
+ self.locals.truncate(cutoff);
+ };
+ }
+
+ fn end_scope(&mut self) {
+ let Some(cutoff) = self.scopes.pop() else {
+ return;
+ };
+ if cutoff < self.locals.len() {
+ self.emit(Instruction::DiscardLocals((self.locals.len() - cutoff) as u16), self.last_pos());
+ self.locals.truncate(cutoff);
+ };
+ }
+
+ fn create_local(&mut self, name: Rc<str>, is_const: bool) -> Local {
+ let local = Local { name, idx: self.locals.len(), scope: self.scopes.len(), is_const };
+ self.locals.push(local.clone());
+ local
+ }
+
+ fn create_global(&mut self, name: Rc<str>, is_const: bool) -> Global {
+ let global = Global { name, idx: self.globals.borrow().len(), is_const };
+ self.globals.borrow_mut().push(global.clone());
+ global
+ }
+
+ fn create_local_checked(&mut self, name: Rc<str>, is_const: bool, pos: Position) -> Result<Local> {
+ if let Some(local) = self.find_local(&name) {
+ if local.scope == self.scopes.len() {
+ return Err(error!("redefined {name}").pos(pos))
+ }
+ };
+ Ok(self.create_local(name, is_const))
+ }
+
+ fn create_global_checked(&mut self, name: Rc<str>, is_const: bool, pos: Position) -> Result<Global> {
+ if let Some(_) = self.find_global(&name) {
+ return Err(error!("redefined {name}").pos(pos))
+ }
+ Ok(self.create_global(name, is_const))
+ }
+
+ fn find_local(&self, name: &str) -> Option<Local> {
+ for local in self.locals.iter().rev() {
+ if local.name.as_ref() == name {
+ return Some(local.clone())
+ }
+ }
+ None
+ }
+
+ fn find_global(&self, name: &str) -> Option<Global> {
+ if let Some(parent) = self.parent {
+ return parent.find_global(name)
+ }
+ for global in self.globals.borrow().iter() {
+ if global.name.as_ref() == name {
+ return Some(global.clone())
+ }
+ }
+ None
+ }
+
+ fn get_name(&mut self, name: Rc<str>) -> usize {
+ // TODO: find name if already exists
+ let idx = self.names.borrow().len();
+ self.names.borrow_mut().push(name);
+ idx
+ }
+
+ fn emit_const(&mut self, val: Value, pos: Position) {
+ // TODO: find constant if already exists
+ self.chunk.constants.push(val);
+ let c = self.chunk.constants.len() - 1;
+ self.emit(Instruction::Const(c as u16), pos);
+ }
+
+ fn can_make_globals(&self) -> bool {
+ self.repl && self.parent.is_none() && self.scopes.len() == 0
+ }
+
+ fn compile_value(&mut self, val: &Value, pos: Position) {
+ match val {
+ V::Nil => self.emit(I::Nil, pos),
+ V::Bool(b) => if *b { self.emit(I::True, pos) } else { self.emit(I::False, pos) },
+ V::Int(i) => {
+ if let Ok(i) = i16::try_from(*i) {
+ self.emit(I::Int(i), pos);
+ } else {
+ self.emit_const(val.clone(), pos);
+ }
+ },
+ _ => self.emit_const(val.clone(), pos),
+ }
+ }
+
+
+
+ fn finish_loop(&mut self) {
+ self.loop_top.pop();
+ while let Some(tmp) = self.loop_bot.pop() {
+ self.re_emit(I::Jump(self.cur()), tmp as usize);
+ }
+ }
+
+ fn compile_expr(&mut self, expr: &Expr) -> Result<()> {
+ match &expr.data {
+ E::NoOp => self.emit(I::Nil, expr.pos),
+ E::If(cond, ifb, elseb) => {
+ self.compile_expr(cond)?;
+ let jmpidx = self.emit_temp(expr.pos);
+ self.compile_expr(ifb)?;
+ let jmpidx2 = self.emit_temp(expr.pos);
+ self.re_emit(I::JumpFalse(self.cur()), jmpidx);
+ if let Some(elseb) = elseb {
+ self.compile_expr(elseb)?;
+ }
+ self.re_emit(I::Jump(self.cur()), jmpidx2);
+ },
+ E::Function(name, params, body, varadic) => {
+ let chunk = self.compile_function(name.clone(), params, body)?;
+ let arity = params.len() - if *varadic { 1 } else { 0 };
+ let fun = Value::Function(Rc::new(
+ Function {
+ name: name.0.clone(),
+ arity,
+ fun: InnerFunction::Compiled(chunk.into()),
+ variadic: *varadic
+ }
+ ));
+ self.emit_const(fun, expr.pos);
+ self.emit(I::Dup, expr.pos);
+ if self.can_make_globals() {
+ let global = self.create_global_checked(name.0.clone(), false, name.1)?;
+ self.emit(I::StoreGlobal(global.idx as u16), expr.pos);
+ } else {
+ self.create_local_checked(name.0.clone(), false, name.1)?;
+ self.emit(I::CreateLocal, expr.pos);
+ }
+ },
+ E::Lambda(params, body, varadic) => {
+ let name: AstName = (Rc::from("<lambda>"), expr.pos);
+ let chunk = self.compile_function(name.clone(), params, body)?;
+ let arity = params.len() - if *varadic { 1 } else { 0 };
+ let fun = Value::Function(Rc::new(
+ Function {
+ name: name.0.clone(),
+ arity,
+ fun: InnerFunction::Compiled(chunk.into()),
+ variadic: *varadic
+ }
+ ));
+ self.emit_const(fun, expr.pos);
+ },
+ E::Loop(expr) => {
+ let idx = self.cur();
+ self.loop_top.push((idx as usize, self.scopes.len()));
+ self.compile_expr(expr)?;
+ self.emit(I::Discard(1), expr.pos);
+ self.emit(I::Jump(idx), expr.pos);
+ self.finish_loop();
+ self.emit(I::Nil, expr.pos);
+ },
+ E::Try(expr, err, catch) => {
+ let jmpidx = self.emit_temp(expr.pos);
+ self.compile_expr(expr)?;
+ self.emit(I::TryEnd, expr.pos);
+ let jmpidx2 = self.emit_temp(expr.pos);
+ self.re_emit(I::Try(self.cur()), jmpidx);
+ self.begin_scope();
+ self.create_local(err.0.clone(), true);
+ self.emit(I::CreateLocal, err.1);
+ self.compile_expr(catch)?;
+ self.end_scope();
+ self.re_emit(I::Jump(self.cur() as u16), jmpidx2);
+ },
+ E::While(cond, expr) => {
+ let top = self.cur();
+ self.compile_expr(cond)?;
+ let jmpidx = self.emit_temp(expr.pos);
+ self.loop_top.push((top as usize, self.scopes.len()));
+ self.compile_expr(expr)?;
+ self.emit(I::Discard(1), expr.pos);
+ self.emit(I::Jump(top), expr.pos);
+ self.re_emit(I::JumpFalse(self.cur()), jmpidx);
+ self.finish_loop();
+ self.emit(I::Nil, expr.pos);
+ },
+ E::DoWhile(expr, cond) => {
+ let top = self.cur();
+ self.loop_top.push((top as usize, self.scopes.len()));
+ self.compile_expr(expr)?;
+ self.emit(I::Discard(1), expr.pos);
+ self.compile_expr(cond)?;
+ self.emit(I::JumpTrue(top), expr.pos);
+ self.finish_loop();
+ self.emit(I::Nil, expr.pos);
+ },
+ E::For(name, cond, expr) => {
+ self.compile_expr(cond)?;
+ self.emit(I::IterCreate, cond.pos);
+ let top = self.cur();
+ self.emit(I::IterNext, cond.pos);
+ self.emit(I::Dup, expr.pos);
+ let jumpidx = self.emit_temp(expr.pos);
+ self.loop_top.push((top as usize, self.scopes.len()));
+ self.begin_scope();
+ self.create_local(name.0.clone(), true);
+ self.emit(I::CreateLocal, name.1);
+ self.compile_expr(expr)?;
+ self.emit(I::Discard(1), expr.pos);
+ self.end_scope();
+ self.emit(I::Jump(top), expr.pos);
+ self.re_emit(I::JumpNil(self.cur()), jumpidx);
+ self.finish_loop();
+ self.emit(I::Discard(2), expr.pos);
+ self.emit(I::Nil, expr.pos);
+ }
+ E::Block(block) => {
+ if block.len() == 0 {
+ self.emit(I::Nil, expr.pos);
+ return Ok(());
+ }
+ self.begin_scope();
+ for (i, expr) in block.iter().enumerate() {
+ self.compile_expr(expr)?;
+ if i + 1 != block.len() {
+ self.emit(I::Discard(1), expr.pos);
+ }
+ }
+ self.end_scope();
+ },
+ E::Let(name, expr) => {
+ self.compile_expr(expr)?;
+ self.emit(I::Dup, expr.pos);
+ if self.can_make_globals() {
+ let global = self.create_global_checked(name.0.clone(), false, name.1)?;
+ self.emit(I::StoreGlobal(global.idx as u16), expr.pos);
+ } else {
+ self.create_local_checked(name.0.clone(), false, name.1)?;
+ self.emit(I::CreateLocal, expr.pos);
+ }
+ },
+ E::Const(name, expr) => {
+ self.compile_expr(expr)?;
+ self.emit(I::Dup, expr.pos);
+ if self.can_make_globals() {
+ let global = self.create_global_checked(name.0.clone(), true, name.1)?;
+ self.emit(I::StoreGlobal(global.idx as u16), expr.pos);
+ } else {
+ self.create_local_checked(name.0.clone(), true, name.1)?;
+ self.emit(I::CreateLocal, expr.pos);
+ }
+ },
+ E::Continue => {
+ let top = self.loop_top.pop();
+ if let Some((top, scope)) = top {
+ self.collapse_scopes(scope);
+ self.emit(I::Jump(top as u16), expr.pos);
+ } else {
+ return Err(error!("invalid continue outside loop").pos(expr.pos))
+ }
+ },
+ E::Break => {
+ let top = self.loop_top.pop();
+ if let Some((_, scope)) = top {
+ self.collapse_scopes(scope);
+ let tmpidx = self.emit_temp(expr.pos);
+ self.loop_bot.push(tmpidx);
+ } else {
+ return Err(error!("invalid break outside loop").pos(expr.pos))
+ }
+ },
+ E::Return(expr) => {
+ self.compile_expr(expr)?;
+ self.emit(I::Return, expr.pos);
+ },
+ E::Literal(val) => self.compile_value(val, expr.pos),
+ E::Ident(name) => {
+ if name.as_ref() == "_" {
+ self.emit(I::Nil, expr.pos);
+ } else if let Some(local) = self.find_local(name) {
+ self.emit(I::LoadLocal(local.idx as u16), expr.pos);
+ } else if let Some(global) = self.find_global(name) {
+ self.emit(I::LoadGlobal(global.idx as u16), expr.pos);
+ } else {
+ return Err(error!("variable '{name}' is undefined").pos(expr.pos))
+ };
+ },
+ E::Assign(lhs, rhs) => {
+ self.compile_expr(rhs)?;
+ self.emit(I::Dup, rhs.pos);
+ match &lhs.data {
+ E::Ident(name) if name.as_ref() != "_" => {
+ if let Some(local) = self.find_local(&name) {
+ if local.is_const {
+ return Err(error!("cannot assign to const '{name}'").pos(lhs.pos))
+ }
+ self.emit(I::StoreLocal(local.idx as u16), lhs.pos);
+ } else if let Some(global) = self.find_global(&name) {
+ if global.is_const {
+ return Err(error!("cannot assign to const '{name}'").pos(lhs.pos))
+ }
+ self.emit(I::StoreGlobal(global.idx as u16), lhs.pos);
+ } else if self.can_make_globals() {
+ let global = self.create_global_checked(name.clone(), false, lhs.pos)?;
+ self.emit(I::StoreGlobal(global.idx as u16), lhs.pos);
+ } else {
+ self.create_local_checked(name.clone(), false, lhs.pos)?;
+ self.emit(I::CreateLocal, lhs.pos);
+ }
+ },
+ E::Index(expr, params) => {
+ self.compile_expr(expr)?;
+ for param in params {
+ self.compile_expr(param)?;
+ }
+ self.emit(I::StoreIndex(params.len() as u8), expr.pos);
+ },
+ E::FieldAccess(expr, ident) => {
+ self.compile_expr(expr)?;
+ let name = self.get_name(ident.0.clone());
+ self.emit(I::StoreField(name as u16), expr.pos);
+ }
+ _ => return Err(error!("assignment to {lhs} is not allowed").pos(lhs.pos))
+ }
+ }
+ E::UnaryOp(expr, op) => {
+ self.compile_expr(expr)?;
+ self.emit(I::UnaryOp(*op), expr.pos);
+ },
+ E::BinaryOp(lhs, rhs, op) => {
+ self.compile_expr(lhs)?;
+ self.compile_expr(rhs)?;
+ self.emit(I::BinaryOp(*op), lhs.pos);
+ },
+ E::Index(expr, params) => {
+ self.compile_expr(expr)?;
+ for param in params {
+ self.compile_expr(param)?;
+ }
+ self.emit(I::Index(params.len() as u8), expr.pos)
+ },
+ E::FnCall(fun, params) => {
+ for expr in params {
+ self.compile_expr(expr)?;
+ }
+ self.compile_expr(fun)?;
+ self.emit(I::Call(params.len() as u8), expr.pos);
+ },
+ E::FieldAccess(expr, field) => {
+ self.compile_expr(expr)?;
+ let idx = self.get_name(field.0.clone());
+ self.emit(I::Field(idx as u16), expr.pos)
+ }
+ E::List(list) => {
+ for expr in list {
+ self.compile_expr(expr)?;
+ }
+ self.emit(I::NewList(list.len() as u16), expr.pos);
+ },
+ E::Matrix(mat) => {
+ for expr in &mat.2 {
+ self.compile_expr(expr)?;
+ }
+ self.emit(I::NewMatrix(mat.2.len() as u16, mat.0 as u8), expr.pos);
+ },
+ E::Table(table) => {
+ for (key, value) in table {
+ self.compile_expr(key)?;
+ self.compile_expr(value)?;
+ }
+ self.emit(I::NewTable(table.len() as u16), expr.pos);
+ },
+ E::And(lhs, rhs) => {
+ self.compile_expr(lhs)?;
+ self.emit(I::Dup, lhs.pos);
+ let jmpidx = self.emit_temp(lhs.pos);
+ self.compile_expr(rhs)?;
+ self.re_emit(I::JumpFalse(self.cur()), jmpidx);
+ },
+ E::Or(lhs, rhs) => {
+ self.compile_expr(lhs)?;
+ self.emit(I::Dup, lhs.pos);
+ let jmpidx = self.emit_temp(lhs.pos);
+ self.compile_expr(rhs)?;
+ self.re_emit(I::JumpTrue(self.cur()), jmpidx);
+ },
+ E::Pipeline(lhs, rhs) => {
+ self.compile_expr(lhs)?;
+ self.compile_expr(rhs)?;
+ self.emit(I::Call(1), expr.pos);
+ }
+ };
+ Ok(())
+ }
+
+ fn compile_function(&mut self, name: AstName, params: &Vec<AstName>, body: &Box<Expr>) -> Result<Chunk> {
+ let mut compiler = self.child(name.0);
+ for (name, pos) in params {
+ compiler.create_local(name.clone(), false);
+ compiler.emit(I::CreateLocal, *pos);
+ }
+ compiler.compile_expr(body)?;
+ compiler.finish(name.1)?;
+ Ok(compiler.chunk)
+ }
+
+ fn cur(&self) -> u16 {
+ self.chunk.code.len() as u16
+ }
+
+ fn emit_temp(&mut self, pos: Position) -> usize {
+ let idx = self.chunk.code.len();
+ self.emit(Instruction::NoOp, pos);
+ idx
+ }
+
+ fn emit(&mut self, ins: Instruction, pos: Position) {
+ //println!("{}: {ins}", self.name);
+ self.chunk.code.push(ins);
+ self.chunk.pos.push(pos);
+ }
+
+ fn re_emit(&mut self, ins: Instruction, idx: usize) {
+ //println!("{} at {}: {ins}", self.name, &self.chunk.code[idx]);
+ self.chunk.code[idx] = ins;
+ }
+
+ fn last_pos(&self) -> Position {
+ if let Some(pos) = self.chunk.pos.last() {
+ *pos
+ } else {
+ Position::default()
+ }
+ }
+
+ fn finish(&mut self, pos: Position) -> Result<()> {
+ let ins = match self.chunk.code.last() {
+ Some(ins) => ins.clone(),
+ None => {
+ self.emit(I::Nil, pos);
+ self.emit(I::Return, pos);
+ if self.debug {
+ println!("{}\n{}", self.name, self.chunk);
+ }
+ return Ok(())
+ }
+ };
+ match ins {
+ I::Return => {},
+ _ => {
+ self.emit(I::Return, pos);
+ }
+ };
+
+ if self.loop_bot.len() > 0 {
+ return Err(error!("invalid break outside loop").pos(pos))
+ }
+
+ if self.debug {
+ println!("{}\n{}", self.name, self.chunk);
+ }
+ Ok(())
+ }
+
+ pub fn compile(
+ &mut self,
+ body: &Expr,
+ ) -> Result<Rc<Function>> {
+ if let ExprData::Block(_) = &body.data {
+ self.root_is_block = true;
+ }
+ self.chunk = Chunk::new();
+ self.compile_expr(body)?;
+ self.finish(self.last_pos())?;
+ let fun = Function { name: self.name.clone(), fun: InnerFunction::Compiled(self.chunk.clone().into()), arity: 0, variadic: false };
+ Ok(Rc::new(fun))
+ }
+}