diff --git a/src/ast.rs b/src/ast.rs index 9296f49..3793a9e 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1,4 +1,7 @@ -use std::{fmt::Debug, rc::Rc}; +use std::{ + fmt::{Debug, Display}, + rc::Rc, +}; use crate::token::{Token, TokenType}; @@ -79,6 +82,33 @@ impl Node for IntegerLiteral {} impl Expression for IntegerLiteral {} +#[derive(Debug)] +pub enum PrefixOperator { + Minus, + Bang, +} + +impl Display for PrefixOperator { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use PrefixOperator::*; + match self { + Bang => write!(f, "!")?, + Minus => write!(f, "-")?, + } + Ok(()) + } +} + +#[derive(Debug)] +pub struct Prefix { + pub operator: PrefixOperator, + pub right: Rc, +} + +impl Node for Prefix {} + +impl Expression for Prefix {} + #[derive(Debug)] pub struct DummyExpression {} diff --git a/src/parser.rs b/src/parser.rs index 08710ae..08a406b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,8 +2,8 @@ use std::{fmt, rc::Rc}; use crate::{ ast::{ - DummyExpression, Expression, ExpressionStatement, Identifier, IntegerLiteral, Let, Program, - Return, Statement, + DummyExpression, Expression, ExpressionStatement, Identifier, IntegerLiteral, Let, Prefix, + PrefixOperator, Program, Return, Statement, }, lexer::Lexer, token::{Token, TokenType}, @@ -20,6 +20,8 @@ pub enum Error { expected: TokenType, actual: Option, }, + NoPrefixParseFnFound, + EmptyExprFollowingPrefixOp(PrefixOperator), } #[derive(PartialEq, PartialOrd)] @@ -40,10 +42,15 @@ impl fmt::Display for Error { match self { UnexpectedTokenType { expected, actual } => write!( f, - "expected token `{:?}`, actual token: `{:?}`", - expected, actual - )?, - } + "expected token `{expected}`, actual token: `{}`", + match actual { + Some(token) => format!("{token}"), + None => "None".to_string(), + }, + ), + NoPrefixParseFnFound => write!(f, "no prefix parse function found"), + EmptyExprFollowingPrefixOp(op) => write!(f, "empty expression following `{op}`"), + }?; Ok(()) } } @@ -153,7 +160,12 @@ impl Parser { } fn parse_expression(&mut self, precedence: Precedence) -> Option> { - let prefix = self.prefix_parse_fn()?; + let prefix = if let Some(prefix) = self.prefix_parse_fn() { + prefix + } else { + self.errors.push(Error::NoPrefixParseFnFound); + return None; + }; let left_exp = prefix(self); Some(left_exp) @@ -176,6 +188,20 @@ impl Parser { Rc::new(IntegerLiteral { value }) } + fn parse_prefix_expression(&mut self, operator: PrefixOperator) -> Rc { + self.next(); + + let right = if let Some(expr) = self.parse_expression(Precedence::Prefix) { + expr + } else { + self.errors + .push(Error::EmptyExprFollowingPrefixOp(operator)); + return Rc::new(DummyExpression {}); + }; + + Rc::new(Prefix { operator, right }) + } + fn expect_peek(&mut self, token_type: TokenType) -> bool { let peek_token = if let Some(token) = &self.peek_token { token @@ -262,6 +288,12 @@ impl Parser { Self::parse_integer_literal(parser, value) })) } + Minus => Some(Box::new(|parser| { + Self::parse_prefix_expression(parser, PrefixOperator::Minus) + })), + Bang => Some(Box::new(|parser| { + Self::parse_prefix_expression(parser, PrefixOperator::Bang) + })), _ => None, } } @@ -294,7 +326,11 @@ impl Iterator for Parser { mod tests { use std::rc::Rc; - use crate::{ast::Statement, lexer::Lexer}; + use crate::{ + ast::{PrefixOperator, Statement}, + lexer::{self, Lexer}, + token::Token, + }; use super::Parser; @@ -386,6 +422,49 @@ mod tests { } } + #[test] + fn prefix_expressions() { + struct Test { + input: String, + token: Token, + operator: PrefixOperator, + value: i64, + } + + let tests = vec![ + Test { + input: "!15".to_owned(), + token: Token::Bang, + operator: PrefixOperator::Bang, + value: 15, + }, + Test { + input: "-15".to_owned(), + token: Token::Minus, + operator: PrefixOperator::Minus, + value: 15, + }, + ]; + + for test in tests { + let l = Lexer::new(test.input); + + let mut parser = Parser::new(l); + + let program = parser.parse().unwrap(); + check_parser_errors(parser); + + assert_eq!(program.statements.len(), 1); + + test_prefix_expression( + program.statements.first().unwrap().clone(), + test.token, + test.operator, + test.value, + ); + } + } + fn test_let_statement(stmt: Rc, name: &str) { assert_eq!( format!("{stmt:?}"), @@ -424,4 +503,16 @@ mod tests { ), ); } + + fn test_prefix_expression( + stmt: Rc, + token: Token, + operator: PrefixOperator, + num: i64, + ) { + assert_eq!( + format!("{stmt:?}"), + format!("ExpressionStatement {{ token: {token:?}, expression: Prefix {{ operator: {operator:?}, right: IntegerLiteral {{ value: {num} }} }} }}"), + ); + } } diff --git a/src/token.rs b/src/token.rs index 396c1ae..ad6584d 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + #[derive(Debug, PartialEq, Clone)] pub enum Token { Illegal, @@ -72,6 +74,43 @@ pub enum TokenType { Return, } +impl Display for TokenType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use TokenType::*; + let name = match self { + Illegal => "Illegal", + EOF => "EOF", + Ident => "Ident", + Int => "Int", + Assign => "=", + Plus => "+", + Minus => "-", + Bang => "!", + Asterisk => "*", + Slash => "/", + Lt => "<", + Gt => ">", + Eq => "==", + NotEq => "!=", + Comma => ",", + Semicolon => ";", + Lparen => "(", + Rparen => ")", + Lbrace => "{", + Rbrace => "}", + Function => "fn", + Let => "let", + True => "true", + False => "false", + If => "if", + Else => "else", + Return => "return", + }; + write!(f, "{name}")?; + Ok(()) + } +} + impl Token { pub fn token_type(&self) -> TokenType { match self {