DEV: Basic primitives and operations on them in a VM Stack Machine.

This commit is contained in:
Jeremy Wall 2019-06-26 18:35:29 -05:00
parent acffc6d5da
commit d4b7bdcd46
3 changed files with 478 additions and 0 deletions

395
src/build/bytecode/mod.rs Normal file
View File

@ -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<Val>),
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<String, Value>,
stack: Vec<Value>,
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<Value, Error> {
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<Frame>,
}
impl VM {
pub fn new() -> Self {
Self { stack: Vec::new() }
}
pub fn run<I>(&mut self, op_stream: I) -> Result<(), Error>
where
I: Iterator<Item = Op>,
{
// 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<Frame> {
self.stack.pop()
}
fn to_val(&self, p: Value) -> Result<Val, Error> {
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<Value, Error> {
match self.stack.first_mut() {
Some(f) => f.pop(),
None => Err(Error {}),
}
}
fn mul(&self, left: Value, right: Value) -> Result<Primitive, Error> {
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<Primitive, Error> {
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<Primitive, Error> {
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<Primitive, Error> {
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;

View File

@ -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);
}
}

View File

@ -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;