diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ba8122f..9f18f45 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -227,7 +227,7 @@ pub enum TemplateAstExpr<'input> { chain: Vec>, }, IfConditional { - if_block: Box>, + expression: Box>, }, ConditionalContent { content: Vec>, @@ -316,10 +316,10 @@ fn parse_conditional_chain<'input>( input: &mut Input<'input>, ) -> Result, 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, 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: [], diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap index 07e4955..0a3b739 100644 --- a/src/ast/snapshots/nomo__ast__tests__check_if_else.snap +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap @@ -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: [ diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap index 2690597..3912e60 100644 --- a/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap @@ -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: [ diff --git a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap index 59d5426..3701cc2 100644 --- a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap +++ b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap @@ -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: [ diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 6f38a91..2c1a105 100644 --- a/src/emit/mod.rs +++ b/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 { @@ -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 = &None; + let mut previous_jump: Option = 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); + } } diff --git a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap new file mode 100644 index 0000000..14b290f --- /dev/null +++ b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap @@ -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), + }, +] diff --git a/src/eval/mod.rs b/src/eval/mod.rs index f94ed8c..0b0b6f9 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -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; diff --git a/tests/cases/2-ast@condition.snap b/tests/cases/2-ast@condition.snap index 22d6b93..ae3a62e 100644 --- a/tests/cases/2-ast@condition.snap +++ b/tests/cases/2-ast@condition.snap @@ -7,16 +7,16 @@ TemplateAst { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"test" (6..10), ), - post_whitespace_content: Some( - [Whitespace]"\n " (13..18), - ), }, + post_whitespace_content: Some( + [Whitespace]"\n " (13..18), + ), }, ConditionalContent { content: [ diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index f79800d..56d187e 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -14,7 +14,7 @@ input_file: tests/cases/condition.nomo emit_slot: VariableSlot { index: 0, }, - jump: 3, + jump: 9223372036854775807, }, AppendContent { content: "\n " (13..18), @@ -22,6 +22,12 @@ input_file: tests/cases/condition.nomo AppendContent { content: "Hello World!" (18..30), }, + Jump { + jump: 3, + }, + AppendContent { + content: "\n " (13..18), + }, AppendContent { content: "\n" (30..31), }, diff --git a/tests/cases/4-output@condition.snap b/tests/cases/4-output@condition.snap index 1d5af6f..78c70f2 100644 --- a/tests/cases/4-output@condition.snap +++ b/tests/cases/4-output@condition.snap @@ -3,4 +3,4 @@ source: tests/file_tests.rs expression: output input_file: tests/cases/condition.nomo --- -"\n Hello World!\n\n\nmore" +"\n Hello World!\n\nmore"