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
123
src/emit/mod.rs
123
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<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,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
"#);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue