|
1 | 1 | # lwhlisp
|
2 | 2 | lwhlisp is a lisp interpreter written in rust, based on [this tutorial](https://www.lwh.jp/lisp/index.html "Building LISP").
|
3 | 3 |
|
4 |
| -# Getting started |
| 4 | +## Getting started |
5 | 5 |
|
6 | 6 | Install [Rust](https://www.rust-lang.org/ "Rust Programming Language") and clone this repository.
|
7 | 7 | Then, inside the repository:
|
8 | 8 |
|
9 | 9 | ```sh
|
10 |
| -cargo run |
| 10 | +cargo run --release |
11 | 11 | ```
|
12 | 12 |
|
13 |
| -This will compile the project, then start an interactive session (no compilation is required on subsequent runs). |
| 13 | +This will compile the project and launch a REPL. The finished binary is `target/release/lwhlisp`. |
| 14 | + |
| 15 | +**NOTE**: |
14 | 16 | The interactive session will start by loading the small included standard library (you can find the library in lib/lib.lisp).
|
15 | 17 |
|
16 |
| -It should look something like this: |
| 18 | +If no such file is found, it will fail to load and you will get an error that looks like this: |
| 19 | + |
| 20 | +```sh |
| 21 | +Error: |
| 22 | + 0: While opening library file |
| 23 | + 1: While opening file lib/lib.lisp |
| 24 | + 2: No such file or directory (os error 2) |
| 25 | +``` |
| 26 | + |
| 27 | +You can solve this problem by manually indicating where lwhlisp can find the library file: |
| 28 | + |
| 29 | +```sh |
| 30 | +cargo run --release -- --library /path/to/library/file.lisp |
| 31 | +``` |
| 32 | +(The `--` separates arguments to cargo and arguments to lwhlisp. It can be omited when calling the `lwhlisp` binary directly.) |
| 33 | + |
| 34 | +The REPL should look something like this: |
17 | 35 |
|
18 | 36 | ```common-lisp
|
19 |
| -Loading standard library... |
20 |
| -(define eq? =) => eq? |
21 |
| -(define (abs x) (if (< x 0) (- 0 x) x)) => abs |
22 |
| -(define (foldl proc init list) (if list (foldl proc (proc init (car list)) (cdr list)) init)) => foldl |
23 |
| -[snip] |
24 |
| -Finished. |
25 | 37 | user>
|
26 | 38 | ```
|
27 | 39 |
|
28 |
| -As you can see, each s-expression from the library file is parsed, evaluated, and then the parsed expression is printed, along with the result. |
29 |
| - |
30 | 40 | Once you are at the `user>` prompt, you may enter lisp code to be evaluated.
|
31 | 41 |
|
32 |
| -**Note:** |
33 |
| -`()` is converted into `nil` at parse time. |
| 42 | +```common-lisp |
| 43 | +user> (+ 1 2 3) |
| 44 | +=> 6 |
| 45 | +user> (if (> 5 4) (println "Five is bigger than Four") (println "Five is smaller than Four")) |
| 46 | +Five is bigger than Four |
| 47 | +=> "Five is bigger than Four" |
| 48 | +``` |
34 | 49 |
|
35 |
| -# Special forms |
| 50 | +You can also run files: |
36 | 51 |
|
37 |
| -## `quote` |
| 52 | +```sh |
| 53 | +cargo run --release -- -f file.lisp |
| 54 | +``` |
| 55 | + |
| 56 | +**factorial.lisp**: |
| 57 | + |
| 58 | +```common-lisp |
| 59 | +(define (factorial x) |
| 60 | + (if (= x 0) |
| 61 | + 1 |
| 62 | + (* x (factorial (- x 1))))) |
| 63 | +
|
| 64 | +(println (factorial 10)) |
| 65 | +``` |
| 66 | + |
| 67 | +```sh |
| 68 | +$ cargo run --release -- -f file.lisp |
| 69 | +3628800 |
| 70 | +``` |
| 71 | + |
| 72 | +## Syntax |
| 73 | +`()` is converted into `nil` at parse time. |
| 74 | + |
| 75 | +### `quote` |
38 | 76 |
|
39 | 77 | Takes a single argument, and returns it without evaluating
|
40 |
| -``` common-lisp |
41 |
| -(quote (a b c)) => (a b c) |
| 78 | +```common-lisp |
| 79 | +user> (quote (a b c)) |
| 80 | +=> (a b c) |
42 | 81 | ```
|
43 | 82 |
|
44 |
| -Since this is a bit long to write, a shorthand is provided: |
45 |
| -``` common-lisp |
46 |
| -'(a b c) => (a b c) |
| 83 | +Since this is used frequently, a shorthand is provided: |
| 84 | +```common-lisp |
| 85 | +user> '(a b c) |
| 86 | +=> (a b c) |
47 | 87 | ```
|
48 | 88 |
|
49 | 89 | The shorthand gets converted into the full version at parse time.
|
50 | 90 |
|
51 |
| -## `lambda` |
| 91 | +### `lambda` |
52 | 92 |
|
53 |
| -``` common-lisp |
54 |
| -(lambda (x) (* x x)) => (lambda (x) ((* x x))) |
55 |
| -(lambda (x) (+ x x) (* x x)) => (lambda (x) ((+ x x) (* x x))) |
| 93 | +```common-lisp |
| 94 | +user> (lambda (x) (* x x)) |
| 95 | +=> (lambda (x) (* x x)) |
56 | 96 | ```
|
57 | 97 |
|
58 |
| -**NOTE:** |
59 |
| -The printing of lambda expressions is flawed. |
60 |
| -Since lambda expressions can contain multiple statements, they are internally stored as a list, and the printing reflects that. |
61 |
| -Ignore the extra set of parenthesis around the body. |
| 98 | +You can have multiple s-expressions in the body: |
62 | 99 |
|
63 |
| -Evaluation: |
64 |
| -``` common-lisp |
65 |
| -((lambda (x) (* x x)) 7) => 49 |
66 |
| -((lambda (x) (+ x x) (* x x)) 7) => 49 |
| 100 | +```common-lisp |
| 101 | +user> (lambda (x) (println x) (* x x)) |
| 102 | +=> (lambda (x) (println x) (* x x)) |
67 | 103 | ```
|
68 | 104 |
|
69 |
| -Only the last s-expression is returned. |
| 105 | +Lambdas can be directly evaluated: |
| 106 | +```common-lisp |
| 107 | +user> ((lambda (x) (* x x)) 7) |
| 108 | +=> 49 |
| 109 | +user> ((lambda (x) (println x) (* x x)) 7) |
| 110 | +7 |
| 111 | +=> 49 |
| 112 | +``` |
70 | 113 |
|
71 |
| -## `define` |
| 114 | +Note that only the last s-expression is returned. |
| 115 | + |
| 116 | +### `define` |
72 | 117 |
|
73 | 118 | Binds a symbol to a value.
|
74 | 119 |
|
75 | 120 | Basic syntax:
|
76 |
| -``` common-lisp |
77 |
| -(define x 7) => x |
78 |
| -x => 7 |
| 121 | +```common-lisp |
| 122 | +user> (define x 7) |
| 123 | +=> x |
| 124 | +user> x |
| 125 | +=> 7 |
79 | 126 | ```
|
80 | 127 |
|
81 | 128 | Creating a function:
|
82 |
| -``` common-lisp |
83 |
| -(define square (lambda (x) (* x x))) => square |
84 |
| -(square 7) => 49 |
| 129 | +```common-lisp |
| 130 | +user> (define square (lambda (x) (* x x))) |
| 131 | +=> square |
| 132 | +user> (square 7) |
| 133 | +=> 49 |
85 | 134 | ```
|
86 | 135 |
|
87 | 136 | Since this is a frequent action, there is special syntax for this:
|
88 |
| -``` common-lisp |
89 |
| -(define (square x) (* x x)) => square |
90 |
| -(square 7) => 49 |
| 137 | +```common-lisp |
| 138 | +user> (define (square x) (* x x)) |
| 139 | +=> square |
| 140 | +user> (square 7) |
| 141 | +=> 49 |
91 | 142 | ```
|
92 | 143 |
|
93 | 144 | You can choose to get the arguments as a list instead:
|
94 |
| -``` common-lisp |
95 |
| -(define (x . a) a) => x |
96 |
| -(x 1 2 3) => (1 2 3) |
| 145 | +```common-lisp |
| 146 | +user> (define (x . a) a) |
| 147 | +=> x |
| 148 | +user> (x 1 2 3) |
| 149 | +=> (1 2 3) |
97 | 150 | ```
|
98 | 151 |
|
99 | 152 | Or have one (or more) required arguments, and get the rest as a list:
|
100 | 153 |
|
101 |
| -``` common-lisp |
102 |
| -(define (x a . b) (list a b)) => x |
103 |
| -(x 1 2 3) => (1 (2 3)) |
| 154 | +```common-lisp |
| 155 | +user> (define (x a . b) (println a) (println b)) |
| 156 | +=> x |
| 157 | +user> (x 1 2 3) |
| 158 | +1 |
| 159 | +(2 3) |
| 160 | +=> "(2 3)" |
104 | 161 | ```
|
105 | 162 |
|
106 |
| -(list is a function from the standard library that constructs a list will all of its arguments) |
| 163 | +(list is a function from the standard library that constructs a list from all of its arguments) |
107 | 164 |
|
108 |
| -## `defmacro` |
| 165 | +### `defmacro` |
109 | 166 |
|
110 |
| -Macros work the exact same way as function, except that the arguments to macros are not evaluated. |
| 167 | +Macros work the same way as function, except that the arguments to macros are not evaluated. |
111 | 168 |
|
112 | 169 | Macros use the following syntax:
|
113 |
| -``` common-lisp |
| 170 | +```common-lisp |
114 | 171 | (defmacro (name arg...) body...)
|
115 | 172 | ```
|
116 | 173 |
|
117 | 174 | For example, consider the following macro:
|
118 |
| -``` common-lisp |
119 |
| -(defmacro (ignore x) (cons 'quote (cons x nil))) => ignore |
| 175 | +```common-lisp |
| 176 | +(defmacro (ignore x) |
| 177 | + (cons 'quote (cons x nil))) |
120 | 178 | ```
|
121 | 179 |
|
122 | 180 | If we then evaluate the expression
|
123 |
| -``` common-lisp |
124 |
| -(ignore foo) => foo |
| 181 | +```common-lisp |
| 182 | +user> (ignore foo) |
| 183 | +=> foo |
125 | 184 | ```
|
126 | 185 | where foo is a (potentially unbound) symbol, the body of `ignore` will be evaluated with the argument `x` bound to the *unevaluated* symbol `foo`.
|
127 | 186 | The result of this is:
|
128 |
| -``` common-lisp |
| 187 | +```common-lisp |
129 | 188 | (quote . (foo . nil))
|
130 | 189 | ```
|
131 | 190 | which is equivalent to:
|
132 |
| -``` common-lisp |
| 191 | +```common-lisp |
133 | 192 | (quote foo)
|
134 | 193 | ```
|
135 | 194 | or
|
136 |
| -``` common-lisp |
| 195 | +```common-lisp |
137 | 196 | 'foo
|
138 | 197 | ```
|
139 | 198 |
|
140 | 199 | Finally, evaluating this value will give us the result of evaluating the macro body:
|
141 |
| -``` common-lisp |
| 200 | +```common-lisp |
142 | 201 | foo
|
143 | 202 | ```
|
144 | 203 |
|
145 |
| -## `if` |
| 204 | +### `if` |
146 | 205 |
|
147 | 206 | The syntax is as follows:
|
148 |
| -``` common-lisp |
| 207 | +```common-lisp |
149 | 208 | (if test true-expr false-expr)
|
150 | 209 | ```
|
151 | 210 |
|
152 | 211 | If `test` is not nil, the result of evaluating this expression will be `false-expr`. Else, it will be `true-expr`.
|
153 | 212 |
|
154 |
| -# Example |
| 213 | +## Example |
155 | 214 | This is a simple program that calculates factorials in a recursive fashion:
|
156 |
| -``` common-lisp |
| 215 | +```common-lisp |
157 | 216 | (define (factorial x)
|
158 |
| - (if (= x 0) |
159 |
| - 1 |
160 |
| - (* x (factorial (- x 1))))) |
| 217 | + (if (= x 0) |
| 218 | + 1 |
| 219 | + (* x (factorial (- x 1))))) |
161 | 220 | ```
|
162 | 221 |
|
163 | 222 | The base case, if `x=0`, will return `1`.
|
164 | 223 | In all other cases, we will return `fact(x - 1) * x`.
|
165 | 224 |
|
166 |
| -``` common-lisp |
167 |
| -(factorial 10) => 3628800 |
| 225 | +```common-lisp |
| 226 | +user> (factorial 10) |
| 227 | +=> 3628800 |
168 | 228 | ```
|
169 |
| - |
170 |
| - |
171 |
| -# TODO |
172 |
| -- [X] When redefining recursive functions, the old version persists in the environment of the new functions, causing recursion to use the old version of the function. |
|
0 commit comments