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::{
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user