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

@@ -1,5 +1,6 @@
use crate::obj::prelude::*;
use shredder::{GcSafe, Scan, Scanner};
use std::collections::BTreeMap;
/// A stack call frame.
#[derive(Debug, Clone)]
@@ -25,20 +26,20 @@ unsafe impl GcSafe for FrameKind {}
#[derive(Scan, Debug, Clone)]
pub struct Frame {
locals: Names,
locals: FrameLocals,
kind: FrameKind,
}
impl Frame {
pub fn new(locals: Names, kind: FrameKind) -> Self {
pub fn new(locals: FrameLocals, kind: FrameKind) -> Self {
Self { locals, kind, }
}
pub fn locals(&self) -> &Names {
pub fn locals(&self) -> &FrameLocals {
&self.locals
}
pub fn locals_mut(&mut self) -> &mut Names {
pub fn locals_mut(&mut self) -> &mut FrameLocals {
&mut self.locals
}
@@ -46,3 +47,5 @@ impl Frame {
&self.kind
}
}
pub type FrameLocals = BTreeMap<usize, ObjRef>;

View File

@@ -10,10 +10,16 @@ pub enum Inst {
PushConst(ConstHandle),
/// Looks up and pushes a local value.
LoadName(Name),
LoadLocal(Name),
/// Looks up and pushes a global value.
LoadGlobal(Name),
/// Pop a value from the stack, possibly into a local symbol.
Pop(Option<Name>),
PopLocal(Option<Name>),
/// Pop a value from the stack, possibly into a global symbol.
PopGlobal(Option<Name>),
/// Pops a symbol value and an object reference.
///
@@ -119,8 +125,10 @@ impl Inst {
match self {
Inst::PushSym(_) => "PUSH_SYM",
Inst::PushConst(_) => "PUSH_CONST",
Inst::LoadName(_) => "LOAD_NAME",
Inst::Pop(_) => "POP",
Inst::LoadLocal(_) => "LOAD_LOCAL",
Inst::LoadGlobal(_) => "LOAD_GLOBAL",
Inst::PopLocal(_) => "POP_LOCAL",
Inst::PopGlobal(_) => "POP_GLOBAL",
Inst::GetAttr(_) => "GET_ATTR",
Inst::SetAttr(_) => "SET_ATTR",
Inst::Jump(_) => "JUMP",

View File

@@ -143,14 +143,16 @@ impl<'p> Vm<'p> {
fn begin_call(&mut self, caller: ObjRef, args: Vec<ObjRef>) {
// create stack frame
let stack_frame = if let Some(user_fun) = std::any::Any::downcast_ref::<UserFunRef>(&caller) {
let names: Names = {
let names: FrameLocals = {
read_obj!(let fun_ref = user_fun);
fun_ref.locals()
.iter()
.enumerate()
.zip(args.into_iter())
.map(|((_, v), arg)| (*v, arg.clone()))
.map(|((index, _), arg)| (index, arg.clone()))
.collect()
};
// TODO : check function arity vs argument count
Frame::new(names, FrameKind::User {
last_pc: self.pc(),
@@ -202,20 +204,31 @@ impl<'p> Vm<'p> {
.clone();
self.push(obj_ref);
}
Inst::LoadName(name) => {
Inst::LoadLocal(local) => {
let obj_ref = self.frames()
.iter()
.rev()
.filter_map(|frame| frame.locals().get(&name))
.filter_map(|frame| frame.locals().get(&local.index()))
.cloned()
.next();
if let Some(obj_ref) = obj_ref {
self.push(obj_ref);
} else {
todo!("TODO: implement \"name value not found\" lookup")
todo!("TODO: implement \"local value not found\" lookup")
}
}
Inst::Pop(name) => {
Inst::LoadGlobal(global) => {
let obj_ref = self.frames()
.first()
.and_then(|frame| frame.locals().get(&global.index()))
.cloned();
if let Some(obj_ref) = obj_ref {
self.push(obj_ref);
} else {
todo!("TODO: implement \"local value not found\" lookup")
}
}
Inst::PopLocal(name) => {
let tos = self.pop()
.expect("stack underflow");
// pop into name
@@ -224,6 +237,15 @@ impl<'p> Vm<'p> {
}
// else discard
}
Inst::PopGlobal(name) => {
let tos = self.pop()
.expect("stack underflow");
// pop into name
if let Some(name) = name {
self.set_global(name, tos);
}
// else discard
}
Inst::GetAttr(sym) => {
let obj_ref = self.pop().expect("getattr object");
read_obj!(let obj = obj_ref);
@@ -281,21 +303,20 @@ impl<'p> Vm<'p> {
}
fn set_local(&mut self, name: Name, value: ObjRef) {
for frame in self.frames.iter_mut().rev() {
let locals = frame.locals_mut();
if locals.contains_key(&name) {
locals.insert(name, value);
return;
}
}
unreachable!("unregistered name {:?}", name);
let frame = self.frame_mut().unwrap();
let locals = frame.locals_mut();
locals.insert(name.index(), value);
}
fn set_global(&mut self, name: Name, value: ObjRef) {
let frame = self.frames.first_mut().unwrap();
let locals = frame.locals_mut();
locals.insert(name.index(), value);
}
fn get_local(&mut self, name: Name) -> Option<ObjRef> {
self.frames.iter()
.rev()
.filter_map(|frame| frame.locals().get(&name))
.next()
self.frame()
.and_then(|frame| frame.locals().get(&name.index()))
.cloned()
}
}

View File

@@ -1,6 +1,5 @@
use crate::{obj::prelude::*, vm::{consts::ConstPool, inst::Inst}};
use shredder::Scan;
use std::io::{self, Write};
/// A compiled package that can be executed by a VM.
#[derive(Scan, Debug)]
@@ -31,74 +30,4 @@ impl Package {
pub fn code(&self) -> &Vec<Inst> {
&self.code
}
/// Dumps a debug output of this package to the given writer.
pub fn dump(&self, writer: &mut dyn Write) -> io::Result<()> {
// column widths
let addr_w = num_digits(self.code().len(), 16).max(4);
let inst_col = 16 - addr_w;
let inst_w = 16;
for (addr, inst) in self.code().iter().enumerate() {
let (param_val, mut param_name) = match inst {
Inst::PushSym(sym) | Inst::GetAttr(sym) | Inst::SetAttr(sym) => (
sym.index().to_string(),
global_sym_lookup(*sym).unwrap().to_string(),
),
Inst::PushConst(hdl) => (
hdl.index().to_string(),
{
let obj_ref = self.const_pool().get(*hdl);
read_obj!(let obj = obj_ref);
format!("{:?}", obj)
},
),
Inst::LoadName(local) => (
local.index().to_string(),
global_sym_lookup(self.names()[local.index()]).unwrap().to_string(),
),
Inst::Pop(local) => {
if let Some(local) = local {
let index = local.index();
let sym = self.names()[index];
let name = global_sym_lookup(sym).unwrap().to_string();
(index.to_string(), name)
} else {
("(discarded)".to_string(), String::new())
}
}
Inst::Jump(addr) | Inst::JumpTrue(addr) => (
addr.to_string(),
String::new(),
),
Inst::Call(argc) => (
argc.to_string(),
String::new(),
),
_ => (String::new(), String::new()),
};
if !param_name.is_empty() {
param_name = format!("({})", param_name);
}
writeln!(
writer,
"{:0addr_w$x}{space: >inst_col$}{: <inst_w$}{: <4} {}",
addr,
inst.name(),
param_val,
param_name,
space = "",
addr_w = addr_w,
inst_w = inst_w,
inst_col = inst_col,
)?;
}
Ok(())
}
}
fn num_digits(n: usize, radix: usize) -> usize {
((n as f64) + 1.0).log(radix as f64).ceil() as usize
}