Add if else if chains
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
ef02e94591
commit
ff308649b9
10 changed files with 241 additions and 115 deletions
|
|
@ -227,7 +227,7 @@ pub enum TemplateAstExpr<'input> {
|
|||
chain: Vec<TemplateAstExpr<'input>>,
|
||||
},
|
||||
IfConditional {
|
||||
if_block: Box<TemplateAstExpr<'input>>,
|
||||
expression: Box<TemplateAstExpr<'input>>,
|
||||
},
|
||||
ConditionalContent {
|
||||
content: Vec<TemplateAstExpr<'input>>,
|
||||
|
|
@ -316,10 +316,10 @@ fn parse_conditional_chain<'input>(
|
|||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace("conditional_chain", |input: &mut Input<'input>| {
|
||||
let if_block = parse_conditional_if.map(Box::new).parse_next(input)?;
|
||||
let if_block = parse_conditional_if.parse_next(input)?;
|
||||
let mut chain = vec![];
|
||||
|
||||
chain.push(TemplateAstExpr::IfConditional { if_block });
|
||||
chain.push(if_block);
|
||||
|
||||
loop {
|
||||
let (content, end_block): (Vec<_>, _) = repeat_till(
|
||||
|
|
@ -344,7 +344,7 @@ fn parse_conditional_chain<'input>(
|
|||
|
||||
chain.push(end_block);
|
||||
|
||||
if dbg!(is_end) {
|
||||
if is_end {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -359,13 +359,17 @@ fn parse_conditional_if<'input>(
|
|||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace(
|
||||
"conditional",
|
||||
parse_block(preceded(
|
||||
TokenKind::ConditionalIf,
|
||||
cut_err(
|
||||
surrounded(ws, parse_value_expression)
|
||||
.context(AstError::ctx().msg("Expected an expression after 'if'")),
|
||||
),
|
||||
)),
|
||||
parse_block(
|
||||
preceded(
|
||||
TokenKind::ConditionalIf,
|
||||
cut_err(
|
||||
surrounded(ws, parse_value_expression)
|
||||
.map(Box::new)
|
||||
.context(AstError::ctx().msg("Expected an expression after 'if'")),
|
||||
),
|
||||
)
|
||||
.map(|expression| TemplateAstExpr::IfConditional { expression }),
|
||||
),
|
||||
)
|
||||
.parse_next(input)
|
||||
}
|
||||
|
|
@ -559,16 +563,16 @@ mod tests {
|
|||
root: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"foo" (6..9),
|
||||
),
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [
|
||||
|
|
@ -687,14 +691,14 @@ mod tests {
|
|||
root: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"foo" (6..9),
|
||||
),
|
||||
post_whitespace_content: None,
|
||||
},
|
||||
post_whitespace_content: None,
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [],
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ TemplateAst {
|
|||
root: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"foo" (6..9),
|
||||
),
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [
|
||||
|
|
|
|||
|
|
@ -6,16 +6,16 @@ TemplateAst {
|
|||
root: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"foo" (6..9),
|
||||
),
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]" " (12..13),
|
||||
),
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [
|
||||
|
|
|
|||
|
|
@ -6,31 +6,31 @@ TemplateAst {
|
|||
root: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"foo" (6..9),
|
||||
),
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]"\n " (12..25),
|
||||
),
|
||||
},
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]"\n " (12..25),
|
||||
),
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [
|
||||
ConditionalChain {
|
||||
chain: [
|
||||
IfConditional {
|
||||
if_block: Block {
|
||||
prev_whitespace_content: None,
|
||||
Block {
|
||||
prev_whitespace_content: None,
|
||||
expression: IfConditional {
|
||||
expression: VariableAccess(
|
||||
[Ident]"bar" (31..34),
|
||||
),
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]"\n " (37..54),
|
||||
),
|
||||
},
|
||||
post_whitespace_content: Some(
|
||||
[Whitespace]"\n " (37..54),
|
||||
),
|
||||
},
|
||||
ConditionalContent {
|
||||
content: [
|
||||
|
|
|
|||
155
src/emit/mod.rs
155
src/emit/mod.rs
|
|
@ -42,6 +42,9 @@ pub enum Instruction {
|
|||
emit_slot: VariableSlot,
|
||||
jump: isize,
|
||||
},
|
||||
Jump {
|
||||
jump: isize,
|
||||
},
|
||||
}
|
||||
|
||||
pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec<Instruction> {
|
||||
|
|
@ -90,76 +93,91 @@ fn emit_ast_expr(
|
|||
}
|
||||
TemplateAstExpr::ConditionalChain { chain } => {
|
||||
let mut chain = chain.iter();
|
||||
let Some(TemplateAstExpr::IfConditional {
|
||||
if_block: expression,
|
||||
}) = chain.next()
|
||||
else {
|
||||
unreachable!("First element in conditional chain should be an IfConditional");
|
||||
};
|
||||
|
||||
let TemplateAstExpr::Block {
|
||||
prev_whitespace_content,
|
||||
expression,
|
||||
post_whitespace_content,
|
||||
} = expression.as_ref()
|
||||
else {
|
||||
unreachable!("The end of an IfConditional must be a Block");
|
||||
};
|
||||
let mut end_indices = vec![];
|
||||
|
||||
let Some(TemplateAstExpr::ConditionalContent { content }) = chain.next() else {
|
||||
unreachable!("The end of an IfConditional must be a Block");
|
||||
};
|
||||
let mut previous_post_whitespace_content: &Option<crate::parser::TemplateToken> = &None;
|
||||
let mut previous_jump: Option<usize> = None;
|
||||
|
||||
if let Some(ws) = prev_whitespace_content {
|
||||
loop {
|
||||
let next = chain.next().unwrap();
|
||||
if let Some(ws) = previous_post_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if let TemplateAstExpr::ConditionalContent { content } = &next {
|
||||
for ast in content {
|
||||
emit_ast_expr(machine, eval, ast);
|
||||
}
|
||||
|
||||
end_indices.push(eval.len());
|
||||
eval.push(Instruction::Jump { jump: isize::MAX });
|
||||
} else if let TemplateAstExpr::Block {
|
||||
prev_whitespace_content,
|
||||
post_whitespace_content,
|
||||
expression,
|
||||
} = &next
|
||||
{
|
||||
previous_post_whitespace_content = post_whitespace_content;
|
||||
if let Some(ws) = prev_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if let TemplateAstExpr::IfConditional { expression } = &**expression {
|
||||
let emit_slot = machine.reserve_slot();
|
||||
emit_expr_load(machine, eval, emit_slot, expression);
|
||||
|
||||
previous_jump = Some(eval.len());
|
||||
eval.push(Instruction::JumpIfNotTrue {
|
||||
emit_slot,
|
||||
jump: isize::MAX,
|
||||
});
|
||||
} else if let TemplateAstExpr::ElseConditional { expression } = &**expression {
|
||||
if let Some(previous_jump) = previous_jump.take() {
|
||||
let new_jump = eval.len() - previous_jump - 1;
|
||||
let Instruction::JumpIfNotTrue { jump, .. } = &mut eval[previous_jump]
|
||||
else {
|
||||
panic!("Jump slot had something that is not a jump?!");
|
||||
};
|
||||
|
||||
*jump = new_jump as isize;
|
||||
} else {
|
||||
panic!("Got an else without a previous if?");
|
||||
};
|
||||
|
||||
if let Some(expression) = expression {
|
||||
let emit_slot = machine.reserve_slot();
|
||||
emit_expr_load(machine, eval, emit_slot, expression);
|
||||
|
||||
previous_jump = Some(eval.len());
|
||||
eval.push(Instruction::JumpIfNotTrue {
|
||||
emit_slot,
|
||||
jump: isize::MAX,
|
||||
});
|
||||
} else {
|
||||
// We don't have to do anything in the else case
|
||||
}
|
||||
} else if let TemplateAstExpr::EndBlock = &**expression {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ws) = previous_post_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let emit_slot = machine.reserve_slot();
|
||||
emit_expr_load(machine, eval, emit_slot, expression);
|
||||
|
||||
let index = eval.len();
|
||||
eval.push(Instruction::JumpIfNotTrue {
|
||||
emit_slot,
|
||||
jump: isize::MAX,
|
||||
});
|
||||
|
||||
if let Some(ws) = post_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
for ast in content {
|
||||
emit_ast_expr(machine, eval, ast);
|
||||
}
|
||||
|
||||
let Some(TemplateAstExpr::Block {
|
||||
prev_whitespace_content,
|
||||
post_whitespace_content,
|
||||
..
|
||||
}) = chain.last()
|
||||
else {
|
||||
unreachable!("The end of an IfConditional must be a End Block");
|
||||
};
|
||||
|
||||
if let Some(ws) = prev_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let jump = eval.len() - index - 1;
|
||||
eval[index] = Instruction::JumpIfNotTrue {
|
||||
emit_slot,
|
||||
jump: jump as isize,
|
||||
};
|
||||
|
||||
if let Some(ws) = post_whitespace_content {
|
||||
eval.push(Instruction::AppendContent {
|
||||
content: ws.source().clone(),
|
||||
});
|
||||
for index in end_indices {
|
||||
let jump = eval.len() - index - 1;
|
||||
eval[index] = Instruction::Jump {
|
||||
jump: jump as isize,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -235,4 +253,17 @@ mod tests {
|
|||
]
|
||||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_if_else_if() {
|
||||
let input = "{{ if foo }} foo {{ else if bar }} bar {{ else }} foobar {{ end }}";
|
||||
|
||||
let parsed = crate::parser::parse(input.into()).unwrap();
|
||||
|
||||
let ast = crate::ast::parse(parsed.tokens()).unwrap();
|
||||
|
||||
let emit = emit_machine(ast);
|
||||
|
||||
insta::assert_debug_snapshot!(emit);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
75
src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap
Normal file
75
src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
---
|
||||
source: src/emit/mod.rs
|
||||
expression: emit
|
||||
---
|
||||
[
|
||||
LoadFromContextToSlot {
|
||||
name: "foo" (6..9),
|
||||
slot: VariableSlot {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 0,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (12..13),
|
||||
},
|
||||
AppendContent {
|
||||
content: "foo" (13..16),
|
||||
},
|
||||
Jump {
|
||||
jump: 14,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (12..13),
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (16..17),
|
||||
},
|
||||
LoadFromContextToSlot {
|
||||
name: "bar" (28..31),
|
||||
slot: VariableSlot {
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
JumpIfNotTrue {
|
||||
emit_slot: VariableSlot {
|
||||
index: 1,
|
||||
},
|
||||
jump: 5,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (34..35),
|
||||
},
|
||||
AppendContent {
|
||||
content: "bar" (35..38),
|
||||
},
|
||||
Jump {
|
||||
jump: 7,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (34..35),
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (38..39),
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (49..50),
|
||||
},
|
||||
AppendContent {
|
||||
content: "foobar" (50..56),
|
||||
},
|
||||
Jump {
|
||||
jump: 2,
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (49..50),
|
||||
},
|
||||
AppendContent {
|
||||
content: " " (56..57),
|
||||
},
|
||||
]
|
||||
|
|
@ -67,6 +67,16 @@ pub fn execute(
|
|||
}
|
||||
}
|
||||
}
|
||||
Instruction::Jump { jump } => {
|
||||
let (new_ip, overflow) = ip.overflowing_add_signed(*jump);
|
||||
|
||||
if overflow {
|
||||
return Err(EvaluationError::InstructionPointerOverflow);
|
||||
} else {
|
||||
ip = new_ip;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ip += 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue