From 8064118490217880e9ca29e5306622cf7f6041b5 Mon Sep 17 00:00:00 2001 From: soup Date: Fri, 31 May 2024 15:25:02 -0400 Subject: [PATCH] Virtual machine execution --- src/internal/mod.rs | 1 + src/internal/vm.rs | 509 ++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 138 +++++------- 3 files changed, 568 insertions(+), 80 deletions(-) create mode 100644 src/internal/vm.rs diff --git a/src/internal/mod.rs b/src/internal/mod.rs index 671a3ea..12dcf0f 100644 --- a/src/internal/mod.rs +++ b/src/internal/mod.rs @@ -5,3 +5,4 @@ pub mod execute; pub mod parse; mod parse_backup; +pub(crate) mod vm; diff --git a/src/internal/vm.rs b/src/internal/vm.rs new file mode 100644 index 0000000..f313be9 --- /dev/null +++ b/src/internal/vm.rs @@ -0,0 +1,509 @@ +use std::fmt::Write; + +use crate::Value; + +use self::ops::{Op, OpRef, Ops}; + +pub(crate) mod ops { + #[cfg(test)] + use crate::go; + + use crate::internal::parse::{AstNodeKind, Path}; + + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub(super) struct OpRef(pub(super) u32); + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub(super) enum Op { + Noop, + Jump(OpRef), + JumpFalse(OpRef), + Exit, + + Emit(String), + + ValueDup, + ValuePop, + ValueLookup(String), + ValueIndex, + ValuePrint, + ValueTruthy, + ValueHas, + + CounterDup, + CounterPop, + CounterIncrement, + } + + #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] + pub(crate) struct Ops(Vec); + impl Ops { + pub(crate) fn new() -> Self { + Self(vec![]) + } + + fn next_ref(&self) -> OpRef { + let next = OpRef( + self.0 + .len() + .try_into() + .expect("Emitted too many instructions"), + ); + + next + } + + fn emit(&mut self, op: Op) -> OpRef { + let next = self.next_ref(); + self.0.push(op); + + next + } + + fn get_mut(&mut self, rf: OpRef) -> &mut Op { + self.0.get_mut(rf.0 as usize).expect("OpRef out of bounds") + } + + pub(super) fn get(&self, rf: OpRef) -> &Op { + self.0.get(rf.0 as usize).expect("OpRef out of bounds") + } + } + + pub(crate) fn lower_ast(ast: &[AstNodeKind]) -> Ops { + let mut ops = Ops::new(); + lower_many(&mut ops, &ast); + ops.emit(Op::Exit); + + ops + } + + fn lower_many(ops: &mut Ops, nodes: &[AstNodeKind]) { + for node in nodes { + lower(ops, node); + } + } + + fn lower(ops: &mut Ops, node: &AstNodeKind) { + match node { + AstNodeKind::Text(p) => { + ops.emit(Op::Emit(p.clone())); + }, + AstNodeKind::Print(p) => { + path_stack(ops, &p.path, |ops| { + ops.emit(Op::ValuePrint); + }); + }, + AstNodeKind::For(f) => path_stack(ops, &f.path, |ops| { + ops.emit(Op::CounterDup); + let dup = ops.emit(Op::ValueDup); + ops.emit(Op::ValueIndex); + let jump_end = ops.emit(Op::Noop); + ops.emit(Op::CounterIncrement); + lower_many(ops, &f.body); + ops.emit(Op::ValuePop); + ops.emit(Op::Jump(dup)); + let counter_pop = ops.emit(Op::CounterPop); + ops.emit(Op::ValuePop); + *ops.get_mut(jump_end) = Op::JumpFalse(counter_pop); + }), + AstNodeKind::With(w) => path_stack(ops, &w.path, |ops| { + lower_many(ops, &w.body); + }), + AstNodeKind::If(i) => { + path_stack(ops, &i.path, |ops| { + ops.emit(Op::ValueTruthy); + }); + + let jump_else = ops.emit(Op::Noop); + lower_many(ops, &i.then); + if let Some(else_) = i.else_.as_ref() { + let jump_end = ops.emit(Op::Noop); + *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); + lower_many(ops, else_); + *ops.get_mut(jump_end) = Op::Jump(ops.next_ref()); + } else { + *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); + }; + }, + AstNodeKind::Has(h) => path_stack(ops, &h.path, |ops| { + ops.emit(Op::ValueHas); + let jump_else = ops.emit(Op::Noop); + lower_many(ops, &h.then); + let next_ref = ops.next_ref(); + *ops.get_mut(jump_else) = Op::JumpFalse(next_ref); + if let Some(else_) = h.else_.as_ref() { + let jump_end = ops.emit(Op::Noop); + *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); + lower_many(ops, else_); + *ops.get_mut(jump_end) = Op::Jump(ops.next_ref()); + } else { + *ops.get_mut(jump_else) = Op::JumpFalse(ops.next_ref()); + }; + }), + } + } + + fn path_stack(ops: &mut Ops, path: &Path, then: impl FnOnce(&mut Ops)) { + if path.0.is_empty() { + then(ops); + } else { + ops.emit(Op::ValueDup); + for path in &path.0 { + ops.emit(Op::ValueLookup(path.clone())); + } + then(ops); + ops.emit(Op::ValuePop); + } + } + + #[cfg(test)] + mod test { + #[macro_export] + macro_rules! go { + ($src:expr, $expected:expr) => { + assert_eq!( + crate::internal::vm::ops::lower_ast( + &crate::internal::parse::parse($src) + ), + Ops($expected), + ) + }; + } + } + + #[test] + fn test_lower_text() { + go!( + "Hello, world!", + vec![Op::Emit("Hello, world!".to_string()), Op::Exit] + ) + } + + #[test] + fn test_lower_print() { + go!("{.}", vec![Op::ValuePrint, Op::Exit]) + } + + #[test] + fn test_lower_print_lookup() { + go!( + "{.test}", + vec![ + Op::ValueDup, + Op::ValueLookup("test".to_string()), + Op::ValuePrint, + Op::ValuePop, + Op::Exit, + ] + ) + } + + #[test] + fn test_lower_for_print_lookup() { + go!( + "{for .test}{.}{/for}", + vec![ + Op::ValueDup, + Op::ValueLookup("test".to_string()), + Op::CounterDup, + Op::ValueDup, + Op::ValueIndex, + Op::JumpFalse(OpRef(10)), + Op::CounterIncrement, + Op::ValuePrint, + Op::ValuePop, + Op::Jump(OpRef(3)), + Op::CounterPop, + Op::ValuePop, + Op::ValuePop, + Op::Exit, + ] + ) + } + + #[test] + fn test_lower_with() { + go!( + "{with .test}{.}{/with}", + vec![ + Op::ValueDup, + Op::ValueLookup("test".to_string()), + Op::ValuePrint, + Op::ValuePop, + Op::Exit, + ] + ) + } + + #[test] + fn test_lower_if() { + go!( + "{if .test}Then{else}Else{/if}", + vec![ + Op::ValueDup, + Op::ValueLookup("test".to_string()), + Op::ValueTruthy, + Op::ValuePop, + Op::JumpFalse(OpRef(7)), + Op::Emit("Then".to_string()), + Op::Jump(OpRef(8)), + Op::Emit("Else".to_string()), + Op::Exit, + ] + ) + } + + #[test] + fn test_lower_if_no_else() { + go!( + "{if .test}Then{/if}", + vec![ + Op::ValueDup, + Op::ValueLookup("test".to_string()), + Op::ValueTruthy, + Op::ValuePop, + Op::JumpFalse(OpRef(6)), + Op::Emit("Then".to_string()), + Op::Exit, + ] + ) + } +} + +#[derive(Debug)] +pub(crate) struct VmState<'a> { + reg_cond: bool, + reg_pc: u32, + reg_value: &'a dyn Value, + reg_counter: usize, + + stack_values: Vec<&'a dyn Value>, + stack_counters: Vec, + + output: String, + + ops: &'a Ops, +} +impl<'a> VmState<'a> { + pub(crate) fn new(ops: &'a Ops, value: &'a dyn Value) -> Self { + Self { + reg_cond: false, + reg_pc: 0, + reg_value: value, + reg_counter: 0, + + stack_values: vec![value], + stack_counters: vec![0], + + output: String::new(), + + ops, + } + } + + pub(crate) fn execute(mut self) -> String { + self.execute_loop(); + self.output + } + + fn execute_loop(&mut self) { + loop { + let should_terminate = self.execute_one(); + if should_terminate { + break; + } + } + } + + fn execute_one(&mut self) -> bool { + let op = self.ops.get(OpRef(self.reg_pc)); + let mut op_offset = 1; + match op { + Op::Exit => return true, + Op::Noop => (), + Op::Jump(r) => { + self.reg_pc = r.0; + op_offset = 0; + }, + Op::JumpFalse(r) => { + if !self.reg_cond { + self.reg_pc = r.0; + op_offset = 0; + } + }, + Op::Emit(s) => { + self.output.push_str(s); + }, + Op::ValueDup => { + self.stack_values.push(self.reg_value); + }, + Op::ValuePop => { + self.reg_value = + self.stack_values.pop().expect("stack_values is empty"); + }, + Op::ValueLookup(field) => { + let new_value = self.reg_value.lookup(&field); + match new_value { + Some(v) => { + self.reg_value = v; + }, + None => { + let _ = write!( + &mut self.output, + "Field `{field}` missing in current value" + ); + }, + } + }, + Op::ValueIndex => { + let value = self.reg_value.for_(self.reg_counter); + if let Some(value) = value { + self.reg_value = value; + self.reg_cond = true; + } else { + self.reg_cond = false; + } + }, + Op::ValuePrint => { + self.reg_value.print(&mut self.output); + }, + Op::ValueTruthy => { + self.reg_cond = self.reg_value.if_(); + }, + Op::ValueHas => { + if let Some(value) = self.reg_value.has() { + self.reg_value = value; + self.reg_cond = true; + } else { + self.reg_cond = false; + } + }, + Op::CounterDup => { + self.stack_counters.push(self.reg_counter); + }, + Op::CounterPop => { + self.reg_counter = + self.stack_counters.pop().expect("stack_counters is empty"); + }, + Op::CounterIncrement => { + self.reg_counter += 1; + }, + }; + + self.reg_pc += op_offset; + false + } +} + +#[cfg(test)] +mod test { + use super::ops::lower_ast; + use super::VmState; + use crate::internal::parse::parse; + use crate::{values, values_map}; + + macro_rules! go { + ($input:expr, $value:expr, $expected:expr) => {{ + let ast = parse($input); + let ops = lower_ast(&ast); + let output = VmState::new(&ops, $value).execute(); + assert_eq!(output, $expected); + }}; + } + + #[test] + fn basic() { + go!("Hello, world!", &(), "Hello, world!"); + } + + #[test] + fn if_else() { + go!("{if .}Hello{else}Goodbye{/if}", &true, "Hello"); + go!("{if .}Hello{else}Goodbye{/if}", &false, "Goodbye"); + } + + #[test] + fn if_else_lookup() { + go!( + "{if .name}Hello, {.name}!{else}Goodbye{/if}", + values_map! { name: "Bob" }, + "Hello, Bob!" + ); + go!( + "{if .name}Hello, {.name}{else}Goodbye{/if}", + values_map! { name: "" }, + "Goodbye" + ); + } + + #[test] + fn print() { + go!("{.}", &"Gorilla", "Gorilla"); + } + + #[test] + fn print_lookup() { + go!("{.name}", values_map! { name: &"Gorilla" }, "Gorilla"); + } + + #[test] + fn has() { + go!( + "{has .name}{.}{else}No name{/has}", + &values_map! { + name: Some("Bob"), + }, + "Bob" + ); + + go!( + "{has .name}{.}{else}No name{/has}", + &values_map! { + name: None::<&str>, + }, + "No name" + ) + } + + #[test] + fn with() { + go!( + "{with .name}{.}{/with}", + &values_map! { + name: "Bob", + }, + "Bob" + ) + } + + #[test] + fn for_() { + go!( + "{for .names}{.}, {/for}", + &values_map! { + names: values!("Bob", "Larry"), + }, + "Bob, Larry, " + ) + } + + #[test] + fn complex_1() { + go!( + r"{for .}{if .}True {else}False {/if}{/for}", + values!(true, false), + "True False " + ) + } + + #[test] + fn complex_2() { + go!( + r"{for .bools}{if .}True {else}False {/if}{/for}", + values_map! { + bools: values!(true, false), + }, + "True False " + ) + } +} diff --git a/src/lib.rs b/src/lib.rs index 4ef8da6..e4784cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,9 +5,13 @@ pub mod internal; use internal::{ execute::execute, parse::{parse, Ast}, + vm::{ + ops::{lower_ast, Ops}, + VmState, + }, }; -pub trait Value { +pub trait Value: core::fmt::Debug { fn print(&self, out: &mut String); fn lookup(&self, name: &str) -> Option<&dyn Value>; fn has(&self) -> Option<&dyn Value>; @@ -202,6 +206,7 @@ where } } +#[derive(Debug)] pub struct ValuesListMap<'a>(pub &'a [(&'a str, &'a dyn Value)]); impl<'a> Value for ValuesListMap<'a> { fn print(&self, w: &mut String) { @@ -327,27 +332,72 @@ where } } +impl Value for () { + fn print(&self, _: &mut String) { + } + + fn lookup(&self, _: &str) -> Option<&dyn Value> { + None + } + + fn has(&self) -> Option<&dyn Value> { + None + } + + fn if_(&self) -> bool { + true + } + + fn for_(&self, _: usize) -> Option<&dyn Value> { + None + } +} + +impl Value for bool { + fn print(&self, out: &mut String) { + let _ = write!(out, "{}", self); + } + + fn lookup(&self, _name: &str) -> Option<&dyn Value> { + None + } + + fn has(&self) -> Option<&dyn Value> { + Some(self) + } + + fn if_(&self) -> bool { + *self + } + + fn for_(&self, _index: usize) -> Option<&dyn Value> { + None + } +} + pub struct Newt { - ast: Ast, + ops: Ops, } impl Newt { pub fn build(tmpl: &str) -> Self { let ast = parse(tmpl); - Self { ast: todo!() } + let ops = lower_ast(&ast); + + Self { ops } } pub fn execute(&self, value: &dyn Value) -> String { - execute(&self.ast, value) + VmState::new(&self.ops, value).execute() } } #[macro_export] -macro_rules! values_list_map { - ($($key:literal: $val:expr),* $(,)?) => {{ +macro_rules! values_map { + ($($key:path: $val:expr),* $(,)?) => {{ &$crate::ValuesListMap( &[ $( - ($key, $val as &dyn Value) + (stringify!($key), &$val as &dyn $crate::Value) )*, ] ) @@ -359,80 +409,8 @@ macro_rules! values { ($($val:expr),* $(,)?) => {{ &[ $( - &$val as &dyn Value, + &$val as &dyn $crate::Value, )* ] }} } - -#[cfg(test)] -mod test { - use crate::{Newt, Value}; - - fn go(tmpl: &str, value: &dyn Value, expected: &str) { - assert_eq!(Newt::build(tmpl).execute(value), expected) - } - - #[test] - fn simple_print() { - go("Hello, {.}!", &"World", "Hello, World!") - } - - #[test] - fn print_lookup() { - go( - "Hello, {.name}!", - values_list_map! { - "name": &"Ted", - }, - "Hello, Ted!", - ); - } - - #[test] - fn print_for() { - go( - "{for .}{.}, {/for}", - values!["Bob", "Larry"], - "Bob, Larry, ", - ); - } - - #[test] - fn print_for_nested() { - go( - "{for .items}{for .names}{.}, {/for}{/for}", - values_list_map! { - "items": values![values_list_map! { - "names": values!["Bob", "Larry", 0], - }] - }, - "Bob, Larry, 0, ", - ) - } - - #[test] - fn print_has() { - go("{has .}{.}{/has}", &Some("Bob"), "Bob"); - } - - #[test] - fn print_has_none() { - go("{has .}{.}{/has}", &None::<&str>, ""); - } - - #[test] - fn print_if() { - go("{if .}{.}{/if}", &"Bob", "Bob"); - } - - #[test] - fn print_if_false() { - go("{if .}{.}{/if}", &"", ""); - } - - #[test] - fn print_if_false_else() { - go("{if .}{.}{else}Frank{/if}", &"", "Frank"); - } -}