Extend lisp language (#278)

* Add map builtin

* Rename ensure_length macro

* Update documentation

* Update map function

* Rename map to mapcar

* Add modulo to calculator

* Add division, modulo, and exponential to lisp

* Add type to built in functions

* Add string comparisons

* Update atom function

* Add basic load function

* Update load to parse multiple expressions

* Escape quote in string display

* Move print definition

* Rename read-file to read

* Simplify mapcar implementation

* Update tests

* Add comment and test

* Add standard library

* Add lib to install

* Refactor eval

* Refactor eq

* Refactor eval with ensure macros

* Add more tests

* Redefine primitives

* Move primitives to /ini/lisp/core.lsp

* Update install file

* Refactor eval

* Create /ini/lisp
This commit is contained in:
Vincent Ollivier 2021-12-12 11:43:57 +01:00 committed by GitHub
parent 985d24a785
commit 313f90ee73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 309 additions and 171 deletions

View File

@ -10,6 +10,10 @@ Computation by Machine" (1960) and "The Roots of Lisp" (2002) by Paul Graham.
MOROS Lisp dialect is also inspired by Scheme and Clojure.
In version 0.2.0 the whole implementation was refactored and the parser was
rewritten to use [Nom](https://github.com/Geal/nom). This allowed the addition
of strings to the language and reading from the filesystem.
## Seven Primitive Operators
- `quote` (with the `'` syntax)
- `atom` (aliased to `atom?`)
@ -23,9 +27,13 @@ MOROS Lisp dialect is also inspired by Scheme and Clojure.
- `label` (aliased to `def`)
- `lambda` (aliased to `fn`)
## Additional primitives
## Additional Builtins
- `defun` (aliased to `defn`)
- `mapcar` (aliased to `map`)
- `print`
- `read-file`
- `lines`
- `parse`
## Usage
@ -41,9 +49,8 @@ MOROS Lisp v0.1.0
> (exit)
```
And it can execute a file.
For example a file located in `/tmp/fib.ls` with the following content:
And it can execute a file. For example a file located in `/tmp/fibonacci.lsp`
with the following content:
```lisp
(label fib
@ -58,6 +65,6 @@ For example a file located in `/tmp/fib.ls` with the following content:
Would produce the following output:
```
> lisp /tmp/fib.ls
> lisp /tmp/fibonacci.lsp
8
```

43
dsk/ini/lisp/core.lsp Normal file
View File

@ -0,0 +1,43 @@
(defn eq? (a b)
(eq a b))
(defn atom? (x)
(atom x))
(defn string? (x)
(eq? (type x) "string"))
(defn boolean? (x)
(eq? (type x) "boolean"))
(defn symbol? (x)
(eq? (type x) "symbol"))
(defn number? (x)
(eq? (type x) "number"))
(defn list? (x)
(eq? (type x) "list"))
(defn function? (x)
(eq? (type x) "function"))
(defn lambda? (x)
(eq? (type x) "lambda"))
(def null '())
(defn null? (x)
(eq? x null))
(defn first (lst)
(car lst))
(defn rest (lst)
(cdr lst))
(defn second (lst)
(first (rest lst)))
(defn third (lst)
(second (rest lst)))

View File

@ -27,6 +27,7 @@ pub enum Exp {
Mul(Box<Exp>, Box<Exp>),
Div(Box<Exp>, Box<Exp>),
Exp(Box<Exp>, Box<Exp>),
Mod(Box<Exp>, Box<Exp>),
}
// Parser
@ -39,7 +40,7 @@ fn parse(input: &str) -> IResult<&str, Exp> {
fn parse_term(input: &str) -> IResult<&str, Exp> {
let (input, num1) = parse_factor(input)?;
let (input, exps) = many0(tuple((alt((char('/'), char('*'))), parse_factor)))(input)?;
let (input, exps) = many0(tuple((alt((char('%'), char('/'), char('*'))), parse_factor)))(input)?;
Ok((input, parse_exp(num1, exps)))
}
@ -69,6 +70,7 @@ fn parse_op(tup: (char, Exp), exp1: Exp) -> Exp {
'*' => Exp::Mul(Box::new(exp1), Box::new(exp2)),
'/' => Exp::Div(Box::new(exp1), Box::new(exp2)),
'^' => Exp::Exp(Box::new(exp1), Box::new(exp2)),
'%' => Exp::Mod(Box::new(exp1), Box::new(exp2)),
_ => panic!("Unknown operation"),
}
}
@ -83,6 +85,7 @@ fn eval(exp: Exp) -> f64 {
Exp::Mul(exp1, exp2) => eval(*exp1) * eval(*exp2),
Exp::Div(exp1, exp2) => eval(*exp1) / eval(*exp2),
Exp::Exp(exp1, exp2) => libm::pow(eval(*exp1), eval(*exp2)),
Exp::Mod(exp1, exp2) => libm::fmod(eval(*exp1), eval(*exp2)),
}
}
@ -164,21 +167,28 @@ fn test_calc() {
};
}
assert_eq!(eval!("1"), "1");
assert_eq!(eval!("1.5"), "1.5");
assert_eq!(eval!("1"), "1");
assert_eq!(eval!("1.5"), "1.5");
assert_eq!(eval!("1 + 2"), "3");
assert_eq!(eval!("1 + 2 + 3"), "6");
assert_eq!(eval!("1 + 2.5"), "3.5");
assert_eq!(eval!("1 + 2.5"), "3.5");
assert_eq!(eval!("2 - 1"), "1");
assert_eq!(eval!("1 - 2"), "-1");
assert_eq!(eval!("2 * 3"), "6");
assert_eq!(eval!("2 * 3.5"), "7");
assert_eq!(eval!("6 / 2"), "3");
assert_eq!(eval!("6 / 4"), "1.5");
assert_eq!(eval!("2 ^ 4"), "16");
assert_eq!(eval!("+1"), "1");
assert_eq!(eval!("-1"), "-1");
assert_eq!(eval!("2 * 3 + 4"), "10");
assert_eq!(eval!("2 * (3 + 4)"), "14");
assert_eq!(eval!("1 + 2"), "3");
assert_eq!(eval!("1 + 2 + 3"), "6");
assert_eq!(eval!("1 + 2.5"), "3.5");
assert_eq!(eval!("1 + 2.5"), "3.5");
assert_eq!(eval!("2 - 1"), "1");
assert_eq!(eval!("1 - 2"), "-1");
assert_eq!(eval!("2 * 3"), "6");
assert_eq!(eval!("2 * 3.5"), "7");
assert_eq!(eval!("6 / 2"), "3");
assert_eq!(eval!("6 / 4"), "1.5");
assert_eq!(eval!("2 ^ 4"), "16");
assert_eq!(eval!("3 % 2"), "1");
assert_eq!(eval!("2 * 3 + 4"), "10");
assert_eq!(eval!("2 * (3 + 4)"), "14");
assert_eq!(eval!("2 ^ 4 + 1"), "17");
assert_eq!(eval!("1 + 2 ^ 4"), "17");
assert_eq!(eval!("1 + 3 * 2 ^ 4 * 2 + 3"), "100");
}

View File

@ -38,6 +38,9 @@ pub fn copy_files(verbose: bool) {
copy_file("/ini/version.txt", include_bytes!("../../dsk/ini/version.txt"), verbose);
copy_file("/ini/palette.csv", include_bytes!("../../dsk/ini/palette.csv"), verbose);
create_dir("/ini/lisp", verbose);
copy_file("/ini/lisp/core.lsp", include_bytes!("../../dsk/ini/lisp/core.lsp"), verbose);
create_dir("/ini/fonts", verbose);
copy_file("/ini/fonts/lat15-terminus-8x16.psf", include_bytes!("../../dsk/ini/fonts/lat15-terminus-8x16.psf"), verbose);
copy_file("/ini/fonts/zap-light-8x16.psf", include_bytes!("../../dsk/ini/fonts/zap-light-8x16.psf"), verbose);

View File

@ -36,41 +36,55 @@ use nom::sequence::delimited;
// See "Recursive Functions of Symic Expressions and Their Computation by Machine" by John McCarthy (1960)
// And "The Roots of Lisp" by Paul Graham (2002)
//
// MOROS Lisp is also inspired by Racket and Clojure
// MOROS Lisp is a lisp-1 like Scheme and Clojure
// See "Technical Issues of Separation in Function Cells and Value Cells" by Richard P. Gabriel (1982)
// Types
#[derive(Clone)]
enum Exp {
Bool(bool),
Func(fn(&[Exp]) -> Result<Exp, Err>),
Lambda(Lambda),
Func(fn(&[Exp]) -> Result<Exp, Err>),
List(Vec<Exp>),
Bool(bool),
Num(f64),
Str(String),
Sym(String),
}
impl PartialEq for Exp {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Exp::Lambda(a), Exp::Lambda(b)) => a == b,
(Exp::List(a), Exp::List(b)) => a == b,
(Exp::Bool(a), Exp::Bool(b)) => a == b,
(Exp::Num(a), Exp::Num(b)) => a == b,
(Exp::Str(a), Exp::Str(b)) => a == b,
(Exp::Sym(a), Exp::Sym(b)) => a == b,
_ => false,
}
}
}
impl fmt::Display for Exp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let str = match self {
Exp::Str(s) => format!("\"{}\"", s),
Exp::Bool(a) => a.to_string(),
Exp::Sym(s) => s.clone(),
Exp::Num(n) => n.to_string(),
Exp::Lambda(_) => "Lambda {}".to_string(),
Exp::Func(_) => "Function {}".to_string(),
Exp::Bool(a) => a.to_string(),
Exp::Num(n) => n.to_string(),
Exp::Sym(s) => s.clone(),
Exp::Str(s) => format!("\"{}\"", s.replace('"', "\\\"")),
Exp::List(list) => {
let xs: Vec<String> = list.iter().map(|x| x.to_string()).collect();
format!("({})", xs.join(" "))
},
Exp::Func(_) => "Function {}".to_string(),
Exp::Lambda(_) => "Lambda {}".to_string(),
};
write!(f, "{}", str)
}
}
#[derive(Clone)]
#[derive(Clone, PartialEq)]
struct Lambda {
params: Rc<Exp>,
body: Rc<Exp>,
@ -90,7 +104,7 @@ struct Env<'a> {
// Parser
fn is_symbol_letter(c: char) -> bool {
let chars = "<>=-+*?:/";
let chars = "<>=-+*/%^?:";
c.is_alphanumeric() || chars.contains(c)
}
@ -148,7 +162,8 @@ macro_rules! ensure_tonicity {
($check_fn:expr) => {
|args: &[Exp]| -> Result<Exp, Err> {
let floats = list_of_floats(args)?;
let first = floats.first().ok_or(Err::Reason("Expected at least one number".to_string()))?;
ensure_length_gt!(floats, 0);
let first = &floats[0];
let rest = &floats[1..];
fn func(prev: &f64, xs: &[f64]) -> bool {
match xs.first() {
@ -161,7 +176,7 @@ macro_rules! ensure_tonicity {
};
}
macro_rules! ensure_len {
macro_rules! ensure_length_eq {
($list:expr, $count:expr) => {
if $list.len() != $count {
let plural = if $count != 1 { "s" } else { "" };
@ -170,70 +185,116 @@ macro_rules! ensure_len {
};
}
macro_rules! ensure_length_gt {
($list:expr, $count:expr) => {
if $list.len() <= $count {
let plural = if $count != 1 { "s" } else { "" };
return Err(Err::Reason(format!("Expected more than {} expression{}", $count, plural)))
}
};
}
fn default_env<'a>() -> Env<'a> {
let mut data: BTreeMap<String, Exp> = BTreeMap::new();
data.insert("=".to_string(), Exp::Func(ensure_tonicity!(|a, b| approx_eq!(f64, a, b))));
data.insert(">".to_string(), Exp::Func(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a > b)));
data.insert(">=".to_string(), Exp::Func(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a > b)));
data.insert("<".to_string(), Exp::Func(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a < b)));
data.insert("<=".to_string(), Exp::Func(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a < b)));
data.insert("*".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let res = list_of_floats(args)?.iter().fold(1.0, |res, a| res * a);
let res = list_of_floats(args)?.iter().fold(1.0, |acc, a| acc * a);
Ok(Exp::Num(res))
}));
data.insert("+".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let res = list_of_floats(args)?.iter().fold(0.0, |res, a| res + a);
let res = list_of_floats(args)?.iter().fold(0.0, |acc, a| acc + a);
Ok(Exp::Num(res))
}));
data.insert("-".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let floats = list_of_floats(args)?;
let first = *floats.first().ok_or(Err::Reason("Expected at least one number".to_string()))?;
let sum_of_rest = floats[1..].iter().fold(0.0, |sum, a| sum + a);
Ok(Exp::Num(first - sum_of_rest))
let args = list_of_floats(args)?;
ensure_length_gt!(args, 0);
let car = args[0];
let cdr = args[1..].iter().fold(0.0, |acc, a| acc + a);
Ok(Exp::Num(car - cdr))
}));
data.insert("read-file".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let arg = first(args)?;
let path = string(&arg)?;
data.insert("/".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let args = list_of_floats(args)?;
ensure_length_gt!(args, 0);
let car = args[0];
let res = args[1..].iter().fold(car, |acc, a| acc / a);
Ok(Exp::Num(res))
}));
data.insert("%".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let args = list_of_floats(args)?;
ensure_length_gt!(args, 0);
let car = args[0];
let res = args[1..].iter().fold(car, |acc, a| libm::fmod(acc, *a));
Ok(Exp::Num(res))
}));
data.insert("^".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let args = list_of_floats(args)?;
ensure_length_gt!(args, 0);
let car = args[0];
let res = args[1..].iter().fold(car, |acc, a| libm::pow(acc, *a));
Ok(Exp::Num(res))
}));
data.insert("print".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match args[0].clone() {
Exp::Str(s) => {
println!("{}", s);
Ok(Exp::Str(s))
}
exp => {
println!("{}", exp);
Ok(exp)
}
}
}));
data.insert("read".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let contents = fs::read_to_string(&path).or(Err(Err::Reason("Could not read file".to_string())))?;
Ok(Exp::Str(contents))
}));
data.insert("lines".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let arg = first(args)?;
let s = string(&arg)?;
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let lines = s.lines().map(|line| Exp::Str(line.to_string())).collect();
Ok(Exp::List(lines))
}));
data.insert("parse".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
let arg = first(args)?;
let s = string(&arg)?;
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let n = s.parse().or(Err(Err::Reason("Could not parse number".to_string())))?;
Ok(Exp::Num(n))
}));
data.insert("type".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let exp = match args[0] {
Exp::Str(_) => "string",
Exp::Bool(_) => "boolean",
Exp::Sym(_) => "symbol",
Exp::Num(_) => "number",
Exp::List(_) => "list",
Exp::Func(_) => "function",
Exp::Lambda(_) => "lambda",
};
Ok(Exp::Str(exp.to_string()))
}));
Env { data, outer: None }
}
fn first(exps: &[Exp]) -> Result<Exp, Err> {
match exps.get(0) {
Some(exp) => Ok(exp.clone()),
None => Err(Err::Reason("Expected first expression".to_string()))
}
}
fn second(exps: &[Exp]) -> Result<Exp, Err> {
match exps.get(1) {
Some(exp) => Ok(exp.clone()),
None => Err(Err::Reason("Expected second expression".to_string()))
}
}
fn third(exps: &[Exp]) -> Result<Exp, Err> {
match exps.get(2) {
Some(exp) => Ok(exp.clone()),
None => Err(Err::Reason("Expected third expression".to_string()))
fn list_of_symbols(form: &Exp) -> Result<Vec<String>, Err> {
match form {
Exp::List(list) => {
list.iter().map(|exp| {
match exp {
Exp::Sym(sym) => Ok(sym.clone()),
_ => Err(Err::Reason("Expected symbols in the argument list".to_string()))
}
}).collect()
}
_ => Err(Err::Reason("Expected args form to be a list".to_string()))
}
}
@ -258,40 +319,30 @@ fn string(exp: &Exp) -> Result<String, Err> {
// Eval
fn eval_quote_args(args: &[Exp]) -> Result<Exp, Err> {
first(args)
ensure_length_eq!(args, 1);
Ok(args[0].clone())
}
fn eval_atom_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
match eval(&first(args)?, env)? {
Exp::Sym(_) => Ok(Exp::Bool(true)),
_ => Ok(Exp::Bool(false)),
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(_) => Ok(Exp::Bool(false)),
_ => Ok(Exp::Bool(true)),
}
}
fn eval_eq_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
match eval(&first(args)?, env)? {
Exp::Sym(a) => {
match eval(&second(args)?, env)? {
Exp::Sym(b) => Ok(Exp::Bool(a == b)),
_ => Ok(Exp::Bool(false)),
}
},
Exp::List(a) => {
match eval(&second(args)?, env)? {
Exp::List(b) => Ok(Exp::Bool(a.is_empty() && b.is_empty())),
_ => Ok(Exp::Bool(false))
}
},
_ => Ok(Exp::Bool(false))
}
ensure_length_eq!(args, 2);
let a = eval(&args[0], env)?;
let b = eval(&args[1], env)?;
Ok(Exp::Bool(a == b))
}
fn eval_car_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
match eval(&first(args)?, env)? {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(list) => {
if list.is_empty() {
return Err(Err::Reason("List cannot be empty".to_string())) // TODO: return nil?
}
ensure_length_gt!(list, 0);
Ok(list[0].clone())
},
_ => Err(Err::Reason("Expected list form".to_string())),
@ -299,11 +350,10 @@ fn eval_car_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
fn eval_cdr_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
match eval(&first(args)?, env)? {
ensure_length_eq!(args, 1);
match eval(&args[0], env)? {
Exp::List(list) => {
if list.is_empty() {
return Err(Err::Reason("List cannot be empty".to_string())) // TODO: return nil?
}
ensure_length_gt!(list, 0);
Ok(Exp::List(list[1..].to_vec()))
},
_ => Err(Err::Reason("Expected list form".to_string())),
@ -311,9 +361,10 @@ fn eval_cdr_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
fn eval_cons_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
match eval(&second(args)?, env)? {
ensure_length_eq!(args, 2);
match eval(&args[1], env)? {
Exp::List(mut list) => {
list.insert(0, eval(&first(args)?, env)?);
list.insert(0, eval(&args[0], env)?);
Ok(Exp::List(list.to_vec()))
},
_ => Err(Err::Reason("Expected list form".to_string())),
@ -321,15 +372,11 @@ fn eval_cons_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
fn eval_cond_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
if args.is_empty() {
return Err(Err::Reason("Expected at least one form".to_string()))
}
ensure_length_gt!(args, 0);
for arg in args {
match arg {
Exp::List(list) => {
if list.len() != 2 {
return Err(Err::Reason("Expected lists of predicate and expression".to_string()))
}
ensure_length_eq!(list, 2);
let pred = eval(&list[0], env)?;
let exp = eval(&list[1], env)?;
match pred {
@ -344,63 +391,72 @@ fn eval_cond_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
}
fn eval_label_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
ensure_len!(args, 2);
let id = match first(args)? {
Exp::Sym(s) => Ok(s.clone()),
ensure_length_eq!(args, 2);
match &args[0] {
Exp::Sym(key) => {
let exp = eval(&args[1], env)?;
env.data.insert(key.clone(), exp);
Ok(Exp::Sym(key.clone()))
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
}?;
let exp = eval(&second(args)?, env)?;
env.data.insert(id.clone(), exp);
Ok(Exp::Sym(id))
}
}
fn eval_lambda_args(args: &[Exp]) -> Result<Exp, Err> {
ensure_len!(args, 2);
let params = first(args)?;
let body = second(args)?;
ensure_length_eq!(args, 2);
Ok(Exp::Lambda(Lambda {
body: Rc::new(body.clone()),
params: Rc::new(params.clone()),
params: Rc::new(args[0].clone()),
body: Rc::new(args[1].clone()),
}))
}
fn eval_defun_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
ensure_len!(args, 3);
let name = first(args)?;
let params = second(args)?;
let exp = third(args)?;
ensure_length_eq!(args, 3);
let name = args[0].clone();
let params = args[1].clone();
let exp = args[2].clone();
let lambda_args = vec![Exp::Sym("lambda".to_string()), params, exp];
let label_args = vec![name, Exp::List(lambda_args)];
eval_label_args(&label_args, env)
}
fn eval_print_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
ensure_len!(args, 1);
match eval(&first(args)?, env) {
Ok(Exp::Str(s)) => {
println!("{}", s);
Ok(Exp::Str(s))
},
Ok(res) => {
println!("{}", res);
Ok(res)
},
Err(res) => {
Err(res)
fn eval_mapcar_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match eval(&args[1], env) {
Ok(Exp::List(list)) => {
Ok(Exp::List(list.iter().map(|exp| {
eval(&Exp::List(vec!(args[0].clone(), exp.clone())), env)
}).collect::<Result<Vec<Exp>, Err>>()?))
}
_ => Err(Err::Reason("Expected second argument to be a list".to_string())),
}
}
fn eval_load_args(args: &[Exp], env: &mut Env) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let path = string(&args[0])?;
let mut code = fs::read_to_string(&path).or(Err(Err::Reason("Could not read file".to_string())))?;
loop {
let (rest, exp) = parse(&code)?;
eval(&exp, env)?;
if rest.is_empty() {
break;
}
code = rest;
}
return Ok(Exp::Bool(true));
}
fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Env) -> Option<Result<Exp, Err>> {
match exp {
Exp::Sym(s) => {
match s.as_ref() {
// Seven Primitive Operators
"quote" => Some(eval_quote_args(args)),
"atom" | "atom?" => Some(eval_atom_args(args, env)),
"eq" | "eq?" => Some(eval_eq_args(args, env)),
"car" | "first" => Some(eval_car_args(args, env)),
"cdr" | "rest" => Some(eval_cdr_args(args, env)),
"atom" => Some(eval_atom_args(args, env)),
"eq" => Some(eval_eq_args(args, env)),
"car" => Some(eval_car_args(args, env)),
"cdr" => Some(eval_cdr_args(args, env)),
"cons" => Some(eval_cons_args(args, env)),
"cond" => Some(eval_cond_args(args, env)),
@ -409,7 +465,8 @@ fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Env) -> Option<Result<E
"lambda" | "fn" => Some(eval_lambda_args(args)),
"defun" | "defn" => Some(eval_defun_args(args, env)),
"print" => Some(eval_print_args(args, env)),
"mapcar" | "map" => Some(eval_mapcar_args(args, env)),
"load" => Some(eval_load_args(args, env)),
_ => None,
}
},
@ -417,60 +474,45 @@ fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Env) -> Option<Result<E
}
}
fn env_get(k: &str, env: &Env) -> Option<Exp> {
match env.data.get(k) {
Some(exp) => Some(exp.clone()),
fn env_get(key: &str, env: &Env) -> Result<Exp, Err> {
match env.data.get(key) {
Some(exp) => Ok(exp.clone()),
None => {
match &env.outer {
Some(outer_env) => env_get(k, outer_env),
None => None
Some(outer_env) => env_get(key, outer_env),
None => Err(Err::Reason(format!("Unexpected symbol '{}'", key))),
}
}
}
}
fn list_of_symbols(form: Rc<Exp>) -> Result<Vec<String>, Err> {
let list = match form.as_ref() {
Exp::List(s) => Ok(s.clone()),
_ => Err(Err::Reason("Expected args form to be a list".to_string()))
}?;
list.iter().map(|x| {
match x {
Exp::Sym(s) => Ok(s.clone()),
_ => Err(Err::Reason("Expected symbols in the argument list".to_string()))
}
}).collect()
}
fn env_for_lambda<'a>(params: Rc<Exp>, args: &[Exp], outer_env: &'a mut Env) -> Result<Env<'a>, Err> {
let ks = list_of_symbols(params)?;
fn env_for_lambda<'a>(params: Rc<Exp>, args: &[Exp], outer: &'a mut Env) -> Result<Env<'a>, Err> {
let ks = list_of_symbols(&params)?;
if ks.len() != args.len() {
let plural = if ks.len() == 1 { "" } else { "s" };
return Err(Err::Reason(format!("Expected {} argument{}, got {}", ks.len(), plural, args.len())));
}
let vs = eval_forms(args, outer_env)?;
let vs = eval_args(args, outer)?;
let mut data: BTreeMap<String, Exp> = BTreeMap::new();
for (k, v) in ks.iter().zip(vs.iter()) {
data.insert(k.clone(), v.clone());
}
Ok(Env {
data,
outer: Some(outer_env),
})
Ok(Env { data, outer: Some(outer) })
}
fn eval_forms(args: &[Exp], env: &mut Env) -> Result<Vec<Exp>, Err> {
fn eval_args(args: &[Exp], env: &mut Env) -> Result<Vec<Exp>, Err> {
args.iter().map(|x| eval(x, env)).collect()
}
fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
match exp {
Exp::Sym(k) => env_get(k, env).ok_or(Err::Reason(format!("Unexpected symbol '{}'", k))),
Exp::Sym(key) => env_get(key, env),
Exp::Bool(_) => Ok(exp.clone()),
Exp::Num(_) => Ok(exp.clone()),
Exp::Str(_) => Ok(exp.clone()),
Exp::List(list) => {
let first_form = list.first().ok_or(Err::Reason("Expected a non-empty list".to_string()))?;
ensure_length_gt!(list, 0);
let first_form = &list[0];
let args = &list[1..];
match eval_built_in_form(first_form, args, env) {
Some(res) => res,
@ -478,11 +520,11 @@ fn eval(exp: &Exp, env: &mut Env) -> Result<Exp, Err> {
let first_eval = eval(first_form, env)?;
match first_eval {
Exp::Func(func) => {
func(&eval_forms(args, env)?)
func(&eval_args(args, env)?)
},
Exp::Lambda(lambda) => {
let new_env = &mut env_for_lambda(lambda.params, args, env)?;
eval(&lambda.body, new_env)
let env = &mut env_for_lambda(lambda.params, args, env)?;
eval(&lambda.body, env)
},
_ => Err(Err::Reason("First form must be a function".to_string())),
}
@ -634,13 +676,19 @@ fn test_lisp() {
// atom
assert_eq!(eval!("(atom (quote a))"), "true");
assert_eq!(eval!("(atom (quote (1 2 3)))"), "false");
assert_eq!(eval!("(atom 1)"), "false");
assert_eq!(eval!("(atom 1)"), "true");
// eq
assert_eq!(eval!("(eq (quote a) (quote a))"), "true");
assert_eq!(eval!("(eq (quote a) (quote b))"), "false");
assert_eq!(eval!("(eq (quote a) (quote ()))"), "false");
assert_eq!(eval!("(eq (quote ()) (quote ()))"), "true");
assert_eq!(eval!("(eq \"a\" \"a\")"), "true");
assert_eq!(eval!("(eq \"a\" \"b\")"), "false");
assert_eq!(eval!("(eq \"a\" 'b)"), "false");
assert_eq!(eval!("(eq 1 1)"), "true");
assert_eq!(eval!("(eq 1 2)"), "false");
assert_eq!(eval!("(eq 1 1.0)"), "true");
// car
assert_eq!(eval!("(car (quote (1)))"), "1");
@ -682,11 +730,29 @@ fn test_lisp() {
assert_eq!(eval!("(+ 2 3 4)"), "9");
assert_eq!(eval!("(+ 2 (+ 3 4))"), "9");
// subtraction
assert_eq!(eval!("(- 8 4 2)"), "2");
assert_eq!(eval!("(- 2 1)"), "1");
assert_eq!(eval!("(- 1 2)"), "-1");
assert_eq!(eval!("(- 2 -1)"), "3");
// multiplication
assert_eq!(eval!("(* 2 2)"), "4");
assert_eq!(eval!("(* 2 3 4)"), "24");
assert_eq!(eval!("(* 2 (* 3 4))"), "24");
// division
assert_eq!(eval!("(/ 4 2)"), "2");
assert_eq!(eval!("(/ 1 2)"), "0.5");
assert_eq!(eval!("(/ 8 4 2)"), "1");
// exponential
assert_eq!(eval!("(^ 2 4)"), "16");
assert_eq!(eval!("(^ 2 4 2)"), "256"); // Left to right
// modulo
assert_eq!(eval!("(% 3 2)"), "1");
// comparisons
assert_eq!(eval!("(< 6 4)"), "false");
assert_eq!(eval!("(> 6 4 3 1)"), "true");
@ -698,4 +764,13 @@ fn test_lisp() {
assert_eq!(eval!("(eq \"Hello, World!\" \"foo\")"), "false");
assert_eq!(eval!("(lines \"a\nb\nc\")"), "(\"a\" \"b\" \"c\")");
assert_eq!(eval!("(parse \"9.75\")"), "9.75");
// map
eval!("(defun inc (a) (+ a 1))");
assert_eq!(eval!("(map inc '(1 2))"), "(2 3)");
assert_eq!(eval!("(map parse '(\"1\" \"2\" \"3\"))"), "(1 2 3)");
assert_eq!(eval!("(map (fn (n) (* n 2)) '(1 2 3))"), "(2 4 6)");
eval!("(defn apply2 (f arg1 arg2) (f arg1 arg2))");
assert_eq!(eval!("(apply2 + 1 2)"), "3");
}