From bf06407d16fdd93393724315ff61ce2c2d5669f4 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sat, 17 May 2025 12:36:29 -0500 Subject: [PATCH] Implement if/if-else statements in grammar, build, unparse, and pretty-print. --- sketching/may_2025/if.dm | 9 ++ src/ast/build.rs | 216 +++++++++++++++++++++++++++++++-------- src/ast/mod.rs | 12 ++- src/ast/pretty_print.rs | 44 ++++++-- src/ast/unparse.rs | 24 +++-- src/parser/deimos.pest | 29 ++++-- src/parser/mod.rs | 58 +++++++++++ 7 files changed, 316 insertions(+), 76 deletions(-) create mode 100644 sketching/may_2025/if.dm diff --git a/sketching/may_2025/if.dm b/sketching/may_2025/if.dm new file mode 100644 index 0000000..adeeca7 --- /dev/null +++ b/sketching/may_2025/if.dm @@ -0,0 +1,9 @@ +fn main(args: Array) { + if args[0] == 'test' { + println('test'); + } else if args[0] == 'foo' { + println('foo'); + } else { + println('not test'); + } +} \ No newline at end of file diff --git a/src/ast/build.rs b/src/ast/build.rs index a5830bd..2dd10fe 100644 --- a/src/ast/build.rs +++ b/src/ast/build.rs @@ -863,15 +863,81 @@ fn build_call_statement(file_id: usize, call_statement_pair: Pair) -> Call } fn build_return_statement(file_id: usize, return_statement_pair: Pair) -> ReturnStatement { - todo!() + let mut inner = return_statement_pair.into_inner(); + if inner.len() == 2 { + inner.next().unwrap(); // return + let expression = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Expression, + build_expression, + ); + ReturnStatement(Some(expression)) + } else { + ReturnStatement(None) + } } fn build_if_statement(file_id: usize, if_statement_pair: Pair) -> IfStatement { - todo!() + let mut inner = if_statement_pair.into_inner(); + inner.next().unwrap(); // if + let condition = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Expression, + build_expression, + ); + let then_block = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::BlockStatement, + build_block_statement, + ); + IfStatement { + condition, + then_block, + } } fn build_if_else_statement(file_id: usize, if_else_statement_pair: Pair) -> IfElseStatement { - todo!() + let mut if_statement = None; + let mut else_ifs = ElseIfs::default(); + let mut else_block = None; + + for inner_pair in if_else_statement_pair.into_inner() { + match inner_pair.as_rule() { + Rule::IfStatement => { + if_statement = Some(build_if_statement(file_id, inner_pair)); + } + Rule::ElseIf => { + let mut else_if_inner = inner_pair.into_inner(); + else_if_inner.next().unwrap(); // else + else_ifs.0.push(expect_and_use( + file_id, + else_if_inner.next().unwrap(), + Rule::IfStatement, + build_if_statement, + )); + } + Rule::ElseBlock => { + let mut else_inner = inner_pair.into_inner(); + else_inner.next().unwrap(); // else + else_block = Some(ElseBlock(expect_and_use( + file_id, + else_inner.next().unwrap(), + Rule::BlockStatement, + build_block_statement, + ))); + } + _ => unreachable!(), + } + } + + IfElseStatement { + if_statement: if_statement.unwrap(), + else_ifs, + else_block, + } } fn build_while_statement(file_id: usize, while_statement_pair: Pair) -> WhileStatement { @@ -1220,31 +1286,27 @@ fn build_closure(file_id: usize, closure_pair: Pair) -> Closure { let mut parameters = ClosureParameters::default(); let mut statements = vec![]; let mut expression = None; - + for inner_pair in closure_pair.into_inner() { match inner_pair.as_rule() { - Rule::Cons => { - modifier = Some(ClosureModifier::Cons) - }, - Rule::Mut => { - modifier = Some(ClosureModifier::Mut) - }, + Rule::Cons => modifier = Some(ClosureModifier::Cons), + Rule::Mut => modifier = Some(ClosureModifier::Mut), Rule::ClosureCaptures => { captures = build_closure_captures(file_id, inner_pair); - }, + } Rule::ClosureParameters => { parameters = build_closure_parameters(file_id, inner_pair); - }, + } Rule::Statement => { statements.push(build_statement(file_id, inner_pair)); - }, + } Rule::Expression => { expression = Some(Box::new(build_expression(file_id, inner_pair))); } _ => unreachable!(), } } - + Closure { modifier, is_move, @@ -1256,55 +1318,85 @@ fn build_closure(file_id: usize, closure_pair: Pair) -> Closure { } fn build_closure_captures(file_id: usize, captures_pair: Pair) -> ClosureCaptures { - ClosureCaptures(captures_pair.into_inner().map(|capture_pair| { - expect_and_use(file_id, capture_pair, Rule::ClosureCapture, build_closure_capture) - }).collect()) + ClosureCaptures( + captures_pair + .into_inner() + .map(|capture_pair| { + expect_and_use( + file_id, + capture_pair, + Rule::ClosureCapture, + build_closure_capture, + ) + }) + .collect(), + ) } fn build_closure_capture(file_id: usize, capture_pair: Pair) -> ClosureCapture { let mut borrow_count = 0; let mut is_mutable = false; let mut identifier = None; - + for inner_pair in capture_pair.into_inner() { match inner_pair.as_rule() { Rule::Borrow => { borrow_count += 1; - }, + } Rule::Mut => { is_mutable = true; - }, + } Rule::Identifier => { identifier = Some(Box::new(build_identifier(file_id, inner_pair))); } _ => unreachable!(), } } - + ClosureCapture { borrow_count, is_mutable, - identifier: identifier.unwrap() + identifier: identifier.unwrap(), } } fn build_closure_parameters(file_id: usize, parameters_pair: Pair) -> ClosureParameters { - ClosureParameters(parameters_pair.into_inner().map(|parameter_pair| { - expect_and_use(file_id, parameter_pair, Rule::ClosureParameter, build_closure_parameter) - }).collect()) + ClosureParameters( + parameters_pair + .into_inner() + .map(|parameter_pair| { + expect_and_use( + file_id, + parameter_pair, + Rule::ClosureParameter, + build_closure_parameter, + ) + }) + .collect(), + ) } fn build_closure_parameter(file_id: usize, parameter_pair: Pair) -> ClosureParameter { let mut inner = parameter_pair.into_inner(); - let identifier = expect_and_use(file_id, inner.next().unwrap(), Rule::Identifier, build_identifier); + let identifier = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Identifier, + build_identifier, + ); let type_use = if let Some(type_use_pair) = inner.next() { - Some(expect_and_use(file_id, type_use_pair, Rule::TypeUse, build_type_use)) + Some(expect_and_use( + file_id, + type_use_pair, + Rule::TypeUse, + build_type_use, + )) } else { None }; ClosureParameter { identifier, - type_use + type_use, } } @@ -1368,12 +1460,7 @@ fn build_single_quote_string(file_id: usize, pair: Pair) -> Literal { fn build_double_quote_string(file_id: usize, pair: Pair) -> Literal { let mut parts: Vec = vec![]; - handle_d_backtick_strings_inner( - Rule::DStringInner, - file_id, - pair, - &mut parts, - ); + handle_d_backtick_strings_inner(Rule::DStringInner, file_id, pair, &mut parts); if parts.len() == 1 && parts[0].is_string() { Literal::String(parts.pop().unwrap().unwrap_string()) } else { @@ -1383,12 +1470,7 @@ fn build_double_quote_string(file_id: usize, pair: Pair) -> Literal { fn build_backtick_string(file_id: usize, pair: Pair) -> Literal { let mut parts: Vec = vec![]; - handle_d_backtick_strings_inner( - Rule::BacktickInner, - file_id, - pair, - &mut parts, - ); + handle_d_backtick_strings_inner(Rule::BacktickInner, file_id, pair, &mut parts); if parts.len() == 1 && parts[0].is_string() { Literal::String(parts.pop().unwrap().unwrap_string()) } else { @@ -1484,9 +1566,61 @@ mod tests { fn d_string_literal_and_expression() { assert_builds("fn main() { \"Hello, ${foo}\" }"); } - + #[test] fn d_string_literal_expression_literal() { assert_builds("fn main() { \"Hello, ${foo}! It is a nice day.\" }"); } + + #[test] + fn if_statement() { + assert_builds(indoc! {" + fn main() { + if true { + foo + } + } + "}) + } + + #[test] + fn if_else_statement() { + assert_builds(indoc! {" + fn main() { + if true { + foo + } else { + bar + } + } + "}) + } + + #[test] + fn if_else_if_statement() { + assert_builds(indoc! {" + fn main() { + if true { + foo + } else if false { + bar + } + } + "}) + } + + #[test] + fn if_else_if_else_statement() { + assert_builds(indoc! {" + fn main() { + if true { + foo + } else if false { + bar + } else { + buzz + } + } + "}) + } } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index b9b1a52..572e430 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -495,15 +495,17 @@ pub struct IfStatement { #[derive(Debug)] pub struct IfElseStatement { - pub condition: Expression, - pub then_block: BlockStatement, + pub if_statement: IfStatement, pub else_ifs: ElseIfs, - pub else_block: BlockStatement, + pub else_block: Option, } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ElseIfs(pub Vec); +#[derive(Debug)] +pub struct ElseBlock(pub BlockStatement); + #[derive(Debug)] pub struct WhileStatement { pub condition: Expression, @@ -587,7 +589,7 @@ pub struct Closure { #[derive(Debug)] pub enum ClosureModifier { Cons, - Mut + Mut, } #[derive(Debug, Default)] diff --git a/src/ast/pretty_print.rs b/src/ast/pretty_print.rs index 616b7a2..274cb31 100644 --- a/src/ast/pretty_print.rs +++ b/src/ast/pretty_print.rs @@ -663,6 +663,7 @@ impl PrettyPrint for IfStatement { fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { writer.writeln_indented("IfStatement")?; writer.increase_indent(); + writer.increase_indent(); self.condition.pretty_print(writer)?; self.then_block.pretty_print(writer)?; writer.decrease_indent(); @@ -674,13 +675,33 @@ impl PrettyPrint for IfElseStatement { fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { writer.writeln_indented("IfElseStatement")?; writer.increase_indent(); - self.condition.pretty_print(writer)?; - self.then_block.pretty_print(writer)?; - for else_if in &self.else_ifs.0 { - else_if.condition.pretty_print(writer)?; - else_if.then_block.pretty_print(writer)?; + self.if_statement.pretty_print(writer)?; + self.else_ifs.pretty_print(writer)?; + if let Some(else_block) = &self.else_block { + else_block.pretty_print(writer)?; } - self.else_block.pretty_print(writer)?; + writer.decrease_indent(); + Ok(()) + } +} + +impl PrettyPrint for ElseIfs { + fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { + writer.writeln_indented("ElseIfs")?; + writer.increase_indent(); + for if_statement in &self.0 { + if_statement.pretty_print(writer)?; + } + writer.decrease_indent(); + Ok(()) + } +} + +impl PrettyPrint for ElseBlock { + fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { + writer.writeln_indented("ElseBlock")?; + writer.increase_indent(); + self.0.pretty_print(writer)?; writer.decrease_indent(); Ok(()) } @@ -845,10 +866,13 @@ impl PrettyPrint for Closure { impl PrettyPrint for ClosureModifier { fn pretty_print(&self, writer: &mut IndentWriter) -> std::io::Result<()> { - writer.writeln_indented(&format!("ClosureModifier({})", match self { - ClosureModifier::Mut => "mut", - ClosureModifier::Cons => "cons" - })) + writer.writeln_indented(&format!( + "ClosureModifier({})", + match self { + ClosureModifier::Mut => "mut", + ClosureModifier::Cons => "cons", + } + )) } } diff --git a/src/ast/unparse.rs b/src/ast/unparse.rs index 1b8c824..77bc403 100644 --- a/src/ast/unparse.rs +++ b/src/ast/unparse.rs @@ -835,15 +835,11 @@ impl Unparse for IfStatement { impl Unparse for IfElseStatement { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { - writer.write_indented("if ")?; - self.condition.unparse(writer)?; - writer.writeln(" {")?; - self.then_block.unparse_inner(writer)?; - writer.write_indented("} ")?; + self.if_statement.unparse(writer)?; self.else_ifs.unparse(writer)?; - writer.writeln("else {")?; - self.else_block.unparse_inner(writer)?; - writer.writeln_indented("}")?; + if let Some(else_block) = &self.else_block { + else_block.unparse(writer)?; + } Ok(()) } } @@ -851,16 +847,24 @@ impl Unparse for IfElseStatement { impl Unparse for ElseIfs { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { for if_statement in &self.0 { - writer.write(" else if ")?; + writer.write_indented("else if ")?; if_statement.condition.unparse(writer)?; writer.writeln(" {")?; if_statement.then_block.unparse_inner(writer)?; - writer.write_indented("} ")?; + writer.writeln_indented("}")?; } Ok(()) } } +impl Unparse for ElseBlock { + fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { + writer.write_indented("else ")?; + self.0.unparse(writer)?; + Ok(()) + } +} + impl Unparse for WhileStatement { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { writer.write_indented("while ")?; diff --git a/src/parser/deimos.pest b/src/parser/deimos.pest index ff549dc..e9249f4 100644 --- a/src/parser/deimos.pest +++ b/src/parser/deimos.pest @@ -488,17 +488,19 @@ BlockStatement = { Statement = { ( - BlockStatement - | VariableDeclaration + VariableDeclaration | AssignmentStatement | CallStatement | ReturnStatement - | IfStatement + ) + ~ Semicolon + | ( + BlockStatement | IfElseStatement + | IfStatement | WhileStatement | ForStatement - )? - ~ Semicolon + ) } VariableDeclaration = { @@ -539,11 +541,18 @@ IfStatement = { } IfElseStatement = { - If - ~ Expression - ~ BlockStatement - ~ ( Else ~ IfStatement )* - ~ Else + IfStatement + ~ ElseIf* + ~ ElseBlock? +} + +ElseIf = { + Else + ~ IfStatement +} + +ElseBlock = { + Else ~ BlockStatement } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f941658..0307d49 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -34,6 +34,12 @@ mod deimos_parser_tests { panic!("Parsing failed.\n{}", e); } else { let mut pairs = parse_result.unwrap(); + if input != pairs.as_str() { + panic!( + "Parsing did not consume entire input. Consumed only:\n{}", + pairs.as_str() + ); + } let first = pairs.next().unwrap(); match_rule!(first; rule) } @@ -89,4 +95,56 @@ mod deimos_parser_tests { fn chained_index_call_on_identifier() { parses_to(Rule::SuffixExpression, "foo[0]()"); } + + #[test] + fn if_statement() { + parses_to( + Rule::IfStatement, + indoc! {" + if foo == 42 { + bar() + }"}, + ) + } + + #[test] + fn if_else_statement() { + parses_to( + Rule::IfElseStatement, + indoc! {" + if foo == 42 { + bar() + } else { + baz() + }"}, + ) + } + + #[test] + fn if_else_if_statement() { + parses_to( + Rule::IfElseStatement, + indoc! {" + if foo == 42 { + bar() + } else if foo == 16 { + baz() + }"}, + ) + } + + #[test] + fn if_else_if_else_statement() { + parses_to( + Rule::IfElseStatement, + indoc! {" + if foo == 42 { + foo() + } else if foo == 16 { + baz() + } else { + fizz() + }"}, + ) + } }