Virtual machine execution
This commit is contained in:
parent
6e0a7375c0
commit
8064118490
|
|
@ -5,3 +5,4 @@
|
||||||
pub mod execute;
|
pub mod execute;
|
||||||
pub mod parse;
|
pub mod parse;
|
||||||
mod parse_backup;
|
mod parse_backup;
|
||||||
|
pub(crate) mod vm;
|
||||||
|
|
|
||||||
509
src/internal/vm.rs
Normal file
509
src/internal/vm.rs
Normal file
|
|
@ -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<Op>);
|
||||||
|
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<usize>,
|
||||||
|
|
||||||
|
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 "
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
138
src/lib.rs
138
src/lib.rs
|
|
@ -5,9 +5,13 @@ pub mod internal;
|
||||||
use internal::{
|
use internal::{
|
||||||
execute::execute,
|
execute::execute,
|
||||||
parse::{parse, Ast},
|
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 print(&self, out: &mut String);
|
||||||
fn lookup(&self, name: &str) -> Option<&dyn Value>;
|
fn lookup(&self, name: &str) -> Option<&dyn Value>;
|
||||||
fn has(&self) -> 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)]);
|
pub struct ValuesListMap<'a>(pub &'a [(&'a str, &'a dyn Value)]);
|
||||||
impl<'a> Value for ValuesListMap<'a> {
|
impl<'a> Value for ValuesListMap<'a> {
|
||||||
fn print(&self, w: &mut String) {
|
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 {
|
pub struct Newt {
|
||||||
ast: Ast,
|
ops: Ops,
|
||||||
}
|
}
|
||||||
impl Newt {
|
impl Newt {
|
||||||
pub fn build(tmpl: &str) -> Self {
|
pub fn build(tmpl: &str) -> Self {
|
||||||
let ast = parse(tmpl);
|
let ast = parse(tmpl);
|
||||||
Self { ast: todo!() }
|
let ops = lower_ast(&ast);
|
||||||
|
|
||||||
|
Self { ops }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute(&self, value: &dyn Value) -> String {
|
pub fn execute(&self, value: &dyn Value) -> String {
|
||||||
execute(&self.ast, value)
|
VmState::new(&self.ops, value).execute()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! values_list_map {
|
macro_rules! values_map {
|
||||||
($($key:literal: $val:expr),* $(,)?) => {{
|
($($key:path: $val:expr),* $(,)?) => {{
|
||||||
&$crate::ValuesListMap(
|
&$crate::ValuesListMap(
|
||||||
&[
|
&[
|
||||||
$(
|
$(
|
||||||
($key, $val as &dyn Value)
|
(stringify!($key), &$val as &dyn $crate::Value)
|
||||||
)*,
|
)*,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
@ -359,80 +409,8 @@ macro_rules! values {
|
||||||
($($val:expr),* $(,)?) => {{
|
($($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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue