Extend and refactor Lisp implementation (#412)

* Adopt a syntax closer to scheme

* Add parse and eval

* Replace Exp::Func with Exp::Primitive

* Refactor built in autocompletion

* Replace null by nil

* Fix test

* Update doc

* Bump version
This commit is contained in:
Vincent Ollivier 2022-09-20 19:57:55 +02:00 committed by GitHub
parent 46137e6244
commit ec57ff1540
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 182 additions and 167 deletions

View File

@ -24,18 +24,18 @@ of strings to the language and reading from the filesystem.
- `cond`
## Two Special Forms
- `label` (aliased to `def`)
- `lambda` (aliased to `fn`)
- `label` (aliased to `define` and `def`)
- `lambda` (aliased to `function`, `fun`, and `fn`)
## Additional Builtins
- `defun` (aliased to `defn`)
- `apply`
- `type`
- `string`
- `string-encode` and `string-decode`
- `number-encode` and `number-decode`
- `string->number`
- `string->bytes` and `bytes->string`
- `number->bytes` and `bytes->number`
- `regex-find`
- `parse`
- `system`
- `load`
@ -47,7 +47,7 @@ of strings to the language and reading from the filesystem.
- File IO: `read-file`, `read-file-bytes`, `write-file-bytes`, `append-file-bytes`
## Core Library
- `null`, `null?`, `eq?`
- `nil`, `nil?`, `eq?`
- `atom?`, `string?`, `boolean?`, `symbol?`, `number?`, `list?`, `function?`, `lambda?`
- `first`, `second`, `third`, `rest`
- `map`, `reduce`, `append`, `reverse`
@ -64,10 +64,10 @@ The interpreter can be invoked from the shell:
```
> lisp
MOROS Lisp v0.1.0
MOROS Lisp v0.4.0
> (+ 1 2)
3
> (+ 1 2 3)
6
> (quit)
```
@ -78,15 +78,15 @@ with the following content:
```lisp
(load "/lib/lisp/core.lsp")
(defn fib (n)
(define (fibonacci n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2))))))
(true (+ (fibonacci (- n 1)) (fibonacci (- n 2))))))
(println
(cond
((null? args) "Usage: fibonacci <num>")
(true (fib(parse (car args))))))
((nil? args) "Usage: fibonacci <num>")
(true (fibonacci (string->number (car args))))))
```
Would produce the following output:

View File

@ -1,114 +1,112 @@
(defn eq? (x y)
(define (eq? x y)
(eq x y))
(defn atom? (x)
(define (atom? x)
(atom x))
(defn string? (x)
(define (string? x)
(eq? (type x) "string"))
(defn boolean? (x)
(define (boolean? x)
(eq? (type x) "boolean"))
(defn symbol? (x)
(define (symbol? x)
(eq? (type x) "symbol"))
(defn number? (x)
(define (number? x)
(eq? (type x) "number"))
(defn list? (x)
(define (list? x)
(eq? (type x) "list"))
(defn function? (x)
(define (function? x)
(eq? (type x) "function"))
(defn lambda? (x)
(eq? (type x) "lambda"))
(define nil '())
(def null '())
(define (nil? x)
(eq? x nil))
(defn null? (x)
(eq? x null))
(defn and (x y)
(define (and x y)
(cond
(x (cond (y true) (true false)))
(true false)))
(defn not (x)
(define (not x)
(cond (x false) (true true)))
(defn or (x y)
(define (or x y)
(cond (x true) (y true) (true false)))
(defn rest (x)
(define (rest x)
(cdr x))
(defn first (x)
(define (first x)
(car x))
(defn second (x)
(define (second x)
(first (rest x)))
(defn third (x)
(define (third x)
(second (rest x)))
(defn reduce (f ls)
(define (reduce f ls)
(cond
((null? (rest ls)) (first ls))
((nil? (rest ls)) (first ls))
(true (f (first ls) (reduce f (rest ls))))))
(defn string-join (ls s)
(define (string-join ls s)
(reduce (fn (x y) (string x s y)) ls))
(defn map (f ls)
(define (map f ls)
(cond
((null? ls) null)
((nil? ls) nil)
(true (cons
(f (first ls))
(map f (rest ls))))))
(defn append (x y)
(define (append x y)
(cond
((null? x) y)
((nil? x) y)
(true (cons (first x) (append (rest x) y)))))
(defn reverse (x)
(define (reverse x)
(cond
((null? x) x)
((nil? x) x)
(true (append (reverse (rest x)) (cons (first x) '())))))
(defn range (i n)
(define (range i n)
(cond
((= i n) null)
((= i n) nil)
(true (append (list i) (range (+ i 1) n)))))
(defn read-line ()
(string-decode (reverse (rest (reverse (read-file-bytes "/dev/console" 256))))))
(define (read-line)
(bytes->string (reverse (rest (reverse (read-file-bytes "/dev/console" 256))))))
(defn read-char ()
(string-decode (read-file-bytes "/dev/console" 4)))
(define (read-char)
(bytes->string (read-file-bytes "/dev/console" 4)))
(defn print (exp)
(do (append-file-bytes "/dev/console" (string-encode (string exp))) '()))
(define (print exp)
(do
(append-file-bytes "/dev/console" (string->bytes (string exp)))
'()))
(defn println (exp)
(do (print exp) (print "\n")))
(define (println exp)
(do
(print exp)
(print "\n")))
(def pr print)
(def prn println)
(define (uptime)
(bytes->number (read-file-bytes "/dev/clk/uptime" 8)))
(defn uptime ()
(number-decode (read-file-bytes "/dev/clk/uptime" 8)))
(define (realtime)
(bytes->number (read-file-bytes "realtime" 8)))
(defn realtime ()
(number-decode (read-file-bytes "realtime" 8)))
(define (write-file path str)
(write-file-bytes path (string->bytes str)))
(defn write-file (path str)
(write-file-bytes path (string-encode str)))
(define (append-file path str)
(append-file-bytes path (string->bytes str)))
(defn append-file (path str)
(append-file-bytes path (string-encode str)))
(defn regex-match? (pattern str)
(not (null? (regex-find pattern str))))
(define (regex-match? pattern str)
(not (nil? (regex-find pattern str))))

View File

@ -1,14 +1,14 @@
(load "/lib/lisp/core.lsp")
(defn fact-acc (n acc)
(define (factorial-helper n acc)
(cond
((< n 2) acc)
(true (fact-acc (- n 1) (* acc n)))))
(true (factorial-helper (- n 1) (* acc n)))))
(defn fact (n)
(fact-acc n 1))
(define (factorial n)
(factorial-helper n 1))
(println
(cond
((null? args) "Usage: factorial <num>")
(true (fact (parse (car args))))))
((nil? args) "Usage: factorial <num>")
(true (factorial (string->number (car args))))))

View File

@ -1,11 +1,11 @@
(load "/lib/lisp/core.lsp")
(defn fib (n)
(define (fibonacci n)
(cond
((< n 2) n)
(true (+ (fib (- n 1)) (fib (- n 2))))))
(true (+ (fibonacci (- n 1)) (fibonacci (- n 2))))))
(println
(cond
((null? args) "Usage: fibonacci <num>")
(true (fib (parse (car args))))))
((nil? args) "Usage: fibonacci <num>")
(true (fibonacci (string->number (car args))))))

View File

@ -1,16 +1,16 @@
(load "/lib/lisp/core.lsp")
(defn pi-nth (n)
(define (pi-nth n)
(* (^ 16 (- n)) (-
(/ 4 (+ 1 (* 8 n)))
(/ 2 (+ 4 (* 8 n)))
(/ 1 (+ 5 (* 8 n)))
(/ 1 (+ 6 (* 8 n))))))
(defn pi-digits (n)
(define (pi-digits n)
(apply + (map pi-nth (range 0 n))))
(println
(cond
((null? args) "Usage: pi <precision>")
(true (pi-digits (parse (car args))))))
((nil? args) "Usage: pi <precision>")
(true (pi-digits (string->number (car args))))))

View File

@ -54,8 +54,8 @@ use nom::sequence::preceded;
#[derive(Clone)]
enum Exp {
Primitive(fn(&[Exp]) -> Result<Exp, Err>),
Lambda(Lambda),
Func(fn(&[Exp]) -> Result<Exp, Err>),
List(Vec<Exp>),
Bool(bool),
Num(f64),
@ -80,13 +80,13 @@ impl PartialEq for Exp {
impl fmt::Display for Exp {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let out = match self {
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),
Exp::List(list) => {
Exp::Primitive(_) => "<function>".to_string(),
Exp::Lambda(_) => "<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),
Exp::List(list) => {
let xs: Vec<String> = list.iter().map(|x| x.to_string()).collect();
format!("({})", xs.join(" "))
},
@ -193,13 +193,13 @@ macro_rules! ensure_tonicity {
ensure_length_gt!(floats, 0);
let first = &floats[0];
let rest = &floats[1..];
fn func(prev: &f64, xs: &[f64]) -> bool {
fn f(prev: &f64, xs: &[f64]) -> bool {
match xs.first() {
Some(x) => $check_fn(*prev, *x) && func(x, &xs[1..]),
Some(x) => $check_fn(*prev, *x) && f(x, &xs[1..]),
None => true,
}
}
Ok(Exp::Bool(func(first, rest)))
Ok(Exp::Bool(f(first, rest)))
}
};
}
@ -225,20 +225,20 @@ macro_rules! ensure_length_gt {
fn default_env() -> Rc<RefCell<Env>> {
let mut data: BTreeMap<String, Exp> = BTreeMap::new();
data.insert("pi".to_string(), Exp::Num(PI));
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> {
data.insert("=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b))));
data.insert(">".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a > b)));
data.insert(">=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a > b)));
data.insert("<".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| !approx_eq!(f64, a, b) && a < b)));
data.insert("<=".to_string(), Exp::Primitive(ensure_tonicity!(|a, b| approx_eq!(f64, a, b) || a < b)));
data.insert("*".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
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> {
data.insert("+".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
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> {
data.insert("-".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_floats(args)?;
let car = args[0];
@ -249,7 +249,7 @@ fn default_env() -> Rc<RefCell<Env>> {
Ok(Exp::Num(car - res))
}
}));
data.insert("/".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("/".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_floats(args)?;
let car = args[0];
@ -260,26 +260,26 @@ fn default_env() -> Rc<RefCell<Env>> {
Ok(Exp::Num(res))
}
}));
data.insert("%".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("%".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_floats(args)?;
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> {
data.insert("^".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_gt!(args, 0);
let args = list_of_floats(args)?;
let car = args[0];
let res = args[1..].iter().fold(car, |acc, a| libm::pow(acc, *a));
Ok(Exp::Num(res))
}));
data.insert("cos".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("cos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
Ok(Exp::Num(libm::cos(args[0])))
}));
data.insert("acos".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("acos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
if -1.0 <= args[0] && args[0] <= 1.0 {
@ -288,7 +288,7 @@ fn default_env() -> Rc<RefCell<Env>> {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}));
data.insert("asin".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("asin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
if -1.0 <= args[0] && args[0] <= 1.0 {
@ -297,22 +297,22 @@ fn default_env() -> Rc<RefCell<Env>> {
Err(Err::Reason("Expected arg to be between -1.0 and 1.0".to_string()))
}
}));
data.insert("atan".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("atan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
Ok(Exp::Num(libm::atan(args[0])))
}));
data.insert("sin".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("sin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
Ok(Exp::Num(libm::sin(args[0])))
}));
data.insert("tan".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("tan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let args = list_of_floats(args)?;
Ok(Exp::Num(libm::tan(args[0])))
}));
data.insert("system".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("system".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let cmd = string(&args[0])?;
match usr::shell::exec(&cmd) {
@ -320,13 +320,13 @@ fn default_env() -> Rc<RefCell<Env>> {
Err(code) => Ok(Exp::Num(code as u8 as f64)),
}
}));
data.insert("read-file".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("read-file".to_string(), Exp::Primitive(|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("read-file-bytes".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("read-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
let len = float(&args[1])?;
@ -335,7 +335,7 @@ fn default_env() -> Rc<RefCell<Env>> {
buf.resize(bytes, 0);
Ok(Exp::List(buf.iter().map(|b| Exp::Num(*b as f64)).collect()))
}));
data.insert("write-file-bytes".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("write-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
@ -347,7 +347,7 @@ fn default_env() -> Rc<RefCell<Env>> {
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}));
data.insert("append-file-bytes".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("append-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
let path = string(&args[0])?;
match &args[1] {
@ -359,20 +359,20 @@ fn default_env() -> Rc<RefCell<Env>> {
_ => Err(Err::Reason("Expected second arg to be a list".to_string()))
}
}));
data.insert("string".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
let args: Vec<String> = args.iter().map(|arg| match arg {
Exp::Str(s) => format!("{}", s),
exp => format!("{}", exp),
}).collect();
Ok(Exp::Str(args.join("")))
}));
data.insert("string-encode".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("string->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let buf = s.as_bytes();
Ok(Exp::List(buf.iter().map(|b| Exp::Num(*b as f64)).collect()))
}));
data.insert("string-decode".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("bytes->string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
@ -383,7 +383,7 @@ fn default_env() -> Rc<RefCell<Env>> {
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
}
}));
data.insert("number-decode".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("bytes->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
match &args[0] {
Exp::List(list) => {
@ -394,12 +394,12 @@ fn default_env() -> Rc<RefCell<Env>> {
_ => Err(Err::Reason("Expected arg to be a list".to_string()))
}
}));
data.insert("number-encode".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("number->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let f = float(&args[0])?;
Ok(Exp::List(f.to_be_bytes().iter().map(|b| Exp::Num(*b as f64)).collect()))
}));
data.insert("regex-find".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("regex-find".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match (&args[0], &args[1]) {
(Exp::Str(regex), Exp::Str(s)) => {
@ -411,19 +411,19 @@ fn default_env() -> Rc<RefCell<Env>> {
_ => Err(Err::Reason("Expected args to be a regex and a string".to_string()))
}
}));
data.insert("lines".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("lines".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
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> {
data.insert("string->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
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> {
data.insert("type".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let exp = match args[0] {
Exp::Str(_) => "string",
@ -431,25 +431,23 @@ fn default_env() -> Rc<RefCell<Env>> {
Exp::Sym(_) => "symbol",
Exp::Num(_) => "number",
Exp::List(_) => "list",
Exp::Func(_) => "function",
Exp::Lambda(_) => "lambda",
Exp::Primitive(_) => "function",
Exp::Lambda(_) => "function",
};
Ok(Exp::Str(exp.to_string()))
}));
data.insert("list".to_string(), Exp::Func(|args: &[Exp]| -> Result<Exp, Err> {
data.insert("list".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
Ok(Exp::List(args.to_vec()))
}));
data.insert("parse".to_string(), Exp::Primitive(|args: &[Exp]| -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let s = string(&args[0])?;
let (_, exp) = parse(&s)?;
Ok(exp)
}));
// Setup autocompletion
let mut forms: Vec<String> = data.keys().map(|k| k.to_string()).collect();
let builtins = vec![
"quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "def", "lambda", "fn",
"defun", "defn", "apply", "progn", "do", "load", "quit"
];
for builtin in builtins {
forms.push(builtin.to_string());
}
*FORMS.lock() = forms;
*FORMS.lock() = data.keys().cloned().chain(BUILT_INS.map(String::from)).collect();
Rc::new(RefCell::new(Env { data, outer: None }))
}
@ -468,10 +466,6 @@ fn list_of_symbols(form: &Exp) -> Result<Vec<String>, Err> {
}
}
fn list_of_strings(args: &[Exp]) -> Result<Vec<String>, Err> {
args.iter().map(string).collect()
}
fn list_of_floats(args: &[Exp]) -> Result<Vec<f64>, Err> {
args.iter().map(float).collect()
}
@ -580,12 +574,22 @@ fn eval_cond_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
fn eval_label_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 2);
match &args[0] {
Exp::Sym(key) => {
Exp::Sym(name) => {
let exp = eval(&args[1], env)?;
env.borrow_mut().data.insert(key.clone(), exp);
Ok(Exp::Sym(key.clone()))
env.borrow_mut().data.insert(name.clone(), exp);
Ok(Exp::Sym(name.clone()))
}
_ => Err(Err::Reason("Expected first argument to be a symbol".to_string()))
Exp::List(params) => {
// (label (add x y) (+ x y)) => (label add (lambda (x y) (+ x y)))
ensure_length_gt!(params, 0);
let name = params[0].clone();
let params = Exp::List(params[1..].to_vec());
let body = args[1].clone();
let lambda_args = vec![Exp::Sym("lambda".to_string()), params, body];
let label_args = vec![name, Exp::List(lambda_args)];
eval_label_args(&label_args, env)
}
_ => Err(Err::Reason("Expected first argument to be a symbol or a list".to_string()))
}
}
@ -598,11 +602,12 @@ fn eval_lambda_args(args: &[Exp]) -> Result<Exp, Err> {
}
fn eval_defun_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
// (defun add (x y) (+ x y)) => (label add (lambda (x y) (+ x y)))
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 body = args[2].clone();
let lambda_args = vec![Exp::Sym("lambda".to_string()), params, body];
let label_args = vec![name, Exp::List(lambda_args)];
eval_label_args(&label_args, env)
}
@ -617,6 +622,12 @@ fn eval_apply_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
eval(&Exp::List(args.to_vec()), env)
}
fn eval_eval_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
let exp = eval(&args[0], env)?;
eval(&exp, env)
}
fn eval_progn_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
let mut res = Ok(Exp::List(vec![]));
for arg in args {
@ -640,28 +651,34 @@ fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(Exp::Bool(true))
}
const BUILT_INS: [&str; 22] = [
"quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "lambda", "define", "def",
"function", "fun", "fn", "defun", "defn", "apply", "eval", "progn", "begin", "do", "load"
];
fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Option<Result<Exp, Err>> {
match exp {
Exp::Sym(s) => {
match s.as_ref() {
// Seven Primitive Operators
"quote" => Some(eval_quote_args(args)),
"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)),
"quote" => Some(eval_quote_args(args)),
"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)),
// Two Special Forms
"label" | "def" => Some(eval_label_args(args, env)),
"lambda" | "fn" => Some(eval_lambda_args(args)),
"label" | "define" | "def" => Some(eval_label_args(args, env)),
"lambda" | "function" | "fn" => Some(eval_lambda_args(args)),
"defun" | "defn" => Some(eval_defun_args(args, env)),
"apply" => Some(eval_apply_args(args, env)),
"progn" | "do" => Some(eval_progn_args(args, env)),
"load" => Some(eval_load_args(args, env)),
_ => None,
"defun" | "defn" => Some(eval_defun_args(args, env)),
"apply" => Some(eval_apply_args(args, env)),
"eval" => Some(eval_eval_args(args, env)),
"progn" | "begin" | "do" => Some(eval_progn_args(args, env)),
"load" => Some(eval_load_args(args, env)),
_ => None,
}
},
_ => None,
@ -681,7 +698,7 @@ fn env_get(key: &str, env: &Rc<RefCell<Env>>) -> Result<Exp, Err> {
}
}
fn env_for_lambda(params: Rc<Exp>, args: &[Exp], outer: &mut Rc<RefCell<Env>>) -> Result<Rc<RefCell<Env>>, Err> {
fn lambda_env(params: Rc<Exp>, args: &[Exp], outer: &mut Rc<RefCell<Env>>) -> Result<Rc<RefCell<Env>>, Err> {
let ks = list_of_symbols(&params)?;
if ks.len() != args.len() {
let plural = if ks.len() == 1 { "" } else { "s" };
@ -714,19 +731,19 @@ fn eval(exp: &Exp, env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
None => {
let first_eval = eval(first_form, env)?;
match first_eval {
Exp::Func(func) => {
func(&eval_args(args, env)?)
Exp::Primitive(f) => {
f(&eval_args(args, env)?)
},
Exp::Lambda(lambda) => {
let mut env = env_for_lambda(lambda.params, args, env)?;
eval(&lambda.body, &mut env)
Exp::Lambda(f) => {
let mut env = lambda_env(f.params, args, env)?;
eval(&f.body, &mut env)
},
_ => Err(Err::Reason("First form must be a function".to_string())),
}
}
}
},
Exp::Func(_) => Err(Err::Reason("Unexpected form".to_string())),
Exp::Primitive(_) => Err(Err::Reason("Unexpected form".to_string())),
Exp::Lambda(_) => Err(Err::Reason("Unexpected form".to_string())),
}
}
@ -749,7 +766,7 @@ fn repl(env: &mut Rc<RefCell<Env>>) -> Result<(), ExitCode> {
let csi_reset = Style::reset();
let prompt_string = format!("{}>{} ", csi_color, csi_reset);
println!("MOROS Lisp v0.3.0\n");
println!("MOROS Lisp v0.4.0\n");
let mut prompt = Prompt::new();
let history_file = "~/.lisp-history";
@ -952,7 +969,7 @@ fn test_lisp() {
assert_eq!(eval!("(= (+ 0.15 0.15) (+ 0.1 0.2))"), "true");
// number
assert_eq!(eval!("(number-decode (number-encode 42))"), "42");
assert_eq!(eval!("(bytes->number (number->bytes 42))"), "42");
// string
assert_eq!(eval!("(parse \"9.75\")"), "9.75");