Compare commits

...

4 commits

Author SHA1 Message Date
ffd9baf90f Add deep access
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-15 14:31:46 +01:00
4f770c1f24 Abstract infix macro
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-15 13:48:51 +01:00
a590839b21 Add parsing of dot operator
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-15 13:39:08 +01:00
145d305c94 Add parsing of '.' (dot)
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-15 13:36:34 +01:00
15 changed files with 766 additions and 36 deletions

View file

@ -108,6 +108,20 @@ pub enum Instruction {
args: Vec<VariableSlot>, args: Vec<VariableSlot>,
slot: VariableSlot, slot: VariableSlot,
}, },
LoadFromSlotToSlot {
from_slot: VariableSlot,
to_slot: VariableSlot,
},
JumpIfUndefined {
slot: VariableSlot,
jump: LabelSlot,
},
IndexSlotToSlot {
name: NomoInput,
from_slot: VariableSlot,
to_slot: VariableSlot,
fail_on_not_found: bool,
},
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -424,8 +438,9 @@ fn emit_ast_expr(
| TemplateAstExpr::Invalid { .. } | TemplateAstExpr::Invalid { .. }
| TemplateAstExpr::Literal { .. } | TemplateAstExpr::Literal { .. }
| TemplateAstExpr::FunctionCall { .. } | TemplateAstExpr::FunctionCall { .. }
| TemplateAstExpr::Operation { .. } | TemplateAstExpr::MathOperation { .. }
| TemplateAstExpr::ConditionalAccess { .. } | TemplateAstExpr::ConditionalAccess { .. }
| TemplateAstExpr::AccessOperation { .. }
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
} }
} }
@ -451,6 +466,63 @@ fn emit_expr_load(
fail_on_not_found: false, fail_on_not_found: false,
}); });
} }
TemplateAstExpr::AccessOperation { op, lhs, rhs } => {
assert_eq!(
op,
&TokenOperator::Dot,
"Only dot can be used to access variables, this is a bug"
);
fn inner_access_op(
eval: &mut Vec<Instruction>,
end_label: LabelSlot,
current_slot: VariableSlot,
next: &TemplateAstExpr<'_>,
) {
match next {
TemplateAstExpr::ConditionalAccess(template_token)
| TemplateAstExpr::VariableAccess(template_token) => {
eval.push(Instruction::IndexSlotToSlot {
name: template_token.source().clone(),
from_slot: current_slot,
to_slot: current_slot,
fail_on_not_found: matches!(
next,
TemplateAstExpr::VariableAccess { .. }
),
});
eval.push(Instruction::JumpIfUndefined {
slot: current_slot,
jump: end_label,
});
}
TemplateAstExpr::AccessOperation { op, lhs, rhs } => {
assert_eq!(
op,
&TokenOperator::Dot,
"Only dot can be used to access variables, this is a bug"
);
inner_access_op(eval, end_label, current_slot, lhs);
inner_access_op(eval, end_label, current_slot, rhs);
}
_ => unreachable!(),
}
}
let end_label = machine.reserve_label();
let start_slot = machine.reserve_slot();
emit_expr_load(machine, eval, start_slot, lhs);
eval.push(Instruction::JumpIfUndefined {
slot: start_slot,
jump: end_label,
});
inner_access_op(eval, end_label, start_slot, rhs);
machine.assign_label(end_label, eval.len());
eval.push(Instruction::LoadFromSlotToSlot {
from_slot: start_slot,
to_slot: emit_slot,
});
}
TemplateAstExpr::Literal { source, value } => { TemplateAstExpr::Literal { source, value } => {
eval.push(Instruction::LoadLiteralToSlot { eval.push(Instruction::LoadLiteralToSlot {
source: source.clone(), source: source.clone(),
@ -458,7 +530,7 @@ fn emit_expr_load(
slot: emit_slot, slot: emit_slot,
}); });
} }
TemplateAstExpr::Operation { op, lhs, rhs } => { TemplateAstExpr::MathOperation { op, lhs, rhs } => {
let left_slot = machine.reserve_slot(); let left_slot = machine.reserve_slot();
emit_expr_load(machine, eval, left_slot, lhs); emit_expr_load(machine, eval, left_slot, lhs);
let right_slot = machine.reserve_slot(); let right_slot = machine.reserve_slot();

View file

@ -119,7 +119,7 @@ pub fn execute(
} else if matches!(value, NomoValue::Undefined) { } else if matches!(value, NomoValue::Undefined) {
String::new() String::new()
} else { } else {
panic!("Unknown variable"); panic!("Could not print out value {value:?}");
}; };
output.push_str(&value); output.push_str(&value);
@ -243,6 +243,46 @@ pub fn execute(
scopes.insert_into_slot(*slot, value); scopes.insert_into_slot(*slot, value);
} }
Instruction::LoadFromSlotToSlot { from_slot, to_slot } => {
let value = scopes.get(from_slot);
scopes.insert_into_slot(*to_slot, value.clone());
}
Instruction::JumpIfUndefined { slot, jump } => {
let is_undefined = matches!(scopes.get(slot), NomoValue::Undefined);
if is_undefined {
let Some(new_ip) = vm.labels.get(jump) else {
return Err(EvaluationError::LabelNotFound);
};
ip = *new_ip;
continue;
}
}
Instruction::IndexSlotToSlot {
name,
from_slot,
to_slot,
fail_on_not_found,
} => {
let value = scopes.get(from_slot);
match value {
NomoValue::Object { value } => {
let value = value.get(name.as_str());
let value = if let Some(value) = value {
value.clone()
} else if *fail_on_not_found {
panic!("Could not index");
} else {
NomoValue::Undefined
};
scopes.insert_into_slot(dbg!(*to_slot), dbg!(value));
}
_ => panic!("Invalid indexing"),
}
}
} }
ip += 1; ip += 1;

View file

@ -239,6 +239,7 @@ pub enum TokenOperator {
Lesser, Lesser,
LesserOrEqual, LesserOrEqual,
QuestionMark, QuestionMark,
Dot,
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]
@ -569,6 +570,7 @@ fn parse_operator<'input>(input: &mut Input<'input>) -> PResult<'input, Template
cut_err(fail), cut_err(fail),
)), )),
'?' => empty.value(TokenOperator::QuestionMark), '?' => empty.value(TokenOperator::QuestionMark),
'.' => empty.value(TokenOperator::Dot),
_ => fail, _ => fail,
}, },
) )
@ -930,4 +932,28 @@ mod tests {
) )
"#); "#);
} }
#[test]
fn parse_dot() {
let input = "{{= foo?.bar }}";
let output = parse(input.into());
insta::assert_debug_snapshot!(output, @r#"
Ok(
ParsedTemplate {
tokens: [
[LeftDelim]"{{" (0..2),
[WantsOutput]"=" (2..3),
[Whitespace]" " (3..4),
[Ident]"foo" (4..7),
[Operator(QuestionMark)]"?" (7..8),
[Operator(Dot)]"." (8..9),
[Ident]"bar" (9..12),
[Whitespace]" " (12..13),
[RightDelim]"}}" (13..15),
],
},
)
"#);
}
} }

View file

@ -262,7 +262,12 @@ pub enum TemplateAstExpr<'input> {
expression: Option<Box<TemplateAstExpr<'input>>>, expression: Option<Box<TemplateAstExpr<'input>>>,
}, },
Invalid(&'input [TemplateToken]), Invalid(&'input [TemplateToken]),
Operation { MathOperation {
op: TokenOperator,
lhs: Box<TemplateAstExpr<'input>>,
rhs: Box<TemplateAstExpr<'input>>,
},
AccessOperation {
op: TokenOperator, op: TokenOperator,
lhs: Box<TemplateAstExpr<'input>>, lhs: Box<TemplateAstExpr<'input>>,
rhs: Box<TemplateAstExpr<'input>>, rhs: Box<TemplateAstExpr<'input>>,
@ -681,10 +686,10 @@ fn parse_expression<'input>(
input: &mut Input<'input>, input: &mut Input<'input>,
) -> Result<TemplateAstExpr<'input>, AstError> { ) -> Result<TemplateAstExpr<'input>, AstError> {
macro_rules! infix { macro_rules! infix {
($parser:expr => [ $($side:tt $val:tt => $prec:expr),* $(,)? ]) => { ($parser:expr => [ $($side:tt $op:tt $val:tt => $prec:expr),* $(,)? ]) => {
dispatch! { surrounded(ws, parse_operator); dispatch! { surrounded(ws, parse_operator);
$( $(
TokenOperator::$val => $side($prec, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { TokenOperator::$val => $side($prec, |_, lhs, rhs| Ok(TemplateAstExpr::$op {
op: TokenOperator::$val, op: TokenOperator::$val,
lhs: Box::new(lhs), lhs: Box::new(lhs),
rhs: Box::new(rhs) rhs: Box::new(rhs)
@ -698,21 +703,22 @@ fn parse_expression<'input>(
"expression", "expression",
expression(surrounded(ws, parse_operand)).infix(infix! { expression(surrounded(ws, parse_operand)).infix(infix! {
surrounded(ws, parse_operator) => [ surrounded(ws, parse_operator) => [
Left Plus => 18, Left MathOperation Plus => 18,
Left Minus => 18, Left MathOperation Minus => 18,
Left Times => 20, Left MathOperation Times => 20,
Left Divide => 20, Left MathOperation Divide => 20,
Left And => 10, Left MathOperation And => 10,
Left Or => 7, Left MathOperation Or => 7,
Left Equal => 12, Left MathOperation Equal => 12,
Left NotEqual => 12, Left MathOperation NotEqual => 12,
Left Greater => 15, Left MathOperation Greater => 15,
Left GreaterOrEqual => 15, Left MathOperation GreaterOrEqual => 15,
Left Lesser => 15, Left MathOperation Lesser => 15,
Left LesserOrEqual => 15, Left MathOperation LesserOrEqual => 15,
Left AccessOperation Dot => 22,
] ]
}).postfix(dispatch! { surrounded(ws, parse_operator); }).postfix(dispatch! { surrounded(ws, parse_operator);
TokenOperator::QuestionMark => Postfix(22, |input, rhs| { TokenOperator::QuestionMark => Postfix(23, |input, rhs| {
match rhs { match rhs {
TemplateAstExpr::VariableAccess(access) => Ok(TemplateAstExpr::ConditionalAccess(access)), TemplateAstExpr::VariableAccess(access) => Ok(TemplateAstExpr::ConditionalAccess(access)),
_ => Err(AstError::from_input(input)), _ => Err(AstError::from_input(input)),
@ -1071,4 +1077,15 @@ mod tests {
insta::assert_debug_snapshot!(ast); insta::assert_debug_snapshot!(ast);
} }
#[test]
fn check_access_operator() {
let input = "{{= foo?.bar }}";
let parsed = crate::lexer::parse(input.into()).unwrap();
let ast = panic_pretty(input, parse(parsed.tokens()));
insta::assert_debug_snapshot!(ast);
}
} }

View file

@ -0,0 +1,21 @@
---
source: src/parser/mod.rs
expression: ast
---
TemplateAst {
root: [
Interpolation {
prev_whitespace_content: None,
expression: AccessOperation {
op: Dot,
lhs: ConditionalAccess(
[Ident]"foo" (4..7),
),
rhs: VariableAccess(
[Ident]"bar" (9..12),
),
},
post_whitespace_content: None,
},
],
}

View file

@ -9,7 +9,7 @@ TemplateAst {
expression: FunctionCall { expression: FunctionCall {
name: [Ident]"foo" (4..7), name: [Ident]"foo" (4..7),
args: [ args: [
Operation { MathOperation {
op: Times, op: Times,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(2))]"2" (8..9), source: [Literal(Integer(2))]"2" (8..9),
@ -27,7 +27,7 @@ TemplateAst {
FunctionCall { FunctionCall {
name: [Ident]"bar" (15..18), name: [Ident]"bar" (15..18),
args: [ args: [
Operation { MathOperation {
op: Plus, op: Plus,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(2))]"2" (19..20), source: [Literal(Integer(2))]"2" (19..20),

View file

@ -6,9 +6,9 @@ TemplateAst {
root: [ root: [
Interpolation { Interpolation {
prev_whitespace_content: None, prev_whitespace_content: None,
expression: Operation { expression: MathOperation {
op: Or, op: Or,
lhs: Operation { lhs: MathOperation {
op: And, op: And,
lhs: Literal { lhs: Literal {
source: [Literal(Bool(true))]"true" (4..8), source: [Literal(Bool(true))]"true" (4..8),
@ -23,9 +23,9 @@ TemplateAst {
}, },
}, },
}, },
rhs: Operation { rhs: MathOperation {
op: And, op: And,
lhs: Operation { lhs: MathOperation {
op: GreaterOrEqual, op: GreaterOrEqual,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(3))]"3" (21..22), source: [Literal(Integer(3))]"3" (21..22),
@ -40,7 +40,7 @@ TemplateAst {
}, },
}, },
}, },
rhs: Operation { rhs: MathOperation {
op: Equal, op: Equal,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(5))]"5" (31..32), source: [Literal(Integer(5))]"5" (31..32),

View file

@ -6,9 +6,9 @@ TemplateAst {
root: [ root: [
Interpolation { Interpolation {
prev_whitespace_content: None, prev_whitespace_content: None,
expression: Operation { expression: MathOperation {
op: Plus, op: Plus,
lhs: Operation { lhs: MathOperation {
op: Times, op: Times,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(5))]"5" (4..5), source: [Literal(Integer(5))]"5" (4..5),
@ -23,7 +23,7 @@ TemplateAst {
}, },
}, },
}, },
rhs: Operation { rhs: MathOperation {
op: Divide, op: Divide,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(2))]"2" (12..13), source: [Literal(Integer(2))]"2" (12..13),

View file

@ -54,6 +54,8 @@ impl NomoValue {
pub fn as_bool(&self) -> Option<bool> { pub fn as_bool(&self) -> Option<bool> {
if let Self::Bool { value } = self { if let Self::Bool { value } = self {
Some(*value) Some(*value)
} else if let Self::Undefined = self {
Some(false)
} else { } else {
None None
} }
@ -438,7 +440,14 @@ impl TryFrom<serde_json::Value> for NomoValue {
.map(TryInto::try_into) .map(TryInto::try_into)
.collect::<Result<_, _>>()?, .collect::<Result<_, _>>()?,
}), }),
serde_json::Value::Object(_map) => todo!(), serde_json::Value::Object(map) => {
let value = map
.into_iter()
.map(|(key, value)| Ok((key, NomoValue::try_from(value)?)))
.collect::<Result<_, _>>()?;
Ok(NomoValue::Object { value })
}
} }
} }
} }

View file

@ -0,0 +1,74 @@
---
source: tests/file_tests.rs
expression: parsed
info:
input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}"
context:
world:
active: true
name: World!
---
ParsedTemplate {
tokens: [
[LeftDelim]"{{" (0..2),
[Whitespace]" " (2..3),
[ConditionalIf]"if" (3..5),
[Whitespace]" " (5..6),
[Ident]"world" (6..11),
[Operator(QuestionMark)]"?" (11..12),
[Operator(Dot)]"." (12..13),
[Ident]"active" (13..19),
[Whitespace]" " (19..20),
[RightDelim]"}}" (20..22),
[Whitespace]"\n" (22..23),
[Content]"Hello" (23..28),
[Whitespace]" " (28..29),
[LeftDelim]"{{" (29..31),
[WantsOutput]"=" (31..32),
[Whitespace]" " (32..33),
[Ident]"world" (33..38),
[Operator(Dot)]"." (38..39),
[Ident]"name" (39..43),
[Whitespace]" " (43..44),
[RightDelim]"}}" (44..46),
[Whitespace]"\n" (46..47),
[LeftDelim]"{{" (47..49),
[Whitespace]" " (49..50),
[End]"end" (50..53),
[Whitespace]" " (53..54),
[RightDelim]"}}" (54..56),
[Whitespace]"\n\n" (56..58),
[LeftDelim]"{{" (58..60),
[Whitespace]" " (60..61),
[ConditionalIf]"if" (61..63),
[Whitespace]" " (63..64),
[Ident]"world" (64..69),
[Operator(QuestionMark)]"?" (69..70),
[Operator(Dot)]"." (70..71),
[Ident]"foo" (71..74),
[Operator(QuestionMark)]"?" (74..75),
[Operator(Dot)]"." (75..76),
[Ident]"bar" (76..79),
[Operator(QuestionMark)]"?" (79..80),
[Operator(Dot)]"." (80..81),
[Ident]"active" (81..87),
[Whitespace]" " (87..88),
[RightDelim]"}}" (88..90),
[Whitespace]"\n " (90..95),
[Content]"Weird!" (95..101),
[Whitespace]"\n" (101..102),
[LeftDelim]"{{" (102..104),
[Whitespace]" " (104..105),
[ConditionalElse]"else" (105..109),
[Whitespace]" " (109..110),
[RightDelim]"}}" (110..112),
[Whitespace]"\n " (112..117),
[Content]"Deep access is working :)" (117..142),
[Whitespace]"\n" (142..143),
[LeftDelim]"{{" (143..145),
[Whitespace]" " (145..146),
[End]"end" (146..149),
[Whitespace]" " (149..150),
[RightDelim]"}}" (150..152),
],
}

View file

@ -0,0 +1,131 @@
---
source: tests/file_tests.rs
expression: ast
info:
input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}"
context:
world:
active: true
name: World!
---
TemplateAst {
root: [
ConditionalChain {
chain: [
Block {
prev_whitespace_content: None,
expression: IfConditional {
expression: AccessOperation {
op: Dot,
lhs: ConditionalAccess(
[Ident]"world" (6..11),
),
rhs: VariableAccess(
[Ident]"active" (13..19),
),
},
},
post_whitespace_content: Some(
[Whitespace]"\n" (22..23),
),
},
ConditionalContent {
content: [
StaticContent(
[Content]"Hello" (23..28),
),
Interpolation {
prev_whitespace_content: Some(
[Whitespace]" " (28..29),
),
expression: AccessOperation {
op: Dot,
lhs: VariableAccess(
[Ident]"world" (33..38),
),
rhs: VariableAccess(
[Ident]"name" (39..43),
),
},
post_whitespace_content: Some(
[Whitespace]"\n" (46..47),
),
},
],
},
Block {
prev_whitespace_content: None,
expression: EndBlock,
post_whitespace_content: Some(
[Whitespace]"\n\n" (56..58),
),
},
],
},
ConditionalChain {
chain: [
Block {
prev_whitespace_content: None,
expression: IfConditional {
expression: AccessOperation {
op: Dot,
lhs: AccessOperation {
op: Dot,
lhs: AccessOperation {
op: Dot,
lhs: ConditionalAccess(
[Ident]"world" (64..69),
),
rhs: ConditionalAccess(
[Ident]"foo" (71..74),
),
},
rhs: ConditionalAccess(
[Ident]"bar" (76..79),
),
},
rhs: VariableAccess(
[Ident]"active" (81..87),
),
},
},
post_whitespace_content: Some(
[Whitespace]"\n " (90..95),
),
},
ConditionalContent {
content: [
StaticContent(
[Content]"Weird!" (95..101),
),
],
},
Block {
prev_whitespace_content: Some(
[Whitespace]"\n" (101..102),
),
expression: ElseConditional {
expression: None,
},
post_whitespace_content: Some(
[Whitespace]"\n " (112..117),
),
},
ConditionalContent {
content: [
StaticContent(
[Content]"Deep access is working :)" (117..142),
),
],
},
Block {
prev_whitespace_content: Some(
[Whitespace]"\n" (142..143),
),
expression: EndBlock,
post_whitespace_content: None,
},
],
},
],
}

View file

@ -0,0 +1,313 @@
---
source: tests/file_tests.rs
expression: emit
info:
input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}"
context:
world:
active: true
name: World!
---
VMInstructions {
labels: {
LabelSlot {
index: 0,
}: 18,
LabelSlot {
index: 2,
}: 4,
LabelSlot {
index: 4,
}: 18,
LabelSlot {
index: 6,
}: 13,
LabelSlot {
index: 8,
}: 43,
LabelSlot {
index: 10,
}: 31,
LabelSlot {
index: 12,
}: 27,
LabelSlot {
index: 14,
}: 23,
LabelSlot {
index: 16,
}: 38,
},
instructions: [
LoadFromContextToSlot {
name: "world" (6..11),
slot: VariableSlot {
index: 3,
},
fail_on_not_found: false,
},
JumpIfUndefined {
slot: VariableSlot {
index: 3,
},
jump: LabelSlot {
index: 2,
},
},
IndexSlotToSlot {
name: "active" (13..19),
from_slot: VariableSlot {
index: 3,
},
to_slot: VariableSlot {
index: 3,
},
fail_on_not_found: true,
},
JumpIfUndefined {
slot: VariableSlot {
index: 3,
},
jump: LabelSlot {
index: 2,
},
},
LoadFromSlotToSlot {
from_slot: VariableSlot {
index: 3,
},
to_slot: VariableSlot {
index: 1,
},
},
JumpIfNotTrue {
emit_slot: VariableSlot {
index: 1,
},
jump: LabelSlot {
index: 4,
},
},
AppendContent {
content: "\n" (22..23),
},
AppendContent {
content: "Hello" (23..28),
},
AppendContent {
content: " " (28..29),
},
LoadFromContextToSlot {
name: "world" (33..38),
slot: VariableSlot {
index: 7,
},
fail_on_not_found: true,
},
JumpIfUndefined {
slot: VariableSlot {
index: 7,
},
jump: LabelSlot {
index: 6,
},
},
IndexSlotToSlot {
name: "name" (39..43),
from_slot: VariableSlot {
index: 7,
},
to_slot: VariableSlot {
index: 7,
},
fail_on_not_found: true,
},
JumpIfUndefined {
slot: VariableSlot {
index: 7,
},
jump: LabelSlot {
index: 6,
},
},
LoadFromSlotToSlot {
from_slot: VariableSlot {
index: 7,
},
to_slot: VariableSlot {
index: 5,
},
},
EmitFromSlot {
slot: VariableSlot {
index: 5,
},
},
AppendContent {
content: "\n" (46..47),
},
Jump {
jump: LabelSlot {
index: 0,
},
},
AppendContent {
content: "\n" (22..23),
},
AppendContent {
content: "\n\n" (56..58),
},
LoadFromContextToSlot {
name: "world" (64..69),
slot: VariableSlot {
index: 15,
},
fail_on_not_found: false,
},
JumpIfUndefined {
slot: VariableSlot {
index: 15,
},
jump: LabelSlot {
index: 14,
},
},
IndexSlotToSlot {
name: "foo" (71..74),
from_slot: VariableSlot {
index: 15,
},
to_slot: VariableSlot {
index: 15,
},
fail_on_not_found: false,
},
JumpIfUndefined {
slot: VariableSlot {
index: 15,
},
jump: LabelSlot {
index: 14,
},
},
LoadFromSlotToSlot {
from_slot: VariableSlot {
index: 15,
},
to_slot: VariableSlot {
index: 13,
},
},
JumpIfUndefined {
slot: VariableSlot {
index: 13,
},
jump: LabelSlot {
index: 12,
},
},
IndexSlotToSlot {
name: "bar" (76..79),
from_slot: VariableSlot {
index: 13,
},
to_slot: VariableSlot {
index: 13,
},
fail_on_not_found: false,
},
JumpIfUndefined {
slot: VariableSlot {
index: 13,
},
jump: LabelSlot {
index: 12,
},
},
LoadFromSlotToSlot {
from_slot: VariableSlot {
index: 13,
},
to_slot: VariableSlot {
index: 11,
},
},
JumpIfUndefined {
slot: VariableSlot {
index: 11,
},
jump: LabelSlot {
index: 10,
},
},
IndexSlotToSlot {
name: "active" (81..87),
from_slot: VariableSlot {
index: 11,
},
to_slot: VariableSlot {
index: 11,
},
fail_on_not_found: true,
},
JumpIfUndefined {
slot: VariableSlot {
index: 11,
},
jump: LabelSlot {
index: 10,
},
},
LoadFromSlotToSlot {
from_slot: VariableSlot {
index: 11,
},
to_slot: VariableSlot {
index: 9,
},
},
JumpIfNotTrue {
emit_slot: VariableSlot {
index: 9,
},
jump: LabelSlot {
index: 16,
},
},
AppendContent {
content: "\n " (90..95),
},
AppendContent {
content: "Weird!" (95..101),
},
AppendContent {
content: "\n" (101..102),
},
Jump {
jump: LabelSlot {
index: 8,
},
},
AppendContent {
content: "\n " (90..95),
},
AppendContent {
content: "\n " (112..117),
},
AppendContent {
content: "Deep access is working :)" (117..142),
},
AppendContent {
content: "\n" (142..143),
},
Jump {
jump: LabelSlot {
index: 8,
},
},
AppendContent {
content: "\n " (112..117),
},
NoOp,
],
}

View file

@ -0,0 +1,11 @@
---
source: tests/file_tests.rs
expression: output
info:
input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}"
context:
world:
active: true
name: World!
---
"\nHello World!\n\n\n\n Deep access is working :)\n"

View file

@ -0,0 +1,16 @@
{
"world": {
"active": true,
"name": "World!"
}
}
---
{{ if world?.active }}
Hello {{= world.name }}
{{ end }}
{{ if world?.foo?.bar?.active }}
Weird!
{{ else }}
Deep access is working :)
{{ end }}

View file

@ -9,7 +9,7 @@ TemplateAst {
root: [ root: [
Interpolation { Interpolation {
prev_whitespace_content: None, prev_whitespace_content: None,
expression: Operation { expression: MathOperation {
op: Times, op: Times,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(5))]"5" (4..5), source: [Literal(Integer(5))]"5" (4..5),
@ -30,9 +30,9 @@ TemplateAst {
}, },
Interpolation { Interpolation {
prev_whitespace_content: None, prev_whitespace_content: None,
expression: Operation { expression: MathOperation {
op: Plus, op: Plus,
lhs: Operation { lhs: MathOperation {
op: Times, op: Times,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(2))]"2" (17..18), source: [Literal(Integer(2))]"2" (17..18),
@ -47,7 +47,7 @@ TemplateAst {
}, },
}, },
}, },
rhs: Operation { rhs: MathOperation {
op: Times, op: Times,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(4))]"4" (25..26), source: [Literal(Integer(4))]"4" (25..26),
@ -69,9 +69,9 @@ TemplateAst {
}, },
Interpolation { Interpolation {
prev_whitespace_content: None, prev_whitespace_content: None,
expression: Operation { expression: MathOperation {
op: Plus, op: Plus,
lhs: Operation { lhs: MathOperation {
op: Divide, op: Divide,
lhs: Literal { lhs: Literal {
source: [Literal(Integer(3))]"3" (38..39), source: [Literal(Integer(3))]"3" (38..39),