From 313f90ee7307f91b04a7b0c56db72f43cf65b141 Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Sun, 12 Dec 2021 11:43:57 +0100 Subject: [PATCH] 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 --- doc/lisp.md | 17 +- dsk/ini/lisp/core.lsp | 43 +++++ src/usr/calc.rs | 42 +++-- src/usr/install.rs | 3 + src/usr/lisp.rs | 375 +++++++++++++++++++++++++----------------- 5 files changed, 309 insertions(+), 171 deletions(-) create mode 100644 dsk/ini/lisp/core.lsp diff --git a/doc/lisp.md b/doc/lisp.md index c173cf5..8d2cfb7 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -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 ``` diff --git a/dsk/ini/lisp/core.lsp b/dsk/ini/lisp/core.lsp new file mode 100644 index 0000000..4bb6cee --- /dev/null +++ b/dsk/ini/lisp/core.lsp @@ -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))) diff --git a/src/usr/calc.rs b/src/usr/calc.rs index 4b7522b..4f47174 100644 --- a/src/usr/calc.rs +++ b/src/usr/calc.rs @@ -27,6 +27,7 @@ pub enum Exp { Mul(Box, Box), Div(Box, Box), Exp(Box, Box), + Mod(Box, Box), } // 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"); } diff --git a/src/usr/install.rs b/src/usr/install.rs index 2f30a2f..c28cea6 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -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); diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs index d123ed3..0d14918 100644 --- a/src/usr/lisp.rs +++ b/src/usr/lisp.rs @@ -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), Lambda(Lambda), + Func(fn(&[Exp]) -> Result), List(Vec), + 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 = 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, body: Rc, @@ -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 { 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 = 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 { - 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 { - 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 { - 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 { - let arg = first(args)?; - let path = string(&arg)?; + data.insert("/".to_string(), Exp::Func(|args: &[Exp]| -> Result { + 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 { + 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 { + 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 { + 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 { + 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 { - 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 { - 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 { + 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 { - match exps.get(0) { - Some(exp) => Ok(exp.clone()), - None => Err(Err::Reason("Expected first expression".to_string())) - } -} - -fn second(exps: &[Exp]) -> Result { - match exps.get(1) { - Some(exp) => Ok(exp.clone()), - None => Err(Err::Reason("Expected second expression".to_string())) - } -} - -fn third(exps: &[Exp]) -> Result { - 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, 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 { // Eval fn eval_quote_args(args: &[Exp]) -> Result { - first(args) + ensure_length_eq!(args, 1); + Ok(args[0].clone()) } fn eval_atom_args(args: &[Exp], env: &mut Env) -> Result { - 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 { - 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 { - 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 { } fn eval_cdr_args(args: &[Exp], env: &mut Env) -> Result { - 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 { } fn eval_cons_args(args: &[Exp], env: &mut Env) -> Result { - 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 { } fn eval_cond_args(args: &[Exp], env: &mut Env) -> Result { - 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 { } fn eval_label_args(args: &[Exp], env: &mut Env) -> Result { - 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 { - 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 { - 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 { - 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 { + 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::, Err>>()?)) } + _ => Err(Err::Reason("Expected second argument to be a list".to_string())), } } +fn eval_load_args(args: &[Exp], env: &mut Env) -> Result { + 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> { 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 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 Option { - match env.data.get(k) { - Some(exp) => Some(exp.clone()), +fn env_get(key: &str, env: &Env) -> Result { + 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) -> Result, 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, args: &[Exp], outer_env: &'a mut Env) -> Result, Err> { - let ks = list_of_symbols(params)?; +fn env_for_lambda<'a>(params: Rc, args: &[Exp], outer: &'a mut Env) -> Result, Err> { + let ks = list_of_symbols(¶ms)?; 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 = 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, Err> { +fn eval_args(args: &[Exp], env: &mut Env) -> Result, Err> { args.iter().map(|x| eval(x, env)).collect() } fn eval(exp: &Exp, env: &mut Env) -> Result { 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 { 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"); }