diff --git a/src/build/opcode/mod.rs b/src/build/opcode/mod.rs index 9245010..a624a78 100644 --- a/src/build/opcode/mod.rs +++ b/src/build/opcode/mod.rs @@ -11,10 +11,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::collections::BTreeMap; - pub mod pointer; +pub mod scope; + use pointer::OpPointer; +use scope::Stack; #[derive(Debug, PartialEq, Clone)] pub enum Primitive { @@ -36,6 +37,20 @@ pub enum Composite { use Composite::{List, Tuple}; +#[derive(Debug, PartialEq, Clone)] +pub struct Func { + ptr: usize, + bindings: Vec, + snapshot: Stack, +} + +#[derive(Debug, PartialEq, Clone)] +pub struct Module { + ptr: usize, + result_ptr: Option, + flds: Vec<(String, Value)>, +} + #[derive(Debug, PartialEq, Clone)] pub enum Value { // Binding names. @@ -46,8 +61,12 @@ pub enum Value { C(Composite), // Program Pointer T(usize), + // Function + F(Func), + // Module + M(Module), } -use Value::{C, P, S, T}; +use Value::{C, F, M, P, S, T}; #[derive(Debug, PartialEq, Clone)] pub enum Op { @@ -69,57 +88,74 @@ pub enum Op { Val(Primitive), // A bareword for use in bindings or lookups Sym(String), + // Reference a binding on the heap + DeRef(String), // Complex Type ops InitTuple, Field, InitList, Element, - // Operations + // Copy Operation Cp, // Control Flow Bang, Jump(usize), JumpIfTrue(usize), + // TODO(jwall): Short circuiting operations + // - And(usize) + // - Or(usize) + // Spacer operation, Does nothing. Noop, // Pending Computation InitThunk(usize), + Module(usize), + Func(usize), Return, // - Call + FCall, // Runtime hooks - // - Map - // - Filter - // - Reduce - // - Import - // - Out - // - Assert - // - Print + // - Map, + // - Filter, + // - Reduce, + // - Import, + // - Out, + // - Assert, + // - Print, } #[derive(Debug)] pub struct Error {} -pub struct VM { +pub struct VM<'a> { stack: Vec, - // TODO(jwall): We may want to preserve order on these. - symbols: BTreeMap, - ops: OpPointer, + symbols: Stack, + ops: OpPointer<'a>, } -impl VM { - pub fn new(ops: Vec) -> Self { +impl<'a> VM<'a> { + pub fn new(ops: &'a Vec) -> Self { Self { stack: Vec::new(), - symbols: BTreeMap::new(), + symbols: Stack::new(), ops: OpPointer::new(ops), } } + pub fn to_scoped(&self, symbols: Stack) -> Self { + Self { + stack: Vec::new(), + symbols: symbols, + ops: self.ops.clone(), + } + } + pub fn run(&mut self) -> Result<(), Error> { while self.ops.next().is_some() { let idx = self.ops.ptr.unwrap(); match self.ops.op().unwrap() { Op::Val(p) => self.push(P(p.clone()))?, Op::Sym(s) => self.push(S(s.clone()))?, + Op::DeRef(s) => self.op_deref(s.clone())?, Op::Add => self.op_add()?, Op::Sub => self.op_sub()?, Op::Mul => self.op_mul()?, @@ -137,16 +173,18 @@ impl VM { Op::Field => self.op_field()?, Op::Element => self.op_element()?, Op::Cp => self.op_copy()?, + // TODO(jwall): Should this whould take a user provided message? Op::Bang => return Err(Error {}), Op::InitThunk(jp) => self.op_thunk(idx, *jp)?, Op::Noop => { // Do nothing } - Op::Return => { - // TODO(jwall): This means we return back to the start of the frame. - } Op::Jump(jp) => self.op_jump(*jp)?, Op::JumpIfTrue(jp) => self.op_jump_if_true(*jp)?, + Op::Module(mptr) => self.op_module(idx, *mptr)?, + Op::Func(jptr) => self.op_func(idx, *jptr)?, + Op::FCall => self.op_fcall()?, + Op::Return => return Ok(()), Op::Pop => { self.pop()?; } @@ -155,6 +193,11 @@ impl VM { Ok(()) } + fn op_deref(&mut self, name: String) -> Result<(), Error> { + let val = dbg!(self.get_binding(&name)?.clone()); + self.push(val) + } + fn op_jump(&mut self, jp: usize) -> Result<(), Error> { self.ops.jump(self.ops.ptr.map(|v| v + jp).unwrap_or(jp))?; Ok(()) @@ -169,8 +212,85 @@ impl VM { Ok(()) } + fn op_module(&mut self, idx: usize, jptr: usize) -> Result<(), Error> { + let (result_ptr, flds) = match self.pop()? { + C(Tuple(flds)) => (None, flds), + T(ptr) => { + if let C(Tuple(flds)) = self.pop()? { + (Some(ptr), flds) + } else { + return dbg!(Err(Error {})); + } + } + _ => { + return dbg!(Err(Error {})); + } + }; + self.push(M(Module { + ptr: dbg!(idx), + result_ptr: result_ptr, + flds: dbg!(flds), + }))?; + self.ops.jump(dbg!(jptr)) + } + + fn op_func(&mut self, idx: usize, jptr: usize) -> Result<(), Error> { + // get arity from stack + let mut scope_snapshot = self.symbols.snapshot(); + scope_snapshot.push(); + scope_snapshot.to_open(); + eprintln!("Defining a new function"); + let mut bindings = Vec::new(); + // get imported symbols from stack + if let C(List(elems)) = self.pop()? { + for e in elems { + if let S(sym) = e { + bindings.push(sym); + } else { + return dbg!(Err(Error {})); + } + } + } else { + return dbg!(Err(Error {})); + } + eprintln!("Pushing function definition on stack"); + self.push(dbg!(F(Func { + ptr: idx, // where the function starts. + bindings: bindings, + snapshot: scope_snapshot, + })))?; + eprintln!("Jumping to {} past the function body", jptr); + self.ops.jump(jptr) + } + + fn op_fcall(&mut self) -> Result<(), Error> { + let f = self.pop()?; + if let F(Func { + ptr, + bindings, + snapshot, + }) = f + { + // TODO(jwall): This is wasteful. We can do better. + let mut vm = self.to_scoped(snapshot); + // use the captured scope snapshot for the function. + for nm in bindings { + // now put each argument on our scope stack as a binding. + let val = self.pop()?; + vm.binding_push(nm, val)?; + } + // proceed to the function body + vm.ops.jump(ptr)?; + vm.run()?; + self.push(vm.pop()?)?; + } else { + return dbg!(Err(Error {})); + } + Ok(()) + } + fn op_thunk(&mut self, idx: usize, jp: usize) -> Result<(), Error> { - self.push(T(idx))?; + self.push(dbg!(T(idx)))?; self.op_jump(jp) } @@ -279,9 +399,9 @@ impl VM { fn op_bind(&mut self) -> Result<(), Error> { // pop val off stack. - let val = self.pop()?; + let val = dbg!(self.pop())?; // pop name off stack. - let name = self.pop()?; + let name = dbg!(self.pop())?; if let S(name) = name { self.binding_push(name, val)?; } else { @@ -331,17 +451,57 @@ impl VM { fn op_copy(&mut self) -> Result<(), Error> { // TODO Use Cow pointers for this? - // get next value. It should be a Composite Tuple. - if let C(Tuple(flds)) = self.pop()? { - // Make a copy of the original - let original = Tuple(flds.clone()); - let copy = Tuple(flds); - // Put the original on the Stack as well as the copy - self.push(C(original))?; - self.push(C(copy))?; - } else { - return Err(Error {}); - }; + // get next value. It should be a Module. + let tgt = self.pop()?; + match tgt { + C(Tuple(mut flds)) => { + let overrides = self.pop()?; + if let C(Tuple(oflds)) = overrides { + for (name, val) in oflds { + self.merge_field_into_tuple(&mut flds, name, val)?; + } + } else { + return dbg!(Err(Error {})); + } + // Put the copy on the Stack + self.push(C(Tuple(flds)))?; + } + M(Module { + ptr, + result_ptr, + mut flds, + }) => { + let overrides = dbg!(self.pop()?); + if let C(Tuple(oflds)) = overrides { + for (name, val) in oflds { + self.merge_field_into_tuple(&mut flds, name, val)?; + } + } else { + return dbg!(Err(Error {})); + } + let mut vm = VM::new(self.ops.ops); + vm.push(S("mod".to_owned()))?; + vm.push(C(Tuple(flds)))?; + vm.ops.jump(ptr)?; + vm.run()?; + let mut flds = Vec::new(); + if let Some(ptr) = dbg!(result_ptr) { + vm.ops.jump(ptr)?; + vm.run()?; + self.push(vm.pop()?)?; + } else { + for sym in vm.symbols.symbol_list() { + if sym != "mod" { + flds.push((sym.clone(), vm.symbols.get(sym).unwrap().clone())); + } + } + self.push(dbg!(C(Tuple(flds))))?; + } + } + _ => { + return Err(Error {}); + } + } Ok(()) } @@ -368,7 +528,7 @@ impl VM { fn binding_push(&mut self, name: String, val: Value) -> Result<(), Error> { // FIXME(jwall): Error if the symbol already exists. - self.symbols.insert(name, val); + self.symbols.add(name, val); Ok(()) } diff --git a/src/build/opcode/pointer.rs b/src/build/opcode/pointer.rs index fe2f72d..815c7dd 100644 --- a/src/build/opcode/pointer.rs +++ b/src/build/opcode/pointer.rs @@ -14,13 +14,14 @@ use super::{Error, Op}; -pub struct OpPointer { - ops: Vec, +#[derive(Clone)] +pub struct OpPointer<'a> { + pub ops: &'a Vec, pub ptr: Option, } -impl OpPointer { - pub fn new(ops: Vec) -> Self { +impl<'a> OpPointer<'a> { + pub fn new(ops: &'a Vec) -> Self { // If we load an empty program what happens? Self { ops: ops, @@ -56,4 +57,11 @@ impl OpPointer { } None } + + pub fn snapshot(&self) -> Self { + Self { + ops: self.ops, + ptr: None, + } + } } diff --git a/src/build/opcode/scope.rs b/src/build/opcode/scope.rs new file mode 100644 index 0000000..73e0e6a --- /dev/null +++ b/src/build/opcode/scope.rs @@ -0,0 +1,179 @@ +// Copyright 2019 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::collections::{BTreeMap, BTreeSet}; +use std::iter::FromIterator; + +use super::{Error, Value}; + +#[derive(Clone, PartialEq, Debug)] +pub enum Bindings { + Sealed(BTreeMap), + Open(BTreeMap), +} +use Bindings::{Open, Sealed}; + +impl Bindings { + pub fn new() -> Self { + Sealed(BTreeMap::new()) + } + + pub fn into_open(self) -> Self { + match self { + Open(flds) => Open(flds), + Sealed(flds) => Open(flds), + } + } + + pub fn into_sealed(self) -> Self { + match self { + Open(flds) => Sealed(flds), + Sealed(flds) => Sealed(flds), + } + } + + pub fn get(&self, name: &str) -> Option<&Value> { + match self { + Open(flds) => flds.get(name), + Sealed(flds) => flds.get(name), + } + } + + pub fn add(&mut self, name: String, val: Value) { + match self { + Sealed(flds) => flds.insert(name, val), + Open(flds) => flds.insert(name, val), + }; + } + + pub fn symbol_list(&self) -> Vec<&String> { + match self { + Sealed(flds) => flds.keys().collect(), + Open(flds) => flds.keys().collect(), + } + } +} + +#[derive(Clone, PartialEq, Debug)] +pub struct Stack { + curr: Bindings, + prev: Vec, +} + +impl Stack { + pub fn new() -> Self { + Stack { + curr: Bindings::new(), + prev: Vec::new(), + } + } + + pub fn get(&self, name: &str) -> Option<&Value> { + match &self.curr { + Sealed(flds) => flds.get(name), + Open(flds) => { + if let Some(v) = flds.get(name) { + return Some(v); + } else { + for b in self.prev.iter() { + match b { + Sealed(bflds) => { + if let Some(v) = bflds.get(name) { + return Some(v); + } + return None; + } + Open(bflds) => { + if let Some(v) = bflds.get(name) { + return Some(v); + } + } + } + } + } + return None; + } + } + } + + pub fn push(&mut self) { + let mut nb = Bindings::new(); + std::mem::swap(&mut nb, &mut self.curr); + self.prev.push(nb); + } + + pub fn to_open(&mut self) { + let mut tmp = Bindings::new(); + std::mem::swap(&mut tmp, &mut self.curr); + tmp = tmp.into_open(); + std::mem::swap(&mut tmp, &mut self.curr); + } + + pub fn pop(&mut self) -> Result<(), Error> { + if let Some(parent) = self.prev.pop() { + self.curr = parent; + Ok(()) + } else { + dbg!(Err(Error {})) + } + } + + pub fn add(&mut self, name: String, val: Value) { + self.curr.add(name, val); + } + + pub fn symbol_list(&self) -> Vec<&String> { + match &self.curr { + Sealed(flds) => flds.keys().collect(), + Open(flds) => { + let mut keys = BTreeSet::new(); + keys.extend(flds.keys()); + for b in self.prev.iter() { + match b { + Sealed(bflds) => { + keys.extend(bflds.keys()); + return Vec::from_iter(keys.iter().cloned()); + } + Open(bflds) => { + keys.extend(bflds.keys()); + } + } + } + return Vec::from_iter(keys.iter().cloned()); + } + } + } + + pub fn snapshot(&self) -> Self { + let curr = self.curr.clone(); + match curr { + Sealed(_) => Self { + curr: curr, + prev: Vec::new(), + }, + Open(_) => { + let mut prev = Vec::new(); + for b in self.prev.iter() { + match b { + Sealed(_) => { + prev.push(b.clone()); + break; + } + Open(_) => prev.push(b.clone()), + } + } + Self { curr, prev } + } + } + } +} diff --git a/src/build/opcode/test.rs b/src/build/opcode/test.rs index 8d12fe2..78845e5 100644 --- a/src/build/opcode/test.rs +++ b/src/build/opcode/test.rs @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use super::scope::Stack; use super::Composite::{List, Tuple}; use super::Op::{ - Add, Bind, Cp, Div, Element, Equal, Field, InitList, InitThunk, InitTuple, Jump, JumpIfTrue, - Mul, Noop, Sub, Sym, Val, + Add, Bind, Cp, DeRef, Div, Element, Equal, FCall, Field, Func, InitList, InitThunk, InitTuple, + Jump, JumpIfTrue, Module, Mul, Noop, Return, Sub, Sym, Val, }; use super::Primitive::{Bool, Float, Int, Str}; use super::Value::{C, P, T}; @@ -60,7 +61,7 @@ fn test_math_ops() { ), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } @@ -76,7 +77,7 @@ fn test_bind_op() { )]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); let (name, result) = case.1; let v = vm.get_binding(name).unwrap(); @@ -110,7 +111,7 @@ fn test_list_ops() { ), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } @@ -148,7 +149,6 @@ fn test_tuple_ops() { Val(Str("foo".to_owned())), Val(Int(1)), Field, - Cp, Val(Str("foo".to_owned())), Val(Int(2)), Field, @@ -169,7 +169,6 @@ fn test_tuple_ops() { Val(Str("foo".to_owned())), Val(Int(1)), Field, - Cp, Val(Str("foo".to_owned())), Val(Int(2)), Field, @@ -179,9 +178,31 @@ fn test_tuple_ops() { ("foo".to_owned(), P(Int(2))), ])), ), + ( + vec![ + InitTuple, // Override tuple + Val(Str("foo".to_owned())), + Val(Int(2)), + Field, + InitTuple, // Target tuple + Sym("bar".to_owned()), + Val(Str("ux".to_owned())), + Val(Str("qu".to_owned())), + Add, + Field, + Val(Str("foo".to_owned())), + Val(Int(1)), + Field, + Cp, // Do the tuple copy operation + ], + C(Tuple(vec![ + ("bar".to_owned(), P(Str("quux".to_owned()))), + ("foo".to_owned(), P(Int(2))), + ])), + ), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } @@ -194,7 +215,7 @@ fn test_jump_ops() { (vec![Jump(1), Val(Int(1)), Noop, Val(Int(1))], P(Int(1))), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } @@ -284,7 +305,7 @@ fn test_equality_ops() { ), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } @@ -336,8 +357,141 @@ fn test_conditional_jump_ops() { ), ]; for case in cases.drain(0..) { - let mut vm = VM::new(case.0); + let mut vm = VM::new(&case.0); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), case.1); } } + +#[test] +fn test_function_definition_and_call() { + let mut cases = vec![ + ( + vec![ + Sym("f".to_owned()), // 0 + InitList, // 1 + Sym("arg".to_owned()), // 2 + Element, // 3 + Func(6), // 4 + DeRef("arg".to_owned()), // 5 + Return, // 6 + Bind, // 7 + Val(Int(1)), // 8 + DeRef("f".to_owned()), // 9 + FCall, // 10 + ], + P(Int(1)), + ), + ( + vec![ + Sym("closed".to_owned()), // 0 + Val(Int(1)), // 1 + Bind, // 2 + Sym("f".to_owned()), // 3 + InitList, // 4 + Sym("arg".to_owned()), // 5 + Element, // 6 + Func(11), // 7 + DeRef("arg".to_owned()), // 8 + DeRef("closed".to_owned()), // 9 + Add, // 10 + Return, // 11 + Bind, // 12 + Val(Int(1)), // 13 + DeRef("f".to_owned()), // 14 + FCall, // 16 + ], + P(Int(2)), + ), + ]; + for case in cases.drain(0..) { + let mut vm = VM::new(&case.0); + vm.run().unwrap(); + assert_eq!(vm.pop().unwrap(), case.1); + } +} + +#[test] +fn test_module_call() { + let mut cases = vec![ + ( + vec![ + InitTuple, // 0 // override tuple + Sym("one".to_owned()), // 1 + Val(Int(11)), // 2 + Field, // 3 + Sym("m".to_owned()), // 4 // binding name for module + InitTuple, // 5 // Module tuple bindings + Sym("one".to_owned()), // 6 + Val(Int(1)), // 7 + Field, // 8 + Sym("two".to_owned()), // 9 + Val(Int(2)), // 10 + Field, // 11 + Module(17), // 12 // Module definition + Bind, // 13 + Sym("foo".to_owned()), // 14 + DeRef("mod".to_owned()), // 15 + Bind, // 16 // bind mod tuple to foo + Return, // 17 // end the module + Bind, // 18 // bind module to the binding name + DeRef("m".to_owned()), // 19 + Cp, // 20 + ], + C(Tuple(vec![( + "foo".to_owned(), + C(Tuple(vec![ + ("one".to_owned(), P(Int(11))), + ("two".to_owned(), P(Int(2))), + ])), + )])), + ), + ( + vec![ + InitTuple, // 0 // override tuple + Sym("one".to_owned()), // 1 + Val(Int(11)), // 2 + Field, // 3 + Sym("m".to_owned()), // 4 // binding name for module + InitTuple, // 5 // Module tuple bindings + Sym("one".to_owned()), // 6 + Val(Int(1)), // 7 + Field, // 8 + Sym("two".to_owned()), // 9 + Val(Int(2)), // 10 + Field, // 11 + InitThunk(2), // 12 // Module Return expression + Val(Int(1)), // 13 + Return, // 14 + Module(20), // 15 // Module definition + Bind, // 16 + Sym("foo".to_owned()), // 17 + DeRef("mod".to_owned()), // 18 + Bind, // 19 // bind mod tuple to foo + Return, // 20 // end the module + Bind, // 21 // bind module to the binding name + DeRef("m".to_owned()), // 22 + Cp, // 23 + ], + P(Int(1)), + ), + ]; + for case in cases.drain(0..) { + let mut vm = VM::new(&case.0); + vm.run().unwrap(); + assert_eq!(vm.pop().unwrap(), case.1); + } +} + +#[test] +fn test_scope_stacks() { + let mut stack = Stack::new(); + stack.add("one".to_owned(), P(Int(1))); + let mut val = stack.get("one").unwrap(); + assert_eq!(val, &P(Int(1))); + stack.push(); + assert!(stack.get("one").is_none()); + stack.to_open(); + val = stack.get("one").unwrap(); + assert_eq!(val, &P(Int(1))); +}