added builtins
This commit is contained in:
parent
6607d1db02
commit
ad2d88e511
28
src/evaluator/builtins.rs
Normal file
28
src/evaluator/builtins.rs
Normal 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
|
||||
});
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![feature(lazy_cell)]
|
||||
#![feature(assert_matches)]
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
|
Loading…
Reference in New Issue
Block a user