use crate::object::{QuoteTable, Value}; use crate::scope::*; use crate::syn::ast::*; use crate::vm::inst::*; use std::rc::Rc; pub struct Compile<'s> { scope_stack: &'s mut ScopeStack, quote_table: &'s mut QuoteTable, } impl<'s> Compile<'s> { pub fn new(scope_stack: &'s mut ScopeStack, quote_table: &'s mut QuoteTable) -> Self { Compile { scope_stack, quote_table, } } pub fn compile(&mut self, stmt_list: Vec) -> Vec { // Compile meta-statements let expr_list = self.compile_meta(stmt_list); self.compile_expr_list(&expr_list) } /// Compile meta-expressions, like includes. fn compile_meta(&mut self, stmt_list: Vec) -> Vec { let mut expr_list = Vec::new(); for stmt in stmt_list { match stmt.into_inner() { Stmt::Include(path) => { let stmt_list = self.parse_include(&path); expr_list.extend(self.compile_meta(stmt_list)); } Stmt::Expr(expr) => expr_list.push(expr), } } expr_list } fn parse_include(&mut self, _path: &str) -> Vec { todo!("includes") } fn compile_expr_list(&mut self, expr_list: &Vec) -> Vec { // Scoping is done here. // Local scopes are implicit. If a variable is assigned to at // all in the current scope, it's considered to be a local. let mut code = Vec::new(); for expr in expr_list { self.discover_locals(&expr); let thunk = self.compile_expr(&expr); code.extend(thunk.flatten()); } code } fn compile_expr(&mut self, expr: &SpExpr) -> Thunk { match expr.inner() { Expr::Atom(atom) => self.compile_atom(atom), // this gets compiled whenever it gets evaluated Expr::Quote(stmts) => { self.scope_stack.push_scope(); // TODO - is self.compile the right thing to do here? let compiled = Rc::new(self.compile(stmts.clone())); let locals = self.scope_stack.pop_scope().unwrap(); let quote = self.quote_table .insert(expr.span().clone(), locals, stmts.clone(), compiled); Inst::PushValue(Value::Quote(quote)).into() } } } fn compile_atom(&mut self, atom: &SpAtom) -> Thunk { match atom.inner() { Atom::Float(f) => Inst::PushValue(Value::Float(*f)).into(), Atom::Int(i) => Inst::PushValue(Value::Int(*i)).into(), Atom::Str(s) => Inst::PushValue(Value::Str(s.clone())).into(), Atom::Assign(text) => { let word = self.scope_stack.insert_local(text); Inst::Store(word).into() } Atom::Word(text) => { // Look for locally defined symbols first. One can't be found, // then create a local variable. The local variable *should* be // defined already, but sometimes things happen. let word = if let Some(word) = self.scope_stack.lookup_scoped(text) { word } else { self.scope_stack.insert_local(text) }; Inst::Load(word).into() } Atom::Apply => Inst::Call.into(), } } /// Discovers and inserts local variables into the scope. /// /// If any assignment for a local variable appears in the scope, it will be /// inserted. fn discover_locals(&mut self, expr: &SpExpr) { if let Expr::Atom(atom) = expr.inner() { if let Atom::Assign(text) = atom.inner() { self.scope_stack.insert_local(text); } } } } #[derive(Debug, Clone)] enum Thunk { Block(Vec), List(Vec), } impl From for Thunk { fn from(inst: Inst) -> Thunk { Thunk::Block(vec![inst]) } } impl Thunk { fn flatten(self) -> Vec { use Thunk::*; match self { Block(block) => block, List(list) => list.into_iter().flat_map(Thunk::flatten).collect(), } } }