From d4b7bdcd461b68cf2002364190a77c97f60febba Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 26 Jun 2019 18:35:29 -0500 Subject: [PATCH] DEV: Basic primitives and operations on them in a VM Stack Machine. --- src/build/bytecode/mod.rs | 395 +++++++++++++++++++++++++++++++++++++ src/build/bytecode/test.rs | 82 ++++++++ src/build/mod.rs | 1 + 3 files changed, 478 insertions(+) create mode 100644 src/build/bytecode/mod.rs create mode 100644 src/build/bytecode/test.rs diff --git a/src/build/bytecode/mod.rs b/src/build/bytecode/mod.rs new file mode 100644 index 0000000..af6cc15 --- /dev/null +++ b/src/build/bytecode/mod.rs @@ -0,0 +1,395 @@ +// 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; +use std::rc::Rc; + +use crate::build::ir::Val; + +#[derive(Debug, PartialEq)] +pub enum Primitive { + // Primitive Types + Int(i64), + Float(f64), + Str(String), + Bool(bool), + Empty, +} + +#[derive(Debug, PartialEq)] +pub enum Composite { + List(Vec), + Tuple(Vec<(String, Val)>), + Thunk(Frame), +} + +#[derive(Debug, PartialEq)] +pub enum Value { + // Binding names. + S(String), + // Primitive Types + P(Primitive), + // Composite Types. + C(Composite), +} + +#[derive(Debug, PartialEq)] +pub enum Op { + // Stack and Name manipulation. + Bind, // Bind a Val to a name in the heap + Pop, // Pop a Value off the value stack and discard it. + // Math ops + Add, + Sub, + Div, + Mul, + // Primitive Types ops + Val(Primitive), + // A bareword for use in bindings or lookups + Sym(String), + // Complex Type ops + InitTuple, + FIELD, + InitList, + Element, + // Operations + Cp, + // Push a new frame on the FrameStack + InitFunc, + InitMod, + EndFrame, + // - Call + // Functional operations + // - Map + // - Filter + // - Reduce +} + +pub struct Heap {} + +#[derive(Debug)] +pub struct Error {} + +/// The type of Frame environment this is +#[derive(Debug, PartialEq)] +pub enum FrameType { + Lib, + Func, + Module, +} + +#[derive(Debug, PartialEq)] +pub struct Frame { + names: BTreeMap, + stack: Vec, + typ: FrameType, +} + +/// Frames represent a functional computation environment on the stack. +/// Frames in the UCG interpreter are hermetic. They can't see their parent. +impl Frame { + fn pop(&mut self) -> Result { + match self.stack.pop() { + Some(p) => Ok(p), + None => Err(Error {}), + } + } + + fn push(&mut self, val: Value) { + self.stack.push(val); + } + + fn push_primitive(&mut self, p: Primitive) { + self.stack.push(Value::P(p)); + } + + fn push_composite(&mut self, c: Composite) { + self.stack.push(Value::C(c)); + } + + fn push_binding(&mut self, name: String, val: Value) { + self.names.insert(name, val); + } + + fn get_binding(&mut self, name: &str) -> Result<&Value, Error> { + self.names.get(name).ok_or(Error {}) + } +} + +pub struct VM { + stack: Vec, +} + +impl VM { + pub fn new() -> Self { + Self { stack: Vec::new() } + } + + pub fn run(&mut self, op_stream: I) -> Result<(), Error> + where + I: Iterator, + { + // Init our first stack frame + self.push_stack(FrameType::Lib); + for op in op_stream { + match op { + Op::Val(p) => { + self.primitive_push(p)?; + } + Op::Sym(s) => { + self.push(Value::S(s))?; + } + Op::Add => { + // Adds the previous two items in the stack. + let left = self.pop()?; + let right = self.pop()?; + // Then pushes the result onto the stack. + self.primitive_push(self.add(left, right)?)?; + } + Op::Sub => { + // Subtracts the previous two items in the stack. + let left = self.pop()?; + let right = self.pop()?; + // Then pushes the result onto the stack. + self.primitive_push(self.sub(left, right)?)?; + } + Op::Mul => { + // Multiplies the previous two items in the stack. + let left = self.pop()?; + let right = self.pop()?; + // Then pushes the result onto the stack. + self.primitive_push(self.mul(left, right)?)?; + } + Op::Div => { + // Divides the previous two items in the stack. + let left = self.pop()?; + let right = self.pop()?; + // Then pushes the result onto the stack. + self.primitive_push(self.div(left, right)?)?; + } + Op::Bind => { + // pop val off stack. + let val = self.pop()?; + // pop name off stack. + let name = self.pop()?; + if let Value::S(name) = name { + self.binding_push(name, val)?; + } else { + return Err(Error {}); + } + } + Op::InitList => { + // Add a Composite list value to the stack + self.composite_push(Composite::List(Vec::new()))?; + } + Op::InitTuple => { + // Add a composite tuple value to the stack + self.composite_push(Composite::Tuple(Vec::new()))?; + } + Op::FIELD => { + // Add a Composite field value to a tuple on the stack + // get value from stack + let val = self.pop()?; + // get name from stack. + let name = if let Value::S(s) | Value::P(Primitive::Str(s)) = self.pop()? { + s + } else { + return Err(Error {}); + }; + // get composite tuple from stack + let tpl = self.pop()?; + if let Value::C(Composite::Tuple(mut flds)) = tpl { + // FIXME(jwall): We need to reuse the field merging logic + // from the ast walker version. + // add name and value to tuple + flds.push((name, self.to_val(val)?)); + // place composite tuple back on stack + self.composite_push(Composite::Tuple(flds))?; + } else { + return Err(Error {}); + }; + } + Op::Element => { + // get element from stack. + let val = self.pop()?; + // get next value. It should be a Composite list. + let lst = self.pop()?; + if let Value::C(Composite::List(mut elems)) = lst { + // add value to list + elems.push(self.to_val(val)?); + // Add that value to the list and put list back on stack. + self.composite_push(Composite::List(elems))?; + } else { + return Err(Error {}); + }; + } + Op::Cp => { + // get next value. It should be a Composite Tuple. + if let Value::C(Composite::Tuple(flds)) = self.pop()? { + // Make a copy of the original + let original = Composite::Tuple(flds.clone()); + // FIXME(jwall): We need to reuse the field merging logic + // from the ast walker version. + let copy = Composite::Tuple(flds); + // Put the original on the Stack as well as the original + self.composite_push(original)?; + self.composite_push(copy)?; + } else { + return Err(Error {}); + }; + } + Op::InitFunc => { + self.push_stack(FrameType::Func); + } + Op::InitMod => { + self.push_stack(FrameType::Module); + } + Op::EndFrame => { + // TODO(jwall): We probably want to push this frame onto the stack + // somehow. + self.pop_stack(); + } + Op::Pop => { + self.pop()?; + } + } + } + Ok(()) + } + + fn push_stack(&mut self, typ: FrameType) { + self.stack.push(Frame { + names: BTreeMap::new(), + stack: Vec::new(), + typ: typ, + }); + } + + fn pop_stack(&mut self) -> Option { + self.stack.pop() + } + + fn to_val(&self, p: Value) -> Result { + Ok(match p { + Value::P(Primitive::Int(i)) => Val::Int(i), + Value::P(Primitive::Float(f)) => Val::Float(f), + Value::P(Primitive::Str(s)) => Val::Str(s), + Value::P(Primitive::Bool(b)) => Val::Boolean(b), + Value::P(Primitive::Empty) => Val::Empty, + Value::C(Composite::List(mut elems)) => { + Val::List(elems.drain(0..).map(|v| Rc::new(v)).collect()) + } + Value::C(Composite::Tuple(mut flds)) => Val::Tuple( + flds.drain(0..) + .map(|(name, val)| (name, Rc::new(val))) + .collect(), + ), + Value::S(_) => return Err(Error {}), + Value::C(Composite::Thunk(_)) => { + // TODO(jwall): This is either a function or a Module + Val::Empty + } + }) + } + + fn push(&mut self, p: Value) -> Result<(), Error> { + match self.stack.first_mut() { + Some(f) => return Ok(f.push(p)), + None => return Err(Error {}), + }; + } + + fn primitive_push(&mut self, p: Primitive) -> Result<(), Error> { + match self.stack.first_mut() { + Some(f) => return Ok(f.push_primitive(p)), + None => return Err(Error {}), + }; + } + + fn composite_push(&mut self, c: Composite) -> Result<(), Error> { + match self.stack.first_mut() { + Some(f) => return Ok(f.push_composite(c)), + None => return Err(Error {}), + }; + } + + fn binding_push(&mut self, name: String, val: Value) -> Result<(), Error> { + match self.stack.first_mut() { + Some(f) => return Ok(f.push_binding(name, val)), + None => return Err(Error {}), + } + } + + pub fn get_binding(&mut self, name: &str) -> Result<&Value, Error> { + match self.stack.first_mut() { + Some(f) => return Ok(f.get_binding(name)?), + None => return Err(Error {}), + } + } + + fn pop(&mut self) -> Result { + match self.stack.first_mut() { + Some(f) => f.pop(), + None => Err(Error {}), + } + } + + fn mul(&self, left: Value, right: Value) -> Result { + Ok(match (left, right) { + (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i * ii), + (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => { + Primitive::Float(f * ff) + } + _ => return Err(Error {}), + }) + } + + fn div(&self, left: Value, right: Value) -> Result { + Ok(match (left, right) { + (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i / ii), + (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => { + Primitive::Float(f / ff) + } + _ => return Err(Error {}), + }) + } + + fn sub(&self, left: Value, right: Value) -> Result { + Ok(match (left, right) { + (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i - ii), + (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => { + Primitive::Float(f - ff) + } + _ => return Err(Error {}), + }) + } + + fn add(&self, left: Value, right: Value) -> Result { + Ok(match (left, right) { + (Value::P(Primitive::Int(i)), Value::P(Primitive::Int(ii))) => Primitive::Int(i + ii), + (Value::P(Primitive::Float(f)), Value::P(Primitive::Float(ff))) => { + Primitive::Float(f + ff) + } + (Value::P(Primitive::Str(s)), Value::P(Primitive::Str(ss))) => { + let mut ns = String::new(); + ns.push_str(&s); + ns.push_str(&ss); + Primitive::Str(ns) + } + _ => return Err(Error {}), + }) + } +} + +#[cfg(test)] +mod test; diff --git a/src/build/bytecode/test.rs b/src/build/bytecode/test.rs new file mode 100644 index 0000000..38354bc --- /dev/null +++ b/src/build/bytecode/test.rs @@ -0,0 +1,82 @@ +// 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 super::Op::{Add, Bind, Div, Mul, Sub, Sym, Val}; +use super::Primitive::{Float, Int, Str}; +use super::Value::P; +use super::VM; + +#[test] +fn test_math_ops() { + let mut cases = vec![ + // 1+1; + (vec![Val(Int(1)), Val(Int(1)), Add], P(Int(2))), + // 1-1; + (vec![Val(Int(1)), Val(Int(1)), Sub], P(Int(0))), + // 2*2; + (vec![Val(Int(2)), Val(Int(2)), Mul], P(Int(4))), + // 6/3; + (vec![Val(Int(2)), Val(Int(6)), Div], P(Int(3))), + // 1.0+1.0; + (vec![Val(Float(1.0)), Val(Float(1.0)), Add], P(Float(2.0))), + // 1.0-1.0; + (vec![Val(Float(1.0)), Val(Float(1.0)), Sub], P(Float(0.0))), + // 2.0*2.0; + (vec![Val(Float(2.0)), Val(Float(2.0)), Mul], P(Float(4.0))), + // 6.0/3.0; + (vec![Val(Float(2.0)), Val(Float(6.0)), Div], P(Float(3.0))), + // string concatenation + ( + vec![Val(Str("bar".to_owned())), Val(Str("foo".to_owned())), Add], + P(Str("foobar".to_owned())), + ), + // Composite operations + ( + vec![ + Val(Int(1)), + Val(Int(1)), + Add, // 1 + 1 + Val(Int(1)), + Add, // 2 + 1 + Val(Int(1)), + Add, // 3 + 1 + ], + P(Int(4)), + ), + ]; + let mut vm = VM::new(); + + for mut case in cases.drain(0..) { + vm.run(case.0.drain(0..)).unwrap(); + assert_eq!(vm.pop().unwrap(), case.1); + } +} + +#[test] +fn test_bind_op() { + let mut cases = vec![( + vec![Sym("foo".to_owned()), Val(Int(1)), Bind], + ("foo", P(Int(1))), + vec![Sym("foo".to_owned()), Val(Int(1)), Val(Int(1)), Add, Bind], + ("foo", P(Int(2))), + )]; + + let mut vm = VM::new(); + for mut case in cases.drain(0..) { + vm.run(case.0.drain(0..)).unwrap(); + let (name, result) = case.1; + let v = vm.get_binding(&name).unwrap(); + assert_eq!(&result, v); + } +} diff --git a/src/build/mod.rs b/src/build/mod.rs index f238479..83c4f46 100644 --- a/src/build/mod.rs +++ b/src/build/mod.rs @@ -39,6 +39,7 @@ use crate::iter::OffsetStrIter; use crate::parse::parse; pub mod assets; +pub mod bytecode; pub mod format; pub mod ir; pub mod scope;