Update how scope rules work, and update implementation

* Currently, scopes are only allowed to look at their locals and the
  globals. Inner functions cannot refer to values in their parent
  functions. This will change eventually.
* Scope lookup is split between globals and locals. Locals are defined
  in a scope if they are explicitly assigned to.
    * i.e. `a = foo` will treat `a` as a local in the current scope if
      it appears anywhere in that scope. This does not extend to
      setattrs; `a.b = foo` will not trigger `a` into being a local var.
* `Package` objects are no longer returned from the compiler - instead,
  a user function is returned.
* Other various changes and renames

Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
2020-09-27 19:33:18 -07:00
parent 4848a342f0
commit 958a6caabb
12 changed files with 304 additions and 171 deletions

View File

@@ -4,7 +4,7 @@ use crate::{
syn::{ast::*, visit::*},
vm::inst::*,
};
use std::mem;
use std::{collections::BTreeMap, mem};
/// A basic block of VM code.
///
@@ -213,9 +213,7 @@ impl<'c> CompileBody<'c> {
}
pub fn compile(&mut self, body: &'c Body) -> Result<Thunk> {
self.compile.push_scope_layer();
let thunk = self.visit_body(body)?;
self.compile.pop_scope_layer();
Ok(thunk)
}
}
@@ -266,8 +264,12 @@ impl Visit for CompileBody<'_> {
}
LhsExpr::Name(local_name) => {
let sym = global_sym(local_name.to_string());
let local = self.compile.lookup_scope_or_create_global(sym);
thunk = Inst::Pop(Some(local)).into();
if let Some(local) = self.compile.lookup_local(sym) {
thunk = Inst::PopLocal(Some(local)).into();
} else {
let global = self.compile.lookup_global(sym).expect("name expected to exist someplace(?)");
thunk = Inst::PopGlobal(Some(global)).into();
}
}
}
Ok(thunk)
@@ -346,6 +348,12 @@ impl Visit for CompileBody<'_> {
}
fn visit_fun_expr(&mut self, expr: &FunExpr) -> Self::Out {
// TODO(fun) : need captures for functions, built dynamically (or statically?)
// - static is not possible, since captures are *created* at runtime, and there's no
// instruction that will look up just one scope level - it's either locals or globals.
// - an entire "create function" instruction is probably the best way to solve it, don't
// try to be clever, just implement it like that (since I mean, python does too...)
// - push const
// (functions are unique const values so a new function will be created for every literal
// function defined in code)
@@ -357,7 +365,23 @@ impl Visit for CompileBody<'_> {
let sym = global_sym(param.to_string());
self.compile.create_local(sym);
}
let locals = self.compile.pop_scope_layer().unwrap();
// remap (Sym -> Name) to be (Name -> Sym) and make sure it's all in order.
let scope_locals: BTreeMap<_, _> = self.compile.pop_scope_layer()
.unwrap()
.into_iter()
.map(|(sym, name)| (name, sym))
.collect();
// this should be in numeric order since:
// 1. locals are created exactly once or looked up
// 2. scope_locals is a btreemap, keyed by names, which are in order from 0..N
let locals: FunLocals = scope_locals.into_iter()
.enumerate()
.map(|(index, (name, sym))| {
assert_eq!(index, name.index());
sym
})
.collect();
let code = self.visit_body(&expr.body)?
.flatten()
.to_vec();
@@ -373,9 +397,14 @@ impl Visit for CompileBody<'_> {
let thunk = match atom {
Atom::Ident(ident) => {
let sym = global_sym(ident.to_string());
let name = self.compile.lookup_scope_or_create_global(sym);
// get local
Inst::LoadName(name).into()
if let Some(local) = self.compile.lookup_local(sym) {
// get local
Inst::LoadLocal(local).into()
} else {
// get or create global
let global = self.compile.create_global(sym);
Inst::LoadGlobal(global).into()
}
}
Atom::Sym(sym) => {
// push symbol