Add if else if chains

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-09 12:51:49 +01:00
parent ef02e94591
commit ff308649b9
10 changed files with 241 additions and 115 deletions

View file

@ -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);
}
}