mirror of https://github.com/vinc/moros.git
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:
parent
985d24a785
commit
313f90ee73
17
doc/lisp.md
17
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
|
||||
```
|
||||
|
|
|
@ -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)))
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
375
src/usr/lisp.rs
375
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<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(¶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<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");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue