From 692411e2328e3bd37ce0f2a695d4f85eecdc7aa7 Mon Sep 17 00:00:00 2001 From: Jesse Brault Date: Sat, 17 May 2025 17:29:06 -0500 Subject: [PATCH] Change grammar to properly allow if, while, and for loops (without confusing it with closures). --- Cargo.lock | 24 ++++---- Cargo.toml | 4 +- sketching/may_2025/if.dm | 4 +- src/ast/build.rs | 119 +++++++++++++++++++++++++++++++++++---- src/ast/unparse.rs | 8 +-- src/parser/deimos.pest | 8 ++- src/parser/mod.rs | 49 +++++++++++++--- 7 files changed, 177 insertions(+), 39 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29092f9..bb676ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,9 +218,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "pest" -version = "2.7.14" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6" dependencies = [ "memchr", "thiserror", @@ -229,9 +229,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.14" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5" dependencies = [ "pest", "pest_generator", @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.14" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841" dependencies = [ "pest", "pest_meta", @@ -252,9 +252,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.14" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0" dependencies = [ "once_cell", "pest", @@ -338,18 +338,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.69" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.69" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 9d3a400..338a3d2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,9 @@ name = "dmc" path = "src/bin/dmc/main.rs" [dependencies] -pest = "2.7.14" +pest = { version = "2.8.0" } clap = { version = "4.5.23", features = ["derive"] } -pest_derive = "2.7.14" +pest_derive = { version = "2.8.0", features = ["grammar-extras"] } codespan-reporting = "0.12.0" log = "0.4.27" indoc = "2.0.6" diff --git a/sketching/may_2025/if.dm b/sketching/may_2025/if.dm index adeeca7..31f0105 100644 --- a/sketching/may_2025/if.dm +++ b/sketching/may_2025/if.dm @@ -1,7 +1,7 @@ fn main(args: Array) { - if args[0] == 'test' { + if (args[0] == 'test') { println('test'); - } else if args[0] == 'foo' { + } else if (args[0] == 'foo') { println('foo'); } else { println('not test'); diff --git a/src/ast/build.rs b/src/ast/build.rs index 2dd10fe..dc5a844 100644 --- a/src/ast/build.rs +++ b/src/ast/build.rs @@ -941,11 +941,51 @@ fn build_if_else_statement(file_id: usize, if_else_statement_pair: Pair) - } fn build_while_statement(file_id: usize, while_statement_pair: Pair) -> WhileStatement { - todo!() + let mut inner = while_statement_pair.into_inner(); + inner.next().unwrap(); // while + let condition = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Expression, + build_expression, + ); + + let body = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::BlockStatement, + build_block_statement, + ); + WhileStatement { condition, body } } fn build_for_statement(file_id: usize, for_statement_pair: Pair) -> ForStatement { - todo!() + let mut inner = for_statement_pair.into_inner(); + inner.next().unwrap(); // for + let variable = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Identifier, + build_identifier, + ); + inner.next().unwrap(); // in + let iterator = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::Expression, + build_expression, + ); + let body = expect_and_use( + file_id, + inner.next().unwrap(), + Rule::BlockStatement, + build_block_statement, + ); + ForStatement { + variable, + iterator, + body, + } } fn build_expression(file_id: usize, expression_pair: Pair) -> Expression { @@ -1514,7 +1554,11 @@ mod tests { if let Err(e) = parse_result { panic!("Parsing failed.\n{}", e) } else { - let ast = build_ast(0, parse_result.unwrap().next().unwrap()); + let mut pairs = parse_result.unwrap(); + if pairs.as_str().trim() != src.trim() { + panic!("Parsing did not consume entire input."); + } + let ast = build_ast(0, pairs.next().unwrap()); dbg!(ast); } } @@ -1576,7 +1620,7 @@ mod tests { fn if_statement() { assert_builds(indoc! {" fn main() { - if true { + if (true) { foo } } @@ -1587,7 +1631,7 @@ mod tests { fn if_else_statement() { assert_builds(indoc! {" fn main() { - if true { + if (true) { foo } else { bar @@ -1600,22 +1644,22 @@ mod tests { fn if_else_if_statement() { assert_builds(indoc! {" fn main() { - if true { + if (true) { foo - } else if false { + } else if (false) { bar } } "}) } - + #[test] fn if_else_if_else_statement() { assert_builds(indoc! {" fn main() { - if true { + if (true) { foo - } else if false { + } else if (false) { bar } else { buzz @@ -1623,4 +1667,59 @@ mod tests { } "}) } + + #[test] + fn while_statement() { + assert_builds(indoc! {" + fn main() { + while (true) { + foo() + } + } + "}) + } + + #[test] + fn for_statement() { + assert_builds(indoc! {" + fn main(args: Array) { + for (arg in args) { + foo(arg); + } + } + "}) + } + + #[test] + fn if_with_call_condition() { + assert_builds(indoc! {" + fn main() { + if (foo()) { + bar() + } + } + "}) + } + + #[test] + fn while_with_call_condition() { + assert_builds(indoc! {" + fn main() { + while (foo()) { + bar() + } + } + "}) + } + + #[test] + fn for_with_call_iterator() { + assert_builds(indoc! {" + fn main() { + for (foo in bar()) { + baz(foo) + } + } + "}) + } } diff --git a/src/ast/unparse.rs b/src/ast/unparse.rs index 77bc403..a2239cb 100644 --- a/src/ast/unparse.rs +++ b/src/ast/unparse.rs @@ -824,9 +824,9 @@ impl Unparse for ReturnStatement { impl Unparse for IfStatement { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { - writer.write_indented("if ")?; + writer.write_indented("if (")?; self.condition.unparse(writer)?; - writer.writeln(" {")?; + writer.writeln(") {")?; self.then_block.unparse_inner(writer)?; writer.writeln_indented("}")?; Ok(()) @@ -847,9 +847,9 @@ 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_indented("else if ")?; + writer.write_indented("else if (")?; if_statement.condition.unparse(writer)?; - writer.writeln(" {")?; + writer.writeln(") {")?; if_statement.then_block.unparse_inner(writer)?; writer.writeln_indented("}")?; } diff --git a/src/parser/deimos.pest b/src/parser/deimos.pest index e9249f4..7a09a0b 100644 --- a/src/parser/deimos.pest +++ b/src/parser/deimos.pest @@ -536,7 +536,9 @@ ReturnStatement = { IfStatement = { If + ~ "(" ~ Expression + ~ ")" ~ BlockStatement } @@ -558,15 +560,19 @@ ElseBlock = { WhileStatement = { While + ~ "(" ~ Expression + ~ ")" ~ BlockStatement } ForStatement = { For - ~ Expression + ~ "(" + ~ Identifier ~ In ~ Expression + ~ ")" ~ BlockStatement } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 0307d49..be090cd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -8,7 +8,7 @@ pub struct DeimosParser; mod deimos_parser_tests { use crate::parser::{DeimosParser, Rule}; use indoc::indoc; - use pest::Parser; + use pest::{parses_to, Parser}; macro_rules! fail_rule { ($pair: expr; $rule:path) => {{ @@ -34,7 +34,7 @@ mod deimos_parser_tests { panic!("Parsing failed.\n{}", e); } else { let mut pairs = parse_result.unwrap(); - if input != pairs.as_str() { + if input.trim() != pairs.as_str().trim() { panic!( "Parsing did not consume entire input. Consumed only:\n{}", pairs.as_str() @@ -101,7 +101,7 @@ mod deimos_parser_tests { parses_to( Rule::IfStatement, indoc! {" - if foo == 42 { + if (foo == 42) { bar() }"}, ) @@ -112,7 +112,7 @@ mod deimos_parser_tests { parses_to( Rule::IfElseStatement, indoc! {" - if foo == 42 { + if (foo == 42) { bar() } else { baz() @@ -125,9 +125,9 @@ mod deimos_parser_tests { parses_to( Rule::IfElseStatement, indoc! {" - if foo == 42 { + if (foo == 42) { bar() - } else if foo == 16 { + } else if (foo == 16) { baz() }"}, ) @@ -138,13 +138,46 @@ mod deimos_parser_tests { parses_to( Rule::IfElseStatement, indoc! {" - if foo == 42 { + if (foo == 42) { foo() - } else if foo == 16 { + } else if (foo == 16) { baz() } else { fizz() }"}, ) } + + #[test] + fn while_statement() { + parses_to(Rule::WhileStatement, "while (foo) { bar() }"); + } + + #[test] + fn for_statement() { + parses_to(Rule::ForStatement, "for (foo in bar) { baz(foo); }"); + } + + #[test] + fn if_statement_with_call_condition() { + parses_to(Rule::IfStatement, indoc! {" + if (foo()) { + bar() + } + "}) + } + + #[test] + fn while_statement_with_call_condition() { + parses_to(Rule::WhileStatement, "while (foo()) { bar(); }") + } + + #[test] + fn for_statement_with_call_iterator() { + parses_to(Rule::ForStatement, indoc! {" + for (foo in bar()) { + baz(foo); + } + "}) + } }