Implement if/if-else statements in grammar, build, unparse, and pretty-print.

This commit is contained in:
Jesse Brault 2025-05-17 12:36:29 -05:00
parent 35d616a538
commit bf06407d16
7 changed files with 316 additions and 76 deletions

9
sketching/may_2025/if.dm Normal file
View File

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

View File

@ -863,15 +863,81 @@ fn build_call_statement(file_id: usize, call_statement_pair: Pair<Rule>) -> Call
}
fn build_return_statement(file_id: usize, return_statement_pair: Pair<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> WhileStatement {
@ -1220,31 +1286,27 @@ fn build_closure(file_id: usize, closure_pair: Pair<Rule>) -> 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<Rule>) -> Closure {
}
fn build_closure_captures(file_id: usize, captures_pair: Pair<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> Literal {
fn build_double_quote_string(file_id: usize, pair: Pair<Rule>) -> Literal {
let mut parts: Vec<DStringPart> = 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<Rule>) -> Literal {
fn build_backtick_string(file_id: usize, pair: Pair<Rule>) -> Literal {
let mut parts: Vec<DStringPart> = 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
}
}
"})
}
}

View File

@ -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<ElseBlock>,
}
#[derive(Debug)]
#[derive(Debug, Default)]
pub struct ElseIfs(pub Vec<IfStatement>);
#[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)]

View File

@ -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",
}
))
}
}

View File

@ -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 ")?;

View File

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

View File

@ -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()
}"},
)
}
}