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,
|
Bang,
|
||||||
Minus,
|
Minus,
|
||||||
If,
|
If,
|
||||||
|
Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&Token> for PrefixOperator {
|
impl TryFrom<&Token> for PrefixOperator {
|
||||||
|
@ -102,6 +103,7 @@ impl TryFrom<&Token> for PrefixOperator {
|
||||||
Token::Bang => Self::Bang,
|
Token::Bang => Self::Bang,
|
||||||
Token::Minus => Self::Minus,
|
Token::Minus => Self::Minus,
|
||||||
Token::If => Self::If,
|
Token::If => Self::If,
|
||||||
|
Token::Function => Self::Function,
|
||||||
|
|
||||||
_ => return Err(LexerError::InvalidToken),
|
_ => return Err(LexerError::InvalidToken),
|
||||||
};
|
};
|
||||||
|
@ -166,7 +168,6 @@ impl TryFrom<&Token> for InfixOperator {
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||||
pub enum Keyword {
|
pub enum Keyword {
|
||||||
Let,
|
Let,
|
||||||
Function,
|
|
||||||
Return,
|
Return,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,7 +183,6 @@ impl TryFrom<&Token> for Keyword {
|
||||||
fn try_from(token: &Token) -> Result<Self, Self::Error> {
|
fn try_from(token: &Token) -> Result<Self, Self::Error> {
|
||||||
let term = match token {
|
let term = match token {
|
||||||
Token::Let => Self::Let,
|
Token::Let => Self::Let,
|
||||||
Token::Function => Self::Function,
|
|
||||||
Token::Return => Self::Return,
|
Token::Return => Self::Return,
|
||||||
|
|
||||||
_ => return Err(LexerError::InvalidToken),
|
_ => return Err(LexerError::InvalidToken),
|
||||||
|
|
|
@ -82,6 +82,11 @@ pub enum Expression {
|
||||||
consequence: Box<Node>,
|
consequence: Box<Node>,
|
||||||
alternative: Option<Box<Node>>,
|
alternative: Option<Box<Node>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Function {
|
||||||
|
parameters: Vec<String>,
|
||||||
|
body: Box<Node>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expression {
|
impl Expression {
|
||||||
|
@ -150,6 +155,10 @@ impl Display for Expression {
|
||||||
None => "N/A".to_string(),
|
None => "N/A".to_string(),
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
|
Expression::Function { parameters, body } => {
|
||||||
|
write!(f, "fn({}) {}", parameters.join(", "), body)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ pub enum ParserError {
|
||||||
ExpectedBoolean,
|
ExpectedBoolean,
|
||||||
ExpectedNumeric,
|
ExpectedNumeric,
|
||||||
ExpectedBlock,
|
ExpectedBlock,
|
||||||
|
ExpectedLeftParenthesis,
|
||||||
ExpectedRightParenthesis,
|
ExpectedRightParenthesis,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +25,7 @@ impl Display for ParserError {
|
||||||
ParserError::ExpectedBoolean => write!(f, "expected boolean expression"),
|
ParserError::ExpectedBoolean => write!(f, "expected boolean expression"),
|
||||||
ParserError::ExpectedNumeric => write!(f, "expected numeric expression"),
|
ParserError::ExpectedNumeric => write!(f, "expected numeric expression"),
|
||||||
ParserError::ExpectedBlock => write!(f, "expected block"),
|
ParserError::ExpectedBlock => write!(f, "expected block"),
|
||||||
|
ParserError::ExpectedLeftParenthesis => write!(f, "expected left parenthesis"),
|
||||||
ParserError::ExpectedRightParenthesis => write!(f, "expected right parenthesis"),
|
ParserError::ExpectedRightParenthesis => write!(f, "expected right parenthesis"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,6 @@ fn next_node(tokens: &mut Tokens) -> Option<Result<Node, ParserError>> {
|
||||||
match keyword {
|
match keyword {
|
||||||
Keyword::Let => parse_let_statement(tokens),
|
Keyword::Let => parse_let_statement(tokens),
|
||||||
Keyword::Return => parse_return_statement(tokens),
|
Keyword::Return => parse_return_statement(tokens),
|
||||||
_ => panic!("not implemented"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,16 +236,21 @@ fn parse_prefix_operator(
|
||||||
_ => return Err(ParserError::ExpectedNumeric),
|
_ => return Err(ParserError::ExpectedNumeric),
|
||||||
},
|
},
|
||||||
|
|
||||||
// It feels weird that 'if' is a prefix operator, but it is. If operators on the expression
|
// It feels weird that 'if' is a prefix operator, but it is. If operators on the
|
||||||
// by converting the expressions output into something different.
|
// expression by converting the expressions output into something different.
|
||||||
PrefixOperator::If => {
|
PrefixOperator::If => {
|
||||||
let condition = parse_expression(tokens, Precedence::Lowest)?;
|
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 consequence = parse_block_statement(tokens)?;
|
||||||
|
|
||||||
let alternative = if tokens.peek() == Some(&Token::Else) {
|
let alternative = if tokens.peek() == Some(&Token::Else) {
|
||||||
// Eat else
|
// Eat else
|
||||||
tokens.next();
|
tokens.next();
|
||||||
|
|
||||||
Some(Box::new(parse_block_statement(tokens)?))
|
Some(Box::new(parse_block_statement(tokens)?))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -258,6 +262,48 @@ fn parse_prefix_operator(
|
||||||
alternative,
|
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)
|
Ok(expr)
|
||||||
|
@ -368,4 +414,14 @@ mod tests {
|
||||||
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
||||||
assert_eq!(&res.to_string(), expected);
|
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