Chapter 3
No idea if garbage collection is needed since it kinda mimics Rust's lifetime system but /shrugs/ probably not what the monkey spec recommends? idk, there aren't any pointers so I think it'd be fine (for now, at least...)
This commit is contained in:
parent
0321e9ba89
commit
124218cfd8
12 changed files with 440 additions and 87 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -2,6 +2,12 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -235,6 +241,7 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||||
name = "moose"
|
name = "moose"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"rstest",
|
"rstest",
|
||||||
]
|
]
|
||||||
|
|
|
@ -6,6 +6,7 @@ description = "Oxidized interpretor for the monkey programming language"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow = "1.0.69"
|
||||||
clap = { version = "4.1.8", features = ["derive"] }
|
clap = { version = "4.1.8", features = ["derive"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
let x = 5 + 6 + 7;
|
let x = 5 + 6 + 7;
|
||||||
|
x
|
||||||
|
|
26
src/evaluator/error.rs
Normal file
26
src/evaluator/error.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use super::object::Object;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum EvalError {
|
||||||
|
TypeError,
|
||||||
|
UndefinedVariable(String),
|
||||||
|
|
||||||
|
// This is not actually an error, but we will abuse it's position in being propegated to
|
||||||
|
// return a value early.
|
||||||
|
Return(Object),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for EvalError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
EvalError::TypeError => write!(f, "improper type"),
|
||||||
|
EvalError::UndefinedVariable(ident) => write!(f, "undefined variable: {}", ident),
|
||||||
|
|
||||||
|
EvalError::Return(val) => write!(f, "return: {}", val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for EvalError {}
|
265
src/evaluator/mod.rs
Normal file
265
src/evaluator/mod.rs
Normal file
|
@ -0,0 +1,265 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::parser::{Expression, Node, Program};
|
||||||
|
|
||||||
|
mod error;
|
||||||
|
mod object;
|
||||||
|
|
||||||
|
use error::EvalError;
|
||||||
|
use object::Object;
|
||||||
|
|
||||||
|
pub type Environment = HashMap<String, Object>;
|
||||||
|
|
||||||
|
pub fn evaluate(ast: Program, env: &mut Environment) -> Result<Object, EvalError> {
|
||||||
|
let mut result = Object::Null;
|
||||||
|
|
||||||
|
for statement in ast.program() {
|
||||||
|
match eval_statement(statement, env) {
|
||||||
|
Ok(res) => result = res,
|
||||||
|
Err(EvalError::Return(res)) => return Ok(res),
|
||||||
|
Err(err) => return Err(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_statement(statement: Node, env: &mut Environment) -> Result<Object, EvalError> {
|
||||||
|
match statement {
|
||||||
|
Node::Let(ident, expr) => {
|
||||||
|
let value = eval_expression(expr, env)?;
|
||||||
|
env.insert(ident, value);
|
||||||
|
Ok(Object::Null)
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Return(expr) => {
|
||||||
|
let res = eval_expression(expr, env)?;
|
||||||
|
return Err(EvalError::Return(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Block(statements) => {
|
||||||
|
let mut result = Object::Null;
|
||||||
|
for statement in statements {
|
||||||
|
result = eval_statement(statement, env)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
Node::Expression(expr) => eval_expression(expr, env),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_expression(expr: Expression, env: &mut Environment) -> Result<Object, EvalError> {
|
||||||
|
match expr {
|
||||||
|
Expression::Identifier(ident) => {
|
||||||
|
// TODO: Is this necessary? Is there a better way than cloning?
|
||||||
|
env.get(&ident)
|
||||||
|
.ok_or(EvalError::UndefinedVariable(ident))
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Literals
|
||||||
|
Expression::Integer(val) => Ok(Object::Integer(val)),
|
||||||
|
Expression::Bool(val) => Ok(Object::Boolean(val)),
|
||||||
|
|
||||||
|
// Prefix Operators
|
||||||
|
Expression::Negative(expr) => match eval_expression(*expr, env)? {
|
||||||
|
Object::Integer(val) => Ok(Object::Integer(-val)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
},
|
||||||
|
Expression::Not(expr) => match eval_expression(*expr, env)? {
|
||||||
|
Object::Boolean(val) => Ok(Object::Boolean(!val)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Infix Numeric Operators
|
||||||
|
Expression::Add(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs + rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Subtract(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs - rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Multiply(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs * rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::Divide(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Integer(lhs / rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Infix Boolean Operators
|
||||||
|
Expression::Equal(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Boolean(lhs), Object::Boolean(rhs)) => Ok(Object::Boolean(lhs == rhs)),
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs == rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::NotEqual(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Boolean(lhs), Object::Boolean(rhs)) => Ok(Object::Boolean(lhs != rhs)),
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs != rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::LessThan(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs < rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expression::GreaterThan(lhs, rhs) => {
|
||||||
|
match (eval_expression(*lhs, env)?, eval_expression(*rhs, env)?) {
|
||||||
|
(Object::Integer(lhs), Object::Integer(rhs)) => Ok(Object::Boolean(lhs > rhs)),
|
||||||
|
_ => Err(EvalError::TypeError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression::If {
|
||||||
|
condition,
|
||||||
|
consequence,
|
||||||
|
alternative,
|
||||||
|
} => {
|
||||||
|
let condition = match eval_expression(*condition, env)? {
|
||||||
|
Object::Boolean(val) => val,
|
||||||
|
_ => return Err(EvalError::TypeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
if condition {
|
||||||
|
eval_statement(*consequence, env)
|
||||||
|
} else if let Some(alt) = alternative {
|
||||||
|
eval_statement(*alt, env)
|
||||||
|
} else {
|
||||||
|
Ok(Object::Null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression::Function { parameters, body } => Ok(Object::Function { parameters, body }),
|
||||||
|
|
||||||
|
Expression::Call { function, args } => {
|
||||||
|
let (params, body) = match eval_expression(*function, env)? {
|
||||||
|
Object::Function { parameters, body } => (parameters, body),
|
||||||
|
_ => return Err(EvalError::TypeError),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut func_env = env.clone();
|
||||||
|
|
||||||
|
args.into_iter()
|
||||||
|
.map(|arg| eval_expression(arg, env))
|
||||||
|
.zip(params.iter())
|
||||||
|
.try_for_each(|(arg, ident)| -> Result<(), EvalError> {
|
||||||
|
func_env.insert(ident.to_owned(), arg?);
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
eval_statement(*body, &mut func_env)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::lexer::tokenize;
|
||||||
|
use crate::parser::parse;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use rstest::rstest;
|
||||||
|
|
||||||
|
// General test function used for evaluation
|
||||||
|
fn test(input: &str, expected: &str) {
|
||||||
|
let tokens = tokenize(input).unwrap();
|
||||||
|
let ast = parse(tokens).unwrap();
|
||||||
|
let mut env: Environment = HashMap::new();
|
||||||
|
let res = evaluate(ast, &mut env).unwrap();
|
||||||
|
assert_eq!(&res.to_string(), expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("5", "5")]
|
||||||
|
#[case("true", "true")]
|
||||||
|
#[case("-5", "-5")]
|
||||||
|
fn literal(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("--5", "5")]
|
||||||
|
#[case("!true", "false")]
|
||||||
|
#[case("!!true", "true")]
|
||||||
|
fn prefix_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("2+2", "4")]
|
||||||
|
#[case("2-2", "0")]
|
||||||
|
#[case("2*3", "6")]
|
||||||
|
#[case("2/2", "1")]
|
||||||
|
// Booleans
|
||||||
|
#[case("true == true", "true")]
|
||||||
|
#[case("true == false", "false")]
|
||||||
|
#[case("5 == 5", "true")]
|
||||||
|
#[case("5 == 6", "false")]
|
||||||
|
#[case("true != false", "true")]
|
||||||
|
#[case("true != true", "false")]
|
||||||
|
#[case("5 != 5", "false")]
|
||||||
|
#[case("5 != 6", "true")]
|
||||||
|
#[case("1 < 2", "true")]
|
||||||
|
#[case("3 < 2", "false")]
|
||||||
|
#[case("1 > 2", "false")]
|
||||||
|
#[case("3 > 2", "true")]
|
||||||
|
// Complex
|
||||||
|
#[case("(3 + 4) * 32 > 16 == 3 < 17 + 90/2", "true")]
|
||||||
|
fn infix_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("if true { 2+2 }", "4")]
|
||||||
|
#[case("if false { 2+2 }", "null")]
|
||||||
|
#[case("if 4 > 2 { 2 + 3 } else { 2 + 2 }", "5")]
|
||||||
|
#[case("if 4 < 2 { 2 + 3 } else { 2 + 2 }", "4")]
|
||||||
|
#[should_panic] // Test that only boolean expressions are valid conditions
|
||||||
|
#[case("if 4 { 2 + 3 } else { 2 + 2 }", "4")]
|
||||||
|
fn if_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("return 2 + 2;", "4")]
|
||||||
|
#[case("return 2 + 2; 2 + 3", "4")]
|
||||||
|
#[case("if true { return 1; 2 + 2 };", "1")]
|
||||||
|
#[case("if true { return 1 }; return 2", "1")]
|
||||||
|
#[case("if true { if true { return 1 }; return 2 }", "1")]
|
||||||
|
fn return_statement(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("let a = 5; a", "5")]
|
||||||
|
#[case("let a = 2 + 2; a", "4")]
|
||||||
|
#[case("let a = 2; let b = a; b", "2")]
|
||||||
|
fn let_statement(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rstest]
|
||||||
|
#[case("fn(x) { x + 2 }", "fn(x) { (x + 2) }")]
|
||||||
|
#[case("fn(x) { x + 2 }(2)", "4")]
|
||||||
|
#[case("let addtwo = fn(x) { x + 2 }; addtwo(3)", "5")]
|
||||||
|
fn function(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
test(input, expected)
|
||||||
|
}
|
||||||
|
}
|
35
src/evaluator/object.rs
Normal file
35
src/evaluator/object.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use crate::parser::Node;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Object {
|
||||||
|
Integer(i64),
|
||||||
|
Boolean(bool),
|
||||||
|
Null,
|
||||||
|
Function {
|
||||||
|
parameters: Vec<String>,
|
||||||
|
body: Box<Node>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Object {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Object::Integer(val) => write!(f, "{}", val),
|
||||||
|
Object::Boolean(val) => write!(f, "{}", val),
|
||||||
|
Object::Null => write!(f, "null"),
|
||||||
|
Object::Function {
|
||||||
|
parameters: params,
|
||||||
|
body,
|
||||||
|
} => write!(
|
||||||
|
f,
|
||||||
|
"fn({}) {}",
|
||||||
|
params
|
||||||
|
.iter()
|
||||||
|
.map(|obj| obj.to_string())
|
||||||
|
.reduce(|acc, elem| acc + ", " + &elem)
|
||||||
|
.unwrap_or(String::new()),
|
||||||
|
body,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,22 +48,8 @@ fn next_token(input: &mut Peekable<Chars>) -> Result<Option<Token>, LexerError>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
'<' => {
|
'<' => Token::LessThan,
|
||||||
if input.peek() == Some(&'=') {
|
'>' => Token::GreaterThan,
|
||||||
input.next();
|
|
||||||
Token::LessThanEqual
|
|
||||||
} else {
|
|
||||||
Token::LessThan
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'>' => {
|
|
||||||
if input.peek() == Some(&'=') {
|
|
||||||
input.next();
|
|
||||||
Token::GreaterThanEqual
|
|
||||||
} else {
|
|
||||||
Token::GreaterThan
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
',' => Token::Comma,
|
',' => Token::Comma,
|
||||||
';' => Token::Semicolon,
|
';' => Token::Semicolon,
|
||||||
|
|
|
@ -36,8 +36,6 @@ pub enum Token {
|
||||||
NotEqual,
|
NotEqual,
|
||||||
LessThan,
|
LessThan,
|
||||||
GreaterThan,
|
GreaterThan,
|
||||||
LessThanEqual,
|
|
||||||
GreaterThanEqual,
|
|
||||||
|
|
||||||
// Delimiters
|
// Delimiters
|
||||||
Comma,
|
Comma,
|
||||||
|
@ -130,8 +128,6 @@ pub enum InfixOperator {
|
||||||
NotEqual,
|
NotEqual,
|
||||||
LessThan,
|
LessThan,
|
||||||
GreaterThan,
|
GreaterThan,
|
||||||
LessThanEqual,
|
|
||||||
GreaterThanEqual,
|
|
||||||
|
|
||||||
Call,
|
Call,
|
||||||
}
|
}
|
||||||
|
@ -155,9 +151,7 @@ impl TryFrom<&Token> for InfixOperator {
|
||||||
Token::Equal => Self::Equal,
|
Token::Equal => Self::Equal,
|
||||||
Token::NotEqual => Self::NotEqual,
|
Token::NotEqual => Self::NotEqual,
|
||||||
Token::LessThan => Self::LessThan,
|
Token::LessThan => Self::LessThan,
|
||||||
Token::LessThanEqual => Self::LessThanEqual,
|
|
||||||
Token::GreaterThan => Self::GreaterThan,
|
Token::GreaterThan => Self::GreaterThan,
|
||||||
Token::GreaterThanEqual => Self::GreaterThanEqual,
|
|
||||||
|
|
||||||
Token::LeftParenthesis => Self::Call,
|
Token::LeftParenthesis => Self::Call,
|
||||||
|
|
||||||
|
|
55
src/main.rs
55
src/main.rs
|
@ -1,8 +1,13 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
|
use anyhow::{Context, Result};
|
||||||
use clap::{CommandFactory, Parser};
|
use clap::{CommandFactory, Parser};
|
||||||
|
|
||||||
|
use crate::evaluator::Environment;
|
||||||
|
|
||||||
|
mod evaluator;
|
||||||
mod lexer;
|
mod lexer;
|
||||||
mod parser;
|
mod parser;
|
||||||
|
|
||||||
|
@ -12,37 +17,63 @@ struct Args {
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
let cmd = Args::command();
|
let cmd = Args::command();
|
||||||
|
|
||||||
match args.path {
|
match args.path {
|
||||||
Some(path) => {
|
Some(path) => {
|
||||||
let input = fs::read_to_string(&path).unwrap();
|
let input = fs::read_to_string(&path).context("could not open file")?;
|
||||||
let tokens = lexer::tokenize(&input).unwrap();
|
let mut env: Environment = HashMap::new();
|
||||||
let ast = parser::parse(tokens);
|
let res = eval(&input, &mut env)?;
|
||||||
println!("{}", ast);
|
println!("{}", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
None => start_repl(cmd.get_version().unwrap()),
|
None => {
|
||||||
}
|
start_repl(cmd.get_version().unwrap());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_repl(version: &str) {
|
fn start_repl(version: &str) {
|
||||||
println!("Moose {} REPL", version);
|
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
let mut env: Environment = HashMap::new();
|
||||||
|
|
||||||
|
println!("Moose {} -- REPL", version);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
print!("> ");
|
print!("> ");
|
||||||
|
// We flush immediately otherwise the `>` is not actually printed until after
|
||||||
|
// the user has hit the return key.
|
||||||
io::stdout().flush().expect("failed to write to stdout");
|
io::stdout().flush().expect("failed to write to stdout");
|
||||||
|
|
||||||
io::stdin()
|
let bytes_read = io::stdin()
|
||||||
.read_line(&mut input)
|
.read_line(&mut input)
|
||||||
.expect("failed to read from stdin");
|
.expect("failed to read from stdin");
|
||||||
|
|
||||||
let tokens = lexer::tokenize(&input).unwrap();
|
// Check for Ctrl+D
|
||||||
let ast = parser::parse(tokens);
|
if bytes_read == 0 {
|
||||||
println!("{}", ast);
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = eval(&input, &mut env).unwrap_or_else(|err| format!("{:?}", err));
|
||||||
|
if res == String::from("null") {
|
||||||
|
input.clear();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("{}", res);
|
||||||
|
|
||||||
input.clear();
|
input.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn eval(input: &str, env: &mut Environment) -> Result<String> {
|
||||||
|
let tokens = lexer::tokenize(&input).context("failed to tokenize input")?;
|
||||||
|
let ast = parser::parse(tokens).context("failed to parse statement")?;
|
||||||
|
let res = evaluator::evaluate(ast, env).context("failed to evaluate statement")?;
|
||||||
|
|
||||||
|
Ok(res.to_string())
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ impl Program {
|
||||||
pub fn new(vec: Vec<Node>) -> Program {
|
pub fn new(vec: Vec<Node>) -> Program {
|
||||||
Program(vec)
|
Program(vec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn program(self) -> Vec<Node> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Program {
|
impl Display for Program {
|
||||||
|
@ -18,14 +22,14 @@ impl Display for Program {
|
||||||
self.0
|
self.0
|
||||||
.iter()
|
.iter()
|
||||||
.map(|stmt| stmt.to_string())
|
.map(|stmt| stmt.to_string())
|
||||||
.collect::<Vec<String>>()
|
.reduce(|acc, elem| acc + "\n" + &elem)
|
||||||
.join("\n")
|
.unwrap_or(String::new())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Statements
|
// Statements
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Node {
|
pub enum Node {
|
||||||
Let(String, Expression),
|
Let(String, Expression),
|
||||||
Return(Expression),
|
Return(Expression),
|
||||||
|
@ -43,8 +47,8 @@ impl Display for Node {
|
||||||
"{{ {} }}",
|
"{{ {} }}",
|
||||||
vec.iter()
|
vec.iter()
|
||||||
.map(|node| node.to_string())
|
.map(|node| node.to_string())
|
||||||
.collect::<Vec<String>>()
|
.reduce(|acc, elem| acc + "\n" + &elem)
|
||||||
.join("\n")
|
.unwrap_or(String::new())
|
||||||
),
|
),
|
||||||
Node::Expression(val) => write!(f, "{}", val),
|
Node::Expression(val) => write!(f, "{}", val),
|
||||||
}
|
}
|
||||||
|
@ -52,7 +56,7 @@ impl Display for Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expressions
|
// Expressions
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Expression {
|
pub enum Expression {
|
||||||
Identifier(String),
|
Identifier(String),
|
||||||
|
|
||||||
|
@ -74,8 +78,6 @@ pub enum Expression {
|
||||||
NotEqual(Box<Expression>, Box<Expression>),
|
NotEqual(Box<Expression>, Box<Expression>),
|
||||||
LessThan(Box<Expression>, Box<Expression>),
|
LessThan(Box<Expression>, Box<Expression>),
|
||||||
GreaterThan(Box<Expression>, Box<Expression>),
|
GreaterThan(Box<Expression>, Box<Expression>),
|
||||||
LessThanEqual(Box<Expression>, Box<Expression>),
|
|
||||||
GreaterThanEqual(Box<Expression>, Box<Expression>),
|
|
||||||
|
|
||||||
If {
|
If {
|
||||||
condition: Box<Expression>,
|
condition: Box<Expression>,
|
||||||
|
@ -103,9 +105,7 @@ impl Expression {
|
||||||
| Expression::Equal(_, _)
|
| Expression::Equal(_, _)
|
||||||
| Expression::NotEqual(_, _)
|
| Expression::NotEqual(_, _)
|
||||||
| Expression::LessThan(_, _)
|
| Expression::LessThan(_, _)
|
||||||
| Expression::GreaterThan(_, _)
|
| Expression::GreaterThan(_, _) => true,
|
||||||
| Expression::LessThanEqual(_, _)
|
|
||||||
| Expression::GreaterThanEqual(_, _) => true,
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,8 +143,6 @@ impl Display for Expression {
|
||||||
Expression::NotEqual(rhs, lhs) => write!(f, "({} != {})", rhs, lhs),
|
Expression::NotEqual(rhs, lhs) => write!(f, "({} != {})", rhs, lhs),
|
||||||
Expression::LessThan(rhs, lhs) => write!(f, "({} < {})", rhs, lhs),
|
Expression::LessThan(rhs, lhs) => write!(f, "({} < {})", rhs, lhs),
|
||||||
Expression::GreaterThan(rhs, lhs) => write!(f, "({} > {})", rhs, lhs),
|
Expression::GreaterThan(rhs, lhs) => write!(f, "({} > {})", rhs, lhs),
|
||||||
Expression::LessThanEqual(rhs, lhs) => write!(f, "({} <= {})", rhs, lhs),
|
|
||||||
Expression::GreaterThanEqual(rhs, lhs) => write!(f, "({} >= {})", rhs, lhs),
|
|
||||||
|
|
||||||
Expression::If {
|
Expression::If {
|
||||||
condition,
|
condition,
|
||||||
|
@ -171,8 +169,8 @@ impl Display for Expression {
|
||||||
function,
|
function,
|
||||||
args.iter()
|
args.iter()
|
||||||
.map(|expr| expr.to_string())
|
.map(|expr| expr.to_string())
|
||||||
.collect::<Vec<String>>()
|
.reduce(|acc, elem| acc + ", " + &elem)
|
||||||
.join(", ")
|
.unwrap_or(String::new())
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@ pub use error::ParserError;
|
||||||
use self::precedence::{get_prescedence, Precedence};
|
use self::precedence::{get_prescedence, Precedence};
|
||||||
|
|
||||||
// Entrypoint
|
// Entrypoint
|
||||||
pub fn parse(tokens: Tokens) -> Program {
|
pub fn parse(tokens: Tokens) -> Result<Program, ParserError> {
|
||||||
// Redefine tokens are mutable so it can be used as an iterator
|
// Redefine tokens are mutable so it can be used as an iterator
|
||||||
let mut tokens = tokens;
|
let mut tokens = tokens;
|
||||||
|
|
||||||
|
@ -19,14 +19,10 @@ pub fn parse(tokens: Tokens) -> Program {
|
||||||
|
|
||||||
// next_node return None on an EOF, so we know to end then
|
// next_node return None on an EOF, so we know to end then
|
||||||
while let Some(node) = next_node(&mut tokens) {
|
while let Some(node) = next_node(&mut tokens) {
|
||||||
match node {
|
ast.push(node?)
|
||||||
Ok(node) => ast.push(node),
|
|
||||||
// TODO: Handle this more gracefully than a panic
|
|
||||||
Err(err) => panic!("{}", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Program::new(ast)
|
Ok(Program::new(ast))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the next statement of our program
|
// Get the next statement of our program
|
||||||
|
@ -56,11 +52,13 @@ fn next_node(tokens: &mut Tokens) -> Option<Result<Node, ParserError>> {
|
||||||
Token::LeftBrace => parse_block_statement(tokens),
|
Token::LeftBrace => parse_block_statement(tokens),
|
||||||
|
|
||||||
Token::Semicolon => {
|
Token::Semicolon => {
|
||||||
// Eat the Semicolon token
|
// Eat the token
|
||||||
tokens.next();
|
tokens.next();
|
||||||
next_node(tokens)?
|
next_node(tokens)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token::RightBrace => return None,
|
||||||
|
|
||||||
tok => panic!("not implemented: {:?}", tok),
|
tok => panic!("not implemented: {:?}", tok),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -105,11 +103,11 @@ fn parse_block_statement(tokens: &mut Tokens) -> Result<Node, ParserError> {
|
||||||
return Err(ParserError::ExpectedBlock);
|
return Err(ParserError::ExpectedBlock);
|
||||||
};
|
};
|
||||||
|
|
||||||
while tokens.peek() != Some(&Token::RightBrace) {
|
while tokens.peek().ok_or(ParserError::EOF)? != &Token::RightBrace {
|
||||||
match next_node(tokens) {
|
match next_node(tokens) {
|
||||||
Some(Ok(stmt)) => statements.push(stmt),
|
Some(Ok(stmt)) => statements.push(stmt),
|
||||||
Some(Err(err)) => return Err(err),
|
Some(Err(err)) => return Err(err),
|
||||||
None => return Err(ParserError::EOF),
|
None => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,6 +222,8 @@ fn parse_prefix_operator(
|
||||||
let expr = match operator {
|
let expr = match operator {
|
||||||
// Not
|
// Not
|
||||||
PrefixOperator::Bang => match parse_expression(tokens, Precedence::Prefix)? {
|
PrefixOperator::Bang => match parse_expression(tokens, Precedence::Prefix)? {
|
||||||
|
// TODO: Technically monkey uses truthy values, so this
|
||||||
|
// should not actually exist...but it does for now
|
||||||
expr if expr.is_bool() => Expression::Not(Box::new(expr)),
|
expr if expr.is_bool() => Expression::Not(Box::new(expr)),
|
||||||
_ => return Err(ParserError::ExpectedBoolean),
|
_ => return Err(ParserError::ExpectedBoolean),
|
||||||
},
|
},
|
||||||
|
@ -243,6 +243,8 @@ fn parse_prefix_operator(
|
||||||
let condition = parse_expression(tokens, Precedence::Lowest)?;
|
let condition = parse_expression(tokens, Precedence::Lowest)?;
|
||||||
|
|
||||||
// Conditions in if statements must evaluate to booleans
|
// Conditions in if statements must evaluate to booleans
|
||||||
|
// TODO: Technically monkey uses truthy values, so this
|
||||||
|
// should not actually exist...but it does for now
|
||||||
if !condition.is_bool() {
|
if !condition.is_bool() {
|
||||||
return Err(ParserError::ExpectedBoolean);
|
return Err(ParserError::ExpectedBoolean);
|
||||||
}
|
}
|
||||||
|
@ -254,6 +256,7 @@ fn parse_prefix_operator(
|
||||||
tokens.next();
|
tokens.next();
|
||||||
Some(Box::new(parse_block_statement(tokens)?))
|
Some(Box::new(parse_block_statement(tokens)?))
|
||||||
} else {
|
} else {
|
||||||
|
dbg!(tokens.peek());
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -363,8 +366,6 @@ fn parse_infix_operator(
|
||||||
|
|
||||||
InfixOperator::GreaterThan => Expression::GreaterThan(lhs, rhs),
|
InfixOperator::GreaterThan => Expression::GreaterThan(lhs, rhs),
|
||||||
InfixOperator::LessThan => Expression::LessThan(lhs, rhs),
|
InfixOperator::LessThan => Expression::LessThan(lhs, rhs),
|
||||||
InfixOperator::GreaterThanEqual => Expression::GreaterThanEqual(lhs, rhs),
|
|
||||||
InfixOperator::LessThanEqual => Expression::LessThanEqual(lhs, rhs),
|
|
||||||
|
|
||||||
InfixOperator::Call => panic!("unreachable"),
|
InfixOperator::Call => panic!("unreachable"),
|
||||||
};
|
};
|
||||||
|
@ -378,6 +379,17 @@ mod tests {
|
||||||
use crate::lexer;
|
use crate::lexer;
|
||||||
use rstest::rstest;
|
use rstest::rstest;
|
||||||
|
|
||||||
|
// General test function used for parsing expressions
|
||||||
|
//
|
||||||
|
// Useful when splitting tests up into groups rather than making a massive block of cases
|
||||||
|
// via rstest for semi-related functionality such as testing term parsing and if
|
||||||
|
// expression parsing
|
||||||
|
fn expression_test(input: &str, expected: &str) {
|
||||||
|
let mut tokens = lexer::tokenize(input).unwrap();
|
||||||
|
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
||||||
|
assert_eq!(&res.to_string(), expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("let int = 5", "let int be 5")]
|
#[case("let int = 5", "let int be 5")]
|
||||||
#[case("return 7", "returning 7")]
|
#[case("return 7", "returning 7")]
|
||||||
|
@ -385,9 +397,9 @@ mod tests {
|
||||||
#[case("return 5 + 6", "returning (5 + 6)")]
|
#[case("return 5 + 6", "returning (5 + 6)")]
|
||||||
#[case("5 + 6; 7+3", "(5 + 6)\n(7 + 3)")]
|
#[case("5 + 6; 7+3", "(5 + 6)\n(7 + 3)")]
|
||||||
#[case("(5 + 5) * 3; 2 + 2", "((5 + 5) * 3)\n(2 + 2)")]
|
#[case("(5 + 5) * 3; 2 + 2", "((5 + 5) * 3)\n(2 + 2)")]
|
||||||
fn test_parser<'a>(#[case] input: &str, #[case] expected: &str) {
|
fn parse_success(#[case] input: &str, #[case] expected: &str) {
|
||||||
let tokens = lexer::tokenize(input).unwrap();
|
let tokens = lexer::tokenize(input).unwrap();
|
||||||
let res = parse(tokens);
|
let res = parse(tokens).unwrap();
|
||||||
|
|
||||||
assert_eq!(&res.to_string(), expected);
|
assert_eq!(&res.to_string(), expected);
|
||||||
}
|
}
|
||||||
|
@ -397,22 +409,32 @@ mod tests {
|
||||||
#[case("return")]
|
#[case("return")]
|
||||||
#[case("let = 8")]
|
#[case("let = 8")]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn test_parser_failure(#[case] input: &str) {
|
fn parse_failure(#[case] input: &str) {
|
||||||
let tokens = lexer::tokenize(input).unwrap();
|
let tokens = lexer::tokenize(input).unwrap();
|
||||||
parse(tokens);
|
parse(tokens).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
// Terms
|
// Terms
|
||||||
|
#[rstest]
|
||||||
#[case("5", "5")]
|
#[case("5", "5")]
|
||||||
#[case("uwu", "uwu")]
|
#[case("uwu", "uwu")]
|
||||||
#[case("true", "true")]
|
#[case("true", "true")]
|
||||||
#[case("false", "false")]
|
#[case("false", "false")]
|
||||||
|
fn term(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
expression_test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
// Prefix operators
|
// Prefix operators
|
||||||
|
#[rstest]
|
||||||
#[case("!true", "(!true)")]
|
#[case("!true", "(!true)")]
|
||||||
#[case("!false", "(!false)")]
|
#[case("!false", "(!false)")]
|
||||||
#[case("-5", "(-5)")]
|
#[case("-5", "(-5)")]
|
||||||
|
fn prefix_operator(#[case] input: &str, #[case] expected: &str) {
|
||||||
|
expression_test(input, expected)
|
||||||
|
}
|
||||||
|
|
||||||
// Infix operators
|
// Infix operators
|
||||||
|
#[rstest]
|
||||||
#[case("5 + 6", "(5 + 6)")]
|
#[case("5 + 6", "(5 + 6)")]
|
||||||
#[case("5 - 6", "(5 - 6)")]
|
#[case("5 - 6", "(5 - 6)")]
|
||||||
#[case("5 * 6", "(5 * 6)")]
|
#[case("5 * 6", "(5 * 6)")]
|
||||||
|
@ -421,8 +443,6 @@ mod tests {
|
||||||
#[case("5 != 6", "(5 != 6)")]
|
#[case("5 != 6", "(5 != 6)")]
|
||||||
#[case("5 < 6", "(5 < 6)")]
|
#[case("5 < 6", "(5 < 6)")]
|
||||||
#[case("5 > 6", "(5 > 6)")]
|
#[case("5 > 6", "(5 > 6)")]
|
||||||
#[case("5 <= 6", "(5 <= 6)")]
|
|
||||||
#[case("5 >= 6", "(5 >= 6)")]
|
|
||||||
// Boolean and numeric operators
|
// Boolean and numeric operators
|
||||||
#[case("3 < 5 == true", "((3 < 5) == true)")]
|
#[case("3 < 5 == true", "((3 < 5) == true)")]
|
||||||
// Operator associativity
|
// Operator associativity
|
||||||
|
@ -434,31 +454,24 @@ mod tests {
|
||||||
#[case("5 - 6 * 7 + 2", "((5 - (6 * 7)) + 2)")]
|
#[case("5 - 6 * 7 + 2", "((5 - (6 * 7)) + 2)")]
|
||||||
#[case("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)")]
|
#[case("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)")]
|
||||||
#[case("(5 + 5) * 2", "((5 + 5) * 2)")]
|
#[case("(5 + 5) * 2", "((5 + 5) * 2)")]
|
||||||
fn test_parse_expression(#[case] input: &str, #[case] expected: &str) {
|
fn infix_operator(#[case] input: &str, #[case] expected: &str) {
|
||||||
let mut tokens = lexer::tokenize(input).unwrap();
|
expression_test(input, expected)
|
||||||
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
|
||||||
dbg!(&res);
|
|
||||||
assert_eq!(&res.to_string(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("if true { 5 + 5 };", "if true then { (5 + 5) } else N/A")]
|
#[case("if true { 5 + 5 };", "if true then { (5 + 5) } else N/A")]
|
||||||
#[case("if x > y { x }", "if (x > y) then { x } else N/A")]
|
#[case("if x > y { x }", "if (x > y) then { x } else N/A")]
|
||||||
#[case("if x > y { x } else { y }", "if (x > y) then { x } else { y }")]
|
#[case("if x > y { x } else { y }", "if (x > y) then { x } else { y }")]
|
||||||
fn test_if_expression(#[case] input: &str, #[case] expected: &str) {
|
fn if_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
let mut tokens = lexer::tokenize(input).unwrap();
|
expression_test(input, expected)
|
||||||
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
|
||||||
assert_eq!(&res.to_string(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
#[case("fn() {true}", "fn() { true }")]
|
#[case("fn() {true}", "fn() { true }")]
|
||||||
#[case("fn(x) {x + 3}", "fn(x) { (x + 3) }")]
|
#[case("fn(x) {x + 3}", "fn(x) { (x + 3) }")]
|
||||||
#[case("fn(x, y) {x + y}", "fn(x, y) { (x + y) }")]
|
#[case("fn(x, y) {x + y}", "fn(x, y) { (x + y) }")]
|
||||||
fn test_function_expression(#[case] input: &str, #[case] expected: &str) {
|
fn function_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
let mut tokens = lexer::tokenize(input).unwrap();
|
expression_test(input, expected)
|
||||||
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
|
||||||
assert_eq!(&res.to_string(), expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[rstest]
|
||||||
|
@ -466,9 +479,7 @@ mod tests {
|
||||||
#[case("add(1 + 2, 3)", "add((1 + 2), 3)")]
|
#[case("add(1 + 2, 3)", "add((1 + 2), 3)")]
|
||||||
#[case("add(x, y)", "add(x, y)")]
|
#[case("add(x, y)", "add(x, y)")]
|
||||||
#[case("fn(x,y) { x + y }(1,2)", "fn(x, y) { (x + y) }(1, 2)")]
|
#[case("fn(x,y) { x + y }(1,2)", "fn(x, y) { (x + y) }(1, 2)")]
|
||||||
fn test_call_expression(#[case] input: &str, #[case] expected: &str) {
|
fn call_expression(#[case] input: &str, #[case] expected: &str) {
|
||||||
let mut tokens = lexer::tokenize(input).unwrap();
|
expression_test(input, expected)
|
||||||
let res = parse_expression(&mut tokens, Precedence::Lowest).unwrap();
|
|
||||||
assert_eq!(&res.to_string(), expected);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,6 @@ pub(super) fn get_prescedence(tok: &InfixOperator) -> Precedence {
|
||||||
|
|
||||||
InfixOperator::LessThan => Precedence::Ordering,
|
InfixOperator::LessThan => Precedence::Ordering,
|
||||||
InfixOperator::GreaterThan => Precedence::Ordering,
|
InfixOperator::GreaterThan => Precedence::Ordering,
|
||||||
InfixOperator::LessThanEqual => Precedence::Ordering,
|
|
||||||
InfixOperator::GreaterThanEqual => Precedence::Ordering,
|
|
||||||
|
|
||||||
InfixOperator::Plus => Precedence::Sum,
|
InfixOperator::Plus => Precedence::Sum,
|
||||||
InfixOperator::Minus => Precedence::Sum,
|
InfixOperator::Minus => Precedence::Sum,
|
||||||
|
|
Loading…
Reference in a new issue