use crate::object::{QuoteTable, Value}; use crate::scope::*; use crate::syn::ast::*; use crate::vm::inst::*; 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, 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(exprs) => { self.scope_stack.push_scope(); let compiled = self.compile(exprs); let locals = self.scope_stack.pop_scope().unwrap(); let quote = self .quote_table .insert(expr.span(), locals, exprs.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) => { // XXX : probably something better than this // Check builtins if text == "." { return Inst::Print.into(); } // 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(), } } }