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::ast::TemplateAstExpr;
use crate::input::NomoInput; use crate::input::NomoInput;
pub struct EmitMachine { pub struct EmitMachine {
current_index: usize, current_index: usize,
labels: HashMap<LabelSlot, usize>,
} }
impl EmitMachine { 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)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -22,7 +41,12 @@ pub struct VariableSlot {
index: usize, index: usize,
} }
#[derive(Debug)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LabelSlot {
index: usize,
}
#[derive(Debug, Clone)]
pub enum Instruction { pub enum Instruction {
AppendContent { AppendContent {
content: NomoInput, content: NomoInput,
@ -40,24 +64,36 @@ pub enum Instruction {
Abort, Abort,
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot, emit_slot: VariableSlot,
jump: isize, jump: LabelSlot,
}, },
Jump { Jump {
jump: isize, jump: LabelSlot,
}, },
NoOp, 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 eval = vec![];
let mut machine = EmitMachine { current_index: 0 }; let mut machine = EmitMachine {
current_index: 0,
labels: HashMap::new(),
};
for ast in input.root() { for ast in input.root() {
emit_ast_expr(&mut machine, &mut eval, ast); emit_ast_expr(&mut machine, &mut eval, ast);
} }
eval VMInstructions {
labels: machine.labels,
instructions: eval,
}
} }
fn emit_ast_expr( fn emit_ast_expr(
@ -95,10 +131,11 @@ fn emit_ast_expr(
TemplateAstExpr::ConditionalChain { chain } => { TemplateAstExpr::ConditionalChain { chain } => {
let mut chain = chain.iter(); let mut chain = chain.iter();
let end_label = machine.reserve_label();
let mut end_indices = vec![]; let mut end_indices = vec![];
let mut previous_post_whitespace_content: &Option<crate::parser::TemplateToken> = &None; 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 { loop {
let next = chain.next().unwrap(); let next = chain.next().unwrap();
@ -114,7 +151,7 @@ fn emit_ast_expr(
} }
end_indices.push(eval.len()); end_indices.push(eval.len());
eval.push(Instruction::Jump { jump: isize::MAX }); eval.push(Instruction::Jump { jump: end_label });
} else if let TemplateAstExpr::Block { } else if let TemplateAstExpr::Block {
prev_whitespace_content, prev_whitespace_content,
post_whitespace_content, post_whitespace_content,
@ -139,20 +176,15 @@ fn emit_ast_expr(
let emit_slot = machine.reserve_slot(); let emit_slot = machine.reserve_slot();
emit_expr_load(machine, eval, emit_slot, expression); 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 { eval.push(Instruction::JumpIfNotTrue {
emit_slot, emit_slot,
jump: isize::MAX, jump: jmp_label,
}); });
} else if let TemplateAstExpr::ElseConditional { expression } = &**expression { } else if let TemplateAstExpr::ElseConditional { expression } = &**expression {
if let Some(previous_jump) = previous_jump.take() { if let Some(previous_jump) = previous_jump.take() {
let new_jump = eval.len() - previous_jump - 1; machine.assign_label(previous_jump, eval.len());
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 { } else {
panic!("Got an else without a previous if?"); panic!("Got an else without a previous if?");
} }
@ -161,10 +193,11 @@ fn emit_ast_expr(
let emit_slot = machine.reserve_slot(); let emit_slot = machine.reserve_slot();
emit_expr_load(machine, eval, emit_slot, expression); 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 { eval.push(Instruction::JumpIfNotTrue {
emit_slot, emit_slot,
jump: isize::MAX, jump: jmp_label,
}); });
} else { } else {
// We don't have to do anything in the else case // 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() { if let Some(previous_jump) = previous_jump.take() {
let new_jump = eval.len() - previous_jump - 1; machine.assign_label(previous_jump, eval.len());
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 { if let Some(ws) = previous_post_whitespace_content {
eval.push(Instruction::AppendContent { eval.push(Instruction::AppendContent {
@ -191,13 +220,6 @@ fn emit_ast_expr(
} else { } else {
eval.push(Instruction::NoOp); 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 { .. } TemplateAstExpr::Block { .. }
@ -258,7 +280,9 @@ mod tests {
let emit = emit_machine(ast); let emit = emit_machine(ast);
insta::assert_debug_snapshot!(emit, @r#" insta::assert_debug_snapshot!(emit, @r#"
[ VMInstructions {
labels: {},
instructions: [
AppendContent { AppendContent {
content: "Hello" (0..5), content: "Hello" (0..5),
}, },
@ -276,7 +300,8 @@ mod tests {
index: 0, index: 0,
}, },
}, },
] ],
}
"#); "#);
} }

View file

@ -2,18 +2,32 @@
source: src/emit/mod.rs source: src/emit/mod.rs
expression: emit expression: emit
--- ---
[ VMInstructions {
labels: {
LabelSlot {
index: 4,
}: 14,
LabelSlot {
index: 0,
}: 19,
LabelSlot {
index: 2,
}: 7,
},
instructions: [
LoadFromContextToSlot { LoadFromContextToSlot {
name: "foo" (6..9), name: "foo" (6..9),
slot: VariableSlot { slot: VariableSlot {
index: 0, index: 1,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 0, index: 1,
},
jump: LabelSlot {
index: 2,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: " " (12..13), content: " " (12..13),
@ -25,7 +39,9 @@ expression: emit
content: " " (16..17), content: " " (16..17),
}, },
Jump { Jump {
jump: 14, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: " " (12..13), content: " " (12..13),
@ -33,14 +49,16 @@ expression: emit
LoadFromContextToSlot { LoadFromContextToSlot {
name: "bar" (28..31), name: "bar" (28..31),
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 1, index: 3,
},
jump: LabelSlot {
index: 4,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: " " (34..35), content: " " (34..35),
@ -52,7 +70,9 @@ expression: emit
content: " " (38..39), content: " " (38..39),
}, },
Jump { Jump {
jump: 7, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: " " (34..35), content: " " (34..35),
@ -67,10 +87,13 @@ expression: emit
content: " " (56..57), content: " " (56..57),
}, },
Jump { Jump {
jump: 2, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: " " (49..50), content: " " (49..50),
}, },
NoOp, NoOp,
] ],
}

View file

@ -5,6 +5,7 @@ use thiserror::Error;
use crate::Context; use crate::Context;
use crate::emit::Instruction; use crate::emit::Instruction;
use crate::emit::VMInstructions;
use crate::input::NomoInput; use crate::input::NomoInput;
#[derive(Debug, Error, Display)] #[derive(Debug, Error, Display)]
@ -17,24 +18,21 @@ pub enum EvaluationError {
** **
** This is an internal error and is a bug that should be reported ** This is an internal error and is a bug that should be reported
*/ */
InstructionPointerOverflow, LabelNotFound,
} }
pub fn execute( pub fn execute(vm: &VMInstructions, global_context: &Context) -> Result<String, EvaluationError> {
instructions: &[Instruction],
global_context: &Context,
) -> Result<String, EvaluationError> {
let mut output = String::new(); let mut output = String::new();
let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = HashMap::new(); let mut scopes: HashMap<crate::emit::VariableSlot, serde_json::Value> = HashMap::new();
let mut ip = 0; let mut ip = 0;
loop { loop {
if ip >= instructions.len() { if ip >= vm.instructions.len() {
break; break;
} }
let instr = instructions.get(ip).unwrap(); let instr = vm.instructions.get(ip).unwrap();
match instr { match instr {
Instruction::NoOp => (), Instruction::NoOp => (),
@ -58,27 +56,23 @@ pub fn execute(
if dont_jump { if dont_jump {
// We are done // We are done
} else { } 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 { ip = *new_ip;
return Err(EvaluationError::InstructionPointerOverflow);
} else {
ip = new_ip;
continue; continue;
} }
} }
}
Instruction::Jump { jump } => { 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 { ip = *new_ip;
return Err(EvaluationError::InstructionPointerOverflow);
} else {
ip = new_ip;
continue; continue;
} }
} }
}
ip += 1; ip += 1;
} }

View file

@ -6,6 +6,7 @@ use serde::Serialize;
use thiserror::Error; use thiserror::Error;
use crate::emit::Instruction; use crate::emit::Instruction;
use crate::emit::VMInstructions;
use crate::input::NomoInput; use crate::input::NomoInput;
pub mod ast; pub mod ast;
@ -84,7 +85,7 @@ impl Nomo {
} }
struct Template { struct Template {
instructions: Vec<Instruction>, instructions: VMInstructions,
} }
pub struct Context { pub struct Context {

View file

@ -3,18 +3,29 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/condition.nomo input_file: tests/cases/condition.nomo
--- ---
[ VMInstructions {
labels: {
LabelSlot {
index: 2,
}: 7,
LabelSlot {
index: 0,
}: 7,
},
instructions: [
LoadFromContextToSlot { LoadFromContextToSlot {
name: "test" (6..10), name: "test" (6..10),
slot: VariableSlot { slot: VariableSlot {
index: 0, index: 1,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 0, index: 1,
},
jump: LabelSlot {
index: 2,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: "\n " (13..18), content: "\n " (13..18),
@ -26,7 +37,9 @@ input_file: tests/cases/condition.nomo
content: "\n" (30..31), content: "\n" (30..31),
}, },
Jump { Jump {
jump: 2, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: "\n " (13..18), content: "\n " (13..18),
@ -37,12 +50,13 @@ input_file: tests/cases/condition.nomo
LoadFromContextToSlot { LoadFromContextToSlot {
name: "stuff" (46..51), name: "stuff" (46..51),
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
EmitFromSlot { EmitFromSlot {
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
] ],
}

View file

@ -3,7 +3,9 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/identifiers.nomo input_file: tests/cases/identifiers.nomo
--- ---
[ VMInstructions {
labels: {},
instructions: [
LoadFromContextToSlot { LoadFromContextToSlot {
name: "_name" (4..9), name: "_name" (4..9),
slot: VariableSlot { slot: VariableSlot {
@ -85,4 +87,5 @@ input_file: tests/cases/identifiers.nomo
index: 5, index: 5,
}, },
}, },
] ],
}

View file

@ -3,18 +3,32 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/if_else_if.nomo input_file: tests/cases/if_else_if.nomo
--- ---
[ VMInstructions {
labels: {
LabelSlot {
index: 2,
}: 7,
LabelSlot {
index: 4,
}: 14,
LabelSlot {
index: 0,
}: 14,
},
instructions: [
LoadFromContextToSlot { LoadFromContextToSlot {
name: "test" (6..10), name: "test" (6..10),
slot: VariableSlot { slot: VariableSlot {
index: 0, index: 1,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 0, index: 1,
},
jump: LabelSlot {
index: 2,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: "\n " (13..18), content: "\n " (13..18),
@ -26,7 +40,9 @@ input_file: tests/cases/if_else_if.nomo
content: "\n" (37..38), content: "\n" (37..38),
}, },
Jump { Jump {
jump: 9, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: "\n " (13..18), content: "\n " (13..18),
@ -34,14 +50,16 @@ input_file: tests/cases/if_else_if.nomo
LoadFromContextToSlot { LoadFromContextToSlot {
name: "another_test" (49..61), name: "another_test" (49..61),
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 1, index: 3,
},
jump: LabelSlot {
index: 4,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: "\n " (64..69), content: "\n " (64..69),
@ -53,10 +71,13 @@ input_file: tests/cases/if_else_if.nomo
content: "\n" (81..82), content: "\n" (81..82),
}, },
Jump { Jump {
jump: 2, jump: LabelSlot {
index: 0,
},
}, },
AppendContent { AppendContent {
content: "\n " (64..69), content: "\n " (64..69),
}, },
NoOp, NoOp,
] ],
}

View file

@ -3,7 +3,9 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/interpolation.nomo input_file: tests/cases/interpolation.nomo
--- ---
[ VMInstructions {
labels: {},
instructions: [
AppendContent { AppendContent {
content: "Hello! I'm" (0..10), content: "Hello! I'm" (0..10),
}, },
@ -21,4 +23,5 @@ input_file: tests/cases/interpolation.nomo
index: 0, index: 0,
}, },
}, },
] ],
}

View file

@ -3,7 +3,9 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/multiple.nomo input_file: tests/cases/multiple.nomo
--- ---
[ VMInstructions {
labels: {},
instructions: [
AppendContent { AppendContent {
content: "Hi there! My name is" (0..20), content: "Hi there! My name is" (0..20),
}, },
@ -35,4 +37,5 @@ input_file: tests/cases/multiple.nomo
index: 1, index: 1,
}, },
}, },
] ],
}

View file

@ -3,8 +3,11 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/simple.nomo input_file: tests/cases/simple.nomo
--- ---
[ VMInstructions {
labels: {},
instructions: [
AppendContent { AppendContent {
content: "Hello World!" (0..12), content: "Hello World!" (0..12),
}, },
] ],
}

View file

@ -3,18 +3,29 @@ source: tests/file_tests.rs
expression: emit expression: emit
input_file: tests/cases/trim_whitespace.nomo input_file: tests/cases/trim_whitespace.nomo
--- ---
[ VMInstructions {
labels: {
LabelSlot {
index: 2,
}: 7,
LabelSlot {
index: 0,
}: 7,
},
instructions: [
LoadFromContextToSlot { LoadFromContextToSlot {
name: "test" (6..10), name: "test" (6..10),
slot: VariableSlot { slot: VariableSlot {
index: 0, index: 1,
}, },
}, },
JumpIfNotTrue { JumpIfNotTrue {
emit_slot: VariableSlot { emit_slot: VariableSlot {
index: 0, index: 1,
},
jump: LabelSlot {
index: 2,
}, },
jump: 5,
}, },
AppendContent { AppendContent {
content: "Hello" (19..24), content: "Hello" (19..24),
@ -25,16 +36,19 @@ input_file: tests/cases/trim_whitespace.nomo
LoadFromContextToSlot { LoadFromContextToSlot {
name: "stuff" (29..34), name: "stuff" (29..34),
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
EmitFromSlot { EmitFromSlot {
slot: VariableSlot { slot: VariableSlot {
index: 1, index: 3,
}, },
}, },
Jump { Jump {
jump: 1, jump: LabelSlot {
index: 0,
},
}, },
NoOp, NoOp,
] ],
}

View file

@ -3,4 +3,4 @@ source: tests/file_tests.rs
expression: output expression: output
input_file: tests/cases/if_else_if.nomo input_file: tests/cases/if_else_if.nomo
--- ---
"\n \n Hello World!\n" "\n Hello World!\n"