diff --git a/doc/lisp.md b/doc/lisp.md index 48df2d45..f73eae77 100644 --- a/doc/lisp.md +++ b/doc/lisp.md @@ -5,38 +5,70 @@ of the Shell. MOROS Lisp is a Lisp-1 dialect inspired by Scheme and Clojure. -It started from [Risp](https://github.com/stopachka/risp) and was extended to -include the seven primitive operators and the two special forms of John -McCarthy's paper "Recursive Functions of Symbolic Expressions and Their +## Changelog + +### 0.1.0 (2021-07-21) +MOROS Lisp started from [Risp](https://github.com/stopachka/risp) and was +extended to include the seven primitive operators and the two special forms of +John McCarthy's paper "Recursive Functions of Symbolic Expressions and Their Computation by Machine" (1960) and "The Roots of Lisp" (2002) by Paul Graham. -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. +### 0.2.0 (2021-12-04) +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. + +### 0.3.0 (2022-12-12) +Rewrite the evaluation code, add new functions and a core library. + +### 0.3.1 (2022-06-06) +Rewrite parts of the code and add new functions and examples. + +### 0.3.2 (2022-07-02) +- Add new functions +### 0.3.2 (2022-08-25) +- Add new functions -## Types +### 0.4.0 (2022-08-25) +- Rewrite a lot of the code +- Add integer and big integer support +- Add tail call optimization (TCO) +- Add macro support + +## Overview + +### Types - Basics: `bool`, `list`, `symbol`, `string` - Numbers: `float`, `int`, `bigint` -## Seven Primitive Operators +### Built-in Operators - `quote` (with the `'` syntax) +- `quasiquote` (with the `` ` ``) +- `unquote` (with the `,` syntax) +- `unquote-splicing` (with the `,@` syntax) - `atom` (aliased to `atom?`) - `eq` (aliased to `eq?`) - `car` (aliased to `first`) - `cdr` (aliased to `rest`) - `cons` +- `if` - `cond` - -## Two Special Forms -- `label` (aliased to `define` and `def`) -- `lambda` (aliased to `function`, `fun`, and `fn`) - -## Additional Builtins -- `defun` (aliased to `defn`) -- `set` - `while` +- `set` +- `define` (aliased to `def` and `label`) +- `function` (aliased to `fun` and `lambda`) +- `macro` (aliased to `mac`) +- `define-function` (aliased to `def-fun`) +- `define-macro` (aliased to `def-mac`) - `apply` +- `eval` +- `expand` +- `do` (aliased to `begin` and `progn`) +- `load` + +### Primitive Operators +- `append` - `type` - `string` - `string->number` @@ -44,20 +76,19 @@ of strings to the language and reading from the filesystem. - `number->bytes` and `bytes->number` - `regex-find` - `system` -- `load` - Arithmetic operations: `+`, `-`, `*`, `/`, `%`, `^` - Trigonometric functions: `acos`, `asin`, `atan`, `cos`, `sin`, `tan` - Comparisons: `>`, `<`, `>=`, `<=`, `=` -- Boolean operations: `not`, `and`, `or` - String operations: `lines` - File IO: `read-file`, `read-file-bytes`, `write-file-bytes`, `append-file-bytes` -## Core Library +### Core Library - `nil`, `nil?`, `eq?` -- `atom?`, `string?`, `boolean?`, `symbol?`, `number?`, `list?`, `function?`, `lambda?` -- `first`, `second`, `third`, `rest` -- `map`, `reduce`, `append`, `reverse` +- `atom?`, `string?`, `boolean?`, `symbol?`, `number?`, `list?`, `function?`, `macro?` +- `caar`, `cadr`, `cdar`, `cddr`, `first`, `second`, `third`, `rest` +- `map`, `reduce`, `reverse`, `range` +- `let` - `string-join` - `read-line`, `read-char` - `print`, `println` @@ -65,6 +96,8 @@ of strings to the language and reading from the filesystem. - `uptime`, `realtime` - `regex-match?` +- Boolean operations: `not`, `and`, `or` + ## Usage The interpreter can be invoked from the shell: @@ -85,7 +118,7 @@ with the following content: ```lisp (load "/lib/lisp/core.lsp") -(def (fibonacci n) +(define (fibonacci n) (if (< n 2) n (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) @@ -106,21 +139,21 @@ Would produce the following output: ```lisp (load "/lib/lisp/core.lsp") -(def foo 42) # Variable definition +(define foo 42) # Variable definition -(def double (fun (x) (* x 2))) # Function definition -(def (double x) (* x 2)) # Shortcut +(define double (fun (x) (* x 2))) # Function definition +(define (double x) (* x 2)) # Shortcut (double foo) # => 84 -(def (map f ls) +(define (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 +(define bar (quote (1 2 3))) +(define bar '(1 2 3)) # Shortcut (map double bar) # => (2 4 6) @@ -135,7 +168,7 @@ Would produce the following output: (= foo 10) # => true -(def name "Alice") +(define name "Alice") (string "Hello, " name) # => "Hello, Alice" diff --git a/dsk/lib/lisp/alias.lsp b/dsk/lib/lisp/alias.lsp new file mode 100644 index 00000000..b7973e30 --- /dev/null +++ b/dsk/lib/lisp/alias.lsp @@ -0,0 +1,28 @@ +(define def + (macro args `(define ,@args))) + +(define mac + (macro args `(macro ,@args))) + +(define fun + (macro args `(function ,@args))) + +(define def-mac + (macro args `(define-macro ,@args))) + +(define def-fun + (macro args `(define-function ,@args))) + + +(define label + (macro args `(define ,@args))) + +(define lambda + (macro args `(function ,@args))) + +(define progn + (macro args `(do ,@args))) + + +(define begin + (macro args `(do ,@args))) diff --git a/dsk/lib/lisp/core.lsp b/dsk/lib/lisp/core.lsp index 43ac82f2..8124462f 100644 --- a/dsk/lib/lisp/core.lsp +++ b/dsk/lib/lisp/core.lsp @@ -1,99 +1,119 @@ -(def (eq? x y) +(load "/lib/lisp/alias.lsp") + +(define (eq? x y) (eq x y)) -(def (atom? x) +(define (atom? x) (atom x)) -(def (string? x) +(define (string? x) (eq? (type x) "string")) -(def (boolean? x) +(define (boolean? x) (eq? (type x) "boolean")) -(def (symbol? x) +(define (symbol? x) (eq? (type x) "symbol")) -(def (number? x) +(define (number? x) (eq? (type x) "number")) -(def (list? x) +(define (list? x) (eq? (type x) "list")) -(def (function? x) +(define (function? x) (eq? (type x) "function")) -(def nil '()) +(define (macro? x) + (eq? (type x) "macro")) + +(define nil '()) -(def (nil? x) +(define (nil? x) (eq? x nil)) -(def (not x) +(define (not x) (if x false true)) -(def (or x y) - (if x true (if y true false))) +(define-macro (or x y) + `(if ,x true (if ,y true false))) + +(define-macro (and x y) + `(if ,x (if ,y true false) false)) + +(define-macro (let params values body) + `((function ,params ,body) ,@values)) + +(define (caar x) + (car (car x))) + +(define (cadr x) + (car (cdr x))) -(def (and x y) - (if x (if y true false) false)) +(define (cdar x) + (cdr (car x))) -(def (rest x) +(define (cddr x) + (cdr (cdr x))) + +(define (rest x) (cdr x)) -(def (first x) +(define (first x) (car x)) -(def (second x) +(define (second x) (first (rest x))) -(def (third x) +(define (third x) (second (rest x))) -(def (reduce f ls) +(define (reduce f ls) (if (nil? (rest ls)) (first ls) (f (first ls) (reduce f (rest ls))))) -(def (string-join ls s) - (reduce (fn (x y) (string x s y)) ls)) - -(def (map f ls) +(define (map f ls) (if (nil? ls) nil (cons (f (first ls)) (map f (rest ls))))) -(def (reverse x) +(define (reverse x) (if (nil? x) x (append (reverse (rest x)) (cons (first x) '())))) -(def (range i n) +(define (range i n) (if (= i n) nil (append (list i) (range (+ i 1) n)))) -(def (read-line) +(define (string-join ls s) + (reduce (fn (x y) (string x s y)) ls)) + +(define (read-line) (bytes->string (reverse (rest (reverse (read-file-bytes "/dev/console" 256)))))) -(def (read-char) +(define (read-char) (bytes->string (read-file-bytes "/dev/console" 4))) -(def (print exp) +(define (print exp) (do (append-file-bytes "/dev/console" (string->bytes (string exp))) '())) -(def (println exp) +(define (println exp) (print (string exp "\n"))) -(def (uptime) +(define (uptime) (bytes->number (read-file-bytes "/dev/clk/uptime" 8) "float")) -(def (realtime) +(define (realtime) (bytes->number (read-file-bytes "realtime" 8) "float")) -(def (write-file path str) +(define (write-file path str) (write-file-bytes path (string->bytes str))) -(def (append-file path str) +(define (append-file path str) (append-file-bytes path (string->bytes str))) -(def (regex-match? 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 847fd75b..4fec0242 100644 --- a/dsk/tmp/lisp/factorial.lsp +++ b/dsk/tmp/lisp/factorial.lsp @@ -1,10 +1,10 @@ (load "/lib/lisp/core.lsp") -(def (factorial-helper n acc) +(define (factorial-helper n acc) (if (< n 2) acc (factorial-helper (- n 1) (* acc n)))) -(def (factorial n) +(define (factorial n) (factorial-helper n 1)) (println diff --git a/dsk/tmp/lisp/fibonacci.lsp b/dsk/tmp/lisp/fibonacci.lsp index a63513c6..7add019c 100644 --- a/dsk/tmp/lisp/fibonacci.lsp +++ b/dsk/tmp/lisp/fibonacci.lsp @@ -1,6 +1,6 @@ (load "/lib/lisp/core.lsp") -(def (fibonacci n) +(define (fibonacci n) (if (< n 2) n (+ (fibonacci (- n 1)) (fibonacci (- n 2))))) diff --git a/dsk/tmp/lisp/pi.lsp b/dsk/tmp/lisp/pi.lsp index 3d9c3c8c..f20eb24d 100644 --- a/dsk/tmp/lisp/pi.lsp +++ b/dsk/tmp/lisp/pi.lsp @@ -1,26 +1,26 @@ (load "/lib/lisp/core.lsp") -(def (pi-digits digits) +(define (pi-digits digits) (do - (def i 0) - (def q 1) - (def r 0) - (def t 1) - (def k 1) - (def n 3) - (def l 3) + (define i 0) + (define q 1) + (define r 0) + (define t 1) + (define k 1) + (define n 3) + (define l 3) (while (<= i digits) (if (< (- (+ (* q 4) r) t) (* n t)) (do (print (string n (if (= i 0) "." ""))) (set i (+ i 1)) - (def nr (* 10 (- r (* n t)))) + (define 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))) + (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)) diff --git a/dsk/tmp/lisp/sum.lsp b/dsk/tmp/lisp/sum.lsp index abf1bc79..25d3b6db 100644 --- a/dsk/tmp/lisp/sum.lsp +++ b/dsk/tmp/lisp/sum.lsp @@ -1,6 +1,6 @@ (load "/lib/lisp/core.lsp") -(def (sum n acc) +(define (sum n acc) (if (= n 0) acc (sum (- n 1) (+ n acc)))) (println diff --git a/src/usr/install.rs b/src/usr/install.rs index 64e81f3b..60f6ee21 100644 --- a/src/usr/install.rs +++ b/src/usr/install.rs @@ -48,6 +48,7 @@ pub fn copy_files(verbose: bool) { create_dir("/lib/lisp", verbose); copy_file("/lib/lisp/core.lsp", include_bytes!("../../dsk/lib/lisp/core.lsp"), verbose); + copy_file("/lib/lisp/alias.lsp", include_bytes!("../../dsk/lib/lisp/alias.lsp"), verbose); copy_file("/tmp/alice.txt", include_bytes!("../../dsk/tmp/alice.txt"), verbose); copy_file("/tmp/machines.txt", include_bytes!("../../dsk/tmp/machines.txt"), verbose); diff --git a/src/usr/lisp/eval.rs b/src/usr/lisp/eval.rs index e81fdf3c..16019607 100644 --- a/src/usr/lisp/eval.rs +++ b/src/usr/lisp/eval.rs @@ -148,10 +148,19 @@ pub fn eval_args(args: &[Exp], env: &mut Rc>) -> Result, E args.iter().map(|x| eval(x, env)).collect() } -pub const BUILT_INS: [&str; 24] = [ - "quote", "atom", "eq", "car", "cdr", "cons", "cond", "label", "lambda", "define", "def", - "function", "fun", "fn", "if", "while", "defun", "defn", "apply", "eval", "progn", "begin", - "do", "load" +pub const BUILT_INS: [&str; 32] = [ + "quote", "quasiquote", "unquote", "unquote-splicing", + "atom", "eq", "car", "cdr", "cons", + "if", "cond", "while", + "set", + "define", "def", "label", + "function", "fun", "lambda", + "macro", "mac", + "define-function", "def-fun", + "define-macro", "def-mac", + "apply", "eval", "expand", + "do", "begin", "progn", + "load" ]; pub fn eval(exp: &Exp, env: &mut Rc>) -> Result { diff --git a/src/usr/lisp/expand.rs b/src/usr/lisp/expand.rs index d6c6b790..f8c25866 100644 --- a/src/usr/lisp/expand.rs +++ b/src/usr/lisp/expand.rs @@ -55,27 +55,7 @@ pub fn expand(exp: &Exp, env: &mut Rc>) -> Result { ensure_length_eq!(list, 2); expand_quasiquote(&list[1]) } - Exp::Sym(s) if s == "begin" || s == "progn" => { - let mut res = vec![Exp::Sym("do".to_string())]; - res.extend_from_slice(&list[1..]); - expand(&Exp::List(res), env) - } - Exp::Sym(s) if s == "def" || s == "label" => { - let mut res = vec![Exp::Sym("define".to_string())]; - res.extend_from_slice(&list[1..]); - expand(&Exp::List(res), env) - } - Exp::Sym(s) if s == "fun" || s == "fn" || s == "lambda" => { - let mut res = vec![Exp::Sym("function".to_string())]; - res.extend_from_slice(&list[1..]); - expand(&Exp::List(res), env) - } - Exp::Sym(s) if s == "mac" => { - let mut res = vec![Exp::Sym("macro".to_string())]; - res.extend_from_slice(&list[1..]); - expand(&Exp::List(res), env) - } - Exp::Sym(s) if s == "define-function" || s == "def-fun" || s == "define" => { + Exp::Sym(s) if s == "define-function" || s == "define" => { ensure_length_eq!(list, 3); match (&list[1], &list[2]) { (Exp::List(args), Exp::List(_)) => { @@ -93,7 +73,7 @@ pub fn expand(exp: &Exp, env: &mut Rc>) -> Result { _ => Err(Err::Reason("Expected first argument to be a symbol or a list".to_string())) } } - Exp::Sym(s) if s == "define-macro" || s == "def-mac" => { + Exp::Sym(s) if s == "define-macro" => { ensure_length_eq!(list, 3); match (&list[1], &list[2]) { (Exp::List(args), Exp::List(_)) => { diff --git a/src/usr/lisp/mod.rs b/src/usr/lisp/mod.rs index 0434c22e..2f3beabf 100644 --- a/src/usr/lisp/mod.rs +++ b/src/usr/lisp/mod.rs @@ -169,6 +169,7 @@ fn parse_eval(exp: &str, env: &mut Rc>) -> Result { } fn strip_comments(s: &str) -> String { + // FIXME: This doesn't handle `#` inside a string s.split('#').next().unwrap().into() } @@ -343,7 +344,7 @@ fn test_lisp() { 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"); + assert_eq!(eval!("(do (define i 0) (while (< i 5) (set i (+ i 1))) i)"), "5"); // define eval!("(define a 2)");