Skip to content

Commit

Permalink
Create some helper utils for testing and alter implicit semicolon par…
Browse files Browse the repository at this point in the history
…sing
  • Loading branch information
andogq committed Mar 12, 2024
1 parent 5295e86 commit 1c01384
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 96 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,24 @@ fn my_func(a: usize, b: usize) -> usize {
// Function calls
my_func(x, y);
```

# Structure

A program is a list of statements

A statement may be:

- An expression
- A `let` binding
- A `return` statement

An expression is optionally followed by a semicolon, and may be:

- A literal (integer, string, identifier, etc)
- An `if` statement
- A block
- Some kind of operation (infix, prefix, etc)

A block is a list of statements (same as a program currently, but this will change)

An `if` statement contains an expression, followed by a block (and optionally more stuff for other branches)
135 changes: 124 additions & 11 deletions src/core/ast/statement/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ use super::ParseNode;
pub enum Statement {
Let(LetStatement),
Return(ReturnStatement),
Expression(Expression),
Expression {
/// The expression
expression: Expression,

/// Whether the expression is terminated with a semicolon
semicolon: bool,
},
}

impl<S> ParseNode<S> for Statement
Expand All @@ -30,15 +36,12 @@ where
match lexer.peek() {
Token::Let(_) => Ok(Statement::Let(LetStatement::parse(lexer)?)),
Token::Return(_) => Ok(Statement::Return(ReturnStatement::parse(lexer)?)),
_ => {
let expression = Expression::parse(lexer)?;

// Expression statement may end in semicolon, or be ommitted for implicit returns
// TODO: Should semicolon checks be done for all statements at this level?
lexer.next_if(|token| matches!(token, Token::Semicolon(_)));

Ok(Statement::Expression(expression))
}
_ => Ok(Statement::Expression {
expression: Expression::parse(lexer)?,
semicolon: lexer
.next_if(|token| matches!(token, Token::Semicolon(_)))
.is_some(),
}),
}
}
}
Expand All @@ -48,7 +51,117 @@ impl Display for Statement {
match self {
Statement::Let(let_statement) => let_statement.fmt(f),
Statement::Return(return_statement) => return_statement.fmt(f),
Statement::Expression(expression) => expression.fmt(f),
Statement::Expression {
expression,
semicolon,
} => {
expression.fmt(f)?;

if *semicolon {
write!(f, ";")?;
}

Ok(())
}
}
}
}

#[cfg(test)]
mod test {
use crate::{
assert_pattern,
core::ast::{Identifier, InfixExpression},
test_parser,
};

use super::*;

#[test]
fn let_statement() {
test_parser!(
Statement,
"let x = 1;",
Statement::Let(LetStatement {
name,
value: Expression::Integer(i),
..
}),
{
assert_eq!(name.value, "x");
assert_eq!(i.value, 1);
}
);
}

#[test]
fn return_statement() {
test_parser!(
Statement,
"return y;",
Statement::Return(ReturnStatement {
value: Expression::Identifier(Identifier { value, .. }),
..
}),
{
assert_eq!(value, "y");
}
);
}

#[test]
fn expression_statement_implicit() {
test_parser!(
Statement,
"a + b",
Statement::Expression {
expression: Expression::Infix(InfixExpression {
operator,
left,
right,
..
}),
semicolon: false
},
{
assert_eq!(operator, "+");

assert_pattern!(*left, Expression::Identifier(Identifier { value, .. }), {
assert_eq!(value, "a");
});

assert_pattern!(*right, Expression::Identifier(Identifier { value, .. }), {
assert_eq!(value, "b");
});
}
);
}

#[test]
fn expression_statement_explicit() {
test_parser!(
Statement,
"a + b;",
Statement::Expression {
expression: Expression::Infix(InfixExpression {
operator,
left,
right,
..
}),
semicolon: true
},
{
assert_eq!(operator, "+");

assert_pattern!(*left, Expression::Identifier(Identifier { value, .. }), {
assert_eq!(value, "a");
});

assert_pattern!(*right, Expression::Identifier(Identifier { value, .. }), {
assert_eq!(value, "b");
});
}
);
}
}
Loading

0 comments on commit 1c01384

Please sign in to comment.