Introduce JumpLabels instead of manually correct jump positions
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
e64256b65f
commit
7182024342
12 changed files with 485 additions and 381 deletions
|
|
@ -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,7 +280,9 @@ mod tests {
|
|||
let emit = emit_machine(ast);
|
||||
|
||||
insta::assert_debug_snapshot!(emit, @r#"
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {},
|
||||
instructions: [
|
||||
AppendContent {
|
||||
content: "Hello" (0..5),
|
||||
},
|
||||
|
|
@ -276,7 +300,8 @@ mod tests {
|
|||
index: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,18 +2,32 @@
|
|||
source: src/emit/mod.rs
|
||||
expression: emit
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {
|
||||
LabelSlot {
|
||||
index: 4,
|
||||
}: 14,
|
||||
LabelSlot {
|
||||
index: 0,
|
||||
}: 19,
|
||||
LabelSlot {
|
||||
index: 2,
|
||||
}: 7,
|
||||
},
|
||||
instructions: [
|
||||
LoadFromContextToSlot {
|
||||
name: "foo" (6..9),
|
||||
slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 2,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (12..13),
|
||||
|
|
@ -25,7 +39,9 @@ expression: emit
|
|||
content: " " (16..17),
|
||||
},
|
||||
Jump {
|
||||
jump: 14,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (12..13),
|
||||
|
|
@ -33,14 +49,16 @@ expression: emit
|
|||
LoadFromContextToSlot {
|
||||
name: "bar" (28..31),
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 4,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (34..35),
|
||||
|
|
@ -52,7 +70,9 @@ expression: emit
|
|||
content: " " (38..39),
|
||||
},
|
||||
Jump {
|
||||
jump: 7,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (34..35),
|
||||
|
|
@ -67,10 +87,13 @@ expression: emit
|
|||
content: " " (56..57),
|
||||
},
|
||||
Jump {
|
||||
jump: 2,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (49..50),
|
||||
},
|
||||
NoOp,
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,27 +56,23 @@ 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;
|
||||
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;
|
||||
ip = *new_ip;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ip += 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -3,18 +3,29 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/condition.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {
|
||||
LabelSlot {
|
||||
index: 2,
|
||||
}: 7,
|
||||
LabelSlot {
|
||||
index: 0,
|
||||
}: 7,
|
||||
},
|
||||
instructions: [
|
||||
LoadFromContextToSlot {
|
||||
name: "test" (6..10),
|
||||
slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 2,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (13..18),
|
||||
|
|
@ -26,7 +37,9 @@ input_file: tests/cases/condition.nomo
|
|||
content: "\n" (30..31),
|
||||
},
|
||||
Jump {
|
||||
jump: 2,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (13..18),
|
||||
|
|
@ -37,12 +50,13 @@ input_file: tests/cases/condition.nomo
|
|||
LoadFromContextToSlot {
|
||||
name: "stuff" (46..51),
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
EmitFromSlot {
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/identifiers.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {},
|
||||
instructions: [
|
||||
LoadFromContextToSlot {
|
||||
name: "_name" (4..9),
|
||||
slot: VariableSlot {
|
||||
|
|
@ -85,4 +87,5 @@ input_file: tests/cases/identifiers.nomo
|
|||
index: 5,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,32 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/if_else_if.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {
|
||||
LabelSlot {
|
||||
index: 2,
|
||||
}: 7,
|
||||
LabelSlot {
|
||||
index: 4,
|
||||
}: 14,
|
||||
LabelSlot {
|
||||
index: 0,
|
||||
}: 14,
|
||||
},
|
||||
instructions: [
|
||||
LoadFromContextToSlot {
|
||||
name: "test" (6..10),
|
||||
slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 2,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (13..18),
|
||||
|
|
@ -26,7 +40,9 @@ input_file: tests/cases/if_else_if.nomo
|
|||
content: "\n" (37..38),
|
||||
},
|
||||
Jump {
|
||||
jump: 9,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (13..18),
|
||||
|
|
@ -34,14 +50,16 @@ input_file: tests/cases/if_else_if.nomo
|
|||
LoadFromContextToSlot {
|
||||
name: "another_test" (49..61),
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 4,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (64..69),
|
||||
|
|
@ -53,10 +71,13 @@ input_file: tests/cases/if_else_if.nomo
|
|||
content: "\n" (81..82),
|
||||
},
|
||||
Jump {
|
||||
jump: 2,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
AppendContent {
|
||||
content: "\n " (64..69),
|
||||
},
|
||||
NoOp,
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/interpolation.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {},
|
||||
instructions: [
|
||||
AppendContent {
|
||||
content: "Hello! I'm" (0..10),
|
||||
},
|
||||
|
|
@ -21,4 +23,5 @@ input_file: tests/cases/interpolation.nomo
|
|||
index: 0,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ 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),
|
||||
},
|
||||
|
|
@ -35,4 +37,5 @@ input_file: tests/cases/multiple.nomo
|
|||
index: 1,
|
||||
},
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/simple.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {},
|
||||
instructions: [
|
||||
AppendContent {
|
||||
content: "Hello World!" (0..12),
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,18 +3,29 @@ source: tests/file_tests.rs
|
|||
expression: emit
|
||||
input_file: tests/cases/trim_whitespace.nomo
|
||||
---
|
||||
[
|
||||
VMInstructions {
|
||||
labels: {
|
||||
LabelSlot {
|
||||
index: 2,
|
||||
}: 7,
|
||||
LabelSlot {
|
||||
index: 0,
|
||||
}: 7,
|
||||
},
|
||||
instructions: [
|
||||
LoadFromContextToSlot {
|
||||
name: "test" (6..10),
|
||||
slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 0,
|
||||
index: 1,
|
||||
},
|
||||
jump: LabelSlot {
|
||||
index: 2,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: "Hello" (19..24),
|
||||
|
|
@ -25,16 +36,19 @@ input_file: tests/cases/trim_whitespace.nomo
|
|||
LoadFromContextToSlot {
|
||||
name: "stuff" (29..34),
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
EmitFromSlot {
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
index: 3,
|
||||
},
|
||||
},
|
||||
Jump {
|
||||
jump: 1,
|
||||
jump: LabelSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
NoOp,
|
||||
]
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue