diff --git a/src/build/opcode/mod.rs b/src/build/opcode/mod.rs index 83f382c..2981be4 100644 --- a/src/build/opcode/mod.rs +++ b/src/build/opcode/mod.rs @@ -28,6 +28,8 @@ pub use vm::VM; use pointer::OpPointer; use scope::Stack; +use crate::ast::Position; + #[derive(Debug, PartialEq, Clone)] pub enum Primitive { // Primitive Types @@ -146,6 +148,7 @@ pub enum Hook { Convert, Regex, Range, + Trace(Position), } #[derive(Debug, PartialEq, Clone)] diff --git a/src/build/opcode/runtime.rs b/src/build/opcode/runtime.rs index 1a41c4a..1646195 100644 --- a/src/build/opcode/runtime.rs +++ b/src/build/opcode/runtime.rs @@ -12,9 +12,10 @@ // 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::fs::File; -use std::io::Read; +use std::io::{Read, Write}; use std::path::{Path, PathBuf}; use std::rc::Rc; @@ -24,12 +25,14 @@ use super::cache; 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, Str, Int}; +use Primitive::{Bool, Empty, Int, Str}; -pub struct Builtins { +pub struct Builtins { op_cache: cache::Ops, val_cache: BTreeMap>, assert_results: AssertCollector, @@ -37,28 +40,40 @@ pub struct Builtins { importer_registry: ImporterRegistry, working_dir: PathBuf, import_path: Vec, - // TODO(jwall): IO sink for stderr - // TODO(jwall): IO sink for stdout + stdout: Out, + stderr: Err, } -impl Builtins { - pub fn new() -> Self { - Self::with_working_dir(std::env::current_dir().unwrap()) +type ByteSink = Vec; + +impl Builtins { + pub fn new(out: Out, err: Err) -> Self { + Self::with_working_dir(std::env::current_dir().unwrap(), out, err) } - pub fn with_working_dir>(path: P) -> Self { + pub fn with_working_dir>(path: P, out: Out, err: Err) -> 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(), - // TODO(jwall): This should move into the VM and not in the Runtime. + // 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 get_stderr(&self) -> &Err { + &self.stderr + } + pub fn handle>( &mut self, path: P, @@ -76,6 +91,7 @@ impl Builtins { Hook::Reduce => self.reduce(path, stack), Hook::Regex => self.regex(stack), Hook::Range => self.range(stack), + Hook::Trace(pos) => self.trace(stack, pos), } } @@ -488,4 +504,30 @@ impl Builtins { stack.push(Rc::new(C(List(elems)))); Ok(()) } + + fn trace(&mut self, mut stack: &mut Vec>, pos: Position) -> Result<(), Error> { + let val = if let Some(val) = dbg!(stack.pop()) { + val + } else { + return Err(dbg!(Error {})); + }; + let expr = stack.pop(); + let expr_pretty = match expr { + Some(ref expr) => match dbg!(expr.as_ref()) { + &P(Str(ref expr)) => expr.clone(), + _ => return Err(dbg!(Error {})), + }, + _ => return Err(dbg!(Error {})), + }; + let writable_val: Val = TryFrom::try_from(val.clone())?; + if let Err(_) = writeln!( + &mut self.stderr, + "TRACE: {} = {} at {}", + expr_pretty, writable_val, pos + ) { + return Err(dbg!(Error {})); + }; + stack.push(val); + Ok(()) + } } diff --git a/src/build/opcode/test.rs b/src/build/opcode/test.rs index 182347d..8b00402 100644 --- a/src/build/opcode/test.rs +++ b/src/build/opcode/test.rs @@ -754,3 +754,19 @@ fn simple_selects() { "select \"quux\", 3, { foo = 1, bar = 2, };" => P(Int(3)), ]; } + +#[test] +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()); + vm.run().unwrap(); + assert_eq!(vm.pop().unwrap(), Rc::new(P(Int(2)))); + let runtime = vm.get_runtime(); + let err_out = runtime.get_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 658eacd..d4daa15 100644 --- a/src/build/opcode/translate.rs +++ b/src/build/opcode/translate.rs @@ -402,7 +402,15 @@ impl AST { ops.push(Op::Cp); } Expression::Debug(def) => { - unimplemented!("Debug expressions are not implmented yet"); + let mut buffer: Vec = Vec::new(); + { + let mut printer = crate::ast::printer::AstPrinter::new(2, &mut buffer); + let _ = printer.render_expr(&def.expr); + } + let expr_pretty = String::from_utf8(buffer).unwrap(); + ops.push(Op::Val(Primitive::Str(expr_pretty))); + Self::translate_expr(*def.expr, &mut ops); + ops.push(Op::Runtime(Hook::Trace(def.pos))); } } } diff --git a/src/build/opcode/vm.rs b/src/build/opcode/vm.rs index e57a1c1..2ddf25e 100644 --- a/src/build/opcode/vm.rs +++ b/src/build/opcode/vm.rs @@ -11,6 +11,7 @@ // 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; @@ -28,7 +29,8 @@ use super::{Func, Module}; pub struct VM { stack: Vec>, symbols: Stack, - runtime: Rc>, + // FIXME(jwall): This should be parameterized. + runtime: Rc, Vec>>>, ops: OpPointer, // TODO(jwall): This should be optional path: PathBuf, @@ -43,12 +45,16 @@ impl<'a> VM { Self { stack: Vec::new(), symbols: Stack::new(), - runtime: Rc::new(RefCell::new(runtime::Builtins::new())), + runtime: Rc::new(RefCell::new(runtime::Builtins::new(Vec::new(), Vec::new()))), ops: ops, path: path.into(), } } + pub fn get_runtime(&self) -> Ref, Vec>> { + self.runtime.as_ref().borrow() + } + pub fn to_scoped(self, symbols: Stack) -> Self { Self { stack: Vec::new(),