Compare commits
7 commits
3f549690c1
...
474324726a
| Author | SHA1 | Date | |
|---|---|---|---|
| 474324726a | |||
| dc8281036c | |||
| 42e0056374 | |||
| 7182024342 | |||
| e64256b65f | |||
| a099c74b1b | |||
| 018ba3cd2c |
26 changed files with 1677 additions and 429 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -371,6 +371,7 @@ dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"globset",
|
"globset",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"serde",
|
||||||
"similar",
|
"similar",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
|
@ -439,7 +440,6 @@ dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"insta",
|
"insta",
|
||||||
"nomo",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
|
|
|
||||||
11
Cargo.toml
11
Cargo.toml
|
|
@ -13,20 +13,21 @@ debug = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
annotate-snippets = "0.12.13"
|
annotate-snippets = "0.12.13"
|
||||||
displaydoc = "0.2.5"
|
displaydoc = "0.2.5"
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde_json = { version = "1.0.149", optional = true }
|
||||||
serde_json = "1.0.149"
|
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
winnow = { version = "0.7.14", features = ["unstable-recover"] }
|
winnow = { version = "0.7.14", features = ["unstable-recover"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
annotate-snippets = { version = "0.12.13", features = ["testing-colors"] }
|
annotate-snippets = { version = "0.12.13", features = ["testing-colors"] }
|
||||||
criterion = "0.8.2"
|
criterion = "0.8.2"
|
||||||
insta = { version = "1.46.3", features = ["glob"] }
|
insta = { version = "1.46.3", features = ["glob", "serde"] }
|
||||||
nomo = { path = ".", features = ["serialize"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
|
||||||
[profile.dev.package]
|
[profile.dev.package]
|
||||||
insta.opt-level = 3
|
insta.opt-level = 3
|
||||||
similar.opt-level = 3
|
similar.opt-level = 3
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
serialize = []
|
default = ["serde_json"]
|
||||||
|
serde_json = ["dep:serde_json"]
|
||||||
|
|
|
||||||
|
|
@ -23,5 +23,26 @@ fn parsing_benchmark(c: &mut Criterion) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
criterion_group!(benches, parsing_benchmark);
|
fn parsing_nested(c: &mut Criterion) {
|
||||||
|
let mut parsing = c.benchmark_group("Parsing");
|
||||||
|
|
||||||
|
for size in [1, 2, 8, 12] {
|
||||||
|
let mut input = String::new();
|
||||||
|
|
||||||
|
for _ in 0..size {
|
||||||
|
input = format!(
|
||||||
|
"{{{{ for foo in bar }}}} {input} {{{{ if foo }}}} Hi! {input} {{{{ end }}}} Yooo {{{{ end }}}}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let input = NomoInput::from(input);
|
||||||
|
|
||||||
|
parsing.throughput(criterion::Throughput::Bytes(input.len() as u64));
|
||||||
|
parsing.bench_with_input(BenchmarkId::from_parameter(size), &input, |b, input| {
|
||||||
|
b.iter(|| nomo::parser::parse(input.clone()).unwrap());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, parsing_benchmark, parsing_nested);
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
|
||||||
2
fuzz/Cargo.lock
generated
2
fuzz/Cargo.lock
generated
|
|
@ -116,7 +116,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"annotate-snippets",
|
"annotate-snippets",
|
||||||
"displaydoc",
|
"displaydoc",
|
||||||
"serde",
|
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"winnow",
|
"winnow",
|
||||||
|
|
@ -161,7 +160,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
"serde_derive",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
109
src/ast/mod.rs
109
src/ast/mod.rs
|
|
@ -217,15 +217,32 @@ pub fn parse(input: &[TemplateToken]) -> Result<TemplateAst<'_>, AstFailure> {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum TemplateAstExpr<'input> {
|
pub enum TemplateAstExpr<'input> {
|
||||||
StaticContent(TemplateToken),
|
StaticContent(TemplateToken),
|
||||||
|
Block {
|
||||||
|
prev_whitespace_content: Option<TemplateToken>,
|
||||||
|
expression: Box<TemplateAstExpr<'input>>,
|
||||||
|
post_whitespace_content: Option<TemplateToken>,
|
||||||
|
},
|
||||||
Interpolation {
|
Interpolation {
|
||||||
prev_whitespace_content: Option<TemplateToken>,
|
prev_whitespace_content: Option<TemplateToken>,
|
||||||
expression: Box<TemplateAstExpr<'input>>,
|
expression: Box<TemplateAstExpr<'input>>,
|
||||||
post_whitespace_content: Option<TemplateToken>,
|
post_whitespace_content: Option<TemplateToken>,
|
||||||
},
|
},
|
||||||
VariableAccess(TemplateToken),
|
|
||||||
ConditionalChain {
|
ConditionalChain {
|
||||||
chain: Vec<TemplateAstExpr<'input>>,
|
chain: Vec<TemplateAstExpr<'input>>,
|
||||||
},
|
},
|
||||||
|
ForChain {
|
||||||
|
for_block: Box<TemplateAstExpr<'input>>,
|
||||||
|
content: Vec<TemplateAstExpr<'input>>,
|
||||||
|
else_block: Option<Box<TemplateAstExpr<'input>>>,
|
||||||
|
else_content: Option<Vec<TemplateAstExpr<'input>>>,
|
||||||
|
end_block: Box<TemplateAstExpr<'input>>,
|
||||||
|
},
|
||||||
|
For {
|
||||||
|
value_ident: TemplateToken,
|
||||||
|
value_expression: Box<TemplateAstExpr<'input>>,
|
||||||
|
},
|
||||||
|
ForElse,
|
||||||
|
VariableAccess(TemplateToken),
|
||||||
IfConditional {
|
IfConditional {
|
||||||
expression: Box<TemplateAstExpr<'input>>,
|
expression: Box<TemplateAstExpr<'input>>,
|
||||||
},
|
},
|
||||||
|
|
@ -237,11 +254,6 @@ pub enum TemplateAstExpr<'input> {
|
||||||
},
|
},
|
||||||
Invalid(&'input [TemplateToken]),
|
Invalid(&'input [TemplateToken]),
|
||||||
EndBlock,
|
EndBlock,
|
||||||
Block {
|
|
||||||
prev_whitespace_content: Option<TemplateToken>,
|
|
||||||
expression: Box<TemplateAstExpr<'input>>,
|
|
||||||
post_whitespace_content: Option<TemplateToken>,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_asts<'input>(input: &mut Input<'input>) -> Result<Vec<TemplateAstExpr<'input>>, AstError> {
|
fn parse_asts<'input>(input: &mut Input<'input>) -> Result<Vec<TemplateAstExpr<'input>>, AstError> {
|
||||||
|
|
@ -316,6 +328,7 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'in
|
||||||
"action",
|
"action",
|
||||||
alt((
|
alt((
|
||||||
parse_conditional_chain,
|
parse_conditional_chain,
|
||||||
|
parse_for_chain,
|
||||||
(parse_block(
|
(parse_block(
|
||||||
cut_err(not(repeat_till(
|
cut_err(not(repeat_till(
|
||||||
0..,
|
0..,
|
||||||
|
|
@ -336,6 +349,67 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'in
|
||||||
.parse_next(input)
|
.parse_next(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_for_chain<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||||
|
trace("for_loop", |input: &mut Input<'input>| {
|
||||||
|
let for_block = parse_for_loop.map(Box::new).parse_next(input)?;
|
||||||
|
|
||||||
|
let loop_end = (
|
||||||
|
opt((
|
||||||
|
parse_loop_else.map(Box::new),
|
||||||
|
repeat_till(0.., parse_ast, peek(parse_end)).map(|(val, _)| val),
|
||||||
|
)),
|
||||||
|
parse_end.map(Box::new),
|
||||||
|
);
|
||||||
|
|
||||||
|
let (content, taken) = resume_after_cut(
|
||||||
|
repeat_till(0.., parse_ast, loop_end),
|
||||||
|
repeat_till(0.., any, parse_end).map(|((), _)| ()),
|
||||||
|
)
|
||||||
|
.with_taken()
|
||||||
|
.parse_next(input)?;
|
||||||
|
|
||||||
|
let Some((content, (else_stuff, end_block))) = content else {
|
||||||
|
return Ok(TemplateAstExpr::Invalid(taken));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (else_block, else_content) = else_stuff.unzip();
|
||||||
|
|
||||||
|
Ok(TemplateAstExpr::ForChain {
|
||||||
|
for_block,
|
||||||
|
content,
|
||||||
|
else_block,
|
||||||
|
else_content,
|
||||||
|
end_block,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_for_loop<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||||
|
trace(
|
||||||
|
"for_loop_inner",
|
||||||
|
parse_block(
|
||||||
|
(
|
||||||
|
ws,
|
||||||
|
TokenKind::For,
|
||||||
|
ws,
|
||||||
|
TokenKind::Ident,
|
||||||
|
ws,
|
||||||
|
TokenKind::In,
|
||||||
|
ws,
|
||||||
|
parse_value_expression.map(Box::new),
|
||||||
|
)
|
||||||
|
.map(|(_, _for, _, value_ident, _, _in, _, value_expression)| {
|
||||||
|
TemplateAstExpr::For {
|
||||||
|
value_ident,
|
||||||
|
value_expression,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_conditional_chain<'input>(
|
fn parse_conditional_chain<'input>(
|
||||||
input: &mut Input<'input>,
|
input: &mut Input<'input>,
|
||||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||||
|
|
@ -447,6 +521,18 @@ fn parse_conditional_else<'input>(
|
||||||
.parse_next(input)
|
.parse_next(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_loop_else<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||||
|
trace(
|
||||||
|
"end",
|
||||||
|
parse_block(
|
||||||
|
TokenKind::ConditionalElse
|
||||||
|
.value(TemplateAstExpr::ForElse)
|
||||||
|
.context(AstError::ctx().msg("Expected an else block here")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_end<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
fn parse_end<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||||
trace(
|
trace(
|
||||||
"end",
|
"end",
|
||||||
|
|
@ -815,4 +901,15 @@ mod tests {
|
||||||
|
|
||||||
insta::assert_debug_snapshot!(ast);
|
insta::assert_debug_snapshot!(ast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_for_loop() {
|
||||||
|
let input = "{{ for value in array }} Hi: {{= value }} {{ else }} No Content :C {{ end }}";
|
||||||
|
|
||||||
|
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
|
let ast = panic_pretty(input, parse(parsed.tokens()));
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(ast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
61
src/ast/snapshots/nomo__ast__tests__check_for_loop.snap
Normal file
61
src/ast/snapshots/nomo__ast__tests__check_for_loop.snap
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
---
|
||||||
|
source: src/ast/mod.rs
|
||||||
|
expression: ast
|
||||||
|
---
|
||||||
|
TemplateAst {
|
||||||
|
root: [
|
||||||
|
ForChain {
|
||||||
|
for_block: Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: For {
|
||||||
|
value_ident: [Ident]"value" (7..12),
|
||||||
|
value_expression: VariableAccess(
|
||||||
|
[Ident]"array" (16..21),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]" " (24..25),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
content: [
|
||||||
|
StaticContent(
|
||||||
|
[Content]"Hi:" (25..28),
|
||||||
|
),
|
||||||
|
Interpolation {
|
||||||
|
prev_whitespace_content: Some(
|
||||||
|
[Whitespace]" " (28..29),
|
||||||
|
),
|
||||||
|
expression: VariableAccess(
|
||||||
|
[Ident]"value" (33..38),
|
||||||
|
),
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]" " (41..42),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
else_block: Some(
|
||||||
|
Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: ForElse,
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]" " (52..53),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
else_content: Some(
|
||||||
|
[
|
||||||
|
StaticContent(
|
||||||
|
[Content]"No Content :C" (53..66),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
end_block: Block {
|
||||||
|
prev_whitespace_content: Some(
|
||||||
|
[Whitespace]" " (66..67),
|
||||||
|
),
|
||||||
|
expression: EndBlock,
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
267
src/emit/mod.rs
267
src/emit/mod.rs
|
|
@ -1,8 +1,11 @@
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
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: BTreeMap<LabelSlot, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmitMachine {
|
impl EmitMachine {
|
||||||
|
|
@ -15,14 +18,35 @@ 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, PartialOrd, Ord)]
|
||||||
pub struct VariableSlot {
|
pub struct VariableSlot {
|
||||||
index: usize,
|
index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct LabelSlot {
|
||||||
|
index: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum Instruction {
|
pub enum Instruction {
|
||||||
AppendContent {
|
AppendContent {
|
||||||
content: NomoInput,
|
content: NomoInput,
|
||||||
|
|
@ -40,24 +64,54 @@ 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,
|
||||||
|
CreateIteratorFromSlotToSlot {
|
||||||
|
iterator_slot: VariableSlot,
|
||||||
|
iterator_source_slot: VariableSlot,
|
||||||
|
},
|
||||||
|
AdvanceIteratorOrJump {
|
||||||
|
iterator_slot: VariableSlot,
|
||||||
|
value_slot: VariableSlot,
|
||||||
|
jump: LabelSlot,
|
||||||
|
},
|
||||||
|
GetIteratorEmptyOrJump {
|
||||||
|
iterator_slot: VariableSlot,
|
||||||
|
jump: LabelSlot,
|
||||||
|
},
|
||||||
|
PopScope,
|
||||||
|
LoadFromSlotToContext {
|
||||||
|
value_ident: NomoInput,
|
||||||
|
value_slot: VariableSlot,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec<Instruction> {
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VMInstructions {
|
||||||
|
pub labels: BTreeMap<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: BTreeMap::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 +149,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 +169,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 +194,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 +211,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 +227,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,12 +238,159 @@ fn emit_ast_expr(
|
||||||
} else {
|
} else {
|
||||||
eval.push(Instruction::NoOp);
|
eval.push(Instruction::NoOp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
TemplateAstExpr::ForChain {
|
||||||
|
for_block,
|
||||||
|
content,
|
||||||
|
else_block,
|
||||||
|
else_content,
|
||||||
|
end_block,
|
||||||
|
} => {
|
||||||
|
let post_for_whitespace_content;
|
||||||
|
let label_to_else_or_empty_index = machine.reserve_label();
|
||||||
|
let label_to_end_index = machine.reserve_label();
|
||||||
|
let label_start_loop = machine.reserve_label();
|
||||||
|
if let TemplateAstExpr::Block {
|
||||||
|
prev_whitespace_content,
|
||||||
|
expression,
|
||||||
|
post_whitespace_content,
|
||||||
|
} = &**for_block
|
||||||
|
&& let TemplateAstExpr::For {
|
||||||
|
value_ident,
|
||||||
|
value_expression,
|
||||||
|
} = &**expression
|
||||||
|
{
|
||||||
|
if let Some(ws) = prev_whitespace_content {
|
||||||
|
eval.push(Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
post_for_whitespace_content = post_whitespace_content;
|
||||||
|
|
||||||
for index in end_indices {
|
eval.push(Instruction::PushScope {
|
||||||
let jump = eval.len() - index - 1;
|
inherit_parent: true,
|
||||||
eval[index] = Instruction::Jump {
|
});
|
||||||
jump: jump as isize,
|
|
||||||
|
let value_slot = machine.reserve_slot();
|
||||||
|
let iterator_source_slot = machine.reserve_slot();
|
||||||
|
let iterator_slot = machine.reserve_slot();
|
||||||
|
|
||||||
|
emit_expr_load(machine, eval, iterator_source_slot, value_expression);
|
||||||
|
eval.push(Instruction::CreateIteratorFromSlotToSlot {
|
||||||
|
iterator_source_slot,
|
||||||
|
iterator_slot,
|
||||||
|
});
|
||||||
|
|
||||||
|
eval.push(Instruction::GetIteratorEmptyOrJump {
|
||||||
|
iterator_slot,
|
||||||
|
jump: label_to_else_or_empty_index,
|
||||||
|
});
|
||||||
|
|
||||||
|
machine.assign_label(label_start_loop, eval.len());
|
||||||
|
eval.push(Instruction::AdvanceIteratorOrJump {
|
||||||
|
iterator_slot,
|
||||||
|
value_slot,
|
||||||
|
jump: label_to_end_index,
|
||||||
|
});
|
||||||
|
|
||||||
|
eval.push(Instruction::LoadFromSlotToContext {
|
||||||
|
value_slot,
|
||||||
|
value_ident: value_ident.source(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
panic!("For block should be a for block");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if let Some(ws) = post_for_whitespace_content {
|
||||||
|
eval.push(Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for content in content {
|
||||||
|
emit_ast_expr(machine, eval, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
let end_of_content_jump = eval.len();
|
||||||
|
eval.push(Instruction::Jump {
|
||||||
|
jump: label_start_loop,
|
||||||
|
});
|
||||||
|
|
||||||
|
let has_else = else_block.is_some();
|
||||||
|
|
||||||
|
if let Some(TemplateAstExpr::Block {
|
||||||
|
prev_whitespace_content,
|
||||||
|
expression,
|
||||||
|
post_whitespace_content,
|
||||||
|
}) = else_block.as_deref()
|
||||||
|
&& let TemplateAstExpr::ForElse = &**expression
|
||||||
|
{
|
||||||
|
if let Some(ws) = prev_whitespace_content {
|
||||||
|
eval.insert(
|
||||||
|
end_of_content_jump.saturating_sub(1),
|
||||||
|
Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
machine.assign_label(label_to_else_or_empty_index, eval.len());
|
||||||
|
|
||||||
|
if let Some(ws) = post_whitespace_content {
|
||||||
|
eval.push(Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for content in else_content
|
||||||
|
.as_ref()
|
||||||
|
.expect("If there is a for block, there should be for content (even if empty)")
|
||||||
|
{
|
||||||
|
emit_ast_expr(machine, eval, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let post_end_whitespace_content;
|
||||||
|
|
||||||
|
if let TemplateAstExpr::Block {
|
||||||
|
prev_whitespace_content,
|
||||||
|
expression,
|
||||||
|
post_whitespace_content,
|
||||||
|
} = &**end_block
|
||||||
|
&& let TemplateAstExpr::EndBlock = &**expression
|
||||||
|
{
|
||||||
|
post_end_whitespace_content = post_whitespace_content;
|
||||||
|
|
||||||
|
if let Some(ws) = prev_whitespace_content {
|
||||||
|
if has_else {
|
||||||
|
eval.push(Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
eval.insert(
|
||||||
|
end_of_content_jump.saturating_sub(1),
|
||||||
|
Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !has_else {
|
||||||
|
machine.assign_label(label_to_else_or_empty_index, eval.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
machine.assign_label(label_to_end_index, eval.len());
|
||||||
|
|
||||||
|
eval.push(Instruction::PopScope);
|
||||||
|
|
||||||
|
if let Some(ws) = post_end_whitespace_content {
|
||||||
|
eval.push(Instruction::AppendContent {
|
||||||
|
content: ws.source().clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic!("End block should be an endblock");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,6 +399,8 @@ fn emit_ast_expr(
|
||||||
| TemplateAstExpr::IfConditional { .. }
|
| TemplateAstExpr::IfConditional { .. }
|
||||||
| TemplateAstExpr::ConditionalContent { .. }
|
| TemplateAstExpr::ConditionalContent { .. }
|
||||||
| TemplateAstExpr::ElseConditional { .. }
|
| TemplateAstExpr::ElseConditional { .. }
|
||||||
|
| TemplateAstExpr::For { .. }
|
||||||
|
| TemplateAstExpr::ForElse
|
||||||
| TemplateAstExpr::Invalid { .. }
|
| TemplateAstExpr::Invalid { .. }
|
||||||
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
|
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +427,10 @@ fn emit_expr_load(
|
||||||
TemplateAstExpr::ElseConditional { .. } => todo!(),
|
TemplateAstExpr::ElseConditional { .. } => todo!(),
|
||||||
TemplateAstExpr::EndBlock => todo!(),
|
TemplateAstExpr::EndBlock => todo!(),
|
||||||
TemplateAstExpr::Block { .. } => todo!(),
|
TemplateAstExpr::Block { .. } => todo!(),
|
||||||
|
TemplateAstExpr::ForChain { .. } => todo!(),
|
||||||
|
TemplateAstExpr::For { .. } => todo!(),
|
||||||
|
TemplateAstExpr::ForElse => todo!(),
|
||||||
|
|
||||||
TemplateAstExpr::IfConditional { .. } => todo!(),
|
TemplateAstExpr::IfConditional { .. } => todo!(),
|
||||||
TemplateAstExpr::ConditionalContent { .. } => todo!(),
|
TemplateAstExpr::ConditionalContent { .. } => todo!(),
|
||||||
}
|
}
|
||||||
|
|
@ -251,7 +451,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),
|
||||||
},
|
},
|
||||||
|
|
@ -269,7 +471,8 @@ mod tests {
|
||||||
index: 0,
|
index: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,18 +2,32 @@
|
||||||
source: src/emit/mod.rs
|
source: src/emit/mod.rs
|
||||||
expression: emit
|
expression: emit
|
||||||
---
|
---
|
||||||
[
|
VMInstructions {
|
||||||
|
labels: {
|
||||||
|
LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
}: 19,
|
||||||
|
LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
}: 7,
|
||||||
|
LabelSlot {
|
||||||
|
index: 4,
|
||||||
|
}: 14,
|
||||||
|
},
|
||||||
|
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,
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
153
src/eval/mod.rs
153
src/eval/mod.rs
|
|
@ -5,7 +5,10 @@ use thiserror::Error;
|
||||||
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
use crate::emit::Instruction;
|
use crate::emit::Instruction;
|
||||||
|
use crate::emit::VMInstructions;
|
||||||
|
use crate::emit::VariableSlot;
|
||||||
use crate::input::NomoInput;
|
use crate::input::NomoInput;
|
||||||
|
use crate::value::NomoValue;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
pub enum EvaluationError {
|
pub enum EvaluationError {
|
||||||
|
|
@ -17,66 +20,164 @@ 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(
|
struct Scope {
|
||||||
instructions: &[Instruction],
|
stack: Vec<HashMap<String, NomoValue>>,
|
||||||
global_context: &Context,
|
slots: HashMap<VariableSlot, NomoValue>,
|
||||||
) -> Result<String, EvaluationError> {
|
}
|
||||||
|
impl Scope {
|
||||||
|
fn insert_into_slot(&mut self, slot: VariableSlot, value: NomoValue) {
|
||||||
|
self.slots.insert(slot, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&self, slot: &VariableSlot) -> &NomoValue {
|
||||||
|
self.slots.get(slot).expect("All slot loads must be valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mut(&mut self, slot: &VariableSlot) -> &mut NomoValue {
|
||||||
|
self.slots
|
||||||
|
.get_mut(slot)
|
||||||
|
.expect("All slot loads must be valid")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push_scope(&mut self) {
|
||||||
|
self.stack.push(Default::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pop_scope(&mut self) {
|
||||||
|
self.stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_into_scope(&mut self, ident: &NomoInput, value: NomoValue) {
|
||||||
|
self.stack
|
||||||
|
.last_mut()
|
||||||
|
.unwrap()
|
||||||
|
.insert(ident.to_string(), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_scoped(&self, name: &NomoInput) -> Option<&NomoValue> {
|
||||||
|
self.stack
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find_map(|scope| scope.get(name.as_str()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(
|
||||||
|
clippy::unnecessary_to_owned,
|
||||||
|
reason = "We cannot do the suggested way as the lifetimes would not match up"
|
||||||
|
)]
|
||||||
|
pub fn execute(vm: &VMInstructions, 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 = Scope {
|
||||||
|
stack: vec![global_context.values().clone()],
|
||||||
|
slots: 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 => (),
|
||||||
Instruction::AppendContent { content } => output.push_str(content),
|
Instruction::AppendContent { content } => output.push_str(content),
|
||||||
Instruction::LoadFromContextToSlot { name, slot } => {
|
Instruction::LoadFromContextToSlot { name, slot } => {
|
||||||
let value = global_context
|
let value = scopes
|
||||||
.values
|
.get_scoped(name)
|
||||||
.get(name.as_str())
|
|
||||||
.ok_or(EvaluationError::UnknownVariable(name.clone()))?;
|
.ok_or(EvaluationError::UnknownVariable(name.clone()))?;
|
||||||
|
|
||||||
scopes.insert(*slot, value.clone());
|
scopes.insert_into_slot(*slot, value.clone());
|
||||||
}
|
}
|
||||||
Instruction::EmitFromSlot { slot } => {
|
Instruction::EmitFromSlot { slot } => {
|
||||||
let value = scopes.get(slot).unwrap().as_str().unwrap();
|
let value = scopes.get(slot).as_str().unwrap();
|
||||||
output.push_str(value);
|
output.push_str(value);
|
||||||
}
|
}
|
||||||
Instruction::PushScope { inherit_parent: _ } => todo!(),
|
Instruction::PushScope { inherit_parent: _ } => {
|
||||||
|
scopes.push_scope();
|
||||||
|
}
|
||||||
Instruction::Abort => return Err(EvaluationError::ExplicitAbort),
|
Instruction::Abort => return Err(EvaluationError::ExplicitAbort),
|
||||||
Instruction::JumpIfNotTrue { emit_slot, jump } => {
|
Instruction::JumpIfNotTrue { emit_slot, jump } => {
|
||||||
let dont_jump = scopes.get(emit_slot).unwrap().as_bool().unwrap();
|
let dont_jump = scopes.get(emit_slot).as_bool().unwrap();
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
Instruction::CreateIteratorFromSlotToSlot {
|
||||||
|
iterator_slot,
|
||||||
|
iterator_source_slot,
|
||||||
|
} => {
|
||||||
|
let value = scopes.get(iterator_source_slot).as_array().unwrap();
|
||||||
|
scopes.insert_into_slot(
|
||||||
|
*iterator_slot,
|
||||||
|
NomoValue::Iterator {
|
||||||
|
value: Box::new(value.to_vec().into_iter()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Instruction::AdvanceIteratorOrJump {
|
||||||
|
iterator_slot,
|
||||||
|
value_slot,
|
||||||
|
jump,
|
||||||
|
} => {
|
||||||
|
let iterator = scopes.get_mut(iterator_slot).as_iterator_mut().unwrap();
|
||||||
|
|
||||||
|
if let Some(value) = iterator.next() {
|
||||||
|
scopes.insert_into_slot(*value_slot, value);
|
||||||
|
} else {
|
||||||
|
let Some(new_ip) = vm.labels.get(jump) else {
|
||||||
|
return Err(EvaluationError::LabelNotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
ip = *new_ip;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::GetIteratorEmptyOrJump {
|
||||||
|
iterator_slot,
|
||||||
|
jump,
|
||||||
|
} => {
|
||||||
|
let iterator = scopes.get(iterator_slot).as_iterator().unwrap();
|
||||||
|
let (min, _) = iterator.size_hint();
|
||||||
|
|
||||||
|
if min == 0 {
|
||||||
|
let Some(new_ip) = vm.labels.get(jump) else {
|
||||||
|
return Err(EvaluationError::LabelNotFound);
|
||||||
|
};
|
||||||
|
|
||||||
|
ip = *new_ip;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Instruction::PopScope => scopes.pop_scope(),
|
||||||
|
Instruction::LoadFromSlotToContext {
|
||||||
|
value_ident,
|
||||||
|
value_slot,
|
||||||
|
} => {
|
||||||
|
let value = scopes.get(value_slot).clone();
|
||||||
|
|
||||||
|
scopes.insert_into_scope(value_ident, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
28
src/lib.rs
28
src/lib.rs
|
|
@ -1,18 +1,19 @@
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use displaydoc::Display;
|
use displaydoc::Display;
|
||||||
use serde::Serialize;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::emit::Instruction;
|
use crate::emit::VMInstructions;
|
||||||
use crate::input::NomoInput;
|
use crate::input::NomoInput;
|
||||||
|
use crate::value::NomoValue;
|
||||||
|
use crate::value::NomoValueError;
|
||||||
|
|
||||||
pub mod ast;
|
pub mod ast;
|
||||||
pub mod emit;
|
pub mod emit;
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
|
pub mod value;
|
||||||
|
|
||||||
#[derive(Debug, Error, Display)]
|
#[derive(Debug, Error, Display)]
|
||||||
pub enum NomoError {
|
pub enum NomoError {
|
||||||
|
|
@ -84,11 +85,11 @@ impl Nomo {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Template {
|
struct Template {
|
||||||
instructions: Vec<Instruction>,
|
instructions: VMInstructions,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
values: BTreeMap<String, serde_json::Value>,
|
values: HashMap<String, NomoValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
|
|
@ -100,23 +101,26 @@ impl Default for Context {
|
||||||
impl Context {
|
impl Context {
|
||||||
pub fn new() -> Context {
|
pub fn new() -> Context {
|
||||||
Context {
|
Context {
|
||||||
values: BTreeMap::new(),
|
values: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_insert(
|
pub fn try_insert(
|
||||||
&mut self,
|
&mut self,
|
||||||
key: impl Into<String>,
|
key: impl Into<String>,
|
||||||
value: impl Serialize,
|
value: impl TryInto<NomoValue, Error = NomoValueError>,
|
||||||
) -> Result<(), serde_json::Error> {
|
) -> Result<(), NomoValueError> {
|
||||||
self.values.insert(key.into(), serde_json::to_value(value)?);
|
self.values.insert(key.into(), value.try_into()?);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(&mut self, key: impl Into<String>, value: impl Serialize) {
|
pub fn insert(&mut self, key: impl Into<String>, value: impl Into<NomoValue>) {
|
||||||
self.try_insert(key, value)
|
self.values.insert(key.into(), value.into());
|
||||||
.expect("inserted value should serialize without error");
|
}
|
||||||
|
|
||||||
|
pub fn values(&self) -> &HashMap<String, NomoValue> {
|
||||||
|
&self.values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,8 @@ pub enum TokenKind {
|
||||||
Invalid,
|
Invalid,
|
||||||
ConditionalIf,
|
ConditionalIf,
|
||||||
ConditionalElse,
|
ConditionalElse,
|
||||||
|
For,
|
||||||
|
In,
|
||||||
End,
|
End,
|
||||||
Literal(TokenLiteral),
|
Literal(TokenLiteral),
|
||||||
}
|
}
|
||||||
|
|
@ -295,6 +297,8 @@ impl TemplateToken {
|
||||||
invalid => TokenKind::Invalid,
|
invalid => TokenKind::Invalid,
|
||||||
conditional_if => TokenKind::ConditionalIf,
|
conditional_if => TokenKind::ConditionalIf,
|
||||||
conditional_else => TokenKind::ConditionalElse,
|
conditional_else => TokenKind::ConditionalElse,
|
||||||
|
keyword_for => TokenKind::For,
|
||||||
|
keyword_in => TokenKind::In,
|
||||||
end => TokenKind::End,
|
end => TokenKind::End,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,14 +385,7 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<T
|
||||||
fn parse_block_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
fn parse_block_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
trace(
|
trace(
|
||||||
"parse_block_token",
|
"parse_block_token",
|
||||||
alt((
|
alt((parse_ident, parse_keyword, parse_whitespace)),
|
||||||
parse_ident,
|
|
||||||
terminated(parse_literal, ident_terminator_check),
|
|
||||||
terminated(parse_condition_if, ident_terminator_check),
|
|
||||||
terminated(parse_condition_else, ident_terminator_check),
|
|
||||||
terminated(parse_end, ident_terminator_check),
|
|
||||||
parse_whitespace,
|
|
||||||
)),
|
|
||||||
)
|
)
|
||||||
.parse_next(input)
|
.parse_next(input)
|
||||||
}
|
}
|
||||||
|
|
@ -431,6 +428,26 @@ fn parse_end<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken
|
||||||
trace("parse_end", "end".map(TemplateToken::end)).parse_next(input)
|
trace("parse_end", "end".map(TemplateToken::end)).parse_next(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_for<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace("parse_for", "for".map(TemplateToken::keyword_for)).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_in<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
trace("parse_in", "in".map(TemplateToken::keyword_in)).parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_keyword<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
|
alt((
|
||||||
|
terminated(parse_literal, ident_terminator_check),
|
||||||
|
terminated(parse_condition_if, ident_terminator_check),
|
||||||
|
terminated(parse_condition_else, ident_terminator_check),
|
||||||
|
terminated(parse_for, ident_terminator_check),
|
||||||
|
terminated(parse_in, ident_terminator_check),
|
||||||
|
terminated(parse_end, ident_terminator_check),
|
||||||
|
))
|
||||||
|
.parse_next(input)
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
|
||||||
trace(
|
trace(
|
||||||
"parse_whitespace",
|
"parse_whitespace",
|
||||||
|
|
@ -458,12 +475,7 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> {
|
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> {
|
||||||
peek(not(alt((
|
peek(not(parse_keyword))
|
||||||
parse_literal,
|
|
||||||
parse_condition_if,
|
|
||||||
parse_condition_else,
|
|
||||||
parse_end,
|
|
||||||
))))
|
|
||||||
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
|
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
|
||||||
.parse_next(input)?;
|
.parse_next(input)?;
|
||||||
|
|
||||||
|
|
@ -627,4 +639,45 @@ mod tests {
|
||||||
)
|
)
|
||||||
"#);
|
"#);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_for_loop() {
|
||||||
|
let input = "{{ for value in array }} Hi: {{= value }} {{ end }}";
|
||||||
|
let output = parse(input.into());
|
||||||
|
|
||||||
|
insta::assert_debug_snapshot!(output, @r#"
|
||||||
|
Ok(
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[Whitespace]" " (2..3),
|
||||||
|
[For]"for" (3..6),
|
||||||
|
[Whitespace]" " (6..7),
|
||||||
|
[Ident]"value" (7..12),
|
||||||
|
[Whitespace]" " (12..13),
|
||||||
|
[In]"in" (13..15),
|
||||||
|
[Whitespace]" " (15..16),
|
||||||
|
[Ident]"array" (16..21),
|
||||||
|
[Whitespace]" " (21..22),
|
||||||
|
[RightDelim]"}}" (22..24),
|
||||||
|
[Whitespace]" " (24..25),
|
||||||
|
[Content]"Hi:" (25..28),
|
||||||
|
[Whitespace]" " (28..29),
|
||||||
|
[LeftDelim]"{{" (29..31),
|
||||||
|
[WantsOutput]"=" (31..32),
|
||||||
|
[Whitespace]" " (32..33),
|
||||||
|
[Ident]"value" (33..38),
|
||||||
|
[Whitespace]" " (38..39),
|
||||||
|
[RightDelim]"}}" (39..41),
|
||||||
|
[Whitespace]" " (41..42),
|
||||||
|
[LeftDelim]"{{" (42..44),
|
||||||
|
[Whitespace]" " (44..45),
|
||||||
|
[End]"end" (45..48),
|
||||||
|
[Whitespace]" " (48..49),
|
||||||
|
[RightDelim]"}}" (49..51),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
"#);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
251
src/value.rs
Normal file
251
src/value.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde_json")]
|
||||||
|
use displaydoc::Display;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum NomoValue {
|
||||||
|
String {
|
||||||
|
value: Cow<'static, str>,
|
||||||
|
},
|
||||||
|
Array {
|
||||||
|
value: Vec<NomoValue>,
|
||||||
|
},
|
||||||
|
Bool {
|
||||||
|
value: bool,
|
||||||
|
},
|
||||||
|
Object {
|
||||||
|
value: BTreeMap<String, NomoValue>,
|
||||||
|
},
|
||||||
|
Integer {
|
||||||
|
value: u64,
|
||||||
|
},
|
||||||
|
SignedInteger {
|
||||||
|
value: i64,
|
||||||
|
},
|
||||||
|
Float {
|
||||||
|
value: f64,
|
||||||
|
},
|
||||||
|
Iterator {
|
||||||
|
value: Box<dyn CloneIterator<Item = NomoValue>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NomoValue {
|
||||||
|
pub fn as_str(&self) -> Option<&str> {
|
||||||
|
if let Self::String { value } = self {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_array(&self) -> Option<&[NomoValue]> {
|
||||||
|
if let Self::Array { value } = self {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
|
if let Self::Bool { value } = self {
|
||||||
|
Some(*value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_object(&self) -> Option<&BTreeMap<String, NomoValue>> {
|
||||||
|
if let Self::Object { value } = self {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_integer(&self) -> Option<u64> {
|
||||||
|
if let Self::Integer { value } = self {
|
||||||
|
Some(*value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_float(&self) -> Option<f64> {
|
||||||
|
if let Self::Float { value } = self {
|
||||||
|
Some(*value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_iterator(&self) -> Option<&dyn CloneIterator<Item = NomoValue>> {
|
||||||
|
if let Self::Iterator { value } = self {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_iterator_mut(&mut self) -> Option<&mut dyn CloneIterator<Item = NomoValue>> {
|
||||||
|
if let Self::Iterator { value } = self {
|
||||||
|
Some(value)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait CloneIterator: Iterator<Item = NomoValue> {
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneIterator<Item = NomoValue>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> CloneIterator for I
|
||||||
|
where
|
||||||
|
I: Iterator<Item = NomoValue> + Clone + 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn CloneIterator<Item = NomoValue>> {
|
||||||
|
Box::new(Clone::clone(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Box<dyn CloneIterator> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
self.clone_box()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for NomoValue {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::String { value } => f.debug_struct("String").field("value", value).finish(),
|
||||||
|
Self::Array { value } => f.debug_struct("Array").field("value", value).finish(),
|
||||||
|
Self::Bool { value } => f.debug_struct("Bool").field("value", value).finish(),
|
||||||
|
Self::Object { value } => f.debug_struct("Object").field("value", value).finish(),
|
||||||
|
Self::Integer { value } => f.debug_struct("Integer").field("value", value).finish(),
|
||||||
|
Self::SignedInteger { value } => f
|
||||||
|
.debug_struct("SignedInteger")
|
||||||
|
.field("value", value)
|
||||||
|
.finish(),
|
||||||
|
Self::Float { value } => f.debug_struct("Float").field("value", value).finish(),
|
||||||
|
Self::Iterator { value: _ } => f
|
||||||
|
.debug_struct("Iterator")
|
||||||
|
.field("value", &"Iterator")
|
||||||
|
.finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> From<&R> for NomoValue
|
||||||
|
where
|
||||||
|
R: Into<NomoValue> + Clone,
|
||||||
|
{
|
||||||
|
fn from(value: &R) -> Self {
|
||||||
|
value.clone().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for NomoValue {
|
||||||
|
fn from(val: String) -> Self {
|
||||||
|
NomoValue::String {
|
||||||
|
value: Cow::Owned(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for NomoValue {
|
||||||
|
fn from(val: &'static str) -> Self {
|
||||||
|
NomoValue::String {
|
||||||
|
value: Cow::Borrowed(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> From<Vec<V>> for NomoValue
|
||||||
|
where
|
||||||
|
V: Into<NomoValue>,
|
||||||
|
{
|
||||||
|
fn from(val: Vec<V>) -> Self {
|
||||||
|
NomoValue::Array {
|
||||||
|
value: val.into_iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> From<std::collections::VecDeque<V>> for NomoValue
|
||||||
|
where
|
||||||
|
V: Into<NomoValue>,
|
||||||
|
{
|
||||||
|
fn from(val: std::collections::VecDeque<V>) -> Self {
|
||||||
|
NomoValue::Array {
|
||||||
|
value: val.into_iter().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V> From<&[V]> for NomoValue
|
||||||
|
where
|
||||||
|
V: Into<NomoValue> + Clone,
|
||||||
|
{
|
||||||
|
fn from(value: &[V]) -> Self {
|
||||||
|
NomoValue::Array {
|
||||||
|
value: value.iter().cloned().map(Into::into).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K, V> From<std::collections::HashMap<K, V>> for NomoValue
|
||||||
|
where
|
||||||
|
K: Into<String>,
|
||||||
|
V: Into<NomoValue>,
|
||||||
|
{
|
||||||
|
fn from(val: std::collections::HashMap<K, V>) -> Self {
|
||||||
|
NomoValue::Object {
|
||||||
|
value: val.into_iter().map(|(k, v)| (k.into(), v.into())).collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "serde_json")]
|
||||||
|
#[derive(Debug, Error, Display)]
|
||||||
|
/// Could not transform value to [`NomoValue`]
|
||||||
|
pub struct NomoValueError;
|
||||||
|
|
||||||
|
#[cfg(feature = "serde_json")]
|
||||||
|
impl TryFrom<serde_json::Value> for NomoValue {
|
||||||
|
type Error = NomoValueError;
|
||||||
|
fn try_from(value: serde_json::Value) -> Result<Self, NomoValueError> {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => todo!(),
|
||||||
|
serde_json::Value::Bool(value) => Ok(NomoValue::Bool { value }),
|
||||||
|
serde_json::Value::Number(number) => {
|
||||||
|
if let Some(value) = number.as_u64() {
|
||||||
|
return Ok(NomoValue::Integer { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = number.as_f64() {
|
||||||
|
return Ok(NomoValue::Float { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(value) = number.as_i64() {
|
||||||
|
return Ok(NomoValue::SignedInteger { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(NomoValueError)
|
||||||
|
}
|
||||||
|
serde_json::Value::String(str) => Ok(NomoValue::String {
|
||||||
|
value: Cow::Owned(str),
|
||||||
|
}),
|
||||||
|
serde_json::Value::Array(values) => Ok(NomoValue::Array {
|
||||||
|
value: values
|
||||||
|
.into_iter()
|
||||||
|
.map(TryInto::try_into)
|
||||||
|
.collect::<Result<_, _>>()?,
|
||||||
|
}),
|
||||||
|
serde_json::Value::Object(_map) => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
80
tests/cases/1-parsed@simple_for.snap
Normal file
80
tests/cases/1-parsed@simple_for.snap
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
---
|
||||||
|
source: tests/file_tests.rs
|
||||||
|
expression: parsed
|
||||||
|
info:
|
||||||
|
input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}"
|
||||||
|
context:
|
||||||
|
no_values: []
|
||||||
|
values:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
input_file: tests/cases/simple_for.nomo
|
||||||
|
---
|
||||||
|
ParsedTemplate {
|
||||||
|
tokens: [
|
||||||
|
[LeftDelim]"{{" (0..2),
|
||||||
|
[Whitespace]" " (2..3),
|
||||||
|
[For]"for" (3..6),
|
||||||
|
[Whitespace]" " (6..7),
|
||||||
|
[Ident]"value" (7..12),
|
||||||
|
[Whitespace]" " (12..13),
|
||||||
|
[In]"in" (13..15),
|
||||||
|
[Whitespace]" " (15..16),
|
||||||
|
[Ident]"values" (16..22),
|
||||||
|
[Whitespace]" " (22..23),
|
||||||
|
[TrimWhitespace]"-" (23..24),
|
||||||
|
[RightDelim]"}}" (24..26),
|
||||||
|
[Whitespace]"\n " (26..31),
|
||||||
|
[LeftDelim]"{{" (31..33),
|
||||||
|
[TrimWhitespace]"-" (33..34),
|
||||||
|
[WantsOutput]"=" (34..35),
|
||||||
|
[Whitespace]" " (35..36),
|
||||||
|
[Ident]"value" (36..41),
|
||||||
|
[Whitespace]" " (41..42),
|
||||||
|
[RightDelim]"}}" (42..44),
|
||||||
|
[Whitespace]"\n" (44..45),
|
||||||
|
[LeftDelim]"{{" (45..47),
|
||||||
|
[TrimWhitespace]"-" (47..48),
|
||||||
|
[Whitespace]" " (48..49),
|
||||||
|
[End]"end" (49..52),
|
||||||
|
[Whitespace]" " (52..53),
|
||||||
|
[RightDelim]"}}" (53..55),
|
||||||
|
[Whitespace]"\n" (55..56),
|
||||||
|
[LeftDelim]"{{" (56..58),
|
||||||
|
[Whitespace]" " (58..59),
|
||||||
|
[For]"for" (59..62),
|
||||||
|
[Whitespace]" " (62..63),
|
||||||
|
[Ident]"value" (63..68),
|
||||||
|
[Whitespace]" " (68..69),
|
||||||
|
[In]"in" (69..71),
|
||||||
|
[Whitespace]" " (71..72),
|
||||||
|
[Ident]"no_values" (72..81),
|
||||||
|
[Whitespace]" " (81..82),
|
||||||
|
[TrimWhitespace]"-" (82..83),
|
||||||
|
[RightDelim]"}}" (83..85),
|
||||||
|
[Whitespace]"\n " (85..90),
|
||||||
|
[LeftDelim]"{{" (90..92),
|
||||||
|
[TrimWhitespace]"-" (92..93),
|
||||||
|
[WantsOutput]"=" (93..94),
|
||||||
|
[Whitespace]" " (94..95),
|
||||||
|
[Ident]"value" (95..100),
|
||||||
|
[Whitespace]" " (100..101),
|
||||||
|
[RightDelim]"}}" (101..103),
|
||||||
|
[Whitespace]"\n" (103..104),
|
||||||
|
[LeftDelim]"{{" (104..106),
|
||||||
|
[Whitespace]" " (106..107),
|
||||||
|
[ConditionalElse]"else" (107..111),
|
||||||
|
[Whitespace]" " (111..112),
|
||||||
|
[TrimWhitespace]"-" (112..113),
|
||||||
|
[RightDelim]"}}" (113..115),
|
||||||
|
[Whitespace]"\n" (115..116),
|
||||||
|
[Content]"No Values >:C" (116..129),
|
||||||
|
[Whitespace]"\n" (129..130),
|
||||||
|
[LeftDelim]"{{" (130..132),
|
||||||
|
[TrimWhitespace]"-" (132..133),
|
||||||
|
[Whitespace]" " (133..134),
|
||||||
|
[End]"end" (134..137),
|
||||||
|
[Whitespace]" " (137..138),
|
||||||
|
[RightDelim]"}}" (138..140),
|
||||||
|
],
|
||||||
|
}
|
||||||
90
tests/cases/2-ast@simple_for.snap
Normal file
90
tests/cases/2-ast@simple_for.snap
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
---
|
||||||
|
source: tests/file_tests.rs
|
||||||
|
expression: ast
|
||||||
|
info:
|
||||||
|
input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}"
|
||||||
|
context:
|
||||||
|
no_values: []
|
||||||
|
values:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
input_file: tests/cases/simple_for.nomo
|
||||||
|
---
|
||||||
|
TemplateAst {
|
||||||
|
root: [
|
||||||
|
ForChain {
|
||||||
|
for_block: Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: For {
|
||||||
|
value_ident: [Ident]"value" (7..12),
|
||||||
|
value_expression: VariableAccess(
|
||||||
|
[Ident]"values" (16..22),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
content: [
|
||||||
|
Interpolation {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: VariableAccess(
|
||||||
|
[Ident]"value" (36..41),
|
||||||
|
),
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]"\n" (44..45),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
else_block: None,
|
||||||
|
else_content: None,
|
||||||
|
end_block: Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: EndBlock,
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]"\n" (55..56),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ForChain {
|
||||||
|
for_block: Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: For {
|
||||||
|
value_ident: [Ident]"value" (63..68),
|
||||||
|
value_expression: VariableAccess(
|
||||||
|
[Ident]"no_values" (72..81),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
content: [
|
||||||
|
Interpolation {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: VariableAccess(
|
||||||
|
[Ident]"value" (95..100),
|
||||||
|
),
|
||||||
|
post_whitespace_content: Some(
|
||||||
|
[Whitespace]"\n" (103..104),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
else_block: Some(
|
||||||
|
Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: ForElse,
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
else_content: Some(
|
||||||
|
[
|
||||||
|
StaticContent(
|
||||||
|
[Content]"No Values >:C" (116..129),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
end_block: Block {
|
||||||
|
prev_whitespace_content: None,
|
||||||
|
expression: EndBlock,
|
||||||
|
post_whitespace_content: None,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -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: 0,
|
||||||
|
}: 7,
|
||||||
|
LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
}: 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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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: 0,
|
||||||
|
}: 14,
|
||||||
|
LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
}: 7,
|
||||||
|
LabelSlot {
|
||||||
|
index: 4,
|
||||||
|
}: 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,
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
},
|
},
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
166
tests/cases/3-instructions@simple_for.snap
Normal file
166
tests/cases/3-instructions@simple_for.snap
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
---
|
||||||
|
source: tests/file_tests.rs
|
||||||
|
expression: emit
|
||||||
|
info:
|
||||||
|
input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}"
|
||||||
|
context:
|
||||||
|
no_values: []
|
||||||
|
values:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
input_file: tests/cases/simple_for.nomo
|
||||||
|
---
|
||||||
|
VMInstructions {
|
||||||
|
labels: {
|
||||||
|
LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
}: 10,
|
||||||
|
LabelSlot {
|
||||||
|
index: 1,
|
||||||
|
}: 10,
|
||||||
|
LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
}: 4,
|
||||||
|
LabelSlot {
|
||||||
|
index: 7,
|
||||||
|
}: 22,
|
||||||
|
LabelSlot {
|
||||||
|
index: 8,
|
||||||
|
}: 23,
|
||||||
|
LabelSlot {
|
||||||
|
index: 9,
|
||||||
|
}: 16,
|
||||||
|
},
|
||||||
|
instructions: [
|
||||||
|
PushScope {
|
||||||
|
inherit_parent: true,
|
||||||
|
},
|
||||||
|
LoadFromContextToSlot {
|
||||||
|
name: "values" (16..22),
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CreateIteratorFromSlotToSlot {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 5,
|
||||||
|
},
|
||||||
|
iterator_source_slot: VariableSlot {
|
||||||
|
index: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GetIteratorEmptyOrJump {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 5,
|
||||||
|
},
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AdvanceIteratorOrJump {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 5,
|
||||||
|
},
|
||||||
|
value_slot: VariableSlot {
|
||||||
|
index: 3,
|
||||||
|
},
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadFromSlotToContext {
|
||||||
|
value_ident: "value" (7..12),
|
||||||
|
value_slot: VariableSlot {
|
||||||
|
index: 3,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadFromContextToSlot {
|
||||||
|
name: "value" (36..41),
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EmitFromSlot {
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: "\n" (44..45),
|
||||||
|
},
|
||||||
|
Jump {
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
PopScope,
|
||||||
|
AppendContent {
|
||||||
|
content: "\n" (55..56),
|
||||||
|
},
|
||||||
|
PushScope {
|
||||||
|
inherit_parent: true,
|
||||||
|
},
|
||||||
|
LoadFromContextToSlot {
|
||||||
|
name: "no_values" (72..81),
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
CreateIteratorFromSlotToSlot {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 12,
|
||||||
|
},
|
||||||
|
iterator_source_slot: VariableSlot {
|
||||||
|
index: 11,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
GetIteratorEmptyOrJump {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 12,
|
||||||
|
},
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 7,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AdvanceIteratorOrJump {
|
||||||
|
iterator_slot: VariableSlot {
|
||||||
|
index: 12,
|
||||||
|
},
|
||||||
|
value_slot: VariableSlot {
|
||||||
|
index: 10,
|
||||||
|
},
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 8,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadFromSlotToContext {
|
||||||
|
value_ident: "value" (63..68),
|
||||||
|
value_slot: VariableSlot {
|
||||||
|
index: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LoadFromContextToSlot {
|
||||||
|
name: "value" (95..100),
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 13,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
EmitFromSlot {
|
||||||
|
slot: VariableSlot {
|
||||||
|
index: 13,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: "\n" (103..104),
|
||||||
|
},
|
||||||
|
Jump {
|
||||||
|
jump: LabelSlot {
|
||||||
|
index: 9,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
AppendContent {
|
||||||
|
content: "No Values >:C" (116..129),
|
||||||
|
},
|
||||||
|
PopScope,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -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: 0,
|
||||||
|
}: 7,
|
||||||
|
LabelSlot {
|
||||||
|
index: 2,
|
||||||
|
}: 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,
|
||||||
]
|
],
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
13
tests/cases/4-output@simple_for.snap
Normal file
13
tests/cases/4-output@simple_for.snap
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: tests/file_tests.rs
|
||||||
|
expression: output
|
||||||
|
info:
|
||||||
|
input: "{{ for value in values -}}\n {{-= value }}\n{{- end }}\n{{ for value in no_values -}}\n {{-= value }}\n{{ else -}}\nNo Values >:C\n{{- end }}"
|
||||||
|
context:
|
||||||
|
no_values: []
|
||||||
|
values:
|
||||||
|
- one
|
||||||
|
- two
|
||||||
|
input_file: tests/cases/simple_for.nomo
|
||||||
|
---
|
||||||
|
"one\ntwo\n\nNo Values >:C"
|
||||||
13
tests/cases/simple_for.nomo
Normal file
13
tests/cases/simple_for.nomo
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"values": [ "one", "two" ],
|
||||||
|
"no_values": []
|
||||||
|
}
|
||||||
|
---
|
||||||
|
{{ for value in values -}}
|
||||||
|
{{-= value }}
|
||||||
|
{{- end }}
|
||||||
|
{{ for value in no_values -}}
|
||||||
|
{{-= value }}
|
||||||
|
{{ else -}}
|
||||||
|
No Values >:C
|
||||||
|
{{- end }}
|
||||||
|
|
@ -2,6 +2,12 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use nomo::Context;
|
use nomo::Context;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct Info {
|
||||||
|
input: String,
|
||||||
|
context: HashMap<String, serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn check_cases() {
|
fn check_cases() {
|
||||||
insta::glob!("cases/*.nomo", |path| {
|
insta::glob!("cases/*.nomo", |path| {
|
||||||
|
|
@ -9,7 +15,6 @@ fn check_cases() {
|
||||||
settings.set_snapshot_path("cases");
|
settings.set_snapshot_path("cases");
|
||||||
settings.set_snapshot_suffix(path.file_stem().unwrap().display().to_string());
|
settings.set_snapshot_suffix(path.file_stem().unwrap().display().to_string());
|
||||||
settings.set_prepend_module_to_snapshot(false);
|
settings.set_prepend_module_to_snapshot(false);
|
||||||
let _guard = settings.bind_to_scope();
|
|
||||||
|
|
||||||
let input = std::fs::read_to_string(path).unwrap();
|
let input = std::fs::read_to_string(path).unwrap();
|
||||||
|
|
||||||
|
|
@ -21,14 +26,21 @@ fn check_cases() {
|
||||||
HashMap::new()
|
HashMap::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settings.set_info(&Info {
|
||||||
|
input: input.to_string(),
|
||||||
|
context: map.clone(),
|
||||||
|
});
|
||||||
|
|
||||||
let mut context = Context::new();
|
let mut context = Context::new();
|
||||||
|
|
||||||
for (k, v) in map {
|
for (k, v) in map {
|
||||||
context.insert(k, v);
|
context.try_insert(k, v).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
let parsed = nomo::parser::parse(input.into()).unwrap();
|
let parsed = nomo::parser::parse(input.into()).unwrap();
|
||||||
|
|
||||||
|
let _guard = settings.bind_to_scope();
|
||||||
|
|
||||||
insta::assert_debug_snapshot!("1-parsed", parsed);
|
insta::assert_debug_snapshot!("1-parsed", parsed);
|
||||||
|
|
||||||
let ast = match nomo::ast::parse(parsed.tokens()) {
|
let ast = match nomo::ast::parse(parsed.tokens()) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue