From fedefdb29f66c223cedd3379274273e8ef4518d4 Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 16 Aug 2024 07:09:46 -0400 Subject: [PATCH] Implement "let" and "let mut" lexing/parsing --- dust-lang/src/ast/statement.rs | 49 ++++++++++++++++++++--- dust-lang/src/lexer.rs | 17 ++++++++ dust-lang/src/parser.rs | 72 ++++++++++++++++++++++++++++++++-- dust-lang/src/token.rs | 54 ++++++++++++++----------- 4 files changed, 161 insertions(+), 31 deletions(-) diff --git a/dust-lang/src/ast/statement.rs b/dust-lang/src/ast/statement.rs index 49cf9bf..bb6bffa 100644 --- a/dust-lang/src/ast/statement.rs +++ b/dust-lang/src/ast/statement.rs @@ -10,7 +10,7 @@ use super::{Expression, Node}; pub enum Statement { Expression(Expression), ExpressionNullified(Node), - Let(Node), + Let(Node), StructDefinition(Node), } @@ -52,14 +52,51 @@ impl Display for Statement { } #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct Let { - pub identifier: Node, - pub value: Node, +pub enum LetStatement { + Let { + identifier: Node, + value: Expression, + }, + LetMut { + identifier: Node, + value: Expression, + }, + LetType { + identifier: Node, + r#type: Node, + value: Expression, + }, + LetMutType { + identifier: Node, + r#type: Node, + value: Expression, + }, } -impl Display for Let { +impl Display for LetStatement { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "let {} = {}", self.identifier, self.value) + match self { + LetStatement::Let { identifier, value } => { + write!(f, "let {identifier} = {value}") + } + LetStatement::LetMut { identifier, value } => { + write!(f, "let mut {identifier} = {value}") + } + LetStatement::LetType { + identifier, + r#type, + value, + } => { + write!(f, "let {identifier}: {type} = {value}") + } + LetStatement::LetMutType { + identifier, + r#type, + value, + } => { + write!(f, "let mut {identifier}: {type} = {value}") + } + } } } diff --git a/dust-lang/src/lexer.rs b/dust-lang/src/lexer.rs index d2b9f6c..c80bb05 100644 --- a/dust-lang/src/lexer.rs +++ b/dust-lang/src/lexer.rs @@ -429,6 +429,7 @@ impl Lexer { "is_even" => Token::IsEven, "is_odd" => Token::IsOdd, "length" => Token::Length, + "let" => Token::Let, "mut" => Token::Mut, "read_line" => Token::ReadLine, "struct" => Token::Struct, @@ -507,6 +508,22 @@ impl Display for LexError { mod tests { use super::*; + #[test] + fn let_statement() { + let input = "let x = 42"; + + assert_eq!( + lex(input), + Ok(vec![ + (Token::Let, (0, 3)), + (Token::Identifier("x"), (4, 5)), + (Token::Equal, (6, 7)), + (Token::Integer("42"), (8, 10)), + (Token::Eof, (10, 10)), + ]) + ); + } + #[test] fn unit_struct() { let input = "struct Foo"; diff --git a/dust-lang/src/parser.rs b/dust-lang/src/parser.rs index 553d240..6329e6a 100644 --- a/dust-lang/src/parser.rs +++ b/dust-lang/src/parser.rs @@ -145,6 +145,41 @@ impl<'src> Parser<'src> { pub fn parse_statement(&mut self) -> Result { let start_position = self.current_position; + if let Token::Let = self.current_token { + self.next_token()?; + + let is_mutable = if let Token::Mut = self.current_token { + self.next_token()?; + + true + } else { + false + }; + + let identifier = self.parse_identifier()?; + + if let Token::Equal = self.current_token { + self.next_token()?; + } else { + return Err(ParseError::ExpectedToken { + expected: TokenKind::Equal, + actual: self.current_token.to_owned(), + position: self.current_position, + }); + } + + let value = self.parse_expression(0)?; + + let r#let = if is_mutable { + LetStatement::LetMut { identifier, value } + } else { + LetStatement::Let { identifier, value } + }; + let position = (start_position.0, self.current_position.1); + + return Ok(Statement::Let(Node::new(r#let, position))); + } + if let Token::Struct = self.current_token { self.next_token()?; @@ -1026,10 +1061,41 @@ mod tests { use super::*; #[test] - fn mutable_variable() { - let source = "mut x = false"; + fn let_statement() { + let source = "let x = 42"; - assert_eq!(parse(source), todo!()); + assert_eq!( + parse(source), + Ok(AbstractSyntaxTree { + statements: [Statement::Let(Node::new( + LetStatement::Let { + identifier: Node::new(Identifier::new("x"), (4, 5)), + value: Expression::literal(LiteralExpression::Integer(42), (8, 10)), + }, + (0, 10), + ))] + .into() + }) + ); + } + + #[test] + fn let_mut_statement() { + let source = "let mut x = false"; + + assert_eq!( + parse(source), + Ok(AbstractSyntaxTree { + statements: [Statement::Let(Node::new( + LetStatement::LetMut { + identifier: Node::new(Identifier::new("x"), (8, 9)), + value: Expression::literal(LiteralExpression::Boolean(false), (12, 17)), + }, + (0, 17), + ))] + .into() + }) + ); } #[test] diff --git a/dust-lang/src/token.rs b/dust-lang/src/token.rs index a4fdd10..dd32287 100644 --- a/dust-lang/src/token.rs +++ b/dust-lang/src/token.rs @@ -26,6 +26,7 @@ pub enum Token<'src> { IsEven, IsOdd, Length, + Let, Mut, ReadLine, Str, @@ -97,6 +98,7 @@ impl<'src> Token<'src> { Token::LeftParenthesis => TokenOwned::LeftParenthesis, Token::LeftSquareBrace => TokenOwned::LeftSquareBrace, Token::Length => TokenOwned::Length, + Token::Let => TokenOwned::Let, Token::Less => TokenOwned::Less, Token::LessEqual => TokenOwned::LessOrEqual, Token::Minus => TokenOwned::Minus, @@ -153,6 +155,7 @@ impl<'src> Token<'src> { Token::LeftCurlyBrace => "{", Token::LeftParenthesis => "(", Token::LeftSquareBrace => "[", + Token::Let => "let", Token::Length => "length", Token::Less => "<", Token::LessEqual => "<=", @@ -207,6 +210,7 @@ impl<'src> Token<'src> { Token::LeftCurlyBrace => TokenKind::LeftCurlyBrace, Token::LeftParenthesis => TokenKind::LeftParenthesis, Token::LeftSquareBrace => TokenKind::LeftSquareBrace, + Token::Let => TokenKind::Let, Token::Length => TokenKind::Length, Token::Less => TokenKind::Less, Token::LessEqual => TokenKind::LessOrEqual, @@ -314,6 +318,7 @@ pub enum TokenOwned { Int, IsEven, IsOdd, + Let, Length, Mut, ReadLine, @@ -387,6 +392,7 @@ impl Display for TokenOwned { TokenOwned::LeftParenthesis => Token::LeftParenthesis.fmt(f), TokenOwned::LeftSquareBrace => Token::LeftSquareBrace.fmt(f), TokenOwned::Length => Token::Length.fmt(f), + TokenOwned::Let => Token::Let.fmt(f), TokenOwned::Less => Token::Less.fmt(f), TokenOwned::LessOrEqual => Token::LessEqual.fmt(f), TokenOwned::Minus => Token::Minus.fmt(f), @@ -435,6 +441,7 @@ pub enum TokenKind { IsEven, IsOdd, Length, + Let, ReadLine, Str, ToString, @@ -506,6 +513,7 @@ impl Display for TokenKind { TokenKind::LeftParenthesis => Token::LeftParenthesis.fmt(f), TokenKind::LeftSquareBrace => Token::LeftSquareBrace.fmt(f), TokenKind::Length => Token::Length.fmt(f), + TokenKind::Let => Token::Let.fmt(f), TokenKind::Less => Token::Less.fmt(f), TokenKind::LessOrEqual => Token::LessEqual.fmt(f), TokenKind::Minus => Token::Minus.fmt(f), @@ -535,13 +543,31 @@ impl Display for TokenKind { pub(crate) mod tests { use super::*; - pub fn all_tokens<'src>() -> [Token<'src>; 49] { + pub fn all_tokens<'src>() -> [Token<'src>; 51] { [ - Token::Async, - Token::Bang, - Token::BangEqual, - Token::Bool, + Token::Eof, + Token::Identifier("identifier"), Token::Boolean("true"), + Token::Float("1.0"), + Token::Integer("1"), + Token::String("string"), + Token::Async, + Token::Bool, + Token::Else, + Token::FloatKeyword, + Token::If, + Token::Int, + Token::IsEven, + Token::IsOdd, + Token::Length, + Token::Let, + Token::ReadLine, + Token::Str, + Token::ToString, + Token::While, + Token::WriteLine, + Token::BangEqual, + Token::Bang, Token::Colon, Token::Comma, Token::Dot, @@ -549,23 +575,12 @@ pub(crate) mod tests { Token::DoubleDot, Token::DoubleEqual, Token::DoublePipe, - Token::Else, - Token::Eof, Token::Equal, - Token::Float("0.0"), - Token::FloatKeyword, Token::Greater, Token::GreaterEqual, - Token::Identifier(""), - Token::If, - Token::Int, - Token::Integer("0"), - Token::IsEven, - Token::IsOdd, Token::LeftCurlyBrace, Token::LeftParenthesis, Token::LeftSquareBrace, - Token::Length, Token::Less, Token::LessEqual, Token::Minus, @@ -574,18 +589,13 @@ pub(crate) mod tests { Token::Percent, Token::Plus, Token::PlusEqual, - Token::ReadLine, Token::RightCurlyBrace, Token::RightParenthesis, Token::RightSquareBrace, Token::Semicolon, Token::Star, - Token::Str, - Token::String(""), Token::Struct, - Token::ToString, - Token::While, - Token::WriteLine, + Token::Slash, ] }