diff --git a/src/lexer/tokens.rs b/src/lexer/tokens.rs index 7c29c66..ce6dc34 100644 --- a/src/lexer/tokens.rs +++ b/src/lexer/tokens.rs @@ -92,6 +92,7 @@ pub enum PrefixOperator { Bang, Minus, If, + Function, } impl TryFrom<&Token> for PrefixOperator { @@ -102,6 +103,7 @@ impl TryFrom<&Token> for PrefixOperator { Token::Bang => Self::Bang, Token::Minus => Self::Minus, Token::If => Self::If, + Token::Function => Self::Function, _ => return Err(LexerError::InvalidToken), }; @@ -166,7 +168,6 @@ impl TryFrom<&Token> for InfixOperator { #[derive(Debug, PartialEq, PartialOrd, Clone)] pub enum Keyword { Let, - Function, Return, } @@ -182,7 +183,6 @@ impl TryFrom<&Token> for Keyword { fn try_from(token: &Token) -> Result { let term = match token { Token::Let => Self::Let, - Token::Function => Self::Function, Token::Return => Self::Return, _ => return Err(LexerError::InvalidToken), diff --git a/src/parser/ast.rs b/src/parser/ast.rs index a7ec40f..6cfc6c0 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -82,6 +82,11 @@ pub enum Expression { consequence: Box, alternative: Option>, }, + + Function { + parameters: Vec, + body: Box, + }, } impl Expression { @@ -150,6 +155,10 @@ impl Display for Expression { None => "N/A".to_string(), } ), + + Expression::Function { parameters, body } => { + write!(f, "fn({}) {}", parameters.join(", "), body) + } } } } diff --git a/src/parser/error.rs b/src/parser/error.rs index f91f6c3..90cd605 100644 --- a/src/parser/error.rs +++ b/src/parser/error.rs @@ -10,6 +10,7 @@ pub enum ParserError { ExpectedBoolean, ExpectedNumeric, ExpectedBlock, + ExpectedLeftParenthesis, ExpectedRightParenthesis, } @@ -24,6 +25,7 @@ impl Display for ParserError { ParserError::ExpectedBoolean => write!(f, "expected boolean expression"), ParserError::ExpectedNumeric => write!(f, "expected numeric expression"), ParserError::ExpectedBlock => write!(f, "expected block"), + ParserError::ExpectedLeftParenthesis => write!(f, "expected left parenthesis"), ParserError::ExpectedRightParenthesis => write!(f, "expected right parenthesis"), } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 2151446..5fcea26 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -45,7 +45,6 @@ fn next_node(tokens: &mut Tokens) -> Option> { match keyword { Keyword::Let => parse_let_statement(tokens), Keyword::Return => parse_return_statement(tokens), - _ => panic!("not implemented"), } } @@ -237,16 +236,21 @@ fn parse_prefix_operator( _ => return Err(ParserError::ExpectedNumeric), }, - // It feels weird that 'if' is a prefix operator, but it is. If operators on the expression - // by converting the expressions output into something different. + // It feels weird that 'if' is a prefix operator, but it is. If operators on the + // expression by converting the expressions output into something different. PrefixOperator::If => { let condition = parse_expression(tokens, Precedence::Lowest)?; + + // Conditions in if statements must evaluate to booleans + if !condition.is_bool() { + return Err(ParserError::ExpectedBoolean); + } + let consequence = parse_block_statement(tokens)?; let alternative = if tokens.peek() == Some(&Token::Else) { // Eat else tokens.next(); - Some(Box::new(parse_block_statement(tokens)?)) } else { None @@ -258,6 +262,48 @@ fn parse_prefix_operator( alternative, } } + + // Once again, strange to think of it as such, but definitely is a prefix operator. + // We're not quite at LISP levels of "everything is an expression!!" but we're not + // that far away + PrefixOperator::Function => { + if Some(Token::LeftParenthesis) != tokens.next() { + return Err(ParserError::ExpectedLeftParenthesis); + } + + let mut parameters = Vec::new(); + // Because of how we're looping, `fn(x,,,,y)` is completely valid and will parse + // to Expression::Function{ parameters: ["x", "y"], body: ... } which is fine but + // technically different than the Monkey spec. We could fix this by simply checking + // if the next token after each comma is an identifier + loop { + let ident = match tokens.next() { + Some(Token::Ident(ident)) => ident, + Some(Token::Comma) => continue, + Some(Token::RightParenthesis) => break, + + // Unexpected tokens + Some(tok) => { + return Err(ParserError::UnexpectedToken( + "identifier, comma, or right parenthesis", + tok, + )) + } + + // Unexpected EOF + None => return Err(ParserError::EOF), + }; + + parameters.push(ident); + } + + let body = parse_block_statement(tokens)?; + + Expression::Function { + parameters, + body: Box::new(body), + } + } }; Ok(expr) @@ -368,4 +414,14 @@ mod tests { let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); assert_eq!(&res.to_string(), expected); } + + #[rstest] + #[case("fn() {true}", "fn() { true }")] + #[case("fn(x) {x + 3}", "fn(x) { (x + 3) }")] + #[case("fn(x, y) {x + y}", "fn(x, y) { (x + y) }")] + fn test_function_expression(#[case] input: &str, #[case] expected: &str) { + let mut tokens = lexer::tokenize(input).unwrap(); + let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap(); + assert_eq!(&res.to_string(), expected); + } }