Change grammar to properly allow if, while, and for loops (without confusing it with closures).

This commit is contained in:
Jesse Brault 2025-05-17 17:29:06 -05:00
parent bf06407d16
commit 692411e232
7 changed files with 177 additions and 39 deletions

24
Cargo.lock generated
View File

@ -218,9 +218,9 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.7.14" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" checksum = "198db74531d58c70a361c42201efde7e2591e976d518caf7662a47dc5720e7b6"
dependencies = [ dependencies = [
"memchr", "memchr",
"thiserror", "thiserror",
@ -229,9 +229,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_derive" name = "pest_derive"
version = "2.7.14" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" checksum = "d725d9cfd79e87dccc9341a2ef39d1b6f6353d68c4b33c177febbe1a402c97c5"
dependencies = [ dependencies = [
"pest", "pest",
"pest_generator", "pest_generator",
@ -239,9 +239,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_generator" name = "pest_generator"
version = "2.7.14" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" checksum = "db7d01726be8ab66ab32f9df467ae8b1148906685bbe75c82d1e65d7f5b3f841"
dependencies = [ dependencies = [
"pest", "pest",
"pest_meta", "pest_meta",
@ -252,9 +252,9 @@ dependencies = [
[[package]] [[package]]
name = "pest_meta" name = "pest_meta"
version = "2.7.14" version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" checksum = "7f9f832470494906d1fca5329f8ab5791cc60beb230c74815dff541cbd2b5ca0"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"pest", "pest",
@ -338,18 +338,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.69" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.69" version = "2.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -12,9 +12,9 @@ name = "dmc"
path = "src/bin/dmc/main.rs" path = "src/bin/dmc/main.rs"
[dependencies] [dependencies]
pest = "2.7.14" pest = { version = "2.8.0" }
clap = { version = "4.5.23", features = ["derive"] } 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" codespan-reporting = "0.12.0"
log = "0.4.27" log = "0.4.27"
indoc = "2.0.6" indoc = "2.0.6"

View File

@ -1,7 +1,7 @@
fn main(args: Array<String>) { fn main(args: Array<String>) {
if args[0] == 'test' { if (args[0] == 'test') {
println('test'); println('test');
} else if args[0] == 'foo' { } else if (args[0] == 'foo') {
println('foo'); println('foo');
} else { } else {
println('not test'); println('not test');

View File

@ -941,11 +941,51 @@ fn build_if_else_statement(file_id: usize, if_else_statement_pair: Pair<Rule>) -
} }
fn build_while_statement(file_id: usize, while_statement_pair: Pair<Rule>) -> WhileStatement { fn build_while_statement(file_id: usize, while_statement_pair: Pair<Rule>) -> 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<Rule>) -> ForStatement { fn build_for_statement(file_id: usize, for_statement_pair: Pair<Rule>) -> 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<Rule>) -> Expression { fn build_expression(file_id: usize, expression_pair: Pair<Rule>) -> Expression {
@ -1514,7 +1554,11 @@ mod tests {
if let Err(e) = parse_result { if let Err(e) = parse_result {
panic!("Parsing failed.\n{}", e) panic!("Parsing failed.\n{}", e)
} else { } 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); dbg!(ast);
} }
} }
@ -1576,7 +1620,7 @@ mod tests {
fn if_statement() { fn if_statement() {
assert_builds(indoc! {" assert_builds(indoc! {"
fn main() { fn main() {
if true { if (true) {
foo foo
} }
} }
@ -1587,7 +1631,7 @@ mod tests {
fn if_else_statement() { fn if_else_statement() {
assert_builds(indoc! {" assert_builds(indoc! {"
fn main() { fn main() {
if true { if (true) {
foo foo
} else { } else {
bar bar
@ -1600,9 +1644,9 @@ mod tests {
fn if_else_if_statement() { fn if_else_if_statement() {
assert_builds(indoc! {" assert_builds(indoc! {"
fn main() { fn main() {
if true { if (true) {
foo foo
} else if false { } else if (false) {
bar bar
} }
} }
@ -1613,9 +1657,9 @@ mod tests {
fn if_else_if_else_statement() { fn if_else_if_else_statement() {
assert_builds(indoc! {" assert_builds(indoc! {"
fn main() { fn main() {
if true { if (true) {
foo foo
} else if false { } else if (false) {
bar bar
} else { } else {
buzz 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<String>) {
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)
}
}
"})
}
} }

View File

@ -824,9 +824,9 @@ impl Unparse for ReturnStatement {
impl Unparse for IfStatement { impl Unparse for IfStatement {
fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> {
writer.write_indented("if ")?; writer.write_indented("if (")?;
self.condition.unparse(writer)?; self.condition.unparse(writer)?;
writer.writeln(" {")?; writer.writeln(") {")?;
self.then_block.unparse_inner(writer)?; self.then_block.unparse_inner(writer)?;
writer.writeln_indented("}")?; writer.writeln_indented("}")?;
Ok(()) Ok(())
@ -847,9 +847,9 @@ impl Unparse for IfElseStatement {
impl Unparse for ElseIfs { impl Unparse for ElseIfs {
fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> { fn unparse(&self, writer: &mut IndentWriter) -> std::io::Result<()> {
for if_statement in &self.0 { for if_statement in &self.0 {
writer.write_indented("else if ")?; writer.write_indented("else if (")?;
if_statement.condition.unparse(writer)?; if_statement.condition.unparse(writer)?;
writer.writeln(" {")?; writer.writeln(") {")?;
if_statement.then_block.unparse_inner(writer)?; if_statement.then_block.unparse_inner(writer)?;
writer.writeln_indented("}")?; writer.writeln_indented("}")?;
} }

View File

@ -536,7 +536,9 @@ ReturnStatement = {
IfStatement = { IfStatement = {
If If
~ "("
~ Expression ~ Expression
~ ")"
~ BlockStatement ~ BlockStatement
} }
@ -558,15 +560,19 @@ ElseBlock = {
WhileStatement = { WhileStatement = {
While While
~ "("
~ Expression ~ Expression
~ ")"
~ BlockStatement ~ BlockStatement
} }
ForStatement = { ForStatement = {
For For
~ Expression ~ "("
~ Identifier
~ In ~ In
~ Expression ~ Expression
~ ")"
~ BlockStatement ~ BlockStatement
} }

View File

@ -8,7 +8,7 @@ pub struct DeimosParser;
mod deimos_parser_tests { mod deimos_parser_tests {
use crate::parser::{DeimosParser, Rule}; use crate::parser::{DeimosParser, Rule};
use indoc::indoc; use indoc::indoc;
use pest::Parser; use pest::{parses_to, Parser};
macro_rules! fail_rule { macro_rules! fail_rule {
($pair: expr; $rule:path) => {{ ($pair: expr; $rule:path) => {{
@ -34,7 +34,7 @@ mod deimos_parser_tests {
panic!("Parsing failed.\n{}", e); panic!("Parsing failed.\n{}", e);
} else { } else {
let mut pairs = parse_result.unwrap(); let mut pairs = parse_result.unwrap();
if input != pairs.as_str() { if input.trim() != pairs.as_str().trim() {
panic!( panic!(
"Parsing did not consume entire input. Consumed only:\n{}", "Parsing did not consume entire input. Consumed only:\n{}",
pairs.as_str() pairs.as_str()
@ -101,7 +101,7 @@ mod deimos_parser_tests {
parses_to( parses_to(
Rule::IfStatement, Rule::IfStatement,
indoc! {" indoc! {"
if foo == 42 { if (foo == 42) {
bar() bar()
}"}, }"},
) )
@ -112,7 +112,7 @@ mod deimos_parser_tests {
parses_to( parses_to(
Rule::IfElseStatement, Rule::IfElseStatement,
indoc! {" indoc! {"
if foo == 42 { if (foo == 42) {
bar() bar()
} else { } else {
baz() baz()
@ -125,9 +125,9 @@ mod deimos_parser_tests {
parses_to( parses_to(
Rule::IfElseStatement, Rule::IfElseStatement,
indoc! {" indoc! {"
if foo == 42 { if (foo == 42) {
bar() bar()
} else if foo == 16 { } else if (foo == 16) {
baz() baz()
}"}, }"},
) )
@ -138,13 +138,46 @@ mod deimos_parser_tests {
parses_to( parses_to(
Rule::IfElseStatement, Rule::IfElseStatement,
indoc! {" indoc! {"
if foo == 42 { if (foo == 42) {
foo() foo()
} else if foo == 16 { } else if (foo == 16) {
baz() baz()
} else { } else {
fizz() 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);
}
"})
}
} }