feat: add monkey-rs
This commit is contained in:
151
__lang/monkey-rs/src/ast.rs
Normal file
151
__lang/monkey-rs/src/ast.rs
Normal file
@@ -0,0 +1,151 @@
|
||||
use std::fmt;
|
||||
use std::fmt::Formatter;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Program {
|
||||
pub stmts: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl fmt::Display for Program {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for stmt in &self.stmts {
|
||||
write!(f, "{}", stmt)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct BlockStatement {
|
||||
pub stmts: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl fmt::Display for BlockStatement {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{{");
|
||||
for stmt in &self.stmts {
|
||||
write!(f, "{}", stmt)?;
|
||||
}
|
||||
write!(f, "}}");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Prefix {
|
||||
Minus,
|
||||
Bang
|
||||
}
|
||||
|
||||
impl fmt::Display for Prefix {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Prefix::Minus => write!(f, "-"),
|
||||
Prefix::Bang => write!(f, "!"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Infix {
|
||||
Eq,
|
||||
NotEq,
|
||||
Lt,
|
||||
Gt,
|
||||
Plus,
|
||||
Minus,
|
||||
Asterisk,
|
||||
Slash,
|
||||
LBracket,
|
||||
}
|
||||
|
||||
impl fmt::Display for Infix {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Infix::Eq => write!(f, "=="),
|
||||
Infix::NotEq => write!(f, "!="),
|
||||
Infix::Lt => write!(f, "<"),
|
||||
Infix::Gt => write!(f, ">"),
|
||||
Infix::Plus => write!(f, "+"),
|
||||
Infix::Minus => write!(f, "-"),
|
||||
Infix::Asterisk => write!(f, "*"),
|
||||
Infix::Slash => write!(f, "/"),
|
||||
Infix::LBracket => write!(f, "["),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expression {
|
||||
Identifier(String),
|
||||
IntegerLiteral(i64),
|
||||
String(String),
|
||||
Boolean(bool),
|
||||
Prefix(Prefix, Box<Expression>),
|
||||
Infix(Infix, Box<Expression>, Box<Expression>),
|
||||
If(Box<Expression>, Box<BlockStatement>, Option<Box<BlockStatement>>),
|
||||
FunctionLiteral(Vec<String>, Box<BlockStatement>),
|
||||
DictionaryLiteral(Vec<(Expression, Expression)>),
|
||||
ArrayLiteral(Vec<Expression>),
|
||||
Index(Box<Expression>, Box<Expression>),
|
||||
Call(Box<Expression>, Vec<Expression>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Expression {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Expression::Identifier(s) => write!(f, "{}", s),
|
||||
Expression::IntegerLiteral(i) => write!(f, "{}", i),
|
||||
Expression::String(s) => write!(f, "\"{}\"", s),
|
||||
Expression::Boolean(b) => write!(f, "{}", b),
|
||||
Expression::Prefix(p, exp) => write!(f, "({}{})", p, exp),
|
||||
Expression::Infix(op, left, right) =>
|
||||
write!(f, "({} {} {})", left, op, right),
|
||||
Expression::If(exp, true_blk, Some(false_blk)) =>
|
||||
write!(f,"if ({}) {} else {}", exp, true_blk, false_blk),
|
||||
Expression::If(exp, true_blk, None) =>
|
||||
write!(f,"if ({}) {}", exp, true_blk),
|
||||
Expression::DictionaryLiteral(key_values) => {
|
||||
let mut str = String::new();
|
||||
str.push_str("{");
|
||||
for (k, v) in key_values{
|
||||
str.push_str(format!("{}:{},", k, v).as_str());
|
||||
}
|
||||
|
||||
if str.ends_with(',') {
|
||||
str.pop();
|
||||
}
|
||||
str.push_str("}");
|
||||
write!(f, "{}", str)
|
||||
},
|
||||
Expression::ArrayLiteral(members) => write!(f, "[{}]",
|
||||
members.iter().map(|a| a.to_string()).collect::<Vec<String>>().join(",")),
|
||||
Expression::Index(arr, idx) => write!(f, "{}[{}]", arr.to_string(), idx.to_string()),
|
||||
Expression::FunctionLiteral(params, block) => write!(f, "fn({}){}", params.join(","), block),
|
||||
Expression::Call(exp, params) => write!(f, "{}({})", exp,
|
||||
params.iter().map(|a| a.to_string()).collect::<Vec<String>>().join(",")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Statement {
|
||||
Let(String, Box<Expression>),
|
||||
Return(Option<Box<Expression>>),
|
||||
Expression(Box<Expression>),
|
||||
}
|
||||
|
||||
impl fmt::Display for Statement {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Statement::Let(s, exp) => write!(f, "let {} = {};", s, exp),
|
||||
Statement::Return(None) => write!(f, "return;"),
|
||||
Statement::Return(Some(val)) => write!(f, "return {};", val),
|
||||
Statement::Expression(exp) => write!(f, "{};", exp),
|
||||
}
|
||||
}
|
||||
}
|
||||
37
__lang/monkey-rs/src/environment.rs
Normal file
37
__lang/monkey-rs/src/environment.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use crate::object::Object;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct Environment {
|
||||
store: HashMap<String, Object>,
|
||||
outer: Option<Rc<RefCell<Environment>>>,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
pub fn extend(outer: Rc<RefCell<Self>>) -> Environment {
|
||||
Environment {
|
||||
store: HashMap::new(),
|
||||
outer: Some(outer),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Object> {
|
||||
match self.store.get(name) {
|
||||
Some(value) => Some(value.clone()),
|
||||
None => self
|
||||
.outer
|
||||
.as_ref()
|
||||
.and_then(|o| o.borrow().get(name).clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, name: &str, val: Object) {
|
||||
self.store.insert(name.to_string(), val);
|
||||
}
|
||||
}
|
||||
495
__lang/monkey-rs/src/evaluator.rs
Normal file
495
__lang/monkey-rs/src/evaluator.rs
Normal file
@@ -0,0 +1,495 @@
|
||||
use crate::ast::*;
|
||||
use crate::object::Object;
|
||||
use crate::environment::Environment;
|
||||
use crate::inbuilt::{get_inbuilt_function, eval_inbuilt_function};
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub fn eval_identifier(identifier: &Expression, env: &Rc<RefCell<Environment>>) -> Object
|
||||
{
|
||||
match identifier {
|
||||
Expression::Identifier(i) => {
|
||||
let id = env.borrow_mut().get(i.as_str());
|
||||
if id.is_some() {
|
||||
return id.unwrap();
|
||||
}
|
||||
|
||||
let inbuilt_func = get_inbuilt_function(i.as_str());
|
||||
if inbuilt_func.is_some() {
|
||||
return inbuilt_func.unwrap();
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected identifier")
|
||||
}
|
||||
|
||||
Object::Nil
|
||||
}
|
||||
|
||||
pub fn eval_prefix_expression(prefix: &Prefix, expression: &Expression, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let expr_val = eval_expression(expression, env);
|
||||
match prefix {
|
||||
Prefix::Minus => {
|
||||
match expr_val {
|
||||
Object::Integer(i) => Object::Integer(-1 * i),
|
||||
_ => panic!("Invalid expression {} in prefix expression, expected integer", expr_val)
|
||||
}
|
||||
}
|
||||
Prefix::Bang => {
|
||||
match expr_val {
|
||||
Object::Boolean(b) => Object::Boolean(!b),
|
||||
_ => panic!("Invalid expression {} in prefix expression, expected boolean", expr_val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_infix_expression(infix: &Infix, left: &Expression, right: &Expression, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let left_obj = eval_expression(left, env);
|
||||
let right_obj = eval_expression(right, env);
|
||||
|
||||
match left_obj {
|
||||
Object::Integer(i) => {
|
||||
let left_int = i;
|
||||
let right_int;
|
||||
match right_obj {
|
||||
Object::Integer(i) => {
|
||||
right_int = i;
|
||||
match infix {
|
||||
Infix::Plus => Object::Integer(left_int + right_int),
|
||||
Infix::Minus => Object::Integer(left_int - right_int),
|
||||
Infix::Asterisk => Object::Integer(left_int * right_int),
|
||||
Infix::Slash => Object::Integer(left_int / right_int),
|
||||
Infix::NotEq => Object::Boolean(left_int != right_int),
|
||||
Infix::Eq => Object::Boolean(left_int == right_int),
|
||||
Infix::Gt => Object::Boolean(left_int > right_int),
|
||||
Infix::Lt => Object::Boolean(left_int < right_int),
|
||||
_ => panic!("Inintid op {}", infix)
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected integer {}", right_obj),
|
||||
}
|
||||
}
|
||||
Object::String(s) => {
|
||||
let left_str = s;
|
||||
let right_str;
|
||||
match right_obj {
|
||||
Object::String(s) => {
|
||||
right_str = s;
|
||||
match infix {
|
||||
Infix::Plus => Object::String(left_str + &*right_str),
|
||||
_ => panic!("Invalid op {} for strings", infix)
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected String{}", right_obj),
|
||||
}
|
||||
}
|
||||
_ => panic!("Invalid value in expression {}, expected int", left_obj),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_block_statement(block: &BlockStatement, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let mut val = Object::Nil;
|
||||
for stmt in &block.stmts {
|
||||
val = match stmt {
|
||||
Statement::Let(x, expr) => eval_let_statement(x.to_string(), &*expr, env),
|
||||
Statement::Return(Some(x)) => {
|
||||
return eval_return_statement(&*x, env);
|
||||
}
|
||||
Statement::Return(None) => {
|
||||
return Object::Nil;
|
||||
}
|
||||
Statement::Expression(expr) => eval_expression(&*expr, env),
|
||||
}
|
||||
}
|
||||
val
|
||||
}
|
||||
|
||||
pub fn eval_if_expression(expr: &Expression, true_block: &BlockStatement,
|
||||
false_block: &Option<Box<BlockStatement>>, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let expr_obj = eval_expression(expr, env);
|
||||
let expr_val = match expr_obj {
|
||||
Object::Boolean(v) => v,
|
||||
_ => panic!("Expected boolean expression in if statement, found {}", expr_obj)
|
||||
};
|
||||
|
||||
if expr_val {
|
||||
eval_block_statement(true_block, env)
|
||||
} else if false_block.is_some() {
|
||||
eval_block_statement(false_block.as_ref().unwrap().as_ref(), env)
|
||||
} else {
|
||||
Object::Nil
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_function_parameters(params: &Vec<Expression>, env: &mut Rc<RefCell<Environment>>) -> Vec<Object> {
|
||||
let mut param_objs = vec![];
|
||||
for param in params.iter() {
|
||||
let param_obj = eval_expression(param, env);
|
||||
if param_obj == Object::Nil {
|
||||
panic!("Unable to evaluate parameter {}", param);
|
||||
}
|
||||
param_objs.push(eval_expression(param, env))
|
||||
}
|
||||
param_objs
|
||||
}
|
||||
|
||||
pub fn eval_user_defined_function_call(func_params: &Vec<String>, param_objs: &Vec<Object>,
|
||||
func_block: &BlockStatement,
|
||||
env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let mut func_new_env = Rc::new(RefCell::new(Environment::extend(env.clone())));
|
||||
|
||||
let mut idx = 0;
|
||||
while idx < param_objs.len() {
|
||||
func_new_env.borrow_mut().set(&*func_params[idx], param_objs[idx].clone());
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
eval_block_statement(&func_block, &mut func_new_env)
|
||||
}
|
||||
|
||||
pub fn eval_dict_literal(dict_expr: &Vec<(Expression, Expression)>,
|
||||
env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let mut dict = HashMap::new();
|
||||
for (key_expr, val_expr) in dict_expr {
|
||||
let key = eval_expression(key_expr, env);
|
||||
let val = eval_expression(val_expr, env);
|
||||
dict.insert(key, val);
|
||||
}
|
||||
|
||||
Object::Dict(dict)
|
||||
}
|
||||
|
||||
pub fn eval_array_literal(member_expr: &Vec<Expression>, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let mut members = vec![];
|
||||
for mem in member_expr.iter() {
|
||||
members.push(eval_expression(mem, env));
|
||||
}
|
||||
Object::Array(members)
|
||||
}
|
||||
|
||||
pub fn eval_arr_idx(arr: &Vec<Object>, idx: &Object) -> Object {
|
||||
match idx {
|
||||
Object::Integer(index) => {
|
||||
if *index as usize > arr.len() - 1 {
|
||||
panic!("Array index {} greater that array size {}", idx, arr.len());
|
||||
}
|
||||
|
||||
let obj = arr.get(*index as usize).unwrap().clone();
|
||||
obj
|
||||
}
|
||||
_ => panic!("Invalid array index {}, expected a positive integer", idx),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_dict_idx(dict: &HashMap<Object, Object>, idx: &Object) -> Object {
|
||||
let mut ret_val = Object::Nil;
|
||||
if dict.contains_key(idx) {
|
||||
ret_val = dict.get(idx).unwrap().clone();
|
||||
}
|
||||
ret_val
|
||||
}
|
||||
|
||||
pub fn eval_index(container_expr: &Box<Expression>, idx_expr: &Box<Expression>,
|
||||
env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let container = eval_expression(container_expr, env);
|
||||
let idx = eval_expression(idx_expr, env);
|
||||
|
||||
match container {
|
||||
Object::Array(arr) => eval_arr_idx(&arr, &idx),
|
||||
Object::Dict(dict) => eval_dict_idx(&dict, &idx),
|
||||
_ => panic!("Expected array or dictionary got {}", container.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_function_call(func_expr: &Box<Expression>, parameters: &Vec<Expression>,
|
||||
env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let func_obj = eval_expression(func_expr, env);
|
||||
let param_objs = eval_function_parameters(parameters, env);
|
||||
|
||||
match func_obj {
|
||||
Object::FunctionLiteral(params, block, mut func_env) =>
|
||||
{
|
||||
if param_objs.len() != params.len() {
|
||||
panic!("Did not find the expected number of arguments for the function");
|
||||
}
|
||||
eval_user_defined_function_call(¶ms, ¶m_objs, &block, &mut func_env)
|
||||
}
|
||||
Object::FunctionInBuilt(_) => eval_inbuilt_function(&func_obj, ¶m_objs),
|
||||
_ => panic!("Invalid object type {}, expected function object", func_obj)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_expression(expr: &Expression, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
match expr {
|
||||
Expression::IntegerLiteral(i) => Object::Integer(*i),
|
||||
Expression::Identifier(_s) => eval_identifier(expr, env),
|
||||
Expression::String(s) => Object::String(s.to_string()),
|
||||
Expression::Boolean(b) => Object::Boolean(*b),
|
||||
Expression::Prefix(prefix, expr) =>
|
||||
eval_prefix_expression(prefix, expr, env),
|
||||
Expression::Infix(infix, left, right) =>
|
||||
eval_infix_expression(infix, left, right, env),
|
||||
Expression::If(expr, true_block, false_block) =>
|
||||
eval_if_expression(expr, true_block, false_block, env),
|
||||
Expression::ArrayLiteral(arr) => eval_array_literal(arr, env),
|
||||
Expression::DictionaryLiteral(dict) => eval_dict_literal(dict, env),
|
||||
Expression::Index(arr, idx) => eval_index(arr, idx, env),
|
||||
Expression::FunctionLiteral(params, block) =>
|
||||
Object::FunctionLiteral(params.clone(), *block.clone(), env.clone()),
|
||||
Expression::Call(func, params) => eval_function_call(func, params, env),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_let_statement(identifier: String, expr: &Expression, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
let expr_val = eval_expression(expr, env);
|
||||
env.borrow_mut().set(identifier.as_str(), expr_val);
|
||||
Object::Nil
|
||||
}
|
||||
|
||||
pub fn eval_return_statement(expr: &Expression, env: &mut Rc<RefCell<Environment>>) -> Object {
|
||||
eval_expression(expr, env)
|
||||
}
|
||||
|
||||
pub fn eval_program(program: &Program, env: &mut Rc<RefCell<Environment>>) -> Object
|
||||
{
|
||||
let mut val = Object::Nil;
|
||||
for stmt in &program.stmts {
|
||||
val = match stmt {
|
||||
Statement::Let(id, expr) => eval_let_statement(id.to_string(), &*expr, env),
|
||||
Statement::Return(Some(expr)) => { return eval_return_statement(&*expr, env); }
|
||||
Statement::Return(None) => { return Object::Nil; }
|
||||
Statement::Expression(expr) => eval_expression(&*expr, env),
|
||||
};
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::Lexer;
|
||||
use crate::parser::Parser;
|
||||
use crate::object::Object;
|
||||
use crate::environment::Environment;
|
||||
use crate::evaluator::*;
|
||||
|
||||
fn test_eval_program(input: &str) -> Object {
|
||||
let mut env = Rc::new(RefCell::new(Environment::new()));
|
||||
let lexer = Lexer::new(input);
|
||||
let mut parser = Parser::new(lexer);
|
||||
let program = parser.parse_program().unwrap();
|
||||
eval_program(program.as_ref(), &mut env)
|
||||
}
|
||||
|
||||
struct TestCase<'a> {
|
||||
test_str: &'a str,
|
||||
val: Object,
|
||||
}
|
||||
|
||||
fn check_test_cases(test_cases: Vec<TestCase>) {
|
||||
for test_case in test_cases {
|
||||
assert_eq!(test_eval_program(test_case.test_str), test_case.val);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_integer() {
|
||||
let test_cases = vec![TestCase { test_str: "10", val: Object::Integer(10) }];
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_boolean() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "true", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "false", val: Object::Boolean(false) },
|
||||
];
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_return() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "return 0;", val: Object::Integer(0) },
|
||||
TestCase { test_str: "return;", val: Object::Nil },
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_prefix() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "!true", val: Object::Boolean(false) },
|
||||
TestCase { test_str: "!false", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "-1", val: Object::Integer(-1) }
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_infix() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "10 > 20", val: Object::Boolean(false) },
|
||||
TestCase { test_str: "-1 + 0", val: Object::Integer(-1) },
|
||||
TestCase { test_str: "5 > 3", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "4 < 2", val: Object::Boolean(false) },
|
||||
TestCase { test_str: "5 == 3", val: Object::Boolean(false) },
|
||||
TestCase { test_str: "4 != 2", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "(2*2 + 1) == 5", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "(2 + 3)*2 == 10", val: Object::Boolean(true) },
|
||||
TestCase { test_str: "\"ab\" + \"cd\"", val: Object::String(String::from("abcd")) },
|
||||
TestCase {
|
||||
test_str: "\"ab\" + \"cd\" + \"ef\"",
|
||||
val: Object::String(String::from("abcdef")),
|
||||
},
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_if_expr() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "if (10 > 20) {20} else {10}", val: Object::Integer(10) },
|
||||
TestCase { test_str: "if (21 > 20) {20} else {10}", val: Object::Integer(20) },
|
||||
TestCase { test_str: "if (21 > 20) {20} ", val: Object::Integer(20) },
|
||||
TestCase { test_str: "if (21 < 20) {20} ", val: Object::Nil },
|
||||
TestCase { test_str: "if (21 > 20) {let x = 30; 20} ", val: Object::Integer(20) },
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_let_statements() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "let x = 10; if (x > 20) {20} else {10}", val: Object::Integer(10) },
|
||||
TestCase { test_str: "let y = 20; if (21 > y) {20} else {10}", val: Object::Integer(20) },
|
||||
TestCase { test_str: "let z = 30; z*z; ", val: Object::Integer(900) },
|
||||
TestCase { test_str: "let a = 30; let b = 40; a + b", val: Object::Integer(70) },
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_functions() {
|
||||
let test_cases = vec![
|
||||
TestCase {
|
||||
test_str: "let sum = fn(x, y){ x + y;}; \
|
||||
sum(10, 20);",
|
||||
val: Object::Integer(30),
|
||||
},
|
||||
TestCase {
|
||||
test_str: "let square = fn(x){x*x}; \
|
||||
square(10)",
|
||||
val: Object::Integer(100),
|
||||
},
|
||||
TestCase {
|
||||
test_str: "let gt = fn(x, y){ \
|
||||
if (!(x > y)) {\
|
||||
x*x\
|
||||
} else {\
|
||||
y\
|
||||
};\
|
||||
}; \
|
||||
gt(3, 2)",
|
||||
val: Object::Integer(2),
|
||||
},
|
||||
TestCase {
|
||||
test_str: "let gt = fn(x, y){ \
|
||||
if (x > y) \
|
||||
{x*x} \
|
||||
else {\
|
||||
y\
|
||||
};\
|
||||
}; \
|
||||
gt(3, 2)",
|
||||
val: Object::Integer(9),
|
||||
},
|
||||
TestCase {
|
||||
test_str: "let fact = fn(x) {\
|
||||
if (x > 1) {\
|
||||
x*fact(x - 1);\
|
||||
} else {\
|
||||
x\
|
||||
};\
|
||||
}; \
|
||||
fact(8);",
|
||||
val: Object::Integer(40320),
|
||||
},
|
||||
TestCase {
|
||||
test_str: "let sum = fn(x,y){x + y;};\
|
||||
let sqr = fn(x){let z = sum(x, x); z*z;};\
|
||||
let z = sum(2, 3) + sqr(2);\
|
||||
z;",
|
||||
val: Object::Integer(21),
|
||||
},
|
||||
TestCase { test_str: "fn(x, y, z){x + y + z}(1, 2, 3);", val: Object::Integer(6) },
|
||||
TestCase {
|
||||
test_str: "let a = \"wx\";\
|
||||
let b = \"yz\";\
|
||||
let z = a + b;\
|
||||
z;",
|
||||
val: Object::String(String::from("wxyz")),
|
||||
},
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closures() {
|
||||
let test_cases = vec![
|
||||
TestCase {
|
||||
test_str: "let adder = fn(x){fn(x,y) { x + y; };};\
|
||||
let a2 = adder(2);
|
||||
a2(10);",
|
||||
val: Object::Integer(12),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_eval_inbuilt_functions() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "let x = \"cartman\"; len(x)", val: Object::Integer(7) },
|
||||
TestCase { test_str: "len(\"cartman\");", val: Object::Integer(7) },
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_arrays() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "let x = [1, 2, 3]; x[2]", val: Object::Integer(3) },
|
||||
TestCase { test_str: "let x = [1, 2, [1, 3]]; x[2][0]", val: Object::Integer(1) },
|
||||
TestCase {
|
||||
test_str: "let x = [1, \"abc\", 32, 43]; x[1]",
|
||||
val: Object::String(String::from("abc")),
|
||||
},
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_eval_dict() {
|
||||
let test_cases = vec![
|
||||
TestCase { test_str: "let x = {1: 2, 2: 3, 3: 4}; x[2]", val: Object::Integer(3)},
|
||||
TestCase { test_str: "{1: 2, 2: 3, 3: 4}[2]", val: Object::Integer(3)},
|
||||
TestCase { test_str: "let x = {1: 2, \"test\": 3, 3: 4}; x[\"test\"]",
|
||||
val: Object::Integer(3)},
|
||||
TestCase { test_str: "let s = 3; let x = {1: 2, \"test\": 3, 3: (2*10)}; x[s]",
|
||||
val: Object::Integer(20)},
|
||||
TestCase { test_str: "let s = \"test\"; let x = {1: 2, \"test\": \"res\", 3: (2*10)}; x[s]",
|
||||
val: Object::String(String::from("res"))},
|
||||
];
|
||||
|
||||
check_test_cases(test_cases);
|
||||
}
|
||||
}
|
||||
36
__lang/monkey-rs/src/inbuilt.rs
Normal file
36
__lang/monkey-rs/src/inbuilt.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use crate::object::Object;
|
||||
|
||||
pub fn get_inbuilt_function(identifier: &str) -> Option<Object> {
|
||||
match identifier {
|
||||
"len" => Some(Object::FunctionInBuilt(String::from("len"))),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn process_len_function(params: &Vec<Object>) -> Object {
|
||||
|
||||
if params.len() != 1 {
|
||||
panic!("Expected a single argument to the len function found {} arguments",
|
||||
params.len())
|
||||
}
|
||||
let argument = ¶ms[0];
|
||||
|
||||
match argument {
|
||||
Object::String(s) => Object::Integer(s.len() as i64),
|
||||
_ => panic!("len expects a string argument")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn eval_inbuilt_function(func_obj: &Object, params: &Vec<Object>) -> Object {
|
||||
match func_obj {
|
||||
Object::FunctionInBuilt(func_name) => {
|
||||
match func_name.as_str() {
|
||||
"len" => {
|
||||
return process_len_function(params)
|
||||
}
|
||||
_ => panic!("Invalid inbuild function {}", func_name),
|
||||
}
|
||||
}
|
||||
_ => panic!("Excepted a function object but found {}", func_obj)
|
||||
}
|
||||
}
|
||||
399
__lang/monkey-rs/src/lexer.rs
Normal file
399
__lang/monkey-rs/src/lexer.rs
Normal file
@@ -0,0 +1,399 @@
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Token {
|
||||
Illegal,
|
||||
Eof,
|
||||
|
||||
// Identifiers + Literals
|
||||
Ident(String),
|
||||
String(String),
|
||||
Int(i64),
|
||||
|
||||
// Operators
|
||||
Assign,
|
||||
Plus,
|
||||
Bang,
|
||||
Minus,
|
||||
Slash,
|
||||
Asterik,
|
||||
Lt,
|
||||
Gt,
|
||||
Eq,
|
||||
NotEq,
|
||||
|
||||
// Delimiters
|
||||
Comma,
|
||||
Colon,
|
||||
Semicolon,
|
||||
|
||||
LParen,
|
||||
RParen,
|
||||
LBrace,
|
||||
RBrace,
|
||||
LBracket,
|
||||
RBracket,
|
||||
|
||||
// Keywords
|
||||
Function,
|
||||
Let,
|
||||
True,
|
||||
False,
|
||||
If,
|
||||
Else,
|
||||
Return,
|
||||
}
|
||||
|
||||
fn token_str_repr(token: &Token) -> Box<String> {
|
||||
let str_repr= match token {
|
||||
Token::Illegal => String::from("Illegal"),
|
||||
Token::Eof => String::from("Eof"),
|
||||
|
||||
// Identifiers + Literals
|
||||
Token::Ident(s) => s.clone(),
|
||||
Token::Int(i) => i.to_string(),
|
||||
Token::String(s) => s.clone(),
|
||||
|
||||
// Operators
|
||||
Token::Assign => String::from("="),
|
||||
Token::Plus => String::from("+"),
|
||||
Token::Bang => String::from("!"),
|
||||
Token::Minus => String::from("-"),
|
||||
Token::Slash => String::from("/"),
|
||||
Token::Asterik => String::from("*"),
|
||||
Token::Lt => String::from("<"),
|
||||
Token::Gt => String::from(">"),
|
||||
Token::Eq => String::from("=="),
|
||||
Token::NotEq => String::from("!="),
|
||||
|
||||
// Delimiters
|
||||
Token::Comma => String::from(","),
|
||||
Token::Semicolon => String::from(";"),
|
||||
Token::Colon => String::from(':'),
|
||||
|
||||
Token::LParen => String::from("("),
|
||||
Token::RParen => String::from(")"),
|
||||
Token::LBrace => String::from("{"),
|
||||
Token::RBrace => String::from("}"),
|
||||
Token::LBracket => String::from("["),
|
||||
Token::RBracket => String::from("]"),
|
||||
|
||||
// Keywords
|
||||
Token::Function => String::from("fn"),
|
||||
Token::Let => String::from("let"),
|
||||
Token::True => String::from("true"),
|
||||
Token::False => String::from("false"),
|
||||
Token::If => String::from("if"),
|
||||
Token::Else => String::from("else"),
|
||||
Token::Return => String::from("return"),
|
||||
};
|
||||
Box::new(str_repr)
|
||||
}
|
||||
|
||||
impl fmt::Display for Token {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", token_str_repr(self))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct Lexer {
|
||||
tokens: Vec<Token>
|
||||
}
|
||||
|
||||
impl Lexer {
|
||||
pub fn new(input: &str) -> Box<Lexer> {
|
||||
let mut tokens: Vec<Token> = vec![];
|
||||
|
||||
let size = input.len();
|
||||
let mut index = 0;
|
||||
let string_start = false;
|
||||
|
||||
while index < size {
|
||||
|
||||
while index < size && input.chars().nth(index).unwrap().is_ascii_whitespace() {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if index >= size {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
let start = index;
|
||||
|
||||
let token = match input.chars().nth(index).unwrap() {
|
||||
'=' => {
|
||||
if size - index > 1 {
|
||||
match input.chars().nth(index + 1).unwrap() {
|
||||
'=' => {
|
||||
index += 1;
|
||||
Token::Eq
|
||||
}
|
||||
_ => Token::Assign
|
||||
}
|
||||
} else {
|
||||
Token::Assign
|
||||
}
|
||||
}
|
||||
';' => Token::Semicolon,
|
||||
':' => Token::Colon,
|
||||
'(' => Token::LParen,
|
||||
')' => Token::RParen,
|
||||
',' => Token::Comma,
|
||||
'+' => Token::Plus,
|
||||
'{' => Token::LBrace,
|
||||
'}' => Token::RBrace,
|
||||
'[' => Token::LBracket,
|
||||
']' => Token::RBracket,
|
||||
'>' => Token::Gt,
|
||||
'<' => Token::Lt,
|
||||
'*' => Token::Asterik,
|
||||
'-' => Token::Minus,
|
||||
'/' => Token::Slash,
|
||||
'!' => {
|
||||
if size - index > 1 {
|
||||
match input.chars().nth(index + 1).unwrap() {
|
||||
'=' => {
|
||||
index += 1;
|
||||
Token::NotEq
|
||||
}
|
||||
_ => Token::Bang
|
||||
}
|
||||
} else {
|
||||
Token::Bang
|
||||
}
|
||||
}
|
||||
_ => Token::Illegal
|
||||
};
|
||||
|
||||
if token != Token::Illegal {
|
||||
index += 1;
|
||||
tokens.push(token);
|
||||
continue;
|
||||
}
|
||||
|
||||
if string_start == false && input.chars().nth(index).unwrap() == '"' {
|
||||
let start = index;
|
||||
index += 1;
|
||||
while index < size && input.chars().nth(index).unwrap() != '"' {
|
||||
index += 1;
|
||||
}
|
||||
let end= index;
|
||||
if index < size {
|
||||
index += 1;
|
||||
}
|
||||
let str_token = Token::String(input[(start + 1)..end].to_string());
|
||||
tokens.push(str_token);
|
||||
}
|
||||
|
||||
// Identifiers and keywords
|
||||
if index < size && input.chars().nth(index).unwrap().is_alphabetic() {
|
||||
while index < size && (input.chars().nth(index).unwrap().is_alphanumeric() ||
|
||||
input.chars().nth(index).unwrap() == '_') {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if start < index {
|
||||
let s = match &input[start..index] {
|
||||
"let" => Token::Let,
|
||||
"true" => Token::True,
|
||||
"false" => Token::False,
|
||||
"if" => Token::If,
|
||||
"else" => Token::Else,
|
||||
"return" => Token::Return,
|
||||
"fn" => Token::Function,
|
||||
_ => Token::Ident(input[start..index].to_string())
|
||||
};
|
||||
tokens.push(s);
|
||||
}
|
||||
}
|
||||
|
||||
// Numbers
|
||||
if index < size && input.chars().nth(index).unwrap().is_ascii_digit() {
|
||||
while index < size && (input.chars().nth(index).unwrap().is_ascii_digit()) {
|
||||
index += 1;
|
||||
}
|
||||
|
||||
if start < index {
|
||||
tokens.push(Token::Int(input[start..index].to_string().parse::<i64>().unwrap()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens.reverse();
|
||||
|
||||
Box::new(Lexer {
|
||||
tokens
|
||||
})
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Token {
|
||||
self.tokens.pop().unwrap_or(Token::Eof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::{Lexer, Token};
|
||||
|
||||
const TEST_STR: &str = "
|
||||
let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
|
||||
let x = \"abcd\";
|
||||
let y = \"\";
|
||||
let arr = arr[1, 2, 3];
|
||||
let y = arr[x];
|
||||
let x = {z: \"vishal\", y: 1};
|
||||
";
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_tokens() {
|
||||
let test_token_vec: Vec<Token> = vec![
|
||||
Token::Let,
|
||||
Token::Ident(String::from("five")),
|
||||
Token::Assign,
|
||||
Token::Int(5),
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("ten")),
|
||||
Token::Assign,
|
||||
Token::Int(10),
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("add")),
|
||||
Token::Assign,
|
||||
Token::Function,
|
||||
Token::LParen,
|
||||
Token::Ident(String::from("x")),
|
||||
Token::Comma,
|
||||
Token::Ident(String::from("y")),
|
||||
Token::RParen,
|
||||
Token::LBrace,
|
||||
Token::Ident(String::from("x")),
|
||||
Token::Plus,
|
||||
Token::Ident(String::from("y")),
|
||||
Token::Semicolon,
|
||||
Token::RBrace,
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("result")),
|
||||
Token::Assign,
|
||||
Token::Ident(String::from("add")),
|
||||
Token::LParen,
|
||||
Token::Ident(String::from("five")),
|
||||
Token::Comma,
|
||||
Token::Ident(String::from("ten")),
|
||||
Token::RParen,
|
||||
Token::Semicolon,
|
||||
Token::Bang,
|
||||
Token::Minus,
|
||||
Token::Slash,
|
||||
Token::Asterik,
|
||||
Token::Int(5),
|
||||
Token::Semicolon,
|
||||
Token::Int(5),
|
||||
Token::Lt,
|
||||
Token::Int(10),
|
||||
Token::Gt,
|
||||
Token::Int(5),
|
||||
Token::Semicolon,
|
||||
Token::If,
|
||||
Token::LParen,
|
||||
Token::Int(5),
|
||||
Token::Lt,
|
||||
Token::Int(10),
|
||||
Token::RParen,
|
||||
Token::LBrace,
|
||||
Token::Return,
|
||||
Token::True,
|
||||
Token::Semicolon,
|
||||
Token::RBrace,
|
||||
Token::Else,
|
||||
Token::LBrace,
|
||||
Token::Return,
|
||||
Token::False,
|
||||
Token::Semicolon,
|
||||
Token::RBrace,
|
||||
Token::Int(10),
|
||||
Token::Eq,
|
||||
Token::Int(10),
|
||||
Token::Semicolon,
|
||||
Token::Int(10),
|
||||
Token::NotEq,
|
||||
Token::Int(9),
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("x")),
|
||||
Token::Assign,
|
||||
Token::String(String::from("abcd")),
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("y")),
|
||||
Token::Assign,
|
||||
Token::String(String::from("")),
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("arr")),
|
||||
Token::Assign,
|
||||
Token::Ident(String::from("arr")),
|
||||
Token::LBracket,
|
||||
Token::Int(1),
|
||||
Token::Comma,
|
||||
Token::Int(2),
|
||||
Token::Comma,
|
||||
Token::Int(3),
|
||||
Token::RBracket,
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("y")),
|
||||
Token::Assign,
|
||||
Token::Ident(String::from("arr")),
|
||||
Token::LBracket,
|
||||
Token::Ident(String::from("x")),
|
||||
Token::RBracket,
|
||||
Token::Semicolon,
|
||||
Token::Let,
|
||||
Token::Ident(String::from("x")),
|
||||
Token::Assign,
|
||||
Token::LBrace,
|
||||
Token::Ident(String::from("z")),
|
||||
Token::Colon,
|
||||
Token::String(String::from("vishal")),
|
||||
Token::Comma,
|
||||
Token::Ident(String::from("y")),
|
||||
Token::Colon,
|
||||
Token::Int(1),
|
||||
Token::RBrace,
|
||||
Token::Semicolon,
|
||||
Token::Eof,
|
||||
];
|
||||
|
||||
let mut lexer = Lexer::new(TEST_STR);
|
||||
|
||||
for test_token in test_token_vec.iter() {
|
||||
let token = lexer.next();
|
||||
assert_eq!(token, *test_token);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
__lang/monkey-rs/src/main.rs
Normal file
35
__lang/monkey-rs/src/main.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
mod lexer;
|
||||
mod ast;
|
||||
mod parser;
|
||||
mod object;
|
||||
mod environment;
|
||||
mod evaluator;
|
||||
mod inbuilt;
|
||||
|
||||
use linefeed::{Interface, ReadResult};
|
||||
use crate::lexer::Lexer;
|
||||
use crate::evaluator::eval_program;
|
||||
use std::rc::Rc;
|
||||
use std::cell::RefCell;
|
||||
use crate::environment::Environment;
|
||||
|
||||
fn main() {
|
||||
|
||||
let reader = Interface::new("monkey-rs").unwrap();
|
||||
let mut env = Rc::new(RefCell::new(Environment::new()));
|
||||
|
||||
reader.set_prompt("monkey-rs> ").unwrap();
|
||||
|
||||
while let ReadResult::Input(input) = reader.read_line().unwrap() {
|
||||
if input.eq("exit") {
|
||||
break;
|
||||
}
|
||||
let lexer = Lexer::new(&*input);
|
||||
let mut parser = parser::Parser::new(lexer);
|
||||
let program = parser.parse_program().unwrap();
|
||||
|
||||
println!("{}", eval_program(program.as_ref(), &mut env));
|
||||
}
|
||||
|
||||
println!("Good bye");
|
||||
}
|
||||
63
__lang/monkey-rs/src/object.rs
Normal file
63
__lang/monkey-rs/src/object.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use std;
|
||||
use crate::ast::BlockStatement;
|
||||
use crate::environment::Environment;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::collections::HashMap;
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Object {
|
||||
Error(String),
|
||||
Nil,
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
Identifier(String),
|
||||
FunctionInBuilt(String),
|
||||
Array(Vec<Object>),
|
||||
Dict(HashMap<Object, Object>),
|
||||
FunctionLiteral(Vec<String>, BlockStatement, Rc<RefCell<Environment>>),
|
||||
}
|
||||
|
||||
impl Eq for Object {}
|
||||
|
||||
impl Hash for Object {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
match self {
|
||||
Object::String(s) => s.hash(state),
|
||||
Object::Integer(i) => i.hash(state),
|
||||
_ => panic!("Invalid dict key {}, supported types are string and integer", self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Object {
|
||||
fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
match self {
|
||||
Object::Error(e) => write!(fmt, "{}", e),
|
||||
Object::Nil => write!(fmt, "nil"),
|
||||
Object::Integer(i) => write!(fmt, "{}", i),
|
||||
Object::Boolean(b) => write!(fmt, "{}", b),
|
||||
Object::Identifier(s) => write!(fmt, "{}", s),
|
||||
Object::String(s) => write!(fmt, "\"{}\"", s),
|
||||
Object::Array(arr) => write!(fmt, "[{}]", arr.iter().map(|a| a.to_string()).collect::<Vec<String>>().join(",")),
|
||||
Object::Dict(dict) => {
|
||||
let mut str = String::new();
|
||||
str.push_str("{");
|
||||
for (k, v) in dict {
|
||||
str.push_str(format!("{}:{},", k, v).as_str());
|
||||
}
|
||||
|
||||
if str.ends_with(',') {
|
||||
str.pop();
|
||||
}
|
||||
str.push_str("}");
|
||||
write!(fmt, "{}", str)
|
||||
},
|
||||
Object::FunctionLiteral(parameters, block, _) => write!(fmt, "fn({}){{ {} }}",
|
||||
parameters.join(","), block.to_string()),
|
||||
_ => panic!("Invalid object {}", self),
|
||||
}
|
||||
}
|
||||
}
|
||||
825
__lang/monkey-rs/src/parser.rs
Normal file
825
__lang/monkey-rs/src/parser.rs
Normal file
@@ -0,0 +1,825 @@
|
||||
use crate::lexer::{Lexer, Token};
|
||||
use crate::ast::*;
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, PartialOrd)]
|
||||
pub enum Precedence {
|
||||
Lowest,
|
||||
Equals,
|
||||
LessGreater,
|
||||
Sum,
|
||||
Product,
|
||||
Prefix,
|
||||
Call,
|
||||
Index,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ParseError {
|
||||
token: Token,
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "Problem parsing near token {}", self.token)
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Parser {
|
||||
lexer: Box<Lexer>,
|
||||
curr_token: Token,
|
||||
next_token: Token,
|
||||
}
|
||||
|
||||
impl Parser {
|
||||
pub fn new(mut lexer: Box<Lexer>) -> Box<Parser> {
|
||||
let curr_token = lexer.next();
|
||||
let next_token = lexer.next();
|
||||
Box::new(Parser { lexer, curr_token, next_token })
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Token {
|
||||
self.curr_token = self.next_token.clone();
|
||||
self.next_token = self.lexer.next();
|
||||
self.curr_token.clone()
|
||||
}
|
||||
|
||||
pub fn peek(&self) -> Token {
|
||||
self.next_token.clone()
|
||||
}
|
||||
|
||||
pub fn precedence(&self, token: &Token) -> Precedence {
|
||||
match token {
|
||||
Token::Eq => Precedence::Equals,
|
||||
Token::NotEq => Precedence::Equals,
|
||||
Token::Lt => Precedence::LessGreater,
|
||||
Token::Gt => Precedence::LessGreater,
|
||||
Token::Plus => Precedence::Sum,
|
||||
Token::Minus => Precedence::Sum,
|
||||
Token::Asterik => Precedence::Product,
|
||||
Token::Slash => Precedence::Product,
|
||||
Token::LParen => Precedence::Call,
|
||||
Token::LBracket => Precedence::Index,
|
||||
_ => Precedence::Lowest,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn peek_precedence(&self) -> Precedence {
|
||||
self.precedence(&self.next_token)
|
||||
}
|
||||
|
||||
pub fn expect_current_token(&mut self, token: Token) {
|
||||
if self.curr_token == token {
|
||||
self.next();
|
||||
} else {
|
||||
panic!("Did not find expected current token {}, found {}", token, self.curr_token);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expect_next_token(&mut self, token: Token) {
|
||||
if self.peek() == token {
|
||||
self.next();
|
||||
} else {
|
||||
panic!("Did not find expected next token {}, found {}", token, self.peek());
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_let_statement(&mut self) -> Box<Statement> {
|
||||
// Get identifier token
|
||||
let token = self.next();
|
||||
let identifer = match token {
|
||||
Token::Ident(s) => Expression::Identifier(s),
|
||||
_ => panic!("Identifier token not found in let statement {}", token)
|
||||
};
|
||||
|
||||
// Check assignment token
|
||||
self.expect_next_token(Token::Assign);
|
||||
self.next();
|
||||
|
||||
// Parse expression
|
||||
let expr = self.parse_expression(Precedence::Lowest);
|
||||
let let_stmt = Statement::Let(identifer.to_string(), expr);
|
||||
|
||||
if self.peek() == Token::Semicolon {
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(let_stmt)
|
||||
}
|
||||
|
||||
fn parse_return_statement(&mut self) -> Box<Statement> {
|
||||
self.next();
|
||||
|
||||
if self.curr_token == Token::Semicolon {
|
||||
return Box::new(Statement::Return(None))
|
||||
}
|
||||
|
||||
let expr = self.parse_expression(Precedence::Lowest);
|
||||
|
||||
if self.peek() == Token::Semicolon {
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(Statement::Return(Some(expr)))
|
||||
}
|
||||
|
||||
fn parse_identifier(&mut self) -> Box<Expression> {
|
||||
let curr_token = &self.curr_token;
|
||||
|
||||
match curr_token {
|
||||
Token::Ident(s) => Box::new(Expression::Identifier(s.to_string())),
|
||||
_ => panic!("Unable to parse identifier {}", self.curr_token)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_string(&mut self) -> Box<Expression> {
|
||||
let curr_token = &self.curr_token;
|
||||
|
||||
match curr_token {
|
||||
Token::String(s) => Box::new(Expression::String(s.to_string())),
|
||||
_ => panic!("Unable to parse string {}", self.curr_token)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_integer(&mut self) -> Box<Expression> {
|
||||
let curr_token = &self.curr_token;
|
||||
|
||||
match curr_token {
|
||||
Token::Int(s) => Box::new(Expression::IntegerLiteral(*s)),
|
||||
_ => panic!("Unable to parse integer {}", self.curr_token)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_boolean(&mut self) -> Box<Expression> {
|
||||
let curr_token = &self.curr_token;
|
||||
|
||||
match curr_token {
|
||||
Token::True => Box::new(Expression::Boolean(true)),
|
||||
Token::False => Box::new(Expression::Boolean(false)),
|
||||
_ => panic!("Invalid boolean {}", curr_token)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_prefix_expression(&mut self) -> Box<Expression> {
|
||||
let op = self.curr_token.clone();
|
||||
|
||||
let prefix = match op {
|
||||
Token::Bang => Prefix::Bang,
|
||||
Token::Minus => Prefix::Minus,
|
||||
_ => panic!("Invalid token {} prefix expression", op)
|
||||
};
|
||||
|
||||
self.next();
|
||||
Box::new(Expression::Prefix(prefix, self.parse_expression(Precedence::Prefix)))
|
||||
}
|
||||
|
||||
fn parse_group_expression(&mut self) -> Box<Expression> {
|
||||
self.expect_current_token(Token::LParen);
|
||||
let expr = self.parse_expression(Precedence::Lowest);
|
||||
self.expect_next_token(Token::RParen);
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
fn parse_if_expression(&mut self) -> Box<Expression> {
|
||||
self.expect_current_token(Token::If);
|
||||
let condition = self.parse_group_expression();
|
||||
let true_block = self.parse_block_statement();
|
||||
self.next();
|
||||
|
||||
let mut false_block: Option<Box<BlockStatement>> = None;
|
||||
if self.curr_token == Token::Else {
|
||||
false_block = Some(self.parse_block_statement());
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(Expression::If(condition, true_block, false_block))
|
||||
}
|
||||
|
||||
fn parse_block_statement(&mut self) -> Box<BlockStatement> {
|
||||
let mut statements: Vec<Statement> = vec![];
|
||||
|
||||
self.expect_next_token(Token::LBrace);
|
||||
self.next();
|
||||
|
||||
while self.curr_token != Token::Eof && self.curr_token != Token::RBrace {
|
||||
let statement = self.parse_statement();
|
||||
statements.push(*statement);
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(BlockStatement{stmts: statements})
|
||||
}
|
||||
|
||||
pub fn parse_expression_statement(&mut self) -> Box<Statement> {
|
||||
let expr = self.parse_expression(Precedence::Lowest);
|
||||
if self.peek() == Token::Semicolon {
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(Statement::Expression(expr))
|
||||
}
|
||||
|
||||
///
|
||||
/// This function has been implemented using the TDOP algorithm mentioned
|
||||
/// [here](https://eli.thegreenplace.net/2010/01/02/top-down-operator-precedence-parsing)
|
||||
///
|
||||
fn parse_expression(&mut self, precedence: Precedence) -> Box<Expression> {
|
||||
let t = self.curr_token.clone();
|
||||
// Prefix
|
||||
let mut expr: Box<Expression> = match t {
|
||||
Token::Ident(_s) => self.parse_identifier(),
|
||||
Token::Int(_s) => self.parse_integer(),
|
||||
Token::String(_s) => self.parse_string(),
|
||||
Token::True | Token::False => self.parse_boolean(),
|
||||
Token::Bang | Token::Minus => self.parse_prefix_expression(),
|
||||
Token::LParen => self.parse_group_expression(),
|
||||
Token::If => self.parse_if_expression(),
|
||||
Token::Function => self.parse_function(),
|
||||
Token::LBracket => self.parse_array_literal(),
|
||||
Token::LBrace => self.parse_dict_literal(),
|
||||
_ => panic!("Invalid token in expression {}, next token {}", t, self.next_token.clone())
|
||||
};
|
||||
|
||||
// Infix
|
||||
while self.peek() != Token::Semicolon && self.peek() != Token::Colon &&
|
||||
self.peek_precedence() > precedence {
|
||||
let token = self.next();
|
||||
|
||||
expr = match token {
|
||||
Token::Plus | Token::Minus | Token::Slash | Token::Asterik |
|
||||
Token::Eq | Token::NotEq | Token::Lt | Token::Gt => {
|
||||
self.next();
|
||||
let infix = match token {
|
||||
Token::Plus => Infix::Plus,
|
||||
Token::Minus => Infix::Minus,
|
||||
Token::Slash => Infix::Slash,
|
||||
Token::Asterik => Infix::Asterisk,
|
||||
Token::Eq => Infix::Eq,
|
||||
Token::NotEq => Infix::NotEq,
|
||||
Token::Lt => Infix::Lt,
|
||||
Token::Gt => Infix::Gt,
|
||||
_ => panic!("Invalid infix token {}", token),
|
||||
};
|
||||
|
||||
Box::new(Expression::Infix(infix, expr,
|
||||
self.parse_expression(self.precedence(&token))))
|
||||
}
|
||||
Token::LParen => {
|
||||
self.parse_function_call(expr)
|
||||
},
|
||||
Token::LBracket => {
|
||||
self.parse_array_index(expr)
|
||||
}
|
||||
_ => expr
|
||||
};
|
||||
}
|
||||
|
||||
expr
|
||||
}
|
||||
|
||||
pub fn parse_call_parameters(&mut self) -> Vec<Expression> {
|
||||
let mut parameters: Vec<Expression> = vec![];
|
||||
self.expect_current_token(Token::LParen);
|
||||
|
||||
while self.curr_token != Token::RParen {
|
||||
let expr = self.parse_expression(Precedence::Lowest);
|
||||
parameters.push(*expr);
|
||||
|
||||
if self.peek() == Token::Comma {
|
||||
self.next();
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
parameters
|
||||
}
|
||||
|
||||
pub fn parse_function_call(&mut self, left: Box<Expression>) -> Box<Expression> {
|
||||
let parameters = self.parse_call_parameters();
|
||||
Box::new(Expression::Call(left, parameters))
|
||||
}
|
||||
|
||||
pub fn parse_array_index(&mut self, left: Box<Expression>) -> Box<Expression> {
|
||||
self.expect_current_token(Token::LBracket);
|
||||
let index_expr = self.parse_expression(Precedence::Lowest);
|
||||
self.expect_next_token(Token::RBracket);
|
||||
Box::new(Expression::Index(left, index_expr))
|
||||
}
|
||||
|
||||
pub fn parse_array_literal(&mut self) -> Box<Expression> {
|
||||
|
||||
let mut members: Vec<Expression> = vec![];
|
||||
|
||||
self.expect_current_token(Token::LBracket);
|
||||
while self.curr_token != Token::RBracket {
|
||||
let member = self.parse_expression(Precedence::Lowest);
|
||||
members.push(*member);
|
||||
|
||||
if self.peek() == Token::Comma {
|
||||
self.next();
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(Expression::ArrayLiteral(members))
|
||||
}
|
||||
|
||||
pub fn parse_dict_literal(&mut self) -> Box<Expression> {
|
||||
|
||||
let mut key_values= vec![];
|
||||
|
||||
self.expect_current_token(Token::LBrace);
|
||||
while self.curr_token != Token::RBrace {
|
||||
let key = self.parse_expression(Precedence::Lowest);
|
||||
|
||||
self.expect_next_token(Token::Colon);
|
||||
self.next();
|
||||
|
||||
let val = self.parse_expression(Precedence::Lowest);
|
||||
key_values.push((*key, *val));
|
||||
|
||||
if self.peek() == Token::Comma {
|
||||
self.next();
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
Box::new(Expression::DictionaryLiteral(key_values))
|
||||
}
|
||||
|
||||
pub fn parse_function_parameters(&mut self) -> Vec<String> {
|
||||
let mut parameters: Vec<String> = vec![];
|
||||
|
||||
self.expect_current_token(Token::LParen);
|
||||
|
||||
while self.curr_token != Token::RParen {
|
||||
let idf = &self.curr_token;
|
||||
|
||||
let identifier = match idf {
|
||||
Token::Ident(i) => i.to_string(),
|
||||
_ => panic!("Unexpected function parameter {}", idf)
|
||||
};
|
||||
parameters.push(identifier);
|
||||
|
||||
if self.peek() == Token::Comma {
|
||||
self.next();
|
||||
}
|
||||
|
||||
self.next();
|
||||
}
|
||||
|
||||
parameters
|
||||
}
|
||||
|
||||
pub fn parse_function(&mut self) -> Box<Expression> {
|
||||
self.expect_current_token(Token::Function);
|
||||
|
||||
let parameters = self.parse_function_parameters();
|
||||
let body = self.parse_block_statement();
|
||||
|
||||
Box::new(Expression::FunctionLiteral(parameters, body))
|
||||
}
|
||||
|
||||
pub fn parse_statement(&mut self) -> Box<Statement> {
|
||||
let statement = match self.curr_token {
|
||||
Token::Let => self.parse_let_statement(),
|
||||
Token::Return => self.parse_return_statement(),
|
||||
_ => self.parse_expression_statement(),
|
||||
};
|
||||
statement
|
||||
}
|
||||
|
||||
pub fn parse_program(&mut self) -> Result<Box<Program>, ParseError> {
|
||||
let mut program = Box::new(Program { stmts: vec![] });
|
||||
|
||||
while self.curr_token != Token::Eof {
|
||||
let statement = self.parse_statement();
|
||||
program.stmts.push(*statement);
|
||||
self.next();
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::lexer::{Lexer, Token};
|
||||
use crate::parser::Parser;
|
||||
use crate::ast::{Statement, Prefix};
|
||||
use crate::ast::Expression;
|
||||
|
||||
const TEST_STR: &str = "
|
||||
let five = 5;
|
||||
let ten = 10;
|
||||
|
||||
let add = fn(x, y) {
|
||||
x + y;
|
||||
};
|
||||
|
||||
let result = add(five, ten);
|
||||
!-/*5;
|
||||
5 < 10 > 5;
|
||||
|
||||
if (5 < 10) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
10 == 10;
|
||||
10 != 9;
|
||||
let x = \"x\";
|
||||
let y = \"y\";
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser() {
|
||||
let lexer = Lexer::new(TEST_STR);
|
||||
let mut parser = Parser::new(lexer);
|
||||
|
||||
let mut token = parser.next();
|
||||
while token != Token::Eof {
|
||||
token = parser.next();
|
||||
let peek_token = parser.peek();
|
||||
println!("{} {}", token, peek_token);
|
||||
}
|
||||
}
|
||||
|
||||
fn test_case_statements(input: &str) -> Vec<Statement> {
|
||||
let lexer = Lexer::new(input);
|
||||
let mut parser = Parser::new(lexer);
|
||||
let program = parser.parse_program().unwrap();
|
||||
program.stmts
|
||||
}
|
||||
|
||||
const TEST_LET_STATEMENTS_STR: &str = "
|
||||
let five = 5;
|
||||
let is_true = true;
|
||||
let t = ten;
|
||||
let twenty = 20 + 20;
|
||||
let zero = 30 - 30;
|
||||
let complex = 11 - 22 + 11 * 22;
|
||||
let x = \"abcd\";
|
||||
let arr = [1, \"abc\", 3];
|
||||
let y = [1, 2, 3][1];
|
||||
let z = arr[2 + 3];
|
||||
let dict = {1:3};
|
||||
let dict = {1:3, 2:4, 3: 3 + 2, 4: \"test\"};
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_let_statements() {
|
||||
let statements = test_case_statements(TEST_LET_STATEMENTS_STR);
|
||||
|
||||
assert_eq!(statements.len(), 12);
|
||||
let mut idx = 0;
|
||||
for stmt in statements.iter() {
|
||||
let _let_stmt = match stmt {
|
||||
Statement::Let(_s, expr) => {
|
||||
match idx {
|
||||
0 => {
|
||||
assert_eq!(stmt.to_string(), "let five = 5;");
|
||||
match **expr {
|
||||
Expression::IntegerLiteral(i) => assert_eq!(i, 5),
|
||||
_ => panic!("Expected integer value of 5"),
|
||||
}
|
||||
},
|
||||
1 => {
|
||||
assert_eq!(stmt.to_string(), "let is_true = true;");
|
||||
match **expr {
|
||||
Expression::Boolean(b) => assert_eq!(b, true),
|
||||
_ => panic!("Expected a true boolean value"),
|
||||
}
|
||||
},
|
||||
2 => assert_eq!(stmt.to_string(), "let t = ten;"),
|
||||
3 => assert_eq!(stmt.to_string(), "let twenty = (20 + 20);"),
|
||||
4 => assert_eq!(stmt.to_string(), "let zero = (30 - 30);"),
|
||||
5 => assert_eq!(stmt.to_string(), "let complex = ((11 - 22) + (11 * 22));"),
|
||||
6 => assert_eq!(stmt.to_string(), "let x = \"abcd\";"),
|
||||
7 => assert_eq!(stmt.to_string(), "let arr = [1,\"abc\",3];"),
|
||||
8 => assert_eq!(stmt.to_string(), "let y = [1,2,3][1];"),
|
||||
9 => assert_eq!(stmt.to_string(), "let z = arr[(2 + 3)];"),
|
||||
10 => assert_eq!(stmt.to_string(), "let dict = {1:3};"),
|
||||
11 => assert_eq!(stmt.to_string(), "let dict = {1:3,2:4,3:(3 + 2),4:\"test\"};"),
|
||||
_ => panic!("Unexcepted index {}", idx)
|
||||
}
|
||||
},
|
||||
_ => panic!("Expected let statement but found {}", stmt),
|
||||
};
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_RETURN_STATEMENTS_STR: &str = "
|
||||
return;
|
||||
return 5;
|
||||
return 10 + 4 * 5;
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_return_statements() {
|
||||
let statements = test_case_statements(TEST_RETURN_STATEMENTS_STR);
|
||||
assert_eq!(statements.len(), 3);
|
||||
|
||||
let mut idx = 0;
|
||||
for stmt in statements.iter() {
|
||||
let _ret_stmt = match stmt {
|
||||
Statement::Return(expr) => {
|
||||
match idx {
|
||||
0 => {
|
||||
assert_eq!(expr.is_none(), true);
|
||||
assert_eq!(stmt.to_string(), "return;")
|
||||
},
|
||||
1 => assert_eq!(stmt.to_string(), "return 5;"),
|
||||
2 => assert_eq!(stmt.to_string(), "return (10 + (4 * 5));"),
|
||||
_ => panic!("Unexcepted index {}", idx)
|
||||
}
|
||||
},
|
||||
_ => panic!("{}: Expected return statement but found {}", idx, stmt),
|
||||
};
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const TEST_PREFIX_STR: &str = "
|
||||
!y;
|
||||
-1;
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_prefix_expressions() {
|
||||
let statements = test_case_statements(TEST_PREFIX_STR);
|
||||
assert_eq!(statements.len(), 2);
|
||||
|
||||
let mut idx = 0;
|
||||
for stmt in statements.iter() {
|
||||
let _prefix_expr= match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::Prefix(prefix, expr2) => match idx {
|
||||
0 => {
|
||||
assert_eq!(*prefix, Prefix::Bang);
|
||||
assert_eq!(expr2.to_string(), "y");
|
||||
},
|
||||
1 => {
|
||||
assert_eq!(*prefix, Prefix::Minus);
|
||||
assert_eq!(expr2.to_string(), "1");
|
||||
},
|
||||
_ => panic!("Unexpected expression index {}", idx)
|
||||
},
|
||||
_ => panic!("Expected prefix expression"),
|
||||
}
|
||||
},
|
||||
_ => panic!("Expected statement with prefix expression but found {}", stmt),
|
||||
};
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_GROUPED_EXPRESSION_STR: &str = "
|
||||
let x = (x + y);
|
||||
let x = (x + y) + (l + k);
|
||||
let x = ((x * 2) + (3 * (2 + 3) + 2));
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_grouped_expressions() {
|
||||
let statements = test_case_statements(TEST_GROUPED_EXPRESSION_STR);
|
||||
assert_eq!(statements.len(), 3);
|
||||
|
||||
let mut idx = 0;
|
||||
for stmt in statements.iter() {
|
||||
match stmt {
|
||||
Statement::Let(_s, _expr) => {
|
||||
match idx {
|
||||
0 => assert_eq!(format!("{}", stmt), "let x = (x + y);"),
|
||||
1 => assert_eq!(format!("{}", stmt), "let x = ((x + y) + (l + k));"),
|
||||
2 => assert_eq!(format!("{}", stmt), "let x = ((x * 2) + ((3 * (2 + 3)) + 2));"),
|
||||
_ => panic!("Unexcepted index {}", idx)
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected let statement found {}", stmt)
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_IF_NO_ELSE_STR: &str = "
|
||||
if (x > y) {
|
||||
let z = x + y;
|
||||
};
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_if_no_else() {
|
||||
let statements = test_case_statements(TEST_IF_NO_ELSE_STR);
|
||||
|
||||
assert_eq!(statements.len(), 1);
|
||||
let stmt = &statements[0];
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::If(cond, true_block, false_block) => {
|
||||
assert_eq!(cond.to_string(), "(x > y)");
|
||||
assert_eq!(true_block.to_string(), "{let z = (x + y);}");
|
||||
assert_eq!(false_block.is_none(), true);
|
||||
}
|
||||
_ => panic!("Expected if expression")
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected expression found")
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_IF_ELSE_STR: &str = "
|
||||
if (x > y) {
|
||||
x*2 + 3;
|
||||
let x = y;
|
||||
} else {
|
||||
4 + 5*y;
|
||||
x + y;
|
||||
}
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_if_else() {
|
||||
let statements = test_case_statements(TEST_IF_ELSE_STR);
|
||||
assert_eq!(statements.len(), 1);
|
||||
|
||||
let stmt = &statements[0];
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::If(cond, true_block, false_block) => {
|
||||
assert_eq!(cond.to_string(), "(x > y)");
|
||||
assert_eq!(true_block.to_string(), "{((x * 2) + 3);let x = y;}");
|
||||
assert_eq!(false_block.as_ref().unwrap().to_string(), "{(4 + (5 * y));(x + y);}");
|
||||
}
|
||||
_ => panic!("Expected if expression")
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected expression found")
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_FUNCTION_STR1: &str = "
|
||||
fn(x,y,z) {
|
||||
let z = x + y;
|
||||
z
|
||||
};
|
||||
let fact = fn(x){
|
||||
if (x > 1) {
|
||||
return x;
|
||||
} else {
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_function() {
|
||||
let statements = test_case_statements(TEST_FUNCTION_STR1);
|
||||
assert_eq!(statements.len(), 2);
|
||||
let stmt = &statements[0];
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::FunctionLiteral(params, block) => {
|
||||
assert_eq!(params.iter().as_ref().join(","), "x,y,z");
|
||||
assert_eq!(block.stmts[0].to_string(), "let z = (x + y);");
|
||||
assert_eq!(block.stmts[1].to_string(), "z;");
|
||||
}
|
||||
_ => panic!("Expected function literal")
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected expression found")
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_FUNCTION_STR2: &str = "
|
||||
fn() {
|
||||
10*20;
|
||||
}
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_function_no_parameters() {
|
||||
let statements = test_case_statements(TEST_FUNCTION_STR2);
|
||||
assert_eq!(statements.len(), 1);
|
||||
let stmt = &statements[0];
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::FunctionLiteral(params, block) => {
|
||||
assert_eq!(params.len(), 0);
|
||||
assert_eq!(block.stmts[0].to_string(), "(10 * 20);");
|
||||
}
|
||||
_ => panic!("Expected function literal")
|
||||
}
|
||||
}
|
||||
_ => panic!("Unexpected expression found")
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_FUNCTION_STR3: &str = "
|
||||
fn(x, y) {
|
||||
x*y;
|
||||
}(10, 20);
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_function_expression() {
|
||||
let statements = test_case_statements(TEST_FUNCTION_STR3);
|
||||
assert_eq!(statements.len(), 1);
|
||||
let stmt = &statements[0];
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
println!("{}", expr);
|
||||
}
|
||||
_ => panic!("Unexpected expression found")
|
||||
}
|
||||
}
|
||||
|
||||
const TEST_FUNCTION_CALL_STR: &str = "
|
||||
sum();
|
||||
sum3(x, y, z);
|
||||
sum_expr(x, y + w, z);
|
||||
fn(x, y){x + y;}(2, 3);
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_call_with_parameters() {
|
||||
let statements = test_case_statements(TEST_FUNCTION_CALL_STR);
|
||||
assert_eq!(statements.len(), 4);
|
||||
|
||||
for idx in 0..statements.len() {
|
||||
match &statements[idx] {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::Call(func_expr, params) => {
|
||||
match idx {
|
||||
0 => {
|
||||
assert_eq!(func_expr.to_string(), "sum");
|
||||
assert_eq!(params.len(), 0);
|
||||
},
|
||||
1 => {
|
||||
assert_eq!(func_expr.to_string(), "sum3");
|
||||
assert_eq!(params.iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","), "x,y,z");
|
||||
},
|
||||
2 => {
|
||||
assert_eq!(func_expr.to_string(), "sum_expr");
|
||||
assert_eq!(params.iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","),
|
||||
"x,(y + w),z");
|
||||
},
|
||||
3 => {
|
||||
assert_eq!(func_expr.to_string(), "fn(x,y){(x + y);}");
|
||||
assert_eq!(params.iter().map(|p| p.to_string()).collect::<Vec<String>>().join(","),
|
||||
"2,3");
|
||||
}
|
||||
_ => panic!("Unexpected idx {}", idx),
|
||||
}
|
||||
|
||||
|
||||
},
|
||||
_ => panic!("Expected call expression")
|
||||
}
|
||||
},
|
||||
_ => panic!("Expected a expression statement"),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const TEST_STRINGS_STR: &str = "
|
||||
\"abcd\";
|
||||
";
|
||||
|
||||
#[test]
|
||||
fn test_parser_string_statements() {
|
||||
let statements = test_case_statements(TEST_STRINGS_STR);
|
||||
|
||||
assert_eq!(statements.len(), 1);
|
||||
for stmt in statements.iter() {
|
||||
match stmt {
|
||||
Statement::Expression(expr) => {
|
||||
match &**expr {
|
||||
Expression::String(s) => println!("{}", s.to_string()),
|
||||
_ => panic!("Expected string literal in expression found {}", expr)
|
||||
}
|
||||
},
|
||||
_ => panic!("Expected a string expression"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user