diff --git a/site/posts/2021-05-03-statements.md b/site/posts/2021-05-03-statements.md new file mode 100644 index 0000000..850482e --- /dev/null +++ b/site/posts/2021-05-03-statements.md @@ -0,0 +1,97 @@ +``` +metadata.title = "Part 9: Statements" +metadata.tags = ["build a programming language", "rust"] +metadata.date = "2021-05-03 17:46:42 -0400" +metadata.shortDesc = "" +metadata.slug = "statements" +metadata.preamble = `

This post is part of a series about learning Rust and building a small programming language.


` +``` + +So the parser can handle a single expression, but since we're not building a Lisp, that's not enough. It needs to handle multiple statements. For context, an expression is a piece of code that represents a value whereas a statement is a piece of code that can be executed but does not result in a value. + + + +In the AST, there's a new top-level type: `Statement`. For now, the only type of statement is one that contains an expression and nothing else. + +```rust +enum Statement { + Expr(Node), +} +``` + +The top level `parse` function has also changed to reflect this. It now returns a vector of statements, instead of a single expression node. The `do_parse` function continues to work exactly as it has, but is renamed `parse_expression` to since that's what it's actually doing. + +```rust +fn parse(tokens: &[Token]) -> Vec { + let mut it = tokens.iter().peekable(); + let mut statements = Vec = vec![]; + while let Some(_) = it.peek() { + match parse_statement(&mut it) { + Some(statement) => statements.push(statement), + None => (), + } + } + statements +} +``` + +The `parse_statement` function does exactly what the name suggests. + +```rust +fn parse_statement<'a, I: Iterator>(it: &mut Peekable<'a, I>) -> Option { + if it.peek().is_none() { + return None; + } + + let node = parse_expression(it).map(|node| Statement::Expr(node)); + node +} +``` + +With that in place, parsing multiple statements is easy. The only change is that, after successfully parsing a statement, we need to consume a semicolon if there is one. Then, the `parse` loop will continue and the next statement can be parsed. + +```rust +fn parse_statement<'a, I: Iterator>(it: &mut Peekable<'a, I>) -> Option { + // ... + match it.peek() { + Some(Token::Semicolon) => { + it.next(); + } + Some(tok) => { + panic!("unexpected token {:?} after statement", tok); + } + None => (), + } + + node +} +``` + +I intend to make semicolons optional and allow newline-delimited statements, but that is more complicated and will have to wait for another time. For now, this is good enough: + +```rust +fn main() { + let tokens = tokenize("1 + 2; foo();"); + print("statements: {:?}", parse(&tokens)); +} +``` + +```sh +$ cargo run +statements: [ + Expr( + BinaryOp { + left: Integer(1), + op: Add, + right: Integer(2), + }, + ), + Expr( + Call { + name: "foo", + params: [], + }, + ), +] +``` +