diff --git a/src/build/opcode/environment.rs b/src/build/opcode/environment.rs new file mode 100644 index 0000000..de4a8de --- /dev/null +++ b/src/build/opcode/environment.rs @@ -0,0 +1,48 @@ +// 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::io::Write; +use std::rc::Rc; + +use super::cache; +use super::Value; +use crate::convert::{ConverterRegistry, ImporterRegistry}; + +// Shared Environmental between VM's for runtime usage. +pub struct Environment +where + Stdout: Write, + Stderr: Write, +{ + pub val_cache: BTreeMap>, + pub op_cache: cache::Ops, // Shared environment + pub converter_registry: ConverterRegistry, // Shared environment + pub importer_registry: ImporterRegistry, // Shared environment + pub stdout: Stdout, // Shared environment + pub stderr: Stderr, // Shared environment + // TODO(jwall): Environment Variables +} + +impl Environment { + pub fn new(out: Stdout, err: Stderr) -> Self { + Self { + val_cache: BTreeMap::new(), + op_cache: cache::Ops::new(), + converter_registry: ConverterRegistry::make_registry(), + importer_registry: ImporterRegistry::make_registry(), + stdout: out, + stderr: err, + } + } +} diff --git a/src/build/opcode/mod.rs b/src/build/opcode/mod.rs index 2981be4..6618549 100644 --- a/src/build/opcode/mod.rs +++ b/src/build/opcode/mod.rs @@ -15,6 +15,7 @@ use std::convert::{TryFrom, TryInto}; use std::rc::Rc; mod cache; +mod environment; mod error; pub mod pointer; mod runtime; diff --git a/src/build/opcode/runtime.rs b/src/build/opcode/runtime.rs index 1646195..4f14f1f 100644 --- a/src/build/opcode/runtime.rs +++ b/src/build/opcode/runtime.rs @@ -11,87 +11,75 @@ // 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::convert::TryFrom; -use std::convert::TryInto; +use std::cell::RefCell; +use std::convert::{TryFrom, TryInto}; use std::fs::File; -use std::io::{Read, Write}; +use std::io::Read; use std::path::{Path, PathBuf}; use std::rc::Rc; use regex::Regex; -use super::cache; +use super::environment::Environment; use super::Value::{C, F, P}; use super::VM; use super::{Composite, Error, Hook, Primitive, Value}; use crate::ast::Position; use crate::build::ir::Val; use crate::build::AssertCollector; -use crate::convert::{ConverterRegistry, ImporterRegistry}; use Composite::{List, Tuple}; use Primitive::{Bool, Empty, Int, Str}; -pub struct Builtins { - op_cache: cache::Ops, - val_cache: BTreeMap>, +pub struct Builtins { assert_results: AssertCollector, - converter_registry: ConverterRegistry, - importer_registry: ImporterRegistry, working_dir: PathBuf, import_path: Vec, - stdout: Out, - stderr: Err, } -type ByteSink = Vec; - -impl Builtins { - pub fn new(out: Out, err: Err) -> Self { - Self::with_working_dir(std::env::current_dir().unwrap(), out, err) +impl Builtins { + pub fn new() -> Self { + Self::with_working_dir(std::env::current_dir().unwrap()) } - pub fn with_working_dir>(path: P, out: Out, err: Err) -> Self { + pub fn with_working_dir>(path: P) -> Self { Self { - op_cache: cache::Ops::new(), - val_cache: BTreeMap::new(), assert_results: AssertCollector::new(), - converter_registry: ConverterRegistry::make_registry(), - importer_registry: ImporterRegistry::make_registry(), - // FIXME(jwall): This should move into the VM and not in the Runtime. working_dir: path.into(), import_path: Vec::new(), - stdout: out, - stderr: err, } } - pub fn get_stdout(&self) -> &Out { - &self.stdout + pub fn clone(&self) -> Self { + Self { + assert_results: AssertCollector::new(), + working_dir: self.working_dir.clone(), + import_path: self.import_path.clone(), + } } - pub fn get_stderr(&self) -> &Err { - &self.stderr - } - - pub fn handle>( + pub fn handle, O, E>( &mut self, path: P, h: Hook, stack: &mut Vec>, - ) -> Result<(), Error> { + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { match h { - Hook::Import => self.import(stack), - Hook::Include => self.include(stack), + Hook::Import => self.import(stack, env), + Hook::Include => self.include(stack, env), Hook::Assert => self.assert(stack), - Hook::Convert => self.convert(stack), - Hook::Out => self.out(path, stack), - Hook::Map => self.map(path, stack), - Hook::Filter => self.filter(path, stack), - Hook::Reduce => self.reduce(path, stack), + Hook::Convert => self.convert(stack, env), + Hook::Out => self.out(path, stack, env), + Hook::Map => self.map(path, stack, env), + Hook::Filter => self.filter(path, stack, env), + Hook::Reduce => self.reduce(path, stack, env), Hook::Regex => self.regex(stack), Hook::Range => self.range(stack), - Hook::Trace(pos) => self.trace(stack, pos), + Hook::Trace(pos) => self.trace(stack, pos, env), } } @@ -142,21 +130,35 @@ impl Builtins { Ok(contents) } - fn import(&mut self, stack: &mut Vec>) -> Result<(), Error> { + fn import( + &mut self, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { let path = stack.pop(); if let Some(val) = path { if let &Value::P(Str(ref path)) = val.as_ref() { - if self.val_cache.contains_key(path) { - stack.push(self.val_cache[path].clone()); + let mut borrowed_env = env.borrow_mut(); + let val_cache = &mut borrowed_env.val_cache; + if val_cache.contains_key(path) { + stack.push(val_cache[path].clone()); } else { - let op_pointer = self.op_cache.entry(path).get_pointer_or_else(|| { - // FIXME(jwall): import - unimplemented!("Compiling paths are not implemented yet"); - }); - let mut vm = VM::with_pointer(path, op_pointer); + let op_pointer = + env.borrow_mut() + .op_cache + .entry(path) + .get_pointer_or_else(|| { + // FIXME(jwall): import + unimplemented!("Compiling paths are not implemented yet"); + }); + let mut vm = VM::with_pointer(path, op_pointer, env.clone()); vm.run()?; let result = Rc::new(vm.symbols_to_tuple(true)); - self.val_cache.insert(path.clone(), result.clone()); + val_cache.insert(path.clone(), result.clone()); stack.push(result); } return Ok(()); @@ -165,7 +167,15 @@ impl Builtins { return Err(dbg!(Error {})); } - fn include(&self, stack: &mut Vec>) -> Result<(), Error> { + fn include( + &self, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { // TODO(jwall): include let path = stack.pop(); let typ = stack.pop(); @@ -190,21 +200,23 @@ impl Builtins { if typ == "str" { stack.push(Rc::new(P(Str(self.get_file_as_string(&path)?)))); } else { - stack.push(Rc::new(match self.importer_registry.get_importer(&typ) { - Some(importer) => { - let contents = self.get_file_as_string(&path)?; - if contents.len() == 0 { - eprintln!("including an empty file. Use NULL as the result"); - P(Empty) - } else { - match importer.import(contents.as_bytes()) { - Ok(v) => v.try_into()?, - Err(_e) => return Err(dbg!(Error {})), + stack.push(Rc::new( + match env.borrow().importer_registry.get_importer(&typ) { + Some(importer) => { + let contents = self.get_file_as_string(&path)?; + if contents.len() == 0 { + eprintln!("including an empty file. Use NULL as the result"); + P(Empty) + } else { + match importer.import(contents.as_bytes()) { + Ok(v) => v.try_into()?, + Err(_e) => return Err(dbg!(Error {})), + } } } - } - None => return Err(dbg!(Error {})), - })); + None => return Err(dbg!(Error {})), + }, + )); } return Err(dbg!(Error {})); } @@ -243,13 +255,22 @@ impl Builtins { return Ok(()); } - fn out>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + fn out, O, E>( + &self, + path: P, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { let val = stack.pop(); if let Some(val) = val { let val = val.try_into()?; if let Some(c_type_val) = stack.pop() { if let &Value::S(ref c_type) = c_type_val.as_ref() { - if let Some(c) = self.converter_registry.get_converter(c_type) { + if let Some(c) = env.borrow().converter_registry.get_converter(c_type) { match c.convert(Rc::new(val), &mut File::create(path)?) { Ok(_) => { // noop @@ -264,13 +285,21 @@ impl Builtins { return Err(dbg!(Error {})); } - fn convert(&self, stack: &mut Vec>) -> Result<(), Error> { + fn convert( + &self, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { let val = stack.pop(); if let Some(val) = val { let val = val.try_into()?; if let Some(c_type_val) = stack.pop() { if let &Value::S(ref c_type) = c_type_val.as_ref() { - if let Some(c) = self.converter_registry.get_converter(c_type) { + if let Some(c) = env.borrow().converter_registry.get_converter(c_type) { let mut buf: Vec = Vec::new(); match c.convert(Rc::new(val), &mut buf) { Ok(_) => { @@ -289,7 +318,16 @@ impl Builtins { return Err(dbg!(Error {})); } - fn map>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + fn map, O, E>( + &self, + path: P, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { // get the list from the stack let list = if let Some(list) = stack.pop() { list @@ -326,13 +364,27 @@ impl Builtins { // push function argument on the stack. stack.push(e.clone()); // call function and push it's result on the stack. - result_elems.push(VM::fcall_impl(path.as_ref().to_owned(), f, stack)?); + result_elems.push(VM::fcall_impl( + path.as_ref().to_owned(), + f, + stack, + env.clone(), + )?); } stack.push(Rc::new(C(List(result_elems)))); Ok(()) } - fn filter>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + fn filter, O, E>( + &self, + path: P, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { // get the list from the stack let list = if let Some(list) = stack.pop() { list @@ -369,7 +421,7 @@ impl Builtins { // push function argument on the stack. stack.push(e.clone()); // call function and push it's result on the stack. - let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack)?; + let condition = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; // Check for empty or boolean results and only push e back in // if they are non empty and true match condition.as_ref() { @@ -412,7 +464,16 @@ impl Builtins { Ok(()) } - fn reduce>(&self, path: P, stack: &mut Vec>) -> Result<(), Error> { + fn reduce, O, E>( + &self, + path: P, + stack: &mut Vec>, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { // get the list from the stack let list = if let Some(list) = stack.pop() { list @@ -455,7 +516,7 @@ impl Builtins { stack.push(e.clone()); stack.push(acc.clone()); // call function and push it's result on the stack. - acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack)?; + acc = VM::fcall_impl(path.as_ref().to_owned(), f, stack, env.clone())?; // Check for empty or boolean results and only push e back in // if they are non empty and true } @@ -505,7 +566,16 @@ impl Builtins { Ok(()) } - fn trace(&mut self, mut stack: &mut Vec>, pos: Position) -> Result<(), Error> { + fn trace( + &mut self, + stack: &mut Vec>, + pos: Position, + env: Rc>>, + ) -> Result<(), Error> + where + O: std::io::Write, + E: std::io::Write, + { let val = if let Some(val) = dbg!(stack.pop()) { val } else { @@ -521,9 +591,11 @@ impl Builtins { }; let writable_val: Val = TryFrom::try_from(val.clone())?; if let Err(_) = writeln!( - &mut self.stderr, + &mut env.borrow_mut().stderr, "TRACE: {} = {} at {}", - expr_pretty, writable_val, pos + expr_pretty, + writable_val, + pos ) { return Err(dbg!(Error {})); }; diff --git a/src/build/opcode/test.rs b/src/build/opcode/test.rs index 8b00402..73e2abe 100644 --- a/src/build/opcode/test.rs +++ b/src/build/opcode/test.rs @@ -11,8 +11,10 @@ // 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::cell::RefCell; use std::rc::Rc; +use super::environment::Environment; use super::scope::Stack; use super::Composite::{List, Tuple}; use super::Op::{ @@ -27,7 +29,8 @@ use super::VM; macro_rules! assert_cases { (__impl__ $cases:expr) => { for case in $cases.drain(0..) { - let mut vm = VM::new("foo.ucg", Rc::new(case.0)); + let env = Rc::new(RefCell::new(Environment::new(Vec::new(), Vec::new()))); + let mut vm = VM::new("foo.ucg", Rc::new(case.0), env); vm.run().unwrap(); assert_eq!(dbg!(vm.pop()).unwrap(), Rc::new(case.1)); } @@ -116,7 +119,8 @@ fn bind_op() { )]; for case in cases.drain(0..) { - let mut vm = VM::new("bar.ucg", Rc::new(case.0)); + let env = Rc::new(RefCell::new(Environment::new(Vec::new(), Vec::new()))); + let mut vm = VM::new("bar.ucg", Rc::new(case.0), env); vm.run().unwrap(); let (name, result) = case.1; let v = vm.get_binding(name).unwrap(); @@ -577,7 +581,8 @@ macro_rules! assert_parse_cases { // TODO(jwall): preprocessor let ops = Rc::new(translate::AST::translate(stmts)); assert!(ops.len() > 0); - let mut vm = VM::new("foo.ucg", ops.clone()); + let env = Rc::new(RefCell::new(Environment::new(Vec::new(), Vec::new()))); + let mut vm = VM::new("foo.ucg", ops.clone(), env); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), Rc::new(case.1)); } @@ -760,11 +765,11 @@ fn simple_trace() { let stmts = parse(OffsetStrIter::from(dbg!("TRACE 1+1;")), None).unwrap(); let ops = Rc::new(translate::AST::translate(stmts)); assert!(ops.len() > 0); - let mut vm = VM::new("foo.ucg", ops.clone()); + let env = Rc::new(RefCell::new(Environment::new(Vec::new(), Vec::new()))); + let mut vm = VM::new("foo.ucg", ops.clone(), env); vm.run().unwrap(); assert_eq!(vm.pop().unwrap(), Rc::new(P(Int(2)))); - let runtime = vm.get_runtime(); - let err_out = runtime.get_stderr(); + let err_out = &vm.env.borrow().stderr; assert_eq!( String::from_utf8_lossy(err_out).to_owned(), "TRACE: 1 + 1 = 2 at line: 1 column: 1\n" diff --git a/src/build/opcode/translate.rs b/src/build/opcode/translate.rs index d4daa15..2372155 100644 --- a/src/build/opcode/translate.rs +++ b/src/build/opcode/translate.rs @@ -12,10 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. use crate::ast::{BinaryExprType, Expression, FormatArgs, Statement, Value}; -use crate::ast::{FuncOpDef, Position, TemplatePart}; +use crate::ast::{FuncOpDef, TemplatePart}; use crate::build::format::{ExpressionTemplate, SimpleTemplate, TemplateParser}; use crate::build::opcode::Primitive; -use crate::build::opcode::{Func, Hook, Op}; +use crate::build::opcode::{Hook, Op}; pub struct AST(); diff --git a/src/build/opcode/vm.rs b/src/build/opcode/vm.rs index 2ddf25e..c8bb779 100644 --- a/src/build/opcode/vm.rs +++ b/src/build/opcode/vm.rs @@ -11,11 +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::cell::Ref; use std::cell::RefCell; use std::path::PathBuf; use std::rc::Rc; +use super::environment::Environment; use super::pointer::OpPointer; use super::runtime; use super::scope::Stack; @@ -26,35 +26,48 @@ use super::Value::{C, F, M, P, S, T}; use super::{Error, Op, Primitive, Value}; use super::{Func, Module}; -pub struct VM { +pub struct VM +where + O: std::io::Write, + E: std::io::Write, +{ stack: Vec>, symbols: Stack, - // FIXME(jwall): This should be parameterized. - runtime: Rc, Vec>>>, + runtime: runtime::Builtins, ops: OpPointer, // TODO(jwall): This should be optional path: PathBuf, + pub env: Rc>>, } -impl<'a> VM { - pub fn new>(path: P, ops: Rc>) -> Self { - Self::with_pointer(path, OpPointer::new(ops)) +impl<'a, O, E> VM +where + O: std::io::Write, + E: std::io::Write, +{ + pub fn new>( + path: P, + ops: Rc>, + env: Rc>>, + ) -> Self { + Self::with_pointer(path, OpPointer::new(ops), env) } - pub fn with_pointer>(path: P, ops: OpPointer) -> Self { + pub fn with_pointer>( + path: P, + ops: OpPointer, + env: Rc>>, + ) -> Self { Self { stack: Vec::new(), symbols: Stack::new(), - runtime: Rc::new(RefCell::new(runtime::Builtins::new(Vec::new(), Vec::new()))), + runtime: runtime::Builtins::new(), ops: ops, path: path.into(), + env: env, } } - pub fn get_runtime(&self) -> Ref, Vec>> { - self.runtime.as_ref().borrow() - } - pub fn to_scoped(self, symbols: Stack) -> Self { Self { stack: Vec::new(), @@ -62,6 +75,7 @@ impl<'a> VM { runtime: self.runtime.clone(), ops: self.ops.clone(), path: self.path.clone(), + env: self.env.clone(), } } @@ -304,6 +318,7 @@ impl<'a> VM { path: P, f: &Func, stack: &mut Vec>, + env: Rc>>, ) -> Result, Error> { let Func { ref ptr, @@ -311,7 +326,7 @@ impl<'a> VM { ref snapshot, } = f; // use the captured scope snapshot for the function. - let mut vm = Self::with_pointer(path, ptr.clone()).to_scoped(snapshot.clone()); + let mut vm = Self::with_pointer(path, ptr.clone(), env).to_scoped(snapshot.clone()); for nm in bindings.iter() { // now put each argument on our scope stack as a binding. // TODO(jwall): This should do a better error if there is @@ -327,7 +342,8 @@ impl<'a> VM { fn op_new_scope(&mut self, jp: i32, ptr: OpPointer) -> Result<(), Error> { let scope_snapshot = self.symbols.snapshot(); dbg!(&ptr); - let mut vm = Self::with_pointer(&self.path, ptr).to_scoped(scope_snapshot); + let mut vm = + Self::with_pointer(&self.path, ptr, self.env.clone()).to_scoped(scope_snapshot); dbg!(&vm.stack); vm.run()?; dbg!(&vm.stack); @@ -339,7 +355,7 @@ impl<'a> VM { fn op_fcall(&mut self) -> Result<(), Error> { let f = dbg!(self.pop())?; if let &F(ref f) = f.as_ref() { - let val = Self::fcall_impl(&self.path, f, &mut self.stack)?; + let val = Self::fcall_impl(&self.path, f, &mut self.stack, self.env.clone())?; self.push(dbg!(val))?; } Ok(()) @@ -641,7 +657,7 @@ impl<'a> VM { } // FIXME(jwall): We need to populate the pkg key for modules. //self.merge_field_into_tuple(&mut flds, "this".to_owned(), this)?; - let mut vm = Self::with_pointer(self.path.clone(), ptr.clone()); + let mut vm = Self::with_pointer(self.path.clone(), ptr.clone(), self.env.clone()); vm.push(Rc::new(S("mod".to_owned())))?; vm.push(Rc::new(C(Tuple(flds))))?; vm.run()?; @@ -757,8 +773,7 @@ impl<'a> VM { fn op_runtime(&mut self, h: Hook) -> Result<(), Error> { self.runtime - .borrow_mut() - .handle(&self.path, h, &mut self.stack) + .handle(&self.path, h, &mut self.stack, self.env.clone()) } fn op_render(&mut self) -> Result<(), Error> {