diff --git a/src/evaluator/builtins.rs b/src/evaluator/builtins.rs new file mode 100644 index 0000000..888176b --- /dev/null +++ b/src/evaluator/builtins.rs @@ -0,0 +1,28 @@ +use std::{cell::LazyCell, collections::HashMap}; + +use super::{BuiltinFunction, Object}; + +pub const BUILTINS: LazyCell> = LazyCell::new(|| { + let mut map = HashMap::new(); + + map.insert( + "len", + Object::Builtin(BuiltinFunction { + func: Box::new(|args: Vec| { + if args.len() != 1 { + return Object::Error(format!( + "wrong number of arguments. got={}, want=1", + args.len() + )); + } + + match &args[0] { + Object::String(s) => Object::Integer(s.len() as i64), + v => Object::Error(format!("argument to `len` not supported, got {}", v)), + } + }), + }), + ); + + map +}); diff --git a/src/evaluator/mod.rs b/src/evaluator/mod.rs index 28fe286..9d7e055 100644 --- a/src/evaluator/mod.rs +++ b/src/evaluator/mod.rs @@ -4,10 +4,11 @@ use { std::{ cell::RefCell, collections::HashMap, - fmt::{Display, Formatter, Result as FmtResult, Write}, + fmt::{self, Display, Formatter, Result as FmtResult, Write}, rc::Rc, }, }; +pub mod builtins; pub mod tree_walker; pub trait Evaluator { @@ -59,13 +60,10 @@ pub enum Object { ReturnValue(Box), Error(String), Function(Function), + Builtin(BuiltinFunction), Null, } -const NULL: Object = Object::Null; -const TRUE: Object = Object::Boolean(true); -const FALSE: Object = Object::Boolean(false); - impl Object { pub fn inspect(&self) -> String { match self { @@ -75,6 +73,7 @@ impl Object { Object::ReturnValue(ret) => ret.inspect(), Object::Error(s) => s.to_string(), Object::Null => "NULL".into(), + Object::Builtin(_) => "builtin function".to_string(), Object::Function(s) => { let mut out = String::new(); @@ -101,6 +100,7 @@ impl Display for Object { Object::Error(_) => "ERROR", Object::Function(_) => "FUNCTION", Object::Null => "NULL", + Object::Builtin(_) => "BUILTIN", }) } } @@ -112,15 +112,39 @@ pub struct Function { env: Rc>, } +type Builtin = fn(Vec) -> Object; + +#[derive(Clone)] +pub struct BuiltinFunction { + func: Box, +} + +impl fmt::Debug for BuiltinFunction { + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + f.debug_struct("BuiltinFunction") + .field("func", &"builtin") + .finish() + } +} + +impl PartialEq for BuiltinFunction { + fn eq(&self, _: &Self) -> bool { + false + } +} + #[cfg(test)] mod tests { use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc}; use crate::{ - evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object, FALSE, NULL, TRUE}, + evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object}, lexer::Lexer, parser::{ast::Node, Parser}, }; + const TRUE: Object = Object::Boolean(true); + const FALSE: Object = Object::Boolean(false); + const NULL: Object = Object::Null; fn run_test_cases(test_cases: &[(&str, Option)]) { for test in test_cases.iter() { @@ -411,4 +435,29 @@ mod tests { run_test_cases(&test_cases); } + + #[test] + fn builtin_function() { + // This test case allocates memory for `foobar=9999` over and over + // even though it is never used. + let test_cases = [ + ("len(\"\")", Some(Object::Integer(0))), + ("len(\"four\")", Some(Object::Integer(4))), + ("len(\"hello world\")", Some(Object::Integer(11))), + ( + "len(1)", + Some(Object::Error( + "argument to `len` not supported, got INTEGER".to_string(), + )), + ), + ( + "len(\"one\", \"two\")", + Some(Object::Error( + "wrong number of arguments. got=2, want=1".to_string(), + )), + ), + ]; + + run_test_cases(&test_cases); + } } diff --git a/src/evaluator/tree_walker.rs b/src/evaluator/tree_walker.rs index 377c7b5..2b97f0f 100644 --- a/src/evaluator/tree_walker.rs +++ b/src/evaluator/tree_walker.rs @@ -4,7 +4,7 @@ use std::{cell::RefCell, rc::Rc}; // It's just constantly unwrapping enums from one place and rewrapping it to some other enum(or even the same enum) and returning it // The error handling story is pretty bad too use crate::{ - evaluator::{Evaluator, Object, FALSE, NULL, TRUE}, + evaluator::{Evaluator, Object}, lexer::TokenType, parser::ast::{ BlockStatement, Expression, ExpressionStatement, Identifier, LetStatement, Node, Program, @@ -12,7 +12,7 @@ use crate::{ }, }; -use super::{Environment, Function}; +use super::{builtins::BUILTINS, Environment, Function}; pub struct TreeWalker; @@ -84,7 +84,7 @@ impl Evaluator for TreeWalker { } else if let Some(alternative) = ie.alternative { self.eval(Node::Statement(Statement::BlockStatement(alternative)), env) } else { - Some(NULL) + Some(Object::Null) } } }, @@ -195,10 +195,10 @@ impl TreeWalker { fn eval_bang_operator_expression(&self, expr: Object) -> Object { match expr { - TRUE => FALSE, - FALSE => TRUE, - NULL => TRUE, - _ => FALSE, + Object::Boolean(true) => Object::Boolean(false), + Object::Boolean(false) => Object::Boolean(true), + Object::Null => Object::Boolean(true), + _ => Object::Boolean(false), } } @@ -211,19 +211,25 @@ impl TreeWalker { fn is_truthy(&self, obj: &Object) -> bool { match *obj { - NULL => false, - TRUE => true, - FALSE => false, + Object::Null => false, + Object::Boolean(true) => true, + Object::Boolean(false) => false, _ => true, } } fn eval_identifier(&self, node: Identifier, env: Rc>) -> Option { let env = env.borrow(); - env.get(&node.to_string()).or(Some(Object::Error(format!( - "identifier not found: {}", - node.to_string() - )))) + + if let Some(v) = env.get(&node.to_string()) { + return Some(v.clone()); + } + + if let Some(v) = BUILTINS.get(&node.to_string().as_str()) { + return Some(v.clone()); + } + + Some(Object::Error(format!("identifier not found: {}", node))) } fn eval_expression( @@ -247,18 +253,22 @@ impl TreeWalker { } fn apply_function(&self, function: Object, args: Vec) -> Option { + if let Object::Builtin(ref func) = function { + return Some((func.func)(args)); + } + let function = match function { Object::Function(f) => f, Object::Error(e) => { - return Some(Object::Error(format!("not a function: {}", e.to_string()))); + return Some(Object::Error(format!("not a function: {}", e))); } - v => return Some(Object::Error(format!("not a function: {}", v.to_string()))), + v => return Some(Object::Error(format!("not a function: {}", v))), }; let mut enclosed_env = Environment::new_enclosed(function.env); for (i, parameter) in function.parameters.iter().enumerate() { if args.len() <= i { - return Some(Object::Error(format!("incorrect number of arguments"))); + return Some(Object::Error("incorrect number of arguments".to_owned())); } enclosed_env.set(parameter.value.clone(), args[i].clone()); diff --git a/src/main.rs b/src/main.rs index 2621f72..f51a8b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#![feature(lazy_cell)] #![feature(assert_matches)] #[macro_use] extern crate lazy_static;