diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 04930c6..5ce4ac7 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -1,8 +1,11 @@ +use std::collections::HashMap; + use crate::ast::TemplateAstExpr; use crate::input::NomoInput; pub struct EmitMachine { current_index: usize, + labels: HashMap, } impl EmitMachine { @@ -15,6 +18,22 @@ 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)] @@ -22,7 +41,12 @@ pub struct VariableSlot { index: usize, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LabelSlot { + index: usize, +} + +#[derive(Debug, Clone)] pub enum Instruction { AppendContent { content: NomoInput, @@ -40,24 +64,36 @@ pub enum Instruction { Abort, JumpIfNotTrue { emit_slot: VariableSlot, - jump: isize, + jump: LabelSlot, }, Jump { - jump: isize, + jump: LabelSlot, }, NoOp, } -pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec { +#[derive(Debug, Clone)] +pub struct VMInstructions { + pub labels: HashMap, + pub instructions: Vec, +} + +pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> VMInstructions { let mut eval = vec![]; - let mut machine = EmitMachine { current_index: 0 }; + let mut machine = EmitMachine { + current_index: 0, + labels: HashMap::new(), + }; for ast in input.root() { emit_ast_expr(&mut machine, &mut eval, ast); } - eval + VMInstructions { + labels: machine.labels, + instructions: eval, + } } fn emit_ast_expr( @@ -95,10 +131,11 @@ 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(); @@ -114,7 +151,7 @@ fn emit_ast_expr( } end_indices.push(eval.len()); - eval.push(Instruction::Jump { jump: isize::MAX }); + eval.push(Instruction::Jump { jump: end_label }); } else if let TemplateAstExpr::Block { prev_whitespace_content, post_whitespace_content, @@ -139,20 +176,15 @@ fn emit_ast_expr( let emit_slot = machine.reserve_slot(); emit_expr_load(machine, eval, emit_slot, expression); - previous_jump = Some(eval.len()); + let jmp_label = machine.reserve_label(); + previous_jump = Some(jmp_label); eval.push(Instruction::JumpIfNotTrue { emit_slot, - jump: isize::MAX, + jump: jmp_label, }); } else if let TemplateAstExpr::ElseConditional { expression } = &**expression { if let Some(previous_jump) = previous_jump.take() { - 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(previous_jump, eval.len()); } else { panic!("Got an else without a previous if?"); } @@ -161,10 +193,11 @@ fn emit_ast_expr( let emit_slot = machine.reserve_slot(); emit_expr_load(machine, eval, emit_slot, expression); - previous_jump = Some(eval.len()); + let jmp_label = machine.reserve_label(); + previous_jump = Some(jmp_label); eval.push(Instruction::JumpIfNotTrue { emit_slot, - jump: isize::MAX, + jump: jmp_label, }); } else { // We don't have to do anything in the else case @@ -176,13 +209,9 @@ fn emit_ast_expr( } if let Some(previous_jump) = previous_jump.take() { - 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(previous_jump, eval.len()); } + machine.assign_label(end_label, eval.len()); if let Some(ws) = previous_post_whitespace_content { eval.push(Instruction::AppendContent { @@ -191,13 +220,6 @@ fn emit_ast_expr( } else { eval.push(Instruction::NoOp); } - - for index in end_indices { - let jump = eval.len() - index - 1; - eval[index] = Instruction::Jump { - jump: jump as isize, - }; - } } TemplateAstExpr::Block { .. } @@ -258,25 +280,28 @@ mod tests { let emit = emit_machine(ast); insta::assert_debug_snapshot!(emit, @r#" - [ - AppendContent { - content: "Hello" (0..5), - }, - AppendContent { - content: " " (5..6), - }, - LoadFromContextToSlot { - name: "world" (10..15), - slot: VariableSlot { - index: 0, + VMInstructions { + labels: {}, + instructions: [ + AppendContent { + content: "Hello" (0..5), }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, + AppendContent { + content: " " (5..6), }, - }, - ] + 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 469aa47..5a6c89f 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,75 +2,98 @@ source: src/emit/mod.rs expression: emit --- -[ - LoadFromContextToSlot { - name: "foo" (6..9), - slot: VariableSlot { +VMInstructions { + labels: { + LabelSlot { + index: 4, + }: 14, + LabelSlot { index: 0, + }: 19, + LabelSlot { + index: 2, + }: 7, + }, + 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), }, - 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, -] + 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, + ], +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 10250f1..b44ecaa 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -5,6 +5,7 @@ use thiserror::Error; use crate::Context; use crate::emit::Instruction; +use crate::emit::VMInstructions; use crate::input::NomoInput; #[derive(Debug, Error, Display)] @@ -17,24 +18,21 @@ pub enum EvaluationError { ** ** This is an internal error and is a bug that should be reported */ - InstructionPointerOverflow, + LabelNotFound, } -pub fn execute( - instructions: &[Instruction], - global_context: &Context, -) -> Result { +pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result { let mut output = String::new(); let mut scopes: HashMap = HashMap::new(); let mut ip = 0; loop { - if ip >= instructions.len() { + if ip >= vm.instructions.len() { break; } - let instr = instructions.get(ip).unwrap(); + let instr = vm.instructions.get(ip).unwrap(); match instr { Instruction::NoOp => (), @@ -58,25 +56,21 @@ pub fn execute( if dont_jump { // We are done } else { - let (new_ip, overflow) = ip.overflowing_add_signed(*jump); + let Some(new_ip) = vm.labels.get(jump) else { + return Err(EvaluationError::LabelNotFound); + }; - if overflow { - return Err(EvaluationError::InstructionPointerOverflow); - } else { - ip = new_ip; - continue; - } + ip = *new_ip; + continue; } } Instruction::Jump { jump } => { - let (new_ip, overflow) = ip.overflowing_add_signed(*jump); + let Some(new_ip) = vm.labels.get(jump) else { + return Err(EvaluationError::LabelNotFound); + }; - if overflow { - return Err(EvaluationError::InstructionPointerOverflow); - } else { - ip = new_ip; - continue; - } + ip = *new_ip; + continue; } } diff --git a/src/lib.rs b/src/lib.rs index 2679358..53024bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ use serde::Serialize; use thiserror::Error; use crate::emit::Instruction; +use crate::emit::VMInstructions; use crate::input::NomoInput; pub mod ast; @@ -84,7 +85,7 @@ impl Nomo { } struct Template { - instructions: Vec, + instructions: VMInstructions, } pub struct Context { diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index c2901f7..1b96697 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -3,46 +3,60 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/condition.nomo --- -[ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { +VMInstructions { + labels: { + LabelSlot { + index: 2, + }: 7, + LabelSlot { index: 0, + }: 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 36bc082..01ec3a8 100644 --- a/tests/cases/3-instructions@identifiers.snap +++ b/tests/cases/3-instructions@identifiers.snap @@ -3,86 +3,89 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/identifiers.nomo --- -[ - LoadFromContextToSlot { - name: "_name" (4..9), - slot: VariableSlot { - index: 0, +VMInstructions { + labels: {}, + instructions: [ + LoadFromContextToSlot { + name: "_name" (4..9), + slot: VariableSlot { + index: 0, + }, }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, + EmitFromSlot { + slot: VariableSlot { + index: 0, + }, }, - }, - AppendContent { - content: "\n" (12..13), - }, - LoadFromContextToSlot { - name: "a_name" (17..23), - slot: VariableSlot { - index: 1, + AppendContent { + content: "\n" (12..13), }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 1, + LoadFromContextToSlot { + name: "a_name" (17..23), + slot: VariableSlot { + index: 1, + }, }, - }, - AppendContent { - content: "\n" (26..27), - }, - LoadFromContextToSlot { - name: "1name" (31..36), - slot: VariableSlot { - index: 2, + EmitFromSlot { + slot: VariableSlot { + index: 1, + }, }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 2, + AppendContent { + content: "\n" (26..27), }, - }, - AppendContent { - content: "\n" (39..40), - }, - LoadFromContextToSlot { - name: "_name1" (44..50), - slot: VariableSlot { - index: 3, + LoadFromContextToSlot { + name: "1name" (31..36), + slot: VariableSlot { + index: 2, + }, }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 3, + EmitFromSlot { + slot: VariableSlot { + index: 2, + }, }, - }, - AppendContent { - content: "\n" (53..54), - }, - LoadFromContextToSlot { - name: "_namE" (58..63), - slot: VariableSlot { - index: 4, + AppendContent { + content: "\n" (39..40), }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 4, + LoadFromContextToSlot { + name: "_name1" (44..50), + slot: VariableSlot { + index: 3, + }, }, - }, - AppendContent { - content: "\n" (66..67), - }, - LoadFromContextToSlot { - name: "name1" (71..76), - slot: VariableSlot { - index: 5, + EmitFromSlot { + slot: VariableSlot { + index: 3, + }, }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 5, + AppendContent { + content: "\n" (53..54), }, - }, -] + 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 c9ab17d..787d521 100644 --- a/tests/cases/3-instructions@if_else_if.snap +++ b/tests/cases/3-instructions@if_else_if.snap @@ -3,60 +3,81 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/if_else_if.nomo --- -[ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { +VMInstructions { + labels: { + LabelSlot { + index: 2, + }: 7, + LabelSlot { + index: 4, + }: 14, + LabelSlot { index: 0, + }: 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), }, - 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, -] + 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, + ], +} diff --git a/tests/cases/3-instructions@interpolation.snap b/tests/cases/3-instructions@interpolation.snap index f4873f3..82dc16b 100644 --- a/tests/cases/3-instructions@interpolation.snap +++ b/tests/cases/3-instructions@interpolation.snap @@ -3,22 +3,25 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/interpolation.nomo --- -[ - AppendContent { - content: "Hello! I'm" (0..10), - }, - AppendContent { - content: " " (10..11), - }, - LoadFromContextToSlot { - name: "name" (15..19), - slot: VariableSlot { - index: 0, +VMInstructions { + labels: {}, + instructions: [ + AppendContent { + content: "Hello! I'm" (0..10), }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, + AppendContent { + content: " " (10..11), }, - }, -] + 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 99ccf8f..b6f13eb 100644 --- a/tests/cases/3-instructions@multiple.snap +++ b/tests/cases/3-instructions@multiple.snap @@ -3,36 +3,39 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/multiple.nomo --- -[ - AppendContent { - content: "Hi there! My name is" (0..20), - }, - AppendContent { - content: " " (20..21), - }, - LoadFromContextToSlot { - name: "name" (25..29), - slot: VariableSlot { - index: 0, +VMInstructions { + labels: {}, + instructions: [ + AppendContent { + content: "Hi there! My name is" (0..20), }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 0, + AppendContent { + content: " " (20..21), }, - }, - AppendContent { - content: " " (32..33), - }, - LoadFromContextToSlot { - name: "lastname" (37..45), - slot: VariableSlot { - index: 1, + LoadFromContextToSlot { + name: "name" (25..29), + slot: VariableSlot { + index: 0, + }, }, - }, - EmitFromSlot { - slot: VariableSlot { - index: 1, + EmitFromSlot { + slot: VariableSlot { + index: 0, + }, }, - }, -] + 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 0f495a6..752a53b 100644 --- a/tests/cases/3-instructions@simple.snap +++ b/tests/cases/3-instructions@simple.snap @@ -3,8 +3,11 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/simple.nomo --- -[ - AppendContent { - content: "Hello World!" (0..12), - }, -] +VMInstructions { + labels: {}, + instructions: [ + AppendContent { + content: "Hello World!" (0..12), + }, + ], +} diff --git a/tests/cases/3-instructions@trim_whitespace.snap b/tests/cases/3-instructions@trim_whitespace.snap index 754e5e9..7add748 100644 --- a/tests/cases/3-instructions@trim_whitespace.snap +++ b/tests/cases/3-instructions@trim_whitespace.snap @@ -3,38 +3,52 @@ source: tests/file_tests.rs expression: emit input_file: tests/cases/trim_whitespace.nomo --- -[ - LoadFromContextToSlot { - name: "test" (6..10), - slot: VariableSlot { +VMInstructions { + labels: { + LabelSlot { + index: 2, + }: 7, + LabelSlot { index: 0, + }: 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), }, - }, - Jump { - jump: 1, - }, - NoOp, -] + LoadFromContextToSlot { + name: "stuff" (29..34), + slot: VariableSlot { + index: 3, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 3, + }, + }, + Jump { + jump: LabelSlot { + index: 0, + }, + }, + NoOp, + ], +} diff --git a/tests/cases/4-output@if_else_if.snap b/tests/cases/4-output@if_else_if.snap index 8908bd0..c815937 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 \n Hello World!\n" +"\n Hello World!\n"