Add if and while to Lisp (#418)

* Change Lisp loop into while

* Rename define to def

* Replace cond by if

* Fix pi computation

* Fix number parsing error

* Fix or function

* Add test to while

* Rewrite test

* Add missing fun

* Add examples to doc
This commit is contained in:
Vincent Ollivier 2022-10-21 10:10:14 +02:00 committed by GitHub
parent b09f881c64
commit fcf3a7e72f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 191 additions and 136 deletions

View File

@ -34,6 +34,8 @@ of strings to the language and reading from the filesystem.
## Additional Builtins
- `defun` (aliased to `defn`)
- `set`
- `while`
- `apply`
- `type`
- `string`
@ -83,15 +85,13 @@ with the following content:
```lisp
(load "/lib/lisp/core.lsp")
(define (fibonacci n)
(cond
((< n 2) n)
(true (+ (fibonacci (- n 1)) (fibonacci (- n 2))))))
(def (fibonacci n)
(if (< n 2) n
(+ (fibonacci (- n 1)) (fibonacci (- n 2)))))
(println
(cond
((nil? args) "Usage: fibonacci <num>")
(true (fibonacci (string->number (car args))))))
(if (nil? args) "Usage: fibonacci <num>"
(fibonacci (string->number (car args)))))
```
Would produce the following output:
@ -100,3 +100,44 @@ Would produce the following output:
> lisp /tmp/lisp/fibonacci.lsp 20
6755
```
## Examples
```lisp
(load "/lib/lisp/core.lsp")
(def foo 42) # Variable definition
(def double (fun (x) (* x 2))) # Function definition
(def (double x) (* x 2)) # Shortcut
(double foo) # => 84
(def (map f ls)
(if (nil? ls) nil
(cons
(f (first ls))
(map f (rest ls)))))
(def bar (quote (1 2 3)))
(def bar '(1 2 3)) # Shortcut
(map double bar) # => (2 4 6)
(map (fun (x) (+ x 1)) '(4 5 6)) # => (5 6 7)
(set foo 0) # Variable assignment
(= foo 10) # => false
(while (< foo 10)
(set foo (+ foo 1)))
(= foo 10) # => true
(def name "Alice")
(string "Hello, " name) # => "Hello, Alice"
(^ 2 128) # => 340282366920938463463374607431768211456
```

View File

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

View File

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

View File

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

View File

@ -1,38 +1,34 @@
(load "/lib/lisp/core.lsp")
(define (pi-digits y)
(def (pi-digits digits)
(do
(define dot true)
(define q 1)
(define r 0)
(define t 1)
(define k 1)
(define n 3)
(define l 3)
(map
(lambda (j)
(def i 0)
(def q 1)
(def r 0)
(def t 1)
(def k 1)
(def n 3)
(def l 3)
(while (<= i digits)
(if (< (- (+ (* q 4) r) t) (* n t))
(do
(cond
((< (- (+ (* q 4) r) t) (* n t)) (do
(print (string n (cond (dot ".") (true ""))))
(set dot false)
(define nr (* 10 (- r (* n t))))
(set n (- (/ (* 10 (+ (* 3 q) r)) t) (* 10 n)))
(set q (* q 10))
(set r nr)))
(true (do
(define nr (* (+ (* 2 q) r) l))
(define nn (/ (+ 2 (* q k 7) (* r l)) (* t l)))
(set q (* q k))
(set t (* t l))
(set l (+ l 2))
(set k (+ k 1))
(set n nn)
(set r nr))))))
(range 0 y))
n))
(print (string n (if (= i 0) "." "")))
(set i (+ i 1))
(def nr (* 10 (- r (* n t))))
(set n (- (/ (* 10 (+ (* 3 q) r)) t) (* 10 n)))
(set q (* q 10))
(set r nr))
(do
(def nr (* (+ (* 2 q) r) l))
(def nn (/ (+ 2 (* q k 7) (* r l)) (* t l)))
(set q (* q k))
(set t (* t l))
(set l (+ l 2))
(set k (+ k 1))
(set n nn)
(set r nr))))
""))
(println
(cond
((nil? args) "Usage: pi <precision>")
(true (pi-digits (string->number (car args))))))
(if (nil? args) "Usage: pi <precision>"
(pi-digits (string->number (car args)))))

View File

@ -80,7 +80,18 @@ fn eval_cond_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
_ => return Err(Err::Reason("Expected lists of predicate and expression".to_string())),
}
}
Ok(Exp::List(Vec::new()))
Ok(Exp::List(vec![]))
}
fn eval_if_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 1);
if eval(&args[0], env)? == Exp::Bool(true) {
eval(&args[1], env)
} else if args.len() > 2 {
eval(&args[2], env)
} else {
Ok(Exp::List(vec![]))
}
}
pub fn eval_label_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
@ -117,11 +128,16 @@ fn eval_set_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
}
}
fn eval_loop_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_eq!(args, 1);
loop {
eval(&args[0], env)?;
fn eval_while_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err> {
ensure_length_gt!(args, 1);
let cond = &args[0];
let mut res = Exp::List(vec![]);
while eval(cond, env)? == Exp::Bool(true) {
for arg in &args[1..] {
res = eval(arg, env)?;
}
}
Ok(res)
}
fn eval_lambda_args(args: &[Exp]) -> Result<Exp, Err> {
@ -182,9 +198,10 @@ fn eval_load_args(args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Result<Exp, Err>
Ok(Exp::Bool(true))
}
pub const BUILT_INS: [&str; 22] = [
pub const BUILT_INS: [&str; 24] = [
"quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "lambda", "define", "def",
"function", "fun", "fn", "defun", "defn", "apply", "eval", "progn", "begin", "do", "load"
"function", "fun", "fn", "if", "while", "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>> {
@ -192,26 +209,27 @@ fn eval_built_in_form(exp: &Exp, args: &[Exp], env: &mut Rc<RefCell<Env>>) -> Op
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" | "define" | "def" => Some(eval_label_args(args, env)),
"lambda" | "function" | "fn" => Some(eval_lambda_args(args)),
"label" | "define" | "def" => Some(eval_label_args(args, env)),
"lambda" | "function" | "fun" | "fn" => Some(eval_lambda_args(args)),
"set" => Some(eval_set_args(args, env)),
"loop" => Some(eval_loop_args(args, env)),
"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,
"if" => Some(eval_if_args(args, env)),
"set" => Some(eval_set_args(args, env)),
"while" => Some(eval_while_args(args, env)),
"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,

View File

@ -23,7 +23,6 @@ use alloc::vec::Vec;
use alloc::vec;
use core::cell::RefCell;
use core::convert::TryInto;
use core::f64::consts::PI;
use core::fmt;
use lazy_static::lazy_static;
use spin::Mutex;
@ -290,6 +289,7 @@ pub fn main(args: &[&str]) -> Result<(), ExitCode> {
#[test_case]
fn test_lisp() {
use core::f64::consts::PI;
let env = &mut default_env();
macro_rules! eval {
@ -343,6 +343,15 @@ fn test_lisp() {
assert_eq!(eval!("(cond ((< 2 4) 1) (true 2))"), "1");
assert_eq!(eval!("(cond ((> 2 4) 1) (true 2))"), "2");
// if
assert_eq!(eval!("(if (< 2 4) 1)"), "1");
assert_eq!(eval!("(if (> 2 4) 1)"), "()");
assert_eq!(eval!("(if (< 2 4) 1 2)"), "1");
assert_eq!(eval!("(if (> 2 4) 1 2)"), "2");
// while
assert_eq!(eval!("(do (def i 0) (while (< i 5) (set i (+ i 1))) i)"), "5");
// label
eval!("(label a 2)");
assert_eq!(eval!("(+ a 1)"), "3");

View File

@ -192,6 +192,7 @@ impl FromStr for Number {
type Err = Err;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let err = Err(Err::Reason("Could not parse number".to_string()));
if s.contains('.') {
if let Ok(n) = s.parse() {
return Ok(Number::Float(n));
@ -206,6 +207,9 @@ impl FromStr for Number {
}
let mut res = BigInt::from(0);
for c in chars {
if !c.is_ascii_digit() {
return err;
}
let d = c as u8 - b'0';
res = res * BigInt::from(10) + BigInt::from(d as u32);
}
@ -214,7 +218,7 @@ impl FromStr for Number {
} /* else if let Ok(n) = s.parse() { // FIXME: rust-lld: error: undefined symbol: fmod
return Ok(Number::BigInt(n));
} */
Err(Err::Reason("Could not parse number".to_string()))
err
}
}