diff --git a/Cargo.lock b/Cargo.lock index 729eb06..e6d1099 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -371,7 +371,6 @@ dependencies = [ "console", "globset", "once_cell", - "serde", "similar", "tempfile", "walkdir", @@ -440,6 +439,7 @@ dependencies = [ "criterion", "displaydoc", "insta", + "nomo", "serde", "serde_json", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index 8e383d2..c75a84e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,21 +13,20 @@ debug = true [dependencies] annotate-snippets = "0.12.13" displaydoc = "0.2.5" -serde_json = { version = "1.0.149", optional = true } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = "1.0.149" thiserror = "2.0.18" winnow = { version = "0.7.14", features = ["unstable-recover"] } [dev-dependencies] annotate-snippets = { version = "0.12.13", features = ["testing-colors"] } criterion = "0.8.2" -insta = { version = "1.46.3", features = ["glob", "serde"] } -serde = { version = "1.0.228", features = ["derive"] } -serde_json = "1.0.149" +insta = { version = "1.46.3", features = ["glob"] } +nomo = { path = ".", features = ["serialize"] } [profile.dev.package] insta.opt-level = 3 similar.opt-level = 3 [features] -default = ["serde_json"] -serde_json = ["dep:serde_json"] +serialize = [] diff --git a/benches/parsing.rs b/benches/parsing.rs index 35de7ae..f7e3da0 100644 --- a/benches/parsing.rs +++ b/benches/parsing.rs @@ -23,26 +23,5 @@ fn parsing_benchmark(c: &mut Criterion) { } } -fn parsing_nested(c: &mut Criterion) { - let mut parsing = c.benchmark_group("Parsing"); - - for size in [1, 2, 8, 12] { - let mut input = String::new(); - - for _ in 0..size { - input = format!( - "{{{{ for foo in bar }}}} {input} {{{{ if foo }}}} Hi! {input} {{{{ end }}}} Yooo {{{{ end }}}}" - ); - } - - let input = NomoInput::from(input); - - parsing.throughput(criterion::Throughput::Bytes(input.len() as u64)); - parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| { - b.iter(|| nomo::parser::parse(input.clone()).unwrap()); - }); - } -} - -criterion_group!(benches, parsing_benchmark, parsing_nested); +criterion_group!(benches, parsing_benchmark); criterion_main!(benches); diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index ee307ed..2b28a53 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -116,6 +116,7 @@ version = "0.1.0" dependencies = [ "annotate-snippets", "displaydoc", + "serde", "serde_json", "thiserror", "winnow", @@ -160,6 +161,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", + "serde_derive", ] [[package]] diff --git a/src/ast/mod.rs b/src/ast/mod.rs index fa78ccc..04a7e5d 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -217,32 +217,15 @@ pub fn parse(input: &[TemplateToken]) -> Result, AstFailure> { #[derive(Debug, Clone)] pub enum TemplateAstExpr<'input> { StaticContent(TemplateToken), - Block { - prev_whitespace_content: Option, - expression: Box>, - post_whitespace_content: Option, - }, Interpolation { prev_whitespace_content: Option, expression: Box>, post_whitespace_content: Option, }, + VariableAccess(TemplateToken), ConditionalChain { chain: Vec>, }, - ForChain { - for_block: Box>, - content: Vec>, - else_block: Option>>, - else_content: Option>>, - end_block: Box>, - }, - For { - value_ident: TemplateToken, - value_expression: Box>, - }, - ForElse, - VariableAccess(TemplateToken), IfConditional { expression: Box>, }, @@ -254,6 +237,11 @@ pub enum TemplateAstExpr<'input> { }, Invalid(&'input [TemplateToken]), EndBlock, + Block { + prev_whitespace_content: Option, + expression: Box>, + post_whitespace_content: Option, + }, } fn parse_asts<'input>(input: &mut Input<'input>) -> Result>, AstError> { @@ -328,7 +316,6 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result(input: &mut Input<'input>) -> Result(input: &mut Input<'input>) -> Result, AstError> { - trace("for_loop", |input: &mut Input<'input>| { - let for_block = parse_for_loop.map(Box::new).parse_next(input)?; - - let loop_end = ( - opt(( - parse_loop_else.map(Box::new), - repeat_till(0.., parse_ast, peek(parse_end)).map(|(val, _)| val), - )), - parse_end.map(Box::new), - ); - - let (content, taken) = resume_after_cut( - repeat_till(0.., parse_ast, loop_end), - repeat_till(0.., any, parse_end).map(|((), _)| ()), - ) - .with_taken() - .parse_next(input)?; - - let Some((content, (else_stuff, end_block))) = content else { - return Ok(TemplateAstExpr::Invalid(taken)); - }; - - let (else_block, else_content) = else_stuff.unzip(); - - Ok(TemplateAstExpr::ForChain { - for_block, - content, - else_block, - else_content, - end_block, - }) - }) - .parse_next(input) -} - -fn parse_for_loop<'input>(input: &mut Input<'input>) -> Result, AstError> { - trace( - "for_loop_inner", - parse_block( - ( - ws, - TokenKind::For, - ws, - TokenKind::Ident, - ws, - TokenKind::In, - ws, - parse_value_expression.map(Box::new), - ) - .map(|(_, _for, _, value_ident, _, _in, _, value_expression)| { - TemplateAstExpr::For { - value_ident, - value_expression, - } - }), - ), - ) - .parse_next(input) -} - fn parse_conditional_chain<'input>( input: &mut Input<'input>, ) -> Result, AstError> { @@ -521,18 +447,6 @@ fn parse_conditional_else<'input>( .parse_next(input) } -fn parse_loop_else<'input>(input: &mut Input<'input>) -> Result, AstError> { - trace( - "end", - parse_block( - TokenKind::ConditionalElse - .value(TemplateAstExpr::ForElse) - .context(AstError::ctx().msg("Expected an else block here")), - ), - ) - .parse_next(input) -} - fn parse_end<'input>(input: &mut Input<'input>) -> Result, AstError> { trace( "end", @@ -901,15 +815,4 @@ mod tests { insta::assert_debug_snapshot!(ast); } - - #[test] - fn check_for_loop() { - let input = "{{ for value in array }} Hi: {{= value }} {{ else }} No Content :C {{ end }}"; - - let parsed = crate::parser::parse(input.into()).unwrap(); - - let ast = panic_pretty(input, parse(parsed.tokens())); - - insta::assert_debug_snapshot!(ast); - } } diff --git a/src/ast/snapshots/nomo__ast__tests__check_for_loop.snap b/src/ast/snapshots/nomo__ast__tests__check_for_loop.snap deleted file mode 100644 index 737e2c0..0000000 --- a/src/ast/snapshots/nomo__ast__tests__check_for_loop.snap +++ /dev/null @@ -1,61 +0,0 @@ ---- -source: src/ast/mod.rs -expression: ast ---- -TemplateAst { - root: [ - ForChain { - for_block: Block { - prev_whitespace_content: None, - expression: For { - value_ident: [Ident]"value" (7..12), - value_expression: VariableAccess( - [Ident]"array" (16..21), - ), - }, - post_whitespace_content: Some( - [Whitespace]" " (24..25), - ), - }, - content: [ - StaticContent( - [Content]"Hi:" (25..28), - ), - Interpolation { - prev_whitespace_content: Some( - [Whitespace]" " (28..29), - ), - expression: VariableAccess( - [Ident]"value" (33..38), - ), - post_whitespace_content: Some( - [Whitespace]" " (41..42), - ), - }, - ], - else_block: Some( - Block { - prev_whitespace_content: None, - expression: ForElse, - post_whitespace_content: Some( - [Whitespace]" " (52..53), - ), - }, - ), - else_content: Some( - [ - StaticContent( - [Content]"No Content :C" (53..66), - ), - ], - ), - end_block: Block { - prev_whitespace_content: Some( - [Whitespace]" " (66..67), - ), - expression: EndBlock, - post_whitespace_content: None, - }, - }, - ], -} diff --git a/src/emit/mod.rs b/src/emit/mod.rs index b34d4cf..daf8e0d 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -1,11 +1,8 @@ -use std::collections::BTreeMap; - use crate::ast::TemplateAstExpr; use crate::input::NomoInput; pub struct EmitMachine { current_index: usize, - labels: BTreeMap, } impl EmitMachine { @@ -18,35 +15,14 @@ impl EmitMachine { }, } } - - fn reserve_label(&mut self) -> LabelSlot { - LabelSlot { - index: { - let val = self.current_index; - self.current_index += 1; - val - }, - } - } - - fn assign_label(&mut self, slot: LabelSlot, idx: usize) { - let no_prev = self.labels.insert(slot, idx).is_none(); - - assert!(no_prev, "A label slot was already assigned") - } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct VariableSlot { index: usize, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct LabelSlot { - index: usize, -} - -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum Instruction { AppendContent { content: NomoInput, @@ -64,54 +40,24 @@ pub enum Instruction { Abort, JumpIfNotTrue { emit_slot: VariableSlot, - jump: LabelSlot, + jump: isize, }, Jump { - jump: LabelSlot, + jump: isize, }, NoOp, - CreateIteratorFromSlotToSlot { - iterator_slot: VariableSlot, - iterator_source_slot: VariableSlot, - }, - AdvanceIteratorOrJump { - iterator_slot: VariableSlot, - value_slot: VariableSlot, - jump: LabelSlot, - }, - GetIteratorEmptyOrJump { - iterator_slot: VariableSlot, - jump: LabelSlot, - }, - PopScope, - LoadFromSlotToContext { - value_ident: NomoInput, - value_slot: VariableSlot, - }, } -#[derive(Debug, Clone)] -pub struct VMInstructions { - pub labels: BTreeMap, - pub instructions: Vec, -} - -pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> VMInstructions { +pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec { let mut eval = vec![]; - let mut machine = EmitMachine { - current_index: 0, - labels: BTreeMap::new(), - }; + let mut machine = EmitMachine { current_index: 0 }; for ast in input.root() { emit_ast_expr(&mut machine, &mut eval, ast); } - VMInstructions { - labels: machine.labels, - instructions: eval, - } + eval } fn emit_ast_expr( @@ -149,11 +95,10 @@ fn emit_ast_expr( TemplateAstExpr::ConditionalChain { chain } => { let mut chain = chain.iter(); - let end_label = machine.reserve_label(); let mut end_indices = vec![]; let mut previous_post_whitespace_content: &Option = &None; - let mut previous_jump: Option = None; + let mut previous_jump: Option = None; loop { let next = chain.next().unwrap(); @@ -169,7 +114,7 @@ fn emit_ast_expr( } end_indices.push(eval.len()); - eval.push(Instruction::Jump { jump: end_label }); + eval.push(Instruction::Jump { jump: isize::MAX }); } else if let TemplateAstExpr::Block { prev_whitespace_content, post_whitespace_content, @@ -194,15 +139,20 @@ fn emit_ast_expr( let emit_slot = machine.reserve_slot(); emit_expr_load(machine, eval, emit_slot, expression); - let jmp_label = machine.reserve_label(); - previous_jump = Some(jmp_label); + previous_jump = Some(eval.len()); eval.push(Instruction::JumpIfNotTrue { emit_slot, - jump: jmp_label, + jump: isize::MAX, }); } else if let TemplateAstExpr::ElseConditional { expression } = &**expression { if let Some(previous_jump) = previous_jump.take() { - machine.assign_label(previous_jump, eval.len()); + let new_jump = eval.len() - previous_jump - 1; + let Instruction::JumpIfNotTrue { jump, .. } = &mut eval[previous_jump] + else { + panic!("Jump slot had something that is not a jump?!"); + }; + + *jump = new_jump as isize; } else { panic!("Got an else without a previous if?"); } @@ -211,11 +161,10 @@ fn emit_ast_expr( let emit_slot = machine.reserve_slot(); emit_expr_load(machine, eval, emit_slot, expression); - let jmp_label = machine.reserve_label(); - previous_jump = Some(jmp_label); + previous_jump = Some(eval.len()); eval.push(Instruction::JumpIfNotTrue { emit_slot, - jump: jmp_label, + jump: isize::MAX, }); } else { // We don't have to do anything in the else case @@ -227,9 +176,13 @@ fn emit_ast_expr( } if let Some(previous_jump) = previous_jump.take() { - machine.assign_label(previous_jump, eval.len()); + let new_jump = eval.len() - previous_jump - 1; + let Instruction::JumpIfNotTrue { jump, .. } = &mut eval[previous_jump] else { + panic!("Jump slot had something that is not a jump?!"); + }; + + *jump = new_jump as isize; } - machine.assign_label(end_label, eval.len()); if let Some(ws) = previous_post_whitespace_content { eval.push(Instruction::AppendContent { @@ -238,159 +191,12 @@ fn emit_ast_expr( } else { eval.push(Instruction::NoOp); } - } - TemplateAstExpr::ForChain { - for_block, - content, - else_block, - else_content, - end_block, - } => { - let post_for_whitespace_content; - let label_to_else_or_empty_index = machine.reserve_label(); - let label_to_end_index = machine.reserve_label(); - let label_start_loop = machine.reserve_label(); - if let TemplateAstExpr::Block { - prev_whitespace_content, - expression, - post_whitespace_content, - } = &**for_block - && let TemplateAstExpr::For { - value_ident, - value_expression, - } = &**expression - { - if let Some(ws) = prev_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - post_for_whitespace_content = post_whitespace_content; - eval.push(Instruction::PushScope { - inherit_parent: true, - }); - - let value_slot = machine.reserve_slot(); - let iterator_source_slot = machine.reserve_slot(); - let iterator_slot = machine.reserve_slot(); - - emit_expr_load(machine, eval, iterator_source_slot, value_expression); - eval.push(Instruction::CreateIteratorFromSlotToSlot { - iterator_source_slot, - iterator_slot, - }); - - eval.push(Instruction::GetIteratorEmptyOrJump { - iterator_slot, - jump: label_to_else_or_empty_index, - }); - - machine.assign_label(label_start_loop, eval.len()); - eval.push(Instruction::AdvanceIteratorOrJump { - iterator_slot, - value_slot, - jump: label_to_end_index, - }); - - eval.push(Instruction::LoadFromSlotToContext { - value_slot, - value_ident: value_ident.source(), - }); - } else { - panic!("For block should be a for block"); - }; - - if let Some(ws) = post_for_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - - for content in content { - emit_ast_expr(machine, eval, content); - } - - let end_of_content_jump = eval.len(); - eval.push(Instruction::Jump { - jump: label_start_loop, - }); - - let has_else = else_block.is_some(); - - if let Some(TemplateAstExpr::Block { - prev_whitespace_content, - expression, - post_whitespace_content, - }) = else_block.as_deref() - && let TemplateAstExpr::ForElse = &**expression - { - if let Some(ws) = prev_whitespace_content { - eval.insert( - end_of_content_jump.saturating_sub(1), - Instruction::AppendContent { - content: ws.source().clone(), - }, - ); - } - - machine.assign_label(label_to_else_or_empty_index, eval.len()); - - if let Some(ws) = post_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - - for content in else_content - .as_ref() - .expect("If there is a for block, there should be for content (even if empty)") - { - emit_ast_expr(machine, eval, content); - } - } - - let post_end_whitespace_content; - - if let TemplateAstExpr::Block { - prev_whitespace_content, - expression, - post_whitespace_content, - } = &**end_block - && let TemplateAstExpr::EndBlock = &**expression - { - post_end_whitespace_content = post_whitespace_content; - - if let Some(ws) = prev_whitespace_content { - if has_else { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } else { - eval.insert( - end_of_content_jump.saturating_sub(1), - Instruction::AppendContent { - content: ws.source().clone(), - }, - ); - } - } - - if !has_else { - machine.assign_label(label_to_else_or_empty_index, eval.len()); - } - - machine.assign_label(label_to_end_index, eval.len()); - - eval.push(Instruction::PopScope); - - if let Some(ws) = post_end_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - } else { - panic!("End block should be an endblock"); + for index in end_indices { + let jump = eval.len() - index - 1; + eval[index] = Instruction::Jump { + jump: jump as isize, + }; } } @@ -399,8 +205,6 @@ fn emit_ast_expr( | TemplateAstExpr::IfConditional { .. } | TemplateAstExpr::ConditionalContent { .. } | TemplateAstExpr::ElseConditional { .. } - | TemplateAstExpr::For { .. } - | TemplateAstExpr::ForElse | TemplateAstExpr::Invalid { .. } | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), } @@ -427,10 +231,6 @@ fn emit_expr_load( TemplateAstExpr::ElseConditional { .. } => todo!(), TemplateAstExpr::EndBlock => todo!(), TemplateAstExpr::Block { .. } => todo!(), - TemplateAstExpr::ForChain { .. } => todo!(), - TemplateAstExpr::For { .. } => todo!(), - TemplateAstExpr::ForElse => todo!(), - TemplateAstExpr::IfConditional { .. } => todo!(), TemplateAstExpr::ConditionalContent { .. } => todo!(), } @@ -451,28 +251,25 @@ mod tests { let emit = emit_machine(ast); insta::assert_debug_snapshot!(emit, @r#" - VMInstructions { - labels: {}, - instructions: [ - AppendContent { - content: "Hello" (0..5), + [ + AppendContent { + content: "Hello" (0..5), + }, + AppendContent { + content: " " (5..6), + }, + LoadFromContextToSlot { + name: "world" (10..15), + slot: VariableSlot { + index: 0, }, - AppendContent { - content: " " (5..6), + }, + EmitFromSlot { + slot: VariableSlot { + index: 0, }, - LoadFromContextToSlot { - name: "world" (10..15), - slot: VariableSlot { - index: 0, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, - }, - }, - ], - } + }, + ] "#); } diff --git a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap index e178993..469aa47 100644 --- a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap +++ b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap @@ -2,98 +2,75 @@ source: src/emit/mod.rs expression: emit --- -VMInstructions { - labels: { - LabelSlot { +[ + LoadFromContextToSlot { + name: "foo" (6..9), + slot: VariableSlot { index: 0, - }: 19, - LabelSlot { - index: 2, - }: 7, - LabelSlot { - index: 4, - }: 14, + }, }, - instructions: [ - LoadFromContextToSlot { - name: "foo" (6..9), - slot: VariableSlot { - index: 1, - }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 1, - }, - jump: LabelSlot { - index: 2, - }, + jump: 5, + }, + AppendContent { + content: " " (12..13), + }, + AppendContent { + content: "foo" (13..16), + }, + AppendContent { + content: " " (16..17), + }, + Jump { + jump: 14, + }, + AppendContent { + content: " " (12..13), + }, + LoadFromContextToSlot { + name: "bar" (28..31), + slot: VariableSlot { + index: 1, }, - AppendContent { - content: " " (12..13), + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 1, }, - AppendContent { - content: "foo" (13..16), - }, - AppendContent { - content: " " (16..17), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: " " (12..13), - }, - LoadFromContextToSlot { - name: "bar" (28..31), - slot: VariableSlot { - index: 3, - }, - }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 3, - }, - jump: LabelSlot { - index: 4, - }, - }, - AppendContent { - content: " " (34..35), - }, - AppendContent { - content: "bar" (35..38), - }, - AppendContent { - content: " " (38..39), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: " " (34..35), - }, - AppendContent { - content: " " (49..50), - }, - AppendContent { - content: "foobar" (50..56), - }, - AppendContent { - content: " " (56..57), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: " " (49..50), - }, - NoOp, - ], -} + jump: 5, + }, + AppendContent { + content: " " (34..35), + }, + AppendContent { + content: "bar" (35..38), + }, + AppendContent { + content: " " (38..39), + }, + Jump { + jump: 7, + }, + AppendContent { + content: " " (34..35), + }, + AppendContent { + content: " " (49..50), + }, + AppendContent { + content: "foobar" (50..56), + }, + AppendContent { + content: " " (56..57), + }, + Jump { + jump: 2, + }, + AppendContent { + content: " " (49..50), + }, + NoOp, +] diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 3059618..10250f1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -5,10 +5,7 @@ use thiserror::Error; use crate::Context; use crate::emit::Instruction; -use crate::emit::VMInstructions; -use crate::emit::VariableSlot; use crate::input::NomoInput; -use crate::value::NomoValue; #[derive(Debug, Error, Display)] pub enum EvaluationError { @@ -20,165 +17,67 @@ pub enum EvaluationError { ** ** This is an internal error and is a bug that should be reported */ - LabelNotFound, + InstructionPointerOverflow, } -struct Scope { - stack: Vec>, - slots: HashMap, -} -impl Scope { - fn insert_into_slot(&mut self, slot: VariableSlot, value: NomoValue) { - self.slots.insert(slot, value); - } - - fn get(&self, slot: &VariableSlot) -> &NomoValue { - self.slots.get(slot).expect("All slot loads must be valid") - } - - fn get_mut(&mut self, slot: &VariableSlot) -> &mut NomoValue { - self.slots - .get_mut(slot) - .expect("All slot loads must be valid") - } - - fn push_scope(&mut self) { - self.stack.push(Default::default()); - } - - fn pop_scope(&mut self) { - self.stack.pop(); - } - - fn insert_into_scope(&mut self, ident: &NomoInput, value: NomoValue) { - self.stack - .last_mut() - .unwrap() - .insert(ident.to_string(), value); - } - - fn get_scoped(&self, name: &NomoInput) -> Option<&NomoValue> { - self.stack - .iter() - .rev() - .find_map(|scope| scope.get(name.as_str())) - } -} - -#[allow( - clippy::unnecessary_to_owned, - reason = "We cannot do the suggested way as the lifetimes would not match up" -)] -pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result { +pub fn execute( + instructions: &[Instruction], + global_context: &Context, +) -> Result { let mut output = String::new(); - let mut scopes = Scope { - stack: vec![global_context.values().clone()], - slots: HashMap::new(), - }; + let mut scopes: HashMap = HashMap::new(); let mut ip = 0; loop { - if ip >= vm.instructions.len() { + if ip >= instructions.len() { break; } - let instr = vm.instructions.get(ip).unwrap(); + let instr = instructions.get(ip).unwrap(); match instr { Instruction::NoOp => (), Instruction::AppendContent { content } => output.push_str(content), Instruction::LoadFromContextToSlot { name, slot } => { - let value = scopes - .get_scoped(name) + let value = global_context + .values + .get(name.as_str()) .ok_or(EvaluationError::UnknownVariable(name.clone()))?; - scopes.insert_into_slot(*slot, value.clone()); + scopes.insert(*slot, value.clone()); } Instruction::EmitFromSlot { slot } => { - let value = scopes.get(slot).as_str().unwrap(); + let value = scopes.get(slot).unwrap().as_str().unwrap(); output.push_str(value); } - Instruction::PushScope { inherit_parent: _ } => { - scopes.push_scope(); - } + Instruction::PushScope { inherit_parent: _ } => todo!(), Instruction::Abort => return Err(EvaluationError::ExplicitAbort), Instruction::JumpIfNotTrue { emit_slot, jump } => { - let dont_jump = scopes.get(emit_slot).as_bool().unwrap(); + let dont_jump = scopes.get(emit_slot).unwrap().as_bool().unwrap(); if dont_jump { // We are done } else { - let Some(new_ip) = vm.labels.get(jump) else { - return Err(EvaluationError::LabelNotFound); - }; + let (new_ip, overflow) = ip.overflowing_add_signed(*jump); - ip = *new_ip; - continue; + if overflow { + return Err(EvaluationError::InstructionPointerOverflow); + } else { + ip = new_ip; + continue; + } } } Instruction::Jump { jump } => { - let Some(new_ip) = vm.labels.get(jump) else { - return Err(EvaluationError::LabelNotFound); - }; + let (new_ip, overflow) = ip.overflowing_add_signed(*jump); - ip = *new_ip; - continue; - } - Instruction::CreateIteratorFromSlotToSlot { - iterator_slot, - iterator_source_slot, - } => { - let value = scopes.get(iterator_source_slot).as_array().unwrap(); - scopes.insert_into_slot( - *iterator_slot, - NomoValue::Iterator { - value: Box::new(value.to_vec().into_iter()), - }, - ); - } - Instruction::AdvanceIteratorOrJump { - iterator_slot, - value_slot, - jump, - } => { - let iterator = scopes.get_mut(iterator_slot).as_iterator_mut().unwrap(); - - if let Some(value) = iterator.next() { - scopes.insert_into_slot(*value_slot, value); + if overflow { + return Err(EvaluationError::InstructionPointerOverflow); } else { - let Some(new_ip) = vm.labels.get(jump) else { - return Err(EvaluationError::LabelNotFound); - }; - - ip = *new_ip; + ip = new_ip; continue; } } - Instruction::GetIteratorEmptyOrJump { - iterator_slot, - jump, - } => { - let iterator = scopes.get(iterator_slot).as_iterator().unwrap(); - let (min, _) = iterator.size_hint(); - - if min == 0 { - let Some(new_ip) = vm.labels.get(jump) else { - return Err(EvaluationError::LabelNotFound); - }; - - ip = *new_ip; - continue; - } - } - Instruction::PopScope => scopes.pop_scope(), - Instruction::LoadFromSlotToContext { - value_ident, - value_slot, - } => { - let value = scopes.get(value_slot).clone(); - - scopes.insert_into_scope(value_ident, value); - } } ip += 1; diff --git a/src/lib.rs b/src/lib.rs index 8d177ce..2679358 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,19 +1,18 @@ +use std::collections::BTreeMap; use std::collections::HashMap; use displaydoc::Display; +use serde::Serialize; use thiserror::Error; -use crate::emit::VMInstructions; +use crate::emit::Instruction; use crate::input::NomoInput; -use crate::value::NomoValue; -use crate::value::NomoValueError; pub mod ast; pub mod emit; pub mod eval; pub mod input; pub mod parser; -pub mod value; #[derive(Debug, Error, Display)] pub enum NomoError { @@ -85,11 +84,11 @@ impl Nomo { } struct Template { - instructions: VMInstructions, + instructions: Vec, } pub struct Context { - values: HashMap, + values: BTreeMap, } impl Default for Context { @@ -101,26 +100,23 @@ impl Default for Context { impl Context { pub fn new() -> Context { Context { - values: HashMap::new(), + values: BTreeMap::new(), } } pub fn try_insert( &mut self, key: impl Into, - value: impl TryInto, - ) -> Result<(), NomoValueError> { - self.values.insert(key.into(), value.try_into()?); + value: impl Serialize, + ) -> Result<(), serde_json::Error> { + self.values.insert(key.into(), serde_json::to_value(value)?); Ok(()) } - pub fn insert(&mut self, key: impl Into, value: impl Into) { - self.values.insert(key.into(), value.into()); - } - - pub fn values(&self) -> &HashMap { - &self.values + pub fn insert(&mut self, key: impl Into, value: impl Serialize) { + self.try_insert(key, value) + .expect("inserted value should serialize without error"); } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index de0ba97..a9171f0 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -209,8 +209,6 @@ pub enum TokenKind { Invalid, ConditionalIf, ConditionalElse, - For, - In, End, Literal(TokenLiteral), } @@ -297,8 +295,6 @@ impl TemplateToken { invalid => TokenKind::Invalid, conditional_if => TokenKind::ConditionalIf, conditional_else => TokenKind::ConditionalElse, - keyword_for => TokenKind::For, - keyword_in => TokenKind::In, end => TokenKind::End, } @@ -385,7 +381,14 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { trace( "parse_block_token", - alt((parse_ident, parse_keyword, parse_whitespace)), + alt(( + parse_ident, + terminated(parse_literal, ident_terminator_check), + terminated(parse_condition_if, ident_terminator_check), + terminated(parse_condition_else, ident_terminator_check), + terminated(parse_end, ident_terminator_check), + parse_whitespace, + )), ) .parse_next(input) } @@ -428,26 +431,6 @@ fn parse_end<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken trace("parse_end", "end".map(TemplateToken::end)).parse_next(input) } -fn parse_for<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { - trace("parse_for", "for".map(TemplateToken::keyword_for)).parse_next(input) -} - -fn parse_in<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { - trace("parse_in", "in".map(TemplateToken::keyword_in)).parse_next(input) -} - -fn parse_keyword<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { - alt(( - terminated(parse_literal, ident_terminator_check), - terminated(parse_condition_if, ident_terminator_check), - terminated(parse_condition_else, ident_terminator_check), - terminated(parse_for, ident_terminator_check), - terminated(parse_in, ident_terminator_check), - terminated(parse_end, ident_terminator_check), - )) - .parse_next(input) -} - fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> { trace( "parse_whitespace", @@ -475,9 +458,14 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok } fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> { - peek(not(parse_keyword)) - .context(ParseError::ctx().msg("Expected an ident, but found a literal instead")) - .parse_next(input)?; + peek(not(alt(( + parse_literal, + parse_condition_if, + parse_condition_else, + parse_end, + )))) + .context(ParseError::ctx().msg("Expected an ident, but found a literal instead")) + .parse_next(input)?; take_while(1.., |c: char| c.is_alphanumeric() || "_".contains(c)).parse_next(input) } @@ -639,45 +627,4 @@ mod tests { ) "#); } - - #[test] - fn parse_for_loop() { - let input = "{{ for value in array }} Hi: {{= value }} {{ end }}"; - let output = parse(input.into()); - - insta::assert_debug_snapshot!(output, @r#" - Ok( - ParsedTemplate { - tokens: [ - [LeftDelim]"{{" (0..2), - [Whitespace]" " (2..3), - [For]"for" (3..6), - [Whitespace]" " (6..7), - [Ident]"value" (7..12), - [Whitespace]" " (12..13), - [In]"in" (13..15), - [Whitespace]" " (15..16), - [Ident]"array" (16..21), - [Whitespace]" " (21..22), - [RightDelim]"}}" (22..24), - [Whitespace]" " (24..25), - [Content]"Hi:" (25..28), - [Whitespace]" " (28..29), - [LeftDelim]"{{" (29..31), - [WantsOutput]"=" (31..32), - [Whitespace]" " (32..33), - [Ident]"value" (33..38), - [Whitespace]" " (38..39), - [RightDelim]"}}" (39..41), - [Whitespace]" " (41..42), - [LeftDelim]"{{" (42..44), - [Whitespace]" " (44..45), - [End]"end" (45..48), - [Whitespace]" " (48..49), - [RightDelim]"}}" (49..51), - ], - }, - ) - "#); - } } diff --git a/src/value.rs b/src/value.rs deleted file mode 100644 index 1dc86e0..0000000 --- a/src/value.rs +++ /dev/null @@ -1,251 +0,0 @@ -use std::borrow::Cow; -use std::collections::BTreeMap; - -#[cfg(feature = "serde_json")] -use displaydoc::Display; -use thiserror::Error; - -#[derive(Clone)] -pub enum NomoValue { - String { - value: Cow<'static, str>, - }, - Array { - value: Vec, - }, - Bool { - value: bool, - }, - Object { - value: BTreeMap, - }, - Integer { - value: u64, - }, - SignedInteger { - value: i64, - }, - Float { - value: f64, - }, - Iterator { - value: Box>, - }, -} - -impl NomoValue { - pub fn as_str(&self) -> Option<&str> { - if let Self::String { value } = self { - Some(value) - } else { - None - } - } - - pub fn as_array(&self) -> Option<&[NomoValue]> { - if let Self::Array { value } = self { - Some(value) - } else { - None - } - } - - pub fn as_bool(&self) -> Option { - if let Self::Bool { value } = self { - Some(*value) - } else { - None - } - } - - pub fn as_object(&self) -> Option<&BTreeMap> { - if let Self::Object { value } = self { - Some(value) - } else { - None - } - } - - pub fn as_integer(&self) -> Option { - if let Self::Integer { value } = self { - Some(*value) - } else { - None - } - } - - pub fn as_float(&self) -> Option { - if let Self::Float { value } = self { - Some(*value) - } else { - None - } - } - - pub fn as_iterator(&self) -> Option<&dyn CloneIterator> { - if let Self::Iterator { value } = self { - Some(value) - } else { - None - } - } - - pub fn as_iterator_mut(&mut self) -> Option<&mut dyn CloneIterator> { - if let Self::Iterator { value } = self { - Some(value) - } else { - None - } - } -} - -pub trait CloneIterator: Iterator { - fn clone_box(&self) -> Box>; -} - -impl CloneIterator for I -where - I: Iterator + Clone + 'static, -{ - fn clone_box(&self) -> Box> { - Box::new(Clone::clone(self)) - } -} - -impl Clone for Box { - fn clone(&self) -> Self { - self.clone_box() - } -} - -impl std::fmt::Debug for NomoValue { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::String { value } => f.debug_struct("String").field("value", value).finish(), - Self::Array { value } => f.debug_struct("Array").field("value", value).finish(), - Self::Bool { value } => f.debug_struct("Bool").field("value", value).finish(), - Self::Object { value } => f.debug_struct("Object").field("value", value).finish(), - Self::Integer { value } => f.debug_struct("Integer").field("value", value).finish(), - Self::SignedInteger { value } => f - .debug_struct("SignedInteger") - .field("value", value) - .finish(), - Self::Float { value } => f.debug_struct("Float").field("value", value).finish(), - Self::Iterator { value: _ } => f - .debug_struct("Iterator") - .field("value", &"Iterator") - .finish(), - } - } -} - -impl From<&R> for NomoValue -where - R: Into + Clone, -{ - fn from(value: &R) -> Self { - value.clone().into() - } -} - -impl From for NomoValue { - fn from(val: String) -> Self { - NomoValue::String { - value: Cow::Owned(val), - } - } -} - -impl From<&'static str> for NomoValue { - fn from(val: &'static str) -> Self { - NomoValue::String { - value: Cow::Borrowed(val), - } - } -} - -impl From> for NomoValue -where - V: Into, -{ - fn from(val: Vec) -> Self { - NomoValue::Array { - value: val.into_iter().map(Into::into).collect(), - } - } -} - -impl From> for NomoValue -where - V: Into, -{ - fn from(val: std::collections::VecDeque) -> Self { - NomoValue::Array { - value: val.into_iter().map(Into::into).collect(), - } - } -} - -impl From<&[V]> for NomoValue -where - V: Into + Clone, -{ - fn from(value: &[V]) -> Self { - NomoValue::Array { - value: value.iter().cloned().map(Into::into).collect(), - } - } -} - -impl From> for NomoValue -where - K: Into, - V: Into, -{ - fn from(val: std::collections::HashMap) -> Self { - NomoValue::Object { - value: val.into_iter().map(|(k, v)| (k.into(), v.into())).collect(), - } - } -} - -#[cfg(feature = "serde_json")] -#[derive(Debug, Error, Display)] -/// Could not transform value to [`NomoValue`] -pub struct NomoValueError; - -#[cfg(feature = "serde_json")] -impl TryFrom for NomoValue { - type Error = NomoValueError; - fn try_from(value: serde_json::Value) -> Result { - match value { - serde_json::Value::Null => todo!(), - serde_json::Value::Bool(value) => Ok(NomoValue::Bool { value }), - serde_json::Value::Number(number) => { - if let Some(value) = number.as_u64() { - return Ok(NomoValue::Integer { value }); - } - - if let Some(value) = number.as_f64() { - return Ok(NomoValue::Float { value }); - } - - if let Some(value) = number.as_i64() { - return Ok(NomoValue::SignedInteger { value }); - } - - Err(NomoValueError) - } - serde_json::Value::String(str) => Ok(NomoValue::String { - value: Cow::Owned(str), - }), - serde_json::Value::Array(values) => Ok(NomoValue::Array { - value: values - .into_iter() - .map(TryInto::try_into) - .collect::>()?, - }), - serde_json::Value::Object(_map) => todo!(), - } - } -} diff --git a/tests/cases/1-parsed@simple_for.snap b/tests/cases/1-parsed@simple_for.snap deleted file mode 100644 index 4e391b7..0000000 --- a/tests/cases/1-parsed@simple_for.snap +++ /dev/null @@ -1,80 +0,0 @@ ---- -source: tests/file_tests.rs -expression: parsed -info: - input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}" - context: - no_values: [] - values: - - one - - two -input_file: tests/cases/simple_for.nomo ---- -ParsedTemplate { - tokens: [ - [LeftDelim]"{{" (0..2), - [Whitespace]" " (2..3), - [For]"for" (3..6), - [Whitespace]" " (6..7), - [Ident]"value" (7..12), - [Whitespace]" " (12..13), - [In]"in" (13..15), - [Whitespace]" " (15..16), - [Ident]"values" (16..22), - [Whitespace]" " (22..23), - [TrimWhitespace]"-" (23..24), - [RightDelim]"}}" (24..26), - [Whitespace]"\n " (26..31), - [LeftDelim]"{{" (31..33), - [TrimWhitespace]"-" (33..34), - [WantsOutput]"=" (34..35), - [Whitespace]" " (35..36), - [Ident]"value" (36..41), - [Whitespace]" " (41..42), - [RightDelim]"}}" (42..44), - [Whitespace]"\n" (44..45), - [LeftDelim]"{{" (45..47), - [TrimWhitespace]"-" (47..48), - [Whitespace]" " (48..49), - [End]"end" (49..52), - [Whitespace]" " (52..53), - [RightDelim]"}}" (53..55), - [Whitespace]"\n" (55..56), - [LeftDelim]"{{" (56..58), - [Whitespace]" " (58..59), - [For]"for" (59..62), - [Whitespace]" " (62..63), - [Ident]"value" (63..68), - [Whitespace]" " (68..69), - [In]"in" (69..71), - [Whitespace]" " (71..72), - [Ident]"no_values" (72..81), - [Whitespace]" " (81..82), - [TrimWhitespace]"-" (82..83), - [RightDelim]"}}" (83..85), - [Whitespace]"\n " (85..90), - [LeftDelim]"{{" (90..92), - [TrimWhitespace]"-" (92..93), - [WantsOutput]"=" (93..94), - [Whitespace]" " (94..95), - [Ident]"value" (95..100), - [Whitespace]" " (100..101), - [RightDelim]"}}" (101..103), - [Whitespace]"\n" (103..104), - [LeftDelim]"{{" (104..106), - [Whitespace]" " (106..107), - [ConditionalElse]"else" (107..111), - [Whitespace]" " (111..112), - [TrimWhitespace]"-" (112..113), - [RightDelim]"}}" (113..115), - [Whitespace]"\n" (115..116), - [Content]"No Values >:C" (116..129), - [Whitespace]"\n" (129..130), - [LeftDelim]"{{" (130..132), - [TrimWhitespace]"-" (132..133), - [Whitespace]" " (133..134), - [End]"end" (134..137), - [Whitespace]" " (137..138), - [RightDelim]"}}" (138..140), - ], -} diff --git a/tests/cases/2-ast@simple_for.snap b/tests/cases/2-ast@simple_for.snap deleted file mode 100644 index 5071ece..0000000 --- a/tests/cases/2-ast@simple_for.snap +++ /dev/null @@ -1,90 +0,0 @@ ---- -source: tests/file_tests.rs -expression: ast -info: - input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}" - context: - no_values: [] - values: - - one - - two -input_file: tests/cases/simple_for.nomo ---- -TemplateAst { - root: [ - ForChain { - for_block: Block { - prev_whitespace_content: None, - expression: For { - value_ident: [Ident]"value" (7..12), - value_expression: VariableAccess( - [Ident]"values" (16..22), - ), - }, - post_whitespace_content: None, - }, - content: [ - Interpolation { - prev_whitespace_content: None, - expression: VariableAccess( - [Ident]"value" (36..41), - ), - post_whitespace_content: Some( - [Whitespace]"\n" (44..45), - ), - }, - ], - else_block: None, - else_content: None, - end_block: Block { - prev_whitespace_content: None, - expression: EndBlock, - post_whitespace_content: Some( - [Whitespace]"\n" (55..56), - ), - }, - }, - ForChain { - for_block: Block { - prev_whitespace_content: None, - expression: For { - value_ident: [Ident]"value" (63..68), - value_expression: VariableAccess( - [Ident]"no_values" (72..81), - ), - }, - post_whitespace_content: None, - }, - content: [ - Interpolation { - prev_whitespace_content: None, - expression: VariableAccess( - [Ident]"value" (95..100), - ), - post_whitespace_content: Some( - [Whitespace]"\n" (103..104), - ), - }, - ], - else_block: Some( - Block { - prev_whitespace_content: None, - expression: ForElse, - post_whitespace_content: None, - }, - ), - else_content: Some( - [ - StaticContent( - [Content]"No Values >:C" (116..129), - ), - ], - ), - end_block: Block { - prev_whitespace_content: None, - expression: EndBlock, - post_whitespace_content: None, - }, - }, - ], -} diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index 5f79488..c2901f7 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -3,60 +3,46 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/condition.nomo --- -VMInstructions { - labels: { - LabelSlot { +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { index: 0, - }: 7, - LabelSlot { - index: 2, - }: 7, + }, }, - instructions: [ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { - index: 1, - }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 1, - }, - jump: LabelSlot { - index: 2, - }, + jump: 5, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "Hello World!" (18..30), + }, + AppendContent { + content: "\n" (30..31), + }, + Jump { + jump: 2, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "\n\n" (40..42), + }, + LoadFromContextToSlot { + name: "stuff" (46..51), + slot: VariableSlot { + index: 1, }, - AppendContent { - content: "\n " (13..18), + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, }, - AppendContent { - content: "Hello World!" (18..30), - }, - AppendContent { - content: "\n" (30..31), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: "\n " (13..18), - }, - AppendContent { - content: "\n\n" (40..42), - }, - LoadFromContextToSlot { - name: "stuff" (46..51), - slot: VariableSlot { - index: 3, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 3, - }, - }, - ], -} + }, +] diff --git a/tests/cases/3-instructions@identifiers.snap b/tests/cases/3-instructions@identifiers.snap index 01ec3a8..36bc082 100644 --- a/tests/cases/3-instructions@identifiers.snap +++ b/tests/cases/3-instructions@identifiers.snap @@ -3,89 +3,86 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/identifiers.nomo --- -VMInstructions { - labels: {}, - instructions: [ - LoadFromContextToSlot { - name: "_name" (4..9), - slot: VariableSlot { - index: 0, - }, +[ + LoadFromContextToSlot { + name: "_name" (4..9), + slot: VariableSlot { + index: 0, }, - EmitFromSlot { - slot: VariableSlot { - index: 0, - }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 0, }, - AppendContent { - content: "\n" (12..13), + }, + AppendContent { + content: "\n" (12..13), + }, + LoadFromContextToSlot { + name: "a_name" (17..23), + slot: VariableSlot { + index: 1, }, - LoadFromContextToSlot { - name: "a_name" (17..23), - slot: VariableSlot { - index: 1, - }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, }, - EmitFromSlot { - slot: VariableSlot { - index: 1, - }, + }, + AppendContent { + content: "\n" (26..27), + }, + LoadFromContextToSlot { + name: "1name" (31..36), + slot: VariableSlot { + index: 2, }, - AppendContent { - content: "\n" (26..27), + }, + EmitFromSlot { + slot: VariableSlot { + index: 2, }, - LoadFromContextToSlot { - name: "1name" (31..36), - slot: VariableSlot { - index: 2, - }, + }, + AppendContent { + content: "\n" (39..40), + }, + LoadFromContextToSlot { + name: "_name1" (44..50), + slot: VariableSlot { + index: 3, }, - EmitFromSlot { - slot: VariableSlot { - index: 2, - }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 3, }, - AppendContent { - content: "\n" (39..40), + }, + AppendContent { + content: "\n" (53..54), + }, + LoadFromContextToSlot { + name: "_namE" (58..63), + slot: VariableSlot { + index: 4, }, - LoadFromContextToSlot { - name: "_name1" (44..50), - slot: VariableSlot { - index: 3, - }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 4, }, - EmitFromSlot { - slot: VariableSlot { - index: 3, - }, + }, + AppendContent { + content: "\n" (66..67), + }, + LoadFromContextToSlot { + name: "name1" (71..76), + slot: VariableSlot { + index: 5, }, - AppendContent { - content: "\n" (53..54), + }, + EmitFromSlot { + slot: VariableSlot { + index: 5, }, - LoadFromContextToSlot { - name: "_namE" (58..63), - slot: VariableSlot { - index: 4, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 4, - }, - }, - AppendContent { - content: "\n" (66..67), - }, - LoadFromContextToSlot { - name: "name1" (71..76), - slot: VariableSlot { - index: 5, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 5, - }, - }, - ], -} + }, +] diff --git a/tests/cases/3-instructions@if_else_if.snap b/tests/cases/3-instructions@if_else_if.snap index 124556e..c9ab17d 100644 --- a/tests/cases/3-instructions@if_else_if.snap +++ b/tests/cases/3-instructions@if_else_if.snap @@ -3,81 +3,60 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/if_else_if.nomo --- -VMInstructions { - labels: { - LabelSlot { +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { index: 0, - }: 14, - LabelSlot { - index: 2, - }: 7, - LabelSlot { - index: 4, - }: 14, + }, }, - instructions: [ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { - index: 1, - }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 1, - }, - jump: LabelSlot { - index: 2, - }, + jump: 5, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "Not Hello World! :C" (18..37), + }, + AppendContent { + content: "\n" (37..38), + }, + Jump { + jump: 9, + }, + AppendContent { + content: "\n " (13..18), + }, + LoadFromContextToSlot { + name: "another_test" (49..61), + slot: VariableSlot { + index: 1, }, - AppendContent { - content: "\n " (13..18), + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 1, }, - AppendContent { - content: "Not Hello World! :C" (18..37), - }, - AppendContent { - content: "\n" (37..38), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: "\n " (13..18), - }, - LoadFromContextToSlot { - name: "another_test" (49..61), - slot: VariableSlot { - index: 3, - }, - }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 3, - }, - jump: LabelSlot { - index: 4, - }, - }, - AppendContent { - content: "\n " (64..69), - }, - AppendContent { - content: "Hello World!" (69..81), - }, - AppendContent { - content: "\n" (81..82), - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - AppendContent { - content: "\n " (64..69), - }, - NoOp, - ], -} + jump: 5, + }, + AppendContent { + content: "\n " (64..69), + }, + AppendContent { + content: "Hello World!" (69..81), + }, + AppendContent { + content: "\n" (81..82), + }, + Jump { + jump: 2, + }, + AppendContent { + content: "\n " (64..69), + }, + NoOp, +] diff --git a/tests/cases/3-instructions@interpolation.snap b/tests/cases/3-instructions@interpolation.snap index 82dc16b..f4873f3 100644 --- a/tests/cases/3-instructions@interpolation.snap +++ b/tests/cases/3-instructions@interpolation.snap @@ -3,25 +3,22 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/interpolation.nomo --- -VMInstructions { - labels: {}, - instructions: [ - AppendContent { - content: "Hello! I'm" (0..10), +[ + AppendContent { + content: "Hello! I'm" (0..10), + }, + AppendContent { + content: " " (10..11), + }, + LoadFromContextToSlot { + name: "name" (15..19), + slot: VariableSlot { + index: 0, }, - AppendContent { - content: " " (10..11), + }, + EmitFromSlot { + slot: VariableSlot { + index: 0, }, - LoadFromContextToSlot { - name: "name" (15..19), - slot: VariableSlot { - index: 0, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, - }, - }, - ], -} + }, +] diff --git a/tests/cases/3-instructions@multiple.snap b/tests/cases/3-instructions@multiple.snap index b6f13eb..99ccf8f 100644 --- a/tests/cases/3-instructions@multiple.snap +++ b/tests/cases/3-instructions@multiple.snap @@ -3,39 +3,36 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/multiple.nomo --- -VMInstructions { - labels: {}, - instructions: [ - AppendContent { - content: "Hi there! My name is" (0..20), +[ + AppendContent { + content: "Hi there! My name is" (0..20), + }, + AppendContent { + content: " " (20..21), + }, + LoadFromContextToSlot { + name: "name" (25..29), + slot: VariableSlot { + index: 0, }, - AppendContent { - content: " " (20..21), + }, + EmitFromSlot { + slot: VariableSlot { + index: 0, }, - LoadFromContextToSlot { - name: "name" (25..29), - slot: VariableSlot { - index: 0, - }, + }, + AppendContent { + content: " " (32..33), + }, + LoadFromContextToSlot { + name: "lastname" (37..45), + slot: VariableSlot { + index: 1, }, - EmitFromSlot { - slot: VariableSlot { - index: 0, - }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, }, - AppendContent { - content: " " (32..33), - }, - LoadFromContextToSlot { - name: "lastname" (37..45), - slot: VariableSlot { - index: 1, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 1, - }, - }, - ], -} + }, +] diff --git a/tests/cases/3-instructions@simple.snap b/tests/cases/3-instructions@simple.snap index 752a53b..0f495a6 100644 --- a/tests/cases/3-instructions@simple.snap +++ b/tests/cases/3-instructions@simple.snap @@ -3,11 +3,8 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/simple.nomo --- -VMInstructions { - labels: {}, - instructions: [ - AppendContent { - content: "Hello World!" (0..12), - }, - ], -} +[ + AppendContent { + content: "Hello World!" (0..12), + }, +] diff --git a/tests/cases/3-instructions@simple_for.snap b/tests/cases/3-instructions@simple_for.snap deleted file mode 100644 index 0cac073..0000000 --- a/tests/cases/3-instructions@simple_for.snap +++ /dev/null @@ -1,166 +0,0 @@ ---- -source: tests/file_tests.rs -expression: emit -info: - input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}" - context: - no_values: [] - values: - - one - - two -input_file: tests/cases/simple_for.nomo ---- -VMInstructions { - labels: { - LabelSlot { - index: 0, - }: 10, - LabelSlot { - index: 1, - }: 10, - LabelSlot { - index: 2, - }: 4, - LabelSlot { - index: 7, - }: 22, - LabelSlot { - index: 8, - }: 23, - LabelSlot { - index: 9, - }: 16, - }, - instructions: [ - PushScope { - inherit_parent: true, - }, - LoadFromContextToSlot { - name: "values" (16..22), - slot: VariableSlot { - index: 4, - }, - }, - CreateIteratorFromSlotToSlot { - iterator_slot: VariableSlot { - index: 5, - }, - iterator_source_slot: VariableSlot { - index: 4, - }, - }, - GetIteratorEmptyOrJump { - iterator_slot: VariableSlot { - index: 5, - }, - jump: LabelSlot { - index: 0, - }, - }, - AdvanceIteratorOrJump { - iterator_slot: VariableSlot { - index: 5, - }, - value_slot: VariableSlot { - index: 3, - }, - jump: LabelSlot { - index: 1, - }, - }, - LoadFromSlotToContext { - value_ident: "value" (7..12), - value_slot: VariableSlot { - index: 3, - }, - }, - LoadFromContextToSlot { - name: "value" (36..41), - slot: VariableSlot { - index: 6, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 6, - }, - }, - AppendContent { - content: "\n" (44..45), - }, - Jump { - jump: LabelSlot { - index: 2, - }, - }, - PopScope, - AppendContent { - content: "\n" (55..56), - }, - PushScope { - inherit_parent: true, - }, - LoadFromContextToSlot { - name: "no_values" (72..81), - slot: VariableSlot { - index: 11, - }, - }, - CreateIteratorFromSlotToSlot { - iterator_slot: VariableSlot { - index: 12, - }, - iterator_source_slot: VariableSlot { - index: 11, - }, - }, - GetIteratorEmptyOrJump { - iterator_slot: VariableSlot { - index: 12, - }, - jump: LabelSlot { - index: 7, - }, - }, - AdvanceIteratorOrJump { - iterator_slot: VariableSlot { - index: 12, - }, - value_slot: VariableSlot { - index: 10, - }, - jump: LabelSlot { - index: 8, - }, - }, - LoadFromSlotToContext { - value_ident: "value" (63..68), - value_slot: VariableSlot { - index: 10, - }, - }, - LoadFromContextToSlot { - name: "value" (95..100), - slot: VariableSlot { - index: 13, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 13, - }, - }, - AppendContent { - content: "\n" (103..104), - }, - Jump { - jump: LabelSlot { - index: 9, - }, - }, - AppendContent { - content: "No Values >:C" (116..129), - }, - PopScope, - ], -} diff --git a/tests/cases/3-instructions@trim_whitespace.snap b/tests/cases/3-instructions@trim_whitespace.snap index 5c1ad06..754e5e9 100644 --- a/tests/cases/3-instructions@trim_whitespace.snap +++ b/tests/cases/3-instructions@trim_whitespace.snap @@ -3,52 +3,38 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/trim_whitespace.nomo --- -VMInstructions { - labels: { - LabelSlot { +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { index: 0, - }: 7, - LabelSlot { - index: 2, - }: 7, + }, }, - instructions: [ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { - index: 1, - }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, }, - JumpIfNotTrue { - emit_slot: VariableSlot { - index: 1, - }, - jump: LabelSlot { - index: 2, - }, + jump: 5, + }, + AppendContent { + content: "Hello" (19..24), + }, + AppendContent { + content: " " (24..25), + }, + LoadFromContextToSlot { + name: "stuff" (29..34), + slot: VariableSlot { + index: 1, }, - AppendContent { - content: "Hello" (19..24), + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, }, - AppendContent { - content: " " (24..25), - }, - LoadFromContextToSlot { - name: "stuff" (29..34), - slot: VariableSlot { - index: 3, - }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 3, - }, - }, - Jump { - jump: LabelSlot { - index: 0, - }, - }, - NoOp, - ], -} + }, + Jump { + jump: 1, + }, + NoOp, +] diff --git a/tests/cases/4-output@if_else_if.snap b/tests/cases/4-output@if_else_if.snap index c815937..8908bd0 100644 --- a/tests/cases/4-output@if_else_if.snap +++ b/tests/cases/4-output@if_else_if.snap @@ -3,4 +3,4 @@ source: tests/file_tests.rs expression: output input_file: tests/cases/if_else_if.nomo --- -"\n Hello World!\n" +"\n \n Hello World!\n" diff --git a/tests/cases/4-output@simple_for.snap b/tests/cases/4-output@simple_for.snap deleted file mode 100644 index 0c2ca8d..0000000 --- a/tests/cases/4-output@simple_for.snap +++ /dev/null @@ -1,13 +0,0 @@ ---- -source: tests/file_tests.rs -expression: output -info: - input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}" - context: - no_values: [] - values: - - one - - two -input_file: tests/cases/simple_for.nomo ---- -"one\ntwo\n\nNo Values >:C" diff --git a/tests/cases/simple_for.nomo b/tests/cases/simple_for.nomo deleted file mode 100644 index b9fa46f..0000000 --- a/tests/cases/simple_for.nomo +++ /dev/null @@ -1,13 +0,0 @@ -{ - "values": [ "one", "two" ], - "no_values": [] -} ---- -{{ for value in values -}} - {{-= value }} -{{- end }} -{{ for value in no_values -}} - {{-= value }} -{{ else -}} -No Values >:C -{{- end }} \ No newline at end of file diff --git a/tests/file_tests.rs b/tests/file_tests.rs index 523758d..3acb9ab 100644 --- a/tests/file_tests.rs +++ b/tests/file_tests.rs @@ -2,12 +2,6 @@ use std::collections::HashMap; use nomo::Context; -#[derive(serde::Serialize)] -struct Info { - input: String, - context: HashMap, -} - #[test] fn check_cases() { insta::glob!("cases/*.nomo", |path| { @@ -15,6 +9,7 @@ fn check_cases() { settings.set_snapshot_path("cases"); settings.set_snapshot_suffix(path.file_stem().unwrap().display().to_string()); settings.set_prepend_module_to_snapshot(false); + let _guard = settings.bind_to_scope(); let input = std::fs::read_to_string(path).unwrap(); @@ -26,21 +21,14 @@ fn check_cases() { HashMap::new() }; - settings.set_info(&Info { - input: input.to_string(), - context: map.clone(), - }); - let mut context = Context::new(); for (k, v) in map { - context.try_insert(k, v).unwrap(); + context.insert(k, v); } let parsed = nomo::parser::parse(input.into()).unwrap(); - let _guard = settings.bind_to_scope(); - insta::assert_debug_snapshot!("1-parsed", parsed); let ast = match nomo::ast::parse(parsed.tokens()) {