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::{
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<Object>),
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<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)]
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<Object>)]) {
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);
}
}

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
// 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<RefCell<Environment>>) -> Option<Object> {
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<Object>) -> Option<Object> {
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());

View File

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