added builtins

This commit is contained in:
Ishan Jain 2024-05-26 16:01:31 +05:30
parent 6607d1db02
commit ad2d88e511
Signed by: ishan
GPG Key ID: 0506DB2A1CC75C27
4 changed files with 111 additions and 23 deletions

28
src/evaluator/builtins.rs Normal file
View File

@ -0,0 +1,28 @@
use std::{cell::LazyCell, collections::HashMap};
use super::{BuiltinFunction, Object};
pub const BUILTINS: LazyCell<HashMap<&'static str, Object>> = LazyCell::new(|| {
let mut map = HashMap::new();
map.insert(
"len",
Object::Builtin(BuiltinFunction {
func: Box::new(|args: Vec<Object>| {
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
});

View File

@ -4,10 +4,11 @@ use {
std::{ std::{
cell::RefCell, cell::RefCell,
collections::HashMap, collections::HashMap,
fmt::{Display, Formatter, Result as FmtResult, Write}, fmt::{self, Display, Formatter, Result as FmtResult, Write},
rc::Rc, rc::Rc,
}, },
}; };
pub mod builtins;
pub mod tree_walker; pub mod tree_walker;
pub trait Evaluator { pub trait Evaluator {
@ -59,13 +60,10 @@ pub enum Object {
ReturnValue(Box<Object>), ReturnValue(Box<Object>),
Error(String), Error(String),
Function(Function), Function(Function),
Builtin(BuiltinFunction),
Null, Null,
} }
const NULL: Object = Object::Null;
const TRUE: Object = Object::Boolean(true);
const FALSE: Object = Object::Boolean(false);
impl Object { impl Object {
pub fn inspect(&self) -> String { pub fn inspect(&self) -> String {
match self { match self {
@ -75,6 +73,7 @@ impl Object {
Object::ReturnValue(ret) => ret.inspect(), Object::ReturnValue(ret) => ret.inspect(),
Object::Error(s) => s.to_string(), Object::Error(s) => s.to_string(),
Object::Null => "NULL".into(), Object::Null => "NULL".into(),
Object::Builtin(_) => "builtin function".to_string(),
Object::Function(s) => { Object::Function(s) => {
let mut out = String::new(); let mut out = String::new();
@ -101,6 +100,7 @@ impl Display for Object {
Object::Error(_) => "ERROR", Object::Error(_) => "ERROR",
Object::Function(_) => "FUNCTION", Object::Function(_) => "FUNCTION",
Object::Null => "NULL", Object::Null => "NULL",
Object::Builtin(_) => "BUILTIN",
}) })
} }
} }
@ -112,15 +112,39 @@ pub struct Function {
env: Rc<RefCell<Environment>>, env: Rc<RefCell<Environment>>,
} }
type Builtin = fn(Vec<Object>) -> Object;
#[derive(Clone)]
pub struct BuiltinFunction {
func: Box<Builtin>,
}
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)] #[cfg(test)]
mod tests { mod tests {
use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc}; use std::{assert_matches::assert_matches, cell::RefCell, rc::Rc};
use crate::{ use crate::{
evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object, FALSE, NULL, TRUE}, evaluator::{tree_walker::TreeWalker, Environment, Evaluator, Object},
lexer::Lexer, lexer::Lexer,
parser::{ast::Node, Parser}, 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<Object>)]) { fn run_test_cases(test_cases: &[(&str, Option<Object>)]) {
for test in test_cases.iter() { for test in test_cases.iter() {
@ -411,4 +435,29 @@ mod tests {
run_test_cases(&test_cases); 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);
}
} }

View File

@ -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 // 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 // The error handling story is pretty bad too
use crate::{ use crate::{
evaluator::{Evaluator, Object, FALSE, NULL, TRUE}, evaluator::{Evaluator, Object},
lexer::TokenType, lexer::TokenType,
parser::ast::{ parser::ast::{
BlockStatement, Expression, ExpressionStatement, Identifier, LetStatement, Node, Program, 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; pub struct TreeWalker;
@ -84,7 +84,7 @@ impl Evaluator for TreeWalker {
} else if let Some(alternative) = ie.alternative { } else if let Some(alternative) = ie.alternative {
self.eval(Node::Statement(Statement::BlockStatement(alternative)), env) self.eval(Node::Statement(Statement::BlockStatement(alternative)), env)
} else { } else {
Some(NULL) Some(Object::Null)
} }
} }
}, },
@ -195,10 +195,10 @@ impl TreeWalker {
fn eval_bang_operator_expression(&self, expr: Object) -> Object { fn eval_bang_operator_expression(&self, expr: Object) -> Object {
match expr { match expr {
TRUE => FALSE, Object::Boolean(true) => Object::Boolean(false),
FALSE => TRUE, Object::Boolean(false) => Object::Boolean(true),
NULL => TRUE, Object::Null => Object::Boolean(true),
_ => FALSE, _ => Object::Boolean(false),
} }
} }
@ -211,19 +211,25 @@ impl TreeWalker {
fn is_truthy(&self, obj: &Object) -> bool { fn is_truthy(&self, obj: &Object) -> bool {
match *obj { match *obj {
NULL => false, Object::Null => false,
TRUE => true, Object::Boolean(true) => true,
FALSE => false, Object::Boolean(false) => false,
_ => true, _ => true,
} }
} }
fn eval_identifier(&self, node: Identifier, env: Rc<RefCell<Environment>>) -> Option<Object> { fn eval_identifier(&self, node: Identifier, env: Rc<RefCell<Environment>>) -> Option<Object> {
let env = env.borrow(); let env = env.borrow();
env.get(&node.to_string()).or(Some(Object::Error(format!(
"identifier not found: {}", if let Some(v) = env.get(&node.to_string()) {
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( fn eval_expression(
@ -247,18 +253,22 @@ impl TreeWalker {
} }
fn apply_function(&self, function: Object, args: Vec<Object>) -> Option<Object> { fn apply_function(&self, function: Object, args: Vec<Object>) -> Option<Object> {
if let Object::Builtin(ref func) = function {
return Some((func.func)(args));
}
let function = match function { let function = match function {
Object::Function(f) => f, Object::Function(f) => f,
Object::Error(e) => { 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); let mut enclosed_env = Environment::new_enclosed(function.env);
for (i, parameter) in function.parameters.iter().enumerate() { for (i, parameter) in function.parameters.iter().enumerate() {
if args.len() <= i { 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()); enclosed_env.set(parameter.value.clone(), args[i].clone());

View File

@ -1,3 +1,4 @@
#![feature(lazy_cell)]
#![feature(assert_matches)] #![feature(assert_matches)]
#[macro_use] #[macro_use]
extern crate lazy_static; extern crate lazy_static;