Introduce JumpLabels instead of manually correct jump positions

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-11 15:50:05 +01:00
parent e64256b65f
commit 7182024342
12 changed files with 485 additions and 381 deletions

View file

@ -1,8 +1,11 @@
use std::collections::HashMap;
use crate::ast::TemplateAstExpr;
use crate::input::NomoInput;
pub struct EmitMachine {
current_index: usize,
labels: HashMap<LabelSlot, usize>,
}
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<Instruction> {
#[derive(Debug, Clone)]
pub struct VMInstructions {
pub labels: HashMap<LabelSlot, usize>,
pub instructions: Vec<Instruction>,
}
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<crate::parser::TemplateToken> = &None;
let mut previous_jump: Option<usize> = None;
let mut previous_jump: Option<LabelSlot> = 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,
},
},
],
}
"#);
}

View file

@ -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,
],
}

View file

@ -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<String, EvaluationError> {
pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String, EvaluationError> {
let mut output = String::new();
let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = 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;
}
}

View file

@ -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<Instruction>,
instructions: VMInstructions,
}
pub struct Context {

View file

@ -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,
},
},
],
}

View file

@ -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,
},
},
],
}

View file

@ -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,
],
}

View file

@ -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,
},
},
],
}

View file

@ -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,
},
},
],
}

View file

@ -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),
},
],
}

View file

@ -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,
],
}

View file

@ -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"