Ch 2 (up to function literals)
This commit is contained in:
parent
218c98dc8a
commit
4ad37f9b71
4 changed files with 73 additions and 6 deletions
|
@ -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<Self, Self::Error> {
|
||||
let term = match token {
|
||||
Token::Let => Self::Let,
|
||||
Token::Function => Self::Function,
|
||||
Token::Return => Self::Return,
|
||||
|
||||
_ => return Err(LexerError::InvalidToken),
|
||||
|
|
|
@ -82,6 +82,11 @@ pub enum Expression {
|
|||
consequence: Box<Node>,
|
||||
alternative: Option<Box<Node>>,
|
||||
},
|
||||
|
||||
Function {
|
||||
parameters: Vec<String>,
|
||||
body: Box<Node>,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,7 +45,6 @@ fn next_node(tokens: &mut Tokens) -> Option<Result<Node, ParserError>> {
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue