From ec57ff154084f7f21f2507e5c9f980ee890b071e Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Tue, 20 Sep 2022 19:57:55 +0200 Subject: [PATCH] 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 --- doc/lisp.md | 26 ++--- dsk/lib/lisp/core.lsp | 104 ++++++++++---------- dsk/tmp/lisp/factorial.lsp | 12 +-- dsk/tmp/lisp/fibonacci.lsp | 8 +- dsk/tmp/lisp/pi.lsp | 8 +- src/usr/lisp.rs | 193 ++++++++++++++++++++----------------- 6 files changed, 183 insertions(+), 168 deletions(-) diff --git a/doc/lisp.md b/doc/lisp.md index 02cbea12..cbb19949 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -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 ") - (true (fib(parse (car args)))))) + ((nil? args) "Usage: fibonacci ") + (true (fibonacci (string->number (car args)))))) ``` Would produce the following output: diff --git a/dsk/lib/lisp/core.lsp b/dsk/lib/lisp/core.lsp index d96f3860..d6f4d024 100644 --- a/dsk/lib/lisp/core.lsp +++ b/dsk/lib/lisp/core.lsp @@ -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)))))) - -(defn read-char () - (string-decode (read-file-bytes "/dev/console" 4))) +(define (read-line) + (bytes->string (reverse (rest (reverse (read-file-bytes "/dev/console" 256)))))) -(defn print (exp) - (do (append-file-bytes "/dev/console" (string-encode (string exp))) '())) +(define (read-char) + (bytes->string (read-file-bytes "/dev/console" 4))) -(defn println (exp) - (do (print exp) (print "\n"))) +(define (print exp) + (do + (append-file-bytes "/dev/console" (string->bytes (string exp))) + '())) -(def pr print) -(def prn println) +(define (println exp) + (do + (print exp) + (print "\n"))) -(defn uptime () - (number-decode (read-file-bytes "/dev/clk/uptime" 8))) +(define (uptime) + (bytes->number (read-file-bytes "/dev/clk/uptime" 8))) -(defn realtime () - (number-decode (read-file-bytes "realtime" 8))) +(define (realtime) + (bytes->number (read-file-bytes "realtime" 8))) -(defn write-file (path str) - (write-file-bytes path (string-encode str))) +(define (write-file path str) + (write-file-bytes path (string->bytes str))) -(defn append-file (path str) - (append-file-bytes path (string-encode str))) +(define (append-file path str) + (append-file-bytes path (string->bytes str))) -(defn regex-match? (pattern str) - (not (null? (regex-find pattern str)))) +(define (regex-match? pattern str) + (not (nil? (regex-find pattern str)))) diff --git a/dsk/tmp/lisp/factorial.lsp b/dsk/tmp/lisp/factorial.lsp index 42c50871..9cc12090 100644 --- a/dsk/tmp/lisp/factorial.lsp +++ b/dsk/tmp/lisp/factorial.lsp @@ -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 ") - (true (fact (parse (car args)))))) + ((nil? args) "Usage: factorial ") + (true (factorial (string->number (car args)))))) diff --git a/dsk/tmp/lisp/fibonacci.lsp b/dsk/tmp/lisp/fibonacci.lsp index ecb28fa5..831ece4e 100644 --- a/dsk/tmp/lisp/fibonacci.lsp +++ b/dsk/tmp/lisp/fibonacci.lsp @@ -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 ") - (true (fib (parse (car args)))))) + ((nil? args) "Usage: fibonacci ") + (true (fibonacci (string->number (car args)))))) diff --git a/dsk/tmp/lisp/pi.lsp b/dsk/tmp/lisp/pi.lsp index aa9a5d43..7c92480e 100644 --- a/dsk/tmp/lisp/pi.lsp +++ b/dsk/tmp/lisp/pi.lsp @@ -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 ") - (true (pi-digits (parse (car args)))))) + ((nil? args) "Usage: pi ") + (true (pi-digits (string->number (car args)))))) diff --git a/src/usr/lisp.rs b/src/usr/lisp.rs index 0410f47c..16da8e9d 100644 --- a/src/usr/lisp.rs +++ b/src/usr/lisp.rs @@ -54,8 +54,8 @@ use nom::sequence::preceded; #[derive(Clone)] enum Exp { + Primitive(fn(&[Exp]) -> Result), Lambda(Lambda), - Func(fn(&[Exp]) -> Result), List(Vec), 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(_) => "".to_string(), - Exp::Func(_) => "".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(_) => "".to_string(), + Exp::Lambda(_) => "".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 = 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> { let mut data: BTreeMap = 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 { + 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 { 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 { + data.insert("+".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("-".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); let args = list_of_floats(args)?; let car = args[0]; @@ -249,7 +249,7 @@ fn default_env() -> Rc> { Ok(Exp::Num(car - res)) } })); - data.insert("/".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("/".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_gt!(args, 0); let args = list_of_floats(args)?; let car = args[0]; @@ -260,26 +260,26 @@ fn default_env() -> Rc> { Ok(Exp::Num(res)) } })); - data.insert("%".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("%".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("^".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("cos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("acos".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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> { 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 { + data.insert("asin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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> { 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 { + data.insert("atan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("sin".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("tan".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("system".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); let cmd = string(&args[0])?; match usr::shell::exec(&cmd) { @@ -320,13 +320,13 @@ fn default_env() -> Rc> { Err(code) => Ok(Exp::Num(code as u8 as f64)), } })); - data.insert("read-file".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("read-file".to_string(), Exp::Primitive(|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("read-file-bytes".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("read-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); let path = string(&args[0])?; let len = float(&args[1])?; @@ -335,7 +335,7 @@ fn default_env() -> Rc> { 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 { + data.insert("write-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); let path = string(&args[0])?; match &args[1] { @@ -347,7 +347,7 @@ fn default_env() -> Rc> { _ => Err(Err::Reason("Expected second arg to be a list".to_string())) } })); - data.insert("append-file-bytes".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("append-file-bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); let path = string(&args[0])?; match &args[1] { @@ -359,20 +359,20 @@ fn default_env() -> Rc> { _ => Err(Err::Reason("Expected second arg to be a list".to_string())) } })); - data.insert("string".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { let args: Vec = 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 { + data.insert("string->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("bytes->string".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); match &args[0] { Exp::List(list) => { @@ -383,7 +383,7 @@ fn default_env() -> Rc> { _ => Err(Err::Reason("Expected arg to be a list".to_string())) } })); - data.insert("number-decode".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("bytes->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); match &args[0] { Exp::List(list) => { @@ -394,12 +394,12 @@ fn default_env() -> Rc> { _ => Err(Err::Reason("Expected arg to be a list".to_string())) } })); - data.insert("number-encode".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("number->bytes".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("regex-find".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 2); match (&args[0], &args[1]) { (Exp::Str(regex), Exp::Str(s)) => { @@ -411,19 +411,19 @@ fn default_env() -> Rc> { _ => Err(Err::Reason("Expected args to be a regex and a string".to_string())) } })); - data.insert("lines".to_string(), Exp::Func(|args: &[Exp]| -> Result { + data.insert("lines".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("string->number".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { 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 { + data.insert("type".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { ensure_length_eq!(args, 1); let exp = match args[0] { Exp::Str(_) => "string", @@ -431,25 +431,23 @@ fn default_env() -> Rc> { 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 { + data.insert("list".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { Ok(Exp::List(args.to_vec())) })); + data.insert("parse".to_string(), Exp::Primitive(|args: &[Exp]| -> Result { + ensure_length_eq!(args, 1); + let s = string(&args[0])?; + let (_, exp) = parse(&s)?; + Ok(exp) + })); // Setup autocompletion - let mut forms: Vec = 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, Err> { } } -fn list_of_strings(args: &[Exp]) -> Result, Err> { - args.iter().map(string).collect() -} - fn list_of_floats(args: &[Exp]) -> Result, Err> { args.iter().map(float).collect() } @@ -580,12 +574,22 @@ fn eval_cond_args(args: &[Exp], env: &mut Rc>) -> Result fn eval_label_args(args: &[Exp], env: &mut Rc>) -> Result { 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())) + } + 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".to_string())) + _ => 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 { } fn eval_defun_args(args: &[Exp], env: &mut Rc>) -> Result { + // (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>) -> Result eval(&Exp::List(args.to_vec()), env) } +fn eval_eval_args(args: &[Exp], env: &mut Rc>) -> Result { + ensure_length_eq!(args, 1); + let exp = eval(&args[0], env)?; + eval(&exp, env) +} + fn eval_progn_args(args: &[Exp], env: &mut Rc>) -> Result { let mut res = Ok(Exp::List(vec![])); for arg in args { @@ -640,28 +651,34 @@ fn eval_load_args(args: &[Exp], env: &mut Rc>) -> Result 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>) -> Option> { 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)), - - "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, + "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)), + "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>) -> Result { } } -fn env_for_lambda(params: Rc, args: &[Exp], outer: &mut Rc>) -> Result>, Err> { +fn lambda_env(params: Rc, args: &[Exp], outer: &mut Rc>) -> Result>, Err> { let ks = list_of_symbols(¶ms)?; if ks.len() != args.len() { let plural = if ks.len() == 1 { "" } else { "s" }; @@ -714,19 +731,19 @@ fn eval(exp: &Exp, env: &mut Rc>) -> Result { 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>) -> 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");