├── Cargo.toml
├── fib10.lisp
├── fib10.js
├── .gitignore
├── README.md
└── src
└── main.rs
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "lisp-to-js"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [dependencies]
7 | pom = "3.4.0"
8 |
--------------------------------------------------------------------------------
/fib10.lisp:
--------------------------------------------------------------------------------
1 | (let ((fib (lambda (n)
2 | (if (< n 2)
3 | n
4 | (+ (fib (- n 1)) (fib (- n 2)))))))
5 | (print (fib 10)))
6 |
--------------------------------------------------------------------------------
/fib10.js:
--------------------------------------------------------------------------------
1 | /* lisp-to-js */
2 | let print = console.log;
3 |
4 |
5 | (() => {
6 | let fib = ((n) => n < 2 ? n : ( fib (( n -1), )+ fib (( n -2), ))
7 |
8 | )
9 | ; print ( fib (10, ), )
10 | })()
11 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | debug/
4 | target/
5 |
6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
8 | Cargo.lock
9 |
10 | # These are backup files generated by rustfmt
11 | **/*.rs.bk
12 |
13 | # MSVC Windows builds of rustc generate these, which store debugging information
14 | *.pdb
15 |
16 | # Files used for local debugging
17 | _.lisp
18 | _.js
19 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # lisp-to-js
2 |
3 | > My blog posts:
4 | >
5 | > - [Compiling Lisp to Bytecode and Running It](https://healeycodes.com/compiling-lisp-to-bytecode-and-running-it)
6 | > - [Lisp Compiler Optimizations](https://healeycodes.com/lisp-compiler-optimizations)
7 | > - [Lisp to JavaScript Compiler](https://healeycodes.com/lisp-to-javascript-compiler)
8 |
9 |
10 |
11 | This project is an optmizing Lisp compiler and bytecode VM.
12 |
13 | It can compile to JavaScript, or compile to bytecode and execute in a VM.
14 |
15 |
16 |
17 | Bytecode VM:
18 |
19 | ```
20 | ./program --vm --debug < fib10.lisp
21 | 0: push_closure ["n"]
22 | -> 0: load_var n
23 | -> 1: push_const 2.0
24 | -> 2: less_than
25 | -> 3: jump 6 // go to 6
26 | -> 4: load_var n
27 | -> 5: jump 17 // exit
28 | -> 6: load_var n
29 | -> 7: push_const 1.0
30 | -> 8: sub 2
31 | -> 9: load_var fib
32 | -> 10: call_lambda 1
33 | -> 11: load_var n
34 | -> 12: push_const 2.0
35 | -> 13: sub 2
36 | -> 14: load_var fib
37 | -> 15: call_lambda 1
38 | -> 16: add 2
39 | 1: store_var fib
40 | 2: push_const 10.0
41 | 3: load_var fib
42 | 4: call_lambda 1
43 | 5: load_var print
44 | 6: call_lambda 1
45 |
46 | 55
47 | ```
48 |
49 |
50 |
51 | Compile to JavaScript:
52 |
53 | ```
54 | ./program --js < fib10.lisp
55 | /* lisp-to-js */
56 | let print = console.log;
57 |
58 |
59 | (() => {
60 | let fib = ((n) => n < 2 ? n : ( fib (( n -1), )+ fib (( n -2), ))
61 |
62 | )
63 | ; print ( fib (10, ), )
64 | })()
65 | ```
66 |
67 |
68 |
69 | The implemented optimizations are constant folding and propagation, and dead
70 | code elimination:
71 |
72 | ```lisp
73 | ; before optimization
74 | (let ((b 2) (c 3))
75 | (print
76 | (+
77 | (+ b 4 c)
78 | (- b c 7)
79 | )))
80 |
81 | ; after optimization
82 | (let () (print 1))
83 | ```
84 |
85 |
86 |
87 | The Lisp variant is very similar to
88 | [Little Lisp](https://maryrosecook.com/blog/post/little-lisp-interpreter).
89 |
90 | ```lisp
91 | ; atoms
92 | 1 ; f64 numbers
93 | a ; symbols
94 |
95 | ; arithmetic expressions
96 | (+ 1 2) ; 3
97 | (- 1 2) ; -1
98 |
99 | ; control flow expressions
100 | (< 1 2) ; true
101 | (> 1 2) ; false
102 | (if (< 1 2) (+ 10 10) (+ 10 5)) ; 20
103 |
104 | ; lambda expressions
105 | (lambda (x) (+ x x)) ; function that doubles
106 |
107 | ; variable definition
108 | (let ((a 1)) (print a)) ; prints 1
109 | (let ((double (lambda (x) (+ x x)))) (double 2)) ; 4
110 | ```
111 |
112 |
113 |
114 | ### Run
115 |
116 | Required (one of):
117 |
118 | - `--js` output JavaScript to stdout
119 | - `--vm` compile to bytecode and execute in VM
120 |
121 | Optional:
122 |
123 | - `--optimize` for optimization
124 | - `--debug` show annotated bytecode
125 |
126 |
127 |
128 | ### Tests
129 |
130 | ```
131 | cargo test
132 | ```
133 |
--------------------------------------------------------------------------------
/src/main.rs:
--------------------------------------------------------------------------------
1 | use pom::parser::*;
2 | use std::{
3 | cell::RefCell,
4 | collections::HashMap,
5 | env,
6 | error::Error,
7 | fmt,
8 | io::{stdin, Read},
9 | process::{self, exit},
10 | rc::Rc,
11 | str::{self, FromStr},
12 | };
13 |
14 | #[derive(Clone, Debug, PartialEq)]
15 | enum Expression {
16 | Atom(Atom),
17 | List(Vec),
18 | LetExpression(LetExpression),
19 | LambdaExpression(LambdaExpression),
20 | IfExpression(Box),
21 | ArithmeticExpression(Box),
22 | }
23 |
24 | #[derive(Clone, Debug, PartialEq)]
25 | enum Atom {
26 | Boolean(bool),
27 | Number(f64),
28 | Symbol(String),
29 | }
30 |
31 | #[derive(Clone, Debug, PartialEq)]
32 | struct LetExpression {
33 | bindings: Vec,
34 | expressions: Vec,
35 | }
36 |
37 | #[derive(Clone, Debug, PartialEq)]
38 | struct Binding {
39 | symbol: String,
40 | expression: Expression,
41 | }
42 |
43 | #[derive(Clone, Debug, PartialEq)]
44 | struct LambdaExpression {
45 | parameters: Vec,
46 | expressions: Vec,
47 | }
48 |
49 | #[derive(Clone, Debug, PartialEq)]
50 | struct IfExpression {
51 | check: Expression,
52 | r#true: Expression,
53 | r#false: Expression,
54 | }
55 |
56 | #[derive(Clone, Debug, PartialEq)]
57 | struct ArithmeticExpression {
58 | op: Op,
59 | expressions: Vec,
60 | }
61 |
62 | #[derive(Clone, Debug, PartialEq)]
63 | enum Op {
64 | Plus,
65 | Minus,
66 | LessThan,
67 | GreaterThan,
68 | }
69 |
70 | fn space<'a>() -> Parser<'a, u8, ()> {
71 | one_of(b" \t\r\n").repeat(0..).discard()
72 | }
73 |
74 | fn lparen<'a>() -> Parser<'a, u8, ()> {
75 | space() * seq(b"(").discard() - space()
76 | }
77 |
78 | fn rparen<'a>() -> Parser<'a, u8, ()> {
79 | space() * seq(b")").discard() - space()
80 | }
81 |
82 | fn number<'a>() -> Parser<'a, u8, f64> {
83 | let number = one_of(b"123456789") - one_of(b"0123456789").repeat(0..) | sym(b'0');
84 | number
85 | .collect()
86 | .convert(str::from_utf8)
87 | .convert(f64::from_str)
88 | }
89 |
90 | fn symbol<'a>() -> Parser<'a, u8, String> {
91 | space()
92 | * one_of(b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
93 | .repeat(1..)
94 | .convert(String::from_utf8)
95 | - space()
96 | }
97 |
98 | fn atom<'a>() -> Parser<'a, u8, Atom> {
99 | space() * (number().map(|n| Atom::Number(n)) | symbol().map(|s| Atom::Symbol(s))) - space()
100 | }
101 |
102 | fn expression<'a>() -> Parser<'a, u8, Expression> {
103 | space()
104 | * (((call(let_expression)
105 | | call(lambda_expression)
106 | | call(if_expression)
107 | | call(arithmetic_expression))
108 | | lparen() * call(expression).repeat(0..).map(Expression::List) - rparen())
109 | | atom().map(|a| Expression::Atom(a)))
110 | - space()
111 | }
112 |
113 | fn let_expression<'a>() -> Parser<'a, u8, Expression> {
114 | (lparen() - seq(b"let"))
115 | * (bindings() + expression().repeat(0..)).map(|(bindings, expressions)| {
116 | Expression::LetExpression(LetExpression {
117 | bindings,
118 | expressions,
119 | })
120 | })
121 | - rparen()
122 | }
123 |
124 | fn bindings<'a>() -> Parser<'a, u8, Vec> {
125 | lparen()
126 | * ((lparen() * symbol() + expression() - rparen())
127 | .repeat(0..)
128 | .map(|bindings| {
129 | bindings
130 | .into_iter()
131 | .map(|(sym, expr)| Binding {
132 | symbol: sym,
133 | expression: expr,
134 | })
135 | .collect()
136 | }))
137 | - rparen()
138 | }
139 |
140 | fn lambda_expression<'a>() -> Parser<'a, u8, Expression> {
141 | ((lparen() + seq(b"lambda") + lparen()).discard() * symbol().repeat(0..) - rparen()
142 | + expression().repeat(1..)
143 | - rparen())
144 | .map(|(parameters, expressions)| {
145 | Expression::LambdaExpression(LambdaExpression {
146 | parameters,
147 | expressions,
148 | })
149 | })
150 | }
151 |
152 | fn if_expression<'a>() -> Parser<'a, u8, Expression> {
153 | ((lparen() + seq(b"if")).discard() * expression() + expression() + expression() - rparen()).map(
154 | |expressions| {
155 | Expression::IfExpression(Box::new(IfExpression {
156 | check: expressions.0 .0,
157 | r#true: expressions.0 .1,
158 | r#false: expressions.1,
159 | }))
160 | },
161 | )
162 | }
163 |
164 | fn arithmetic_expression<'a>() -> Parser<'a, u8, Expression> {
165 | let sum_or_difference = one_of(b"+-") + expression().repeat(2..);
166 | let lt_or_gt = one_of(b"<>") + expression().repeat(2);
167 | (lparen() * (sum_or_difference | lt_or_gt) - rparen()).map(|(op, expressions)| {
168 | Expression::ArithmeticExpression(Box::new(ArithmeticExpression {
169 | op: match op {
170 | b'+' => Op::Plus,
171 | b'-' => Op::Minus,
172 | b'<' => Op::LessThan,
173 | b'>' => Op::GreaterThan,
174 | _ => unreachable!(""),
175 | },
176 | expressions: expressions,
177 | }))
178 | })
179 | }
180 |
181 | fn program<'a>() -> Parser<'a, u8, Vec> {
182 | expression().repeat(0..) - end()
183 | }
184 |
185 | fn compile_expression(expression: Expression) -> String {
186 | let mut ret = String::new();
187 | match expression {
188 | Expression::Atom(a) => match a {
189 | Atom::Boolean(b) => match b {
190 | true => ret.push_str("true"),
191 | false => ret.push_str("false"),
192 | },
193 | Atom::Number(n) => ret.push_str(&n.to_string()),
194 | Atom::Symbol(s) => ret.push_str(&format!(" {} ", &s.to_string())),
195 | },
196 | Expression::List(list) => {
197 | let mut i = 0;
198 | list.into_iter().for_each(|expression| {
199 | ret.push_str(&compile_expression(expression));
200 | if i == 0 {
201 | ret.push_str("(")
202 | } else {
203 | ret.push_str(", ")
204 | }
205 | i += 1;
206 | });
207 | if i > 0 {
208 | ret.push_str(")")
209 | }
210 | }
211 | Expression::LetExpression(let_expression) => {
212 | let mut bound_area = "(() => {\n".to_string();
213 | let_expression.bindings.into_iter().for_each(|binding| {
214 | bound_area.push_str(&format!(
215 | "let {} = {};",
216 | binding.symbol,
217 | compile_expression(binding.expression)
218 | ));
219 | });
220 | let_expression
221 | .expressions
222 | .into_iter()
223 | .for_each(|expression| {
224 | bound_area.push_str(&compile_expression(expression));
225 | });
226 | bound_area.push_str("\n})()");
227 | ret.push_str(&bound_area);
228 | }
229 | Expression::LambdaExpression(lambda_expression) => {
230 | let params = lambda_expression.parameters.join(",");
231 | let mut body = "".to_string();
232 |
233 | for expression in lambda_expression.expressions {
234 | body.push_str(&format!("{}\n", &compile_expression(expression)));
235 | }
236 |
237 | ret.push_str(&format!(" (({}) => {})\n", params, body));
238 | }
239 | Expression::IfExpression(if_expression) => ret.push_str(&format!(
240 | "{} ? {} : {}\n",
241 | compile_expression(if_expression.check),
242 | compile_expression(if_expression.r#true),
243 | compile_expression(if_expression.r#false)
244 | )),
245 | Expression::ArithmeticExpression(arithmetic_expression) => {
246 | let mut compiled_expressions: Vec = vec![];
247 | for expression in arithmetic_expression.expressions {
248 | compiled_expressions.push(compile_expression(expression));
249 | }
250 |
251 | match arithmetic_expression.op {
252 | Op::Plus => {
253 | ret.push_str("(");
254 | ret.push_str(&compiled_expressions.join("+"));
255 | ret.push_str(")");
256 | }
257 | Op::Minus => {
258 | ret.push_str("(");
259 | ret.push_str(&compiled_expressions.join("-"));
260 | ret.push_str(")");
261 | }
262 | Op::LessThan => {
263 | ret.push_str(&compiled_expressions.join(" < "));
264 | }
265 | Op::GreaterThan => {
266 | ret.push_str(&compiled_expressions.join(" > "));
267 | }
268 | }
269 | }
270 | };
271 | ret
272 | }
273 |
274 | fn compile(program: Vec) -> String {
275 | // Uncomment for debugging
276 | // println!("compiling: {:?}\n", program);
277 |
278 | let mut output = "/* lisp-to-js */
279 | let print = console.log;
280 |
281 |
282 | "
283 | .to_string();
284 |
285 | program.into_iter().for_each(|expression| {
286 | output.push_str(&compile_expression(expression));
287 | });
288 |
289 | output
290 | }
291 |
292 | fn optimize(program: Vec) -> Vec {
293 | return program
294 | .into_iter()
295 | .map(|expr| optimize_expression(expr, &mut HashMap::new()))
296 | .collect();
297 | }
298 |
299 | // get_expr_from_context returns an atom mber from a context if it exists
300 | fn get_expr_from_context(
301 | symbol: String,
302 | context: &HashMap>,
303 | ) -> Option {
304 | match context.get(&symbol) {
305 | Some(expr) => match expr {
306 | Some(expr) => match expr {
307 | Expression::Atom(atom) => match atom {
308 | Atom::Number(n) => Some(Atom::Number(*n)),
309 | Atom::Boolean(b) => Some(Atom::Boolean(*b)),
310 | _ => None,
311 | },
312 | _ => None,
313 | },
314 | _ => None,
315 | },
316 | None => None,
317 | }
318 | }
319 |
320 | fn optimize_expression(
321 | expression: Expression,
322 | context: &mut HashMap>,
323 | ) -> Expression {
324 | match expression {
325 | // Only internal optimizations are possible
326 | Expression::List(list_expr) => {
327 | return Expression::List(
328 | list_expr
329 | .into_iter()
330 | .map(|expr| optimize_expression(expr, context))
331 | .collect(),
332 | )
333 | }
334 |
335 | // Only the internals of let expressions can be optimized.
336 | // The bindings can be reduced to an empty list of bindings if they all fold into number assignments
337 | // (let (a 1) a) -> (let () 1)
338 | Expression::LetExpression(let_expr) => {
339 | let mut optimized_bindings: Vec = vec![];
340 |
341 | let_expr.bindings.into_iter().for_each(|binding| {
342 | let binding_expr = optimize_expression(binding.expression, context);
343 |
344 | // When the expression we're about to bind is an atom, we can optimize the binding away
345 | match binding_expr {
346 | Expression::Atom(ref atom) => match atom {
347 | // Insert literals, overwriting variables from any higher scopes.
348 | // Return before pushing the binding so it's removed from the AST
349 | Atom::Number(n) => {
350 | context
351 | .insert(binding.symbol, Some(Expression::Atom(Atom::Number(*n))));
352 | return;
353 | }
354 | Atom::Boolean(b) => {
355 | context
356 | .insert(binding.symbol, Some(Expression::Atom(Atom::Boolean(*b))));
357 | return;
358 | }
359 |
360 | // No need to overwrite symbols that refer to already-tracked and potentially optimized values
361 | Atom::Symbol(s) => match context.get(s) {
362 | Some(_) => return,
363 | None => {}
364 | },
365 | },
366 | _ => {}
367 | }
368 |
369 | // This binding can't be removed but may have been optimized internally
370 | optimized_bindings.push(Binding {
371 | symbol: binding.symbol,
372 | expression: binding_expr,
373 | })
374 | });
375 |
376 | return Expression::LetExpression(LetExpression {
377 | bindings: optimized_bindings,
378 | expressions: let_expr
379 | .expressions
380 | .into_iter()
381 | .map(|expr| optimize_expression(expr, context))
382 | .collect(),
383 | });
384 | }
385 |
386 | // Only internal optimizations are possible
387 | Expression::LambdaExpression(lambda_expr) => {
388 | Expression::LambdaExpression(LambdaExpression {
389 | parameters: lambda_expr.parameters,
390 | expressions: lambda_expr
391 | .expressions
392 | .into_iter()
393 | .map(|expr| optimize_expression(expr, context))
394 | .collect(),
395 | })
396 | }
397 |
398 | // The goal with if expressions is to remove the check and replace it with the winning branch
399 | Expression::IfExpression(if_expr) => {
400 | let check_expr = optimize_expression(if_expr.check, context);
401 | match check_expr {
402 | Expression::Atom(ref atom) => match atom {
403 | Atom::Boolean(b) => {
404 | if *b {
405 | return optimize_expression(if_expr.r#true, context);
406 | } else {
407 | return optimize_expression(if_expr.r#false, context);
408 | }
409 | }
410 | _ => {}
411 | },
412 | _ => {}
413 | }
414 | return Expression::IfExpression(Box::new(IfExpression {
415 | check: optimize_expression(check_expr, context),
416 | r#true: optimize_expression(if_expr.r#true, context),
417 | r#false: optimize_expression(if_expr.r#false, context),
418 | }));
419 | }
420 |
421 | // Arithmetic expressions can be replaced with atoms or reduced
422 | // (+ 1 2) -> 3
423 | // (+ 1 a 2) -> (+ a 3)
424 | // (< 1 2) -> true
425 | // (< 1 2 a) -> (< 1 a)
426 | Expression::ArithmeticExpression(arth_expr) => {
427 | let optimized_exprs: Vec = arth_expr
428 | .expressions
429 | .into_iter()
430 | .map(|expr| optimize_expression(expr, context))
431 | .collect();
432 |
433 | if optimized_exprs.len() < 2 {
434 | unreachable!("parser (should) assume arithmetic expressions contain 2+ items")
435 | }
436 |
437 | let mut nums: Vec = vec![];
438 | let mut optimized_exprs_without_numbers: Vec = vec![];
439 | for expr in &optimized_exprs {
440 | match expr {
441 | Expression::Atom(atom) => match atom {
442 | Atom::Number(n) => nums.push(*n),
443 | Atom::Boolean(b) => optimized_exprs_without_numbers
444 | .push(Expression::Atom(Atom::Boolean(*b))),
445 | Atom::Symbol(s) => match get_expr_from_context(s.to_string(), context) {
446 | Some(atom) => match atom {
447 | Atom::Number(n) => nums.push(n),
448 | Atom::Boolean(_) => unreachable!("parser (should) have stopped a bool from entering an arithmetic expression"),
449 | Atom::Symbol(_) => unreachable!("optimizer shouldn't insert symbols into context"),
450 | },
451 | _ => optimized_exprs_without_numbers
452 | .push(Expression::Atom(Atom::Symbol(s.to_string()))),
453 | },
454 | },
455 | Expression::List(list_expr) => {
456 | optimized_exprs_without_numbers.push(Expression::List(list_expr.to_vec()))
457 | }
458 | Expression::LetExpression(let_expr) => optimized_exprs_without_numbers
459 | .push(Expression::LetExpression(let_expr.clone())),
460 | Expression::LambdaExpression(lambda_expr) => optimized_exprs_without_numbers
461 | .push(Expression::LambdaExpression(lambda_expr.clone())),
462 | Expression::IfExpression(if_expr) => optimized_exprs_without_numbers
463 | .push(Expression::IfExpression(if_expr.clone())),
464 | Expression::ArithmeticExpression(arth_expr) => optimized_exprs_without_numbers
465 | .push(Expression::ArithmeticExpression(arth_expr.clone())),
466 | }
467 | }
468 |
469 | // When there are no literals (after optimization) we just return as-is
470 | if nums.len() == 0 {
471 | return Expression::ArithmeticExpression(Box::new(ArithmeticExpression {
472 | op: arth_expr.op,
473 | expressions: optimized_exprs,
474 | }));
475 | }
476 |
477 | match arth_expr.op {
478 | Op::Plus => {
479 | // Best case: no expressions after optimization, return atom
480 | if optimized_exprs_without_numbers.len() == 0 {
481 | return Expression::Atom(Atom::Number(nums.iter().sum()));
482 | }
483 |
484 | // Sum any literals, may reduce add-operations produced at code generation
485 | optimized_exprs_without_numbers
486 | .push(Expression::Atom(Atom::Number(nums.iter().sum())));
487 | return Expression::ArithmeticExpression(Box::new(ArithmeticExpression {
488 | op: arth_expr.op,
489 | expressions: optimized_exprs_without_numbers,
490 | }));
491 | }
492 | Op::Minus => {
493 | // Note: the minus expression isn't "negate all numbers"
494 | // it's minus all numbers from the first
495 | let first = *nums.first().unwrap_or(&0.0);
496 | let compressed =
497 | Atom::Number(nums.iter().skip(1).fold(first, |acc, &x| acc - x));
498 |
499 | // Best case: no expressions after optimization, return atom
500 | if optimized_exprs_without_numbers.len() == 0 {
501 | return Expression::Atom(compressed);
502 | }
503 |
504 | optimized_exprs_without_numbers.push(Expression::Atom(compressed));
505 | return Expression::ArithmeticExpression(Box::new(ArithmeticExpression {
506 | op: arth_expr.op,
507 | expressions: optimized_exprs_without_numbers,
508 | }));
509 | }
510 | Op::LessThan | Op::GreaterThan => {
511 | let compare_func: fn(f64, f64) -> bool = match arth_expr.op {
512 | Op::LessThan => lt,
513 | Op::GreaterThan => gt,
514 | _ => unreachable!(),
515 | };
516 |
517 | // Best case: after optimization the expression is redundant
518 | if optimized_exprs_without_numbers.len() == 0 {
519 | if nums.len() != 2 {
520 | unreachable!("parser (should) have ensured two expressions");
521 | }
522 |
523 | return Expression::Atom(Atom::Boolean(compare_func(nums[0], nums[1])));
524 | };
525 |
526 | return Expression::ArithmeticExpression(Box::new(ArithmeticExpression {
527 | op: arth_expr.op,
528 | expressions: optimized_exprs,
529 | }));
530 | }
531 | }
532 | }
533 | Expression::Atom(ref atom) => match atom {
534 | Atom::Symbol(s) => match get_expr_from_context(s.to_string(), context) {
535 | Some(atom) => return Expression::Atom(atom),
536 | _ => return expression,
537 | },
538 | _ => return expression,
539 | },
540 | }
541 | }
542 |
543 | #[derive(Debug, Clone, PartialEq)]
544 | enum ByteCodeInstruction {
545 | PushConst(f64), // Pushes a constant (float) onto the stack
546 | PushBool(bool), // Pushes a boolean onto the stack
547 | LoadVar(String), // Loads a variable by its name
548 | StoreVar(String), // Stores the top of the stack into a variable
549 | Add(usize), // Adds n numbers on the stack
550 | Sub(usize), // Subtracts n numbers on the stack
551 | LessThan, // Compares top two stack values (x < y)
552 | GreaterThan, // Compares top two stack values (x > y)
553 | Jump(usize), // Unconditional jump
554 | CallLambda(usize), // Calls a lambda expression with n arguments
555 | PushClosure(Vec, Vec), // Pushes a closure onto the stack
556 | }
557 |
558 | fn compile_byte_code(expressions: Vec) -> Vec {
559 | let mut bytecode = Vec::new();
560 | for expr in expressions {
561 | compile_byte_code_expression(&expr, &mut bytecode);
562 | }
563 | bytecode
564 | }
565 |
566 | fn compile_byte_code_expression(expr: &Expression, bytecode: &mut Vec) {
567 | match expr {
568 | Expression::Atom(atom) => compile_byte_code_atom(atom, bytecode),
569 | Expression::List(list) => compile_byte_code_list(list, bytecode),
570 | Expression::LetExpression(let_expr) => compile_byte_code_let(let_expr, bytecode),
571 | Expression::LambdaExpression(lambda_expr) => {
572 | compile_byte_code_lambda(lambda_expr, bytecode)
573 | }
574 | Expression::IfExpression(if_expr) => compile_byte_code_if(if_expr, bytecode),
575 | Expression::ArithmeticExpression(arith_expr) => {
576 | compile_byte_code_arithmetic(arith_expr, bytecode)
577 | }
578 | }
579 | }
580 |
581 | fn compile_byte_code_atom(atom: &Atom, bytecode: &mut Vec) {
582 | match atom {
583 | Atom::Boolean(val) => bytecode.push(ByteCodeInstruction::PushBool(*val)),
584 | Atom::Number(num) => bytecode.push(ByteCodeInstruction::PushConst(*num)),
585 | Atom::Symbol(sym) => bytecode.push(ByteCodeInstruction::LoadVar(sym.clone())),
586 | }
587 | }
588 |
589 | fn compile_byte_code_list(list: &Vec, bytecode: &mut Vec) {
590 | if let Some((first, rest)) = list.split_first() {
591 | // Compile arguments first
592 | for expr in rest {
593 | compile_byte_code_expression(expr, bytecode);
594 | }
595 |
596 | // Compile the function expression (e.g., a symbol for `print`)
597 | compile_byte_code_expression(first, bytecode);
598 |
599 | // Emit CallLambda with the number of arguments
600 | bytecode.push(ByteCodeInstruction::CallLambda(rest.len()));
601 | }
602 | }
603 |
604 | fn compile_byte_code_let(let_expr: &LetExpression, bytecode: &mut Vec) {
605 | // Compile the bindings: store expressions in the variables
606 | for binding in &let_expr.bindings {
607 | compile_byte_code_expression(&binding.expression, bytecode);
608 | bytecode.push(ByteCodeInstruction::StoreVar(binding.symbol.clone()));
609 | }
610 |
611 | // Compile the expressions within the `let` body
612 | for expr in &let_expr.expressions {
613 | compile_byte_code_expression(expr, bytecode);
614 | }
615 | }
616 |
617 | fn compile_byte_code_lambda(
618 | lambda_expr: &LambdaExpression,
619 | bytecode: &mut Vec,
620 | ) {
621 | // Capture free variables (we assume all parameters are bound)
622 | let mut closure_bytecode = Vec::new();
623 |
624 | for expr in &lambda_expr.expressions {
625 | compile_byte_code_expression(expr, &mut closure_bytecode);
626 | }
627 |
628 | // Push a closure with its captured parameters and bytecode
629 | bytecode.push(ByteCodeInstruction::PushClosure(
630 | lambda_expr.parameters.clone(),
631 | closure_bytecode,
632 | ));
633 | }
634 |
635 | fn compile_byte_code_if(if_expr: &IfExpression, bytecode: &mut Vec) {
636 | // Compile the condition expression
637 | compile_byte_code_expression(&if_expr.check, bytecode);
638 |
639 | // Placeholder index for jump_if_false, to be patched later
640 | let jump_if_false_pos = bytecode.len();
641 | bytecode.push(ByteCodeInstruction::Jump(0)); // Placeholder for jump to start of false branch if condition is false
642 |
643 | // Compile the true branch
644 | compile_byte_code_expression(&if_expr.r#true, bytecode);
645 |
646 | // Placeholder index for jump_over_false, to skip the false branch after true branch is executed
647 | let jump_over_false_pos = bytecode.len();
648 | bytecode.push(ByteCodeInstruction::Jump(0)); // Placeholder for jump over false branch after true branch
649 |
650 | // Patch the jump_if_false instruction to jump to the false branch
651 | let false_branch_pos = bytecode.len();
652 | if let ByteCodeInstruction::Jump(ref mut target) = bytecode[jump_if_false_pos] {
653 | *target = false_branch_pos;
654 | }
655 |
656 | // Compile the false branch
657 | compile_byte_code_expression(&if_expr.r#false, bytecode);
658 |
659 | // Patch the jump instruction to jump past the false branch
660 | let end_pos = bytecode.len();
661 | if let ByteCodeInstruction::Jump(ref mut target) = bytecode[jump_over_false_pos] {
662 | *target = end_pos;
663 | }
664 | }
665 |
666 | fn compile_byte_code_arithmetic(
667 | arith_expr: &ArithmeticExpression,
668 | bytecode: &mut Vec,
669 | ) {
670 | // Compile each expression in the arithmetic expression
671 | for expr in &arith_expr.expressions {
672 | compile_byte_code_expression(expr, bytecode);
673 | }
674 |
675 | // Emit the appropriate arithmetic operation
676 | match arith_expr.op {
677 | Op::Plus => bytecode.push(ByteCodeInstruction::Add(arith_expr.expressions.len())),
678 | Op::Minus => bytecode.push(ByteCodeInstruction::Sub(arith_expr.expressions.len())),
679 | Op::LessThan => bytecode.push(ByteCodeInstruction::LessThan),
680 | Op::GreaterThan => bytecode.push(ByteCodeInstruction::GreaterThan),
681 | }
682 | }
683 |
684 | fn debug_byte_code(byte_code: Vec, depth: usize) -> String {
685 | let mut output = String::new();
686 |
687 | for (index, instruction) in byte_code.iter().enumerate() {
688 | if depth > 0 {
689 | for _ in 0..depth {
690 | output.push_str(" ");
691 | }
692 | output.push_str("->")
693 | }
694 | match instruction {
695 | ByteCodeInstruction::PushConst(value) => {
696 | output.push_str(&format!("{:>4}: push_const {:.1}\n", index, value));
697 | }
698 | ByteCodeInstruction::PushBool(value) => {
699 | output.push_str(&format!("{:>4}: push_bool {}\n", index, value));
700 | }
701 | ByteCodeInstruction::LoadVar(var_name) => {
702 | output.push_str(&format!("{:>4}: load_var {}\n", index, var_name));
703 | }
704 | ByteCodeInstruction::StoreVar(var_name) => {
705 | output.push_str(&format!("{:>4}: store_var {}\n", index, var_name));
706 | }
707 | ByteCodeInstruction::Add(n) => {
708 | output.push_str(&format!("{:>4}: add {}\n", index, n));
709 | }
710 | ByteCodeInstruction::Sub(n) => {
711 | output.push_str(&format!("{:>4}: sub {}\n", index, n));
712 | }
713 | ByteCodeInstruction::LessThan => {
714 | output.push_str(&format!("{:>4}: less_than\n", index));
715 | }
716 | ByteCodeInstruction::GreaterThan => {
717 | output.push_str(&format!("{:>4}: greater_than\n", index));
718 | }
719 | ByteCodeInstruction::Jump(target) => {
720 | let detail = match target >= &byte_code.len() {
721 | true => "exit",
722 | false => &format!("go to {}", target),
723 | };
724 | output.push_str(&format!("{:>4}: jump {} // {}\n", index, target, detail));
725 | }
726 | ByteCodeInstruction::CallLambda(arg_count) => {
727 | output.push_str(&format!("{:>4}: call_lambda {}\n", index, arg_count));
728 | }
729 | ByteCodeInstruction::PushClosure(params, instructions) => {
730 | output.push_str(&format!("{:>4}: push_closure {:?}\n", index, params));
731 | output.push_str(&debug_byte_code(instructions.to_vec(), depth + 1));
732 | }
733 | }
734 | }
735 |
736 | output
737 | }
738 |
739 | #[derive(Debug)]
740 | struct RuntimeError {
741 | details: String,
742 | }
743 |
744 | impl RuntimeError {
745 | fn new(msg: &str) -> RuntimeError {
746 | RuntimeError {
747 | details: msg.to_string(),
748 | }
749 | }
750 | }
751 |
752 | impl fmt::Display for RuntimeError {
753 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
754 | write!(f, "{}", self.details)
755 | }
756 | }
757 |
758 | impl Error for RuntimeError {
759 | fn description(&self) -> &str {
760 | &self.details
761 | }
762 | }
763 |
764 | #[derive(Debug, Clone)]
765 | enum StackValue {
766 | Number(f64),
767 | Bool(bool),
768 | Closure(Vec, Vec),
769 | BuiltinFunction(fn(Vec) -> Result<(), RuntimeError>),
770 | }
771 |
772 | #[derive(Clone)]
773 | struct StackFrame {
774 | parent: Option>>,
775 | variables: HashMap,
776 | }
777 |
778 | impl StackFrame {
779 | fn new() -> Rc> {
780 | Rc::new(RefCell::new(StackFrame {
781 | parent: None,
782 | variables: HashMap::new(),
783 | }))
784 | }
785 |
786 | fn child(parent: Rc>) -> Rc> {
787 | Rc::new(RefCell::new(StackFrame {
788 | parent: Some(parent),
789 | variables: HashMap::new(),
790 | }))
791 | }
792 |
793 | fn store(&mut self, variable: String, value: StackValue) {
794 | self.variables.insert(variable, value);
795 | }
796 |
797 | fn look_up(&self, variable: &str) -> Result {
798 | match self.variables.get(variable) {
799 | Some(value) => Ok(value.clone()),
800 | None => match &self.parent {
801 | Some(parent) => parent.borrow().look_up(variable),
802 | None => Err(RuntimeError::new(&format!(
803 | "unknown variable: {}",
804 | variable
805 | ))),
806 | },
807 | }
808 | }
809 | }
810 |
811 | fn print_builtin(args: Vec) -> Result<(), RuntimeError> {
812 | for arg in args {
813 | match arg {
814 | StackValue::Number(n) => print!("{}", n),
815 | StackValue::Bool(b) => print!("{}", b),
816 | StackValue::Closure(_, _) => print!("closure"),
817 | StackValue::BuiltinFunction(_) => print!("built-in"),
818 | }
819 | }
820 | println!();
821 | Ok(())
822 | }
823 |
824 | fn byte_code_vm(
825 | byte_code: Vec,
826 | stack: &mut Vec,
827 | frame: Rc>,
828 | ) -> Result<(), RuntimeError> {
829 | let mut position: usize = 0;
830 |
831 | while position < byte_code.len() {
832 | let instruction = &byte_code[position];
833 |
834 | // // Uncomment for debugging (TODO: use verbose debug flag?)
835 | // println!(
836 | // "{}",
837 | // format!(
838 | // "{}: {:?} stack: {:?} frame: {:?}",
839 | // position,
840 | // instruction,
841 | // stack,
842 | // frame.borrow().variables
843 | // )
844 | // );
845 |
846 | match instruction {
847 | ByteCodeInstruction::PushConst(value) => stack.push(StackValue::Number(*value)),
848 | ByteCodeInstruction::PushBool(value) => stack.push(StackValue::Bool(*value)),
849 | ByteCodeInstruction::LoadVar(variable) => {
850 | if variable == "print" {
851 | stack.push(StackValue::BuiltinFunction(print_builtin));
852 | } else {
853 | stack.push(frame.borrow().look_up(variable)?)
854 | }
855 | }
856 | ByteCodeInstruction::StoreVar(variable) => match stack.pop() {
857 | None => {
858 | return Err(RuntimeError::new(&format!(
859 | "stack empty when setting {}",
860 | variable
861 | )))
862 | }
863 | Some(value) => frame.borrow_mut().store(variable.to_string(), value),
864 | },
865 | ByteCodeInstruction::Add(n) => {
866 | if stack.len() < *n {
867 | return Err(RuntimeError::new(&format!(
868 | "not enough items on the stack (unreachable)"
869 | )));
870 | }
871 | let values: Vec = stack.split_off(stack.len() - n);
872 |
873 | if values.iter().any(|v| matches!(v, StackValue::Bool(_))) {
874 | return Err(RuntimeError::new(&format!("cannot add boolean")));
875 | }
876 |
877 | let sum: f64 = values
878 | .into_iter()
879 | .map(|v| match v {
880 | StackValue::Number(num) => num,
881 | _ => unreachable!(),
882 | })
883 | .sum();
884 |
885 | stack.push(StackValue::Number(sum));
886 | }
887 | ByteCodeInstruction::Sub(n) => {
888 | if stack.len() < *n {
889 | return Err(RuntimeError::new(&format!(
890 | "not enough items on the stack (unreachable)"
891 | )));
892 | }
893 | let values: Vec = stack.split_off(stack.len() - n);
894 |
895 | if values.iter().any(|v| matches!(v, StackValue::Bool(_))) {
896 | return Err(RuntimeError::new(&format!("cannot subtract boolean")));
897 | }
898 |
899 | let mut iter = values.into_iter().map(|v| match v {
900 | StackValue::Number(num) => num,
901 | _ => unreachable!(),
902 | });
903 |
904 | let first = iter.next().unwrap();
905 | let difference: f64 = iter.fold(first, |acc, num| acc - num);
906 |
907 | stack.push(StackValue::Number(difference));
908 | }
909 | ByteCodeInstruction::LessThan => {
910 | if stack.len() < 2 {
911 | return Err(RuntimeError::new("not enough items on the stack"));
912 | }
913 |
914 | let values = stack.split_off(stack.len() - 2);
915 |
916 | let (num1, num2) = match (&values[0], &values[1]) {
917 | (StackValue::Number(n1), StackValue::Number(n2)) => (n1, n2),
918 | _ => return Err(RuntimeError::new("cannot compare non-numeric values")),
919 | };
920 |
921 | if num1 < num2 {
922 | position += 1;
923 | }
924 | }
925 | ByteCodeInstruction::GreaterThan => {
926 | if stack.len() < 2 {
927 | return Err(RuntimeError::new("not enough items on the stack"));
928 | }
929 |
930 | let values = stack.split_off(stack.len() - 2);
931 |
932 | let (num1, num2) = match (&values[0], &values[1]) {
933 | (StackValue::Number(n1), StackValue::Number(n2)) => (n1, n2),
934 | _ => return Err(RuntimeError::new("cannot compare non-numeric values")),
935 | };
936 |
937 | if num1 > num2 {
938 | position += 1;
939 | }
940 | }
941 | ByteCodeInstruction::Jump(new_position) => {
942 | position = *new_position;
943 | continue;
944 | }
945 | ByteCodeInstruction::CallLambda(n) => {
946 | if stack.len() <= *n {
947 | return Err(RuntimeError::new("not enough items on the stack"));
948 | }
949 |
950 | match stack.pop() {
951 | Some(value) => match value {
952 | StackValue::Closure(params, closure_byte_code) => {
953 | let child_frame = StackFrame::child(Rc::clone(&frame));
954 |
955 | // Retrieve the arguments from the stack
956 | let args = stack.split_off(stack.len() - n);
957 | for (param, arg) in params.iter().zip(args) {
958 | child_frame.borrow_mut().store(param.clone(), arg);
959 | }
960 |
961 | byte_code_vm(closure_byte_code, stack, Rc::clone(&child_frame))?;
962 | }
963 | StackValue::BuiltinFunction(func) => {
964 | let args = stack.split_off(stack.len() - n);
965 | func(args)?;
966 | }
967 | _ => {
968 | return Err(RuntimeError::new(
969 | "cannot call non-closure or non-function",
970 | ))
971 | }
972 | },
973 | None => unreachable!(),
974 | }
975 | }
976 | ByteCodeInstruction::PushClosure(params, closure_byte_code) => stack.push(
977 | StackValue::Closure(params.to_vec(), closure_byte_code.clone()),
978 | ),
979 | }
980 |
981 | position += 1;
982 | }
983 |
984 | Ok(())
985 | }
986 |
987 | fn main() {
988 | let mut buffer = Vec::new();
989 | stdin()
990 | .read_to_end(&mut buffer)
991 | .expect("error reading from stdin");
992 |
993 | let mut expressions = match (program()).parse(&buffer) {
994 | Err(e) => {
995 | eprintln!("{}", e);
996 | exit(1);
997 | }
998 | Ok(ast) => ast,
999 | };
1000 |
1001 | let args: Vec = env::args().collect();
1002 | if args.contains(&"--optimize".to_string()) {
1003 | expressions = optimize(expressions);
1004 | }
1005 |
1006 | if args.contains(&"--vm".to_string()) {
1007 | let byte_code = compile_byte_code(expressions);
1008 |
1009 | if args.contains(&"--debug".to_string()) {
1010 | println!("{}", debug_byte_code(byte_code.clone(), 0));
1011 | }
1012 |
1013 | match byte_code_vm(byte_code, &mut vec![], StackFrame::new()) {
1014 | Err(err) => {
1015 | eprintln!("{}", format!("{}", err));
1016 | std::process::exit(1);
1017 | }
1018 | _ => {}
1019 | }
1020 | } else if args.contains(&"--js".to_string()) {
1021 | println!("{}", compile(expressions));
1022 | } else {
1023 | println!("must pass '--vm' or '--js'");
1024 | process::exit(1);
1025 | }
1026 | }
1027 |
1028 | #[cfg(test)]
1029 | mod tests {
1030 | use super::*;
1031 |
1032 | #[test]
1033 | fn test_byte_code_compile() {
1034 | let compiled = compile_byte_code(
1035 | program()
1036 | .parse(
1037 | b"(let ((fib (lambda (n)
1038 | (if (< n 2)
1039 | n
1040 | (+ (fib (- n 1)) (fib (- n 2)))))))
1041 | (print (fib 10)))",
1042 | )
1043 | .unwrap(),
1044 | );
1045 | assert_eq!(format!("{:?}", compiled), "[PushClosure([\"n\"], [LoadVar(\"n\"), PushConst(2.0), LessThan, Jump(6), LoadVar(\"n\"), Jump(17), LoadVar(\"n\"), PushConst(1.0), Sub(2), LoadVar(\"fib\"), CallLambda(1), LoadVar(\"n\"), PushConst(2.0), Sub(2), LoadVar(\"fib\"), CallLambda(1), Add(2)]), StoreVar(\"fib\"), PushConst(10.0), LoadVar(\"fib\"), CallLambda(1), LoadVar(\"print\"), CallLambda(1)]");
1046 | }
1047 |
1048 | #[test]
1049 | fn test_byte_code_vm_arithmetic() {
1050 | let compiled = compile_byte_code(program().parse(b"(+ 1 2 (- 5 100))").unwrap());
1051 |
1052 | let mut stack = vec![];
1053 | byte_code_vm(compiled, &mut stack, StackFrame::new()).unwrap();
1054 | assert_eq!(format!("{:?}", stack), "[Number(-92.0)]");
1055 | }
1056 |
1057 | #[test]
1058 | fn test_byte_code_vm_fib() {
1059 | let compiled = compile_byte_code(
1060 | program()
1061 | .parse(
1062 | b"(let ((fib (lambda (n)
1063 | (if (< n 2)
1064 | n
1065 | (+ (fib (- n 1)) (fib (- n 2)))))))
1066 | (fib 10))",
1067 | )
1068 | .unwrap(),
1069 | );
1070 |
1071 | let mut stack = vec![];
1072 | byte_code_vm(compiled, &mut stack, StackFrame::new()).unwrap();
1073 | assert_eq!(format!("{:?}", stack), "[Number(55.0)]");
1074 | }
1075 |
1076 | #[test]
1077 | fn test_byte_code_vm_fib_print() {
1078 | let compiled = compile_byte_code(
1079 | program()
1080 | .parse(
1081 | b"(let ((fib (lambda (n)
1082 | (if (< n 2)
1083 | n
1084 | (+ (fib (- n 1)) (fib (- n 2)))))))
1085 | (print (fib 10)))",
1086 | )
1087 | .unwrap(),
1088 | );
1089 |
1090 | let mut stack = vec![];
1091 | byte_code_vm(compiled, &mut stack, StackFrame::new()).unwrap();
1092 |
1093 | // Empty because print pops it
1094 | assert_eq!(format!("{:?}", stack), "[]");
1095 | }
1096 |
1097 | #[test]
1098 | fn test_byte_code_vm_double() {
1099 | let compiled = compile_byte_code(
1100 | program()
1101 | .parse(b"(let ((double (lambda (x) (+ x x)))) (double 2))")
1102 | .unwrap(),
1103 | );
1104 |
1105 | let mut stack = vec![];
1106 | byte_code_vm(compiled, &mut stack, StackFrame::new()).unwrap();
1107 | assert_eq!(format!("{:?}", stack), "[Number(4.0)]");
1108 | }
1109 |
1110 | #[test]
1111 | fn test_byte_code_vm_nested_lambda() {
1112 | let compiled = compile_byte_code(
1113 | program()
1114 | .parse(
1115 | b"(let ((fib (lambda (n)
1116 | (if (< n 2)
1117 | (let ((double (lambda (x) (+ x x)))) (double n))
1118 | (+ (fib (- n 1)) (fib (- n 2)))))))
1119 | (fib 10))",
1120 | )
1121 | .unwrap(),
1122 | );
1123 |
1124 | let mut stack = vec![];
1125 | byte_code_vm(compiled, &mut stack, StackFrame::new()).unwrap();
1126 | assert_eq!(format!("{:?}", stack), "[Number(110.0)]");
1127 | }
1128 |
1129 | #[test]
1130 | fn test_parse_atom() {
1131 | assert_eq!(
1132 | format!("{:?}", program().parse(b"(1)").unwrap()),
1133 | "[List([Atom(Number(1.0))])]"
1134 | );
1135 | }
1136 |
1137 | #[test]
1138 | fn test_bindings_in_func() {
1139 | assert_eq!(
1140 | format!(
1141 | "{:?}",
1142 | compile(
1143 | program()
1144 | .parse(
1145 | b"(let ((a (lambda () (let ((b 1)) b)))) (print (a)))"
1146 | )
1147 | .unwrap()
1148 | )
1149 | ),
1150 | "\"/* lisp-to-js */\\nlet print = console.log;\\n\\n\\n(() => {\\nlet a = (() => (() => {\\nlet b = 1; b \\n})()\\n)\\n; print ( a (), )\\n})()\""
1151 | );
1152 | }
1153 |
1154 | #[test]
1155 | fn test_compile_fib() {
1156 | assert_eq!(
1157 | format!(
1158 | "{:?}",
1159 | compile(
1160 | program()
1161 | .parse(
1162 | b"(let ((fib (lambda (n)
1163 | (if (< n 2)
1164 | n
1165 | (+ (fib (- n 1)) (fib (- n 2)))))))
1166 | (print (fib 10)))"
1167 | )
1168 | .unwrap()
1169 | )
1170 | ),
1171 | "\"/* lisp-to-js */\\nlet print = console.log;\\n\\n\\n(() => {\\nlet fib = ((n) => n < 2 ? n : ( fib (( n -1), )+ fib (( n -2), ))\\n\\n)\\n; print ( fib (10, ), )\\n})()\""
1172 | );
1173 | }
1174 |
1175 | #[test]
1176 | fn test_optimize_add() {
1177 | assert_eq!(
1178 | format!("{:?}", optimize(program().parse(b"(+ 1 2)").unwrap())),
1179 | "[Atom(Number(3.0))]"
1180 | );
1181 | assert_eq!(
1182 | format!(
1183 | "{:?}",
1184 | optimize(program().parse(b"(let ((a 1)) (+ 1 a 2))").unwrap())
1185 | ),
1186 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Number(4.0))] })]"
1187 | );
1188 | }
1189 |
1190 | #[test]
1191 | fn test_optimize_sub() {
1192 | assert_eq!(
1193 | format!("{:?}", optimize(program().parse(b"(- 1 2)").unwrap())),
1194 | "[Atom(Number(-1.0))]"
1195 | );
1196 | assert_eq!(
1197 | format!(
1198 | "{:?}",
1199 | optimize(program().parse(b"(let ((a 1)) (- 1 a 2))").unwrap())
1200 | ),
1201 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Number(-2.0))] })]"
1202 | );
1203 | }
1204 |
1205 | #[test]
1206 | fn test_optimize_many() {
1207 | assert_eq!(
1208 | format!(
1209 | "{:?}",
1210 | optimize(program().parse(b"(if (< 2 1) 1 2)").unwrap())
1211 | ),
1212 | "[Atom(Number(2.0))]"
1213 | );
1214 | assert_eq!(
1215 | format!(
1216 | "{:?}",
1217 | optimize(program().parse(b"(if (> 2 1) 1 2)").unwrap())
1218 | ),
1219 | "[Atom(Number(1.0))]"
1220 | );
1221 | assert_eq!(
1222 | format!(
1223 | "{:?}",
1224 | optimize(
1225 | program()
1226 | .parse(
1227 | b"(let ((a 1)) (let ((a 2)) a))"
1228 | )
1229 | .unwrap()
1230 | )
1231 | ),
1232 | "[LetExpression(LetExpression { bindings: [], expressions: [LetExpression(LetExpression { bindings: [], expressions: [Atom(Number(2.0))] })] })]"
1233 | );
1234 | assert_eq!(
1235 | format!(
1236 | "{:?}",
1237 | optimize(
1238 | program()
1239 | .parse(
1240 | b"(let ((a 5) (b (+ a 5)) (c 10))
1241 | (print (if (> c 5) (+ a b) (- c 1))))"
1242 | )
1243 | .unwrap()
1244 | )
1245 | ),
1246 | "[LetExpression(LetExpression { bindings: [], expressions: [List([Atom(Symbol(\"print\")), Atom(Number(15.0))])] })]"
1247 | );
1248 | assert_eq!(
1249 | format!(
1250 | "{:?}",
1251 | optimize(
1252 | program()
1253 | .parse(
1254 | b"(lambda (a) (let (a 2) (+ a 2 2)) (+ 5 5))"
1255 | )
1256 | .unwrap()
1257 | )
1258 | ),
1259 | "[LambdaExpression(LambdaExpression { parameters: [\"a\"], expressions: [List([Atom(Symbol(\"let\")), List([Atom(Symbol(\"a\")), Atom(Number(2.0))]), ArithmeticExpression(ArithmeticExpression { op: Plus, expressions: [Atom(Symbol(\"a\")), Atom(Number(4.0))] })]), Atom(Number(10.0))] })]"
1260 | );
1261 | assert_eq!(
1262 | format!(
1263 | "{:?}",
1264 | optimize(program().parse(b"(let ((a (if (< 1 2) 1 2))) a)").unwrap())
1265 | ),
1266 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Number(1.0))] })]"
1267 | );
1268 | assert_eq!(
1269 | format!(
1270 | "{:?}",
1271 | optimize(program().parse(b"(let ((a 1)) (< 1 a))").unwrap())
1272 | ),
1273 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Boolean(false))] })]"
1274 | );
1275 | assert_eq!(
1276 | format!(
1277 | "{:?}",
1278 | optimize(program().parse(b"(let ((a (+ 1 2))) (< 1 a))").unwrap())
1279 | ),
1280 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Boolean(true))] })]"
1281 | );
1282 | assert_eq!(
1283 | format!(
1284 | "{:?}",
1285 | optimize(program().parse(b"(< a 1)").unwrap())
1286 | ),
1287 | "[ArithmeticExpression(ArithmeticExpression { op: LessThan, expressions: [Atom(Symbol(\"a\")), Atom(Number(1.0))] })]"
1288 | );
1289 | assert_eq!(
1290 | format!(
1291 | "{:?}",
1292 | optimize(program().parse(b"(let ((a (+ 1 2)))
1293 | (print (+ a a))
1294 | )").unwrap())
1295 | ),
1296 | "[LetExpression(LetExpression { bindings: [], expressions: [List([Atom(Symbol(\"print\")), Atom(Number(6.0))])] })]"
1297 | );
1298 | assert_eq!(
1299 | format!(
1300 | "{:?}",
1301 | optimize(program().parse(b"(let ((a (< 1 2))) a)").unwrap())
1302 | ),
1303 | "[LetExpression(LetExpression { bindings: [], expressions: [Atom(Boolean(true))] })]"
1304 | );
1305 | }
1306 | }
1307 |
1308 | fn gt(a: T, b: T) -> bool {
1309 | a > b
1310 | }
1311 |
1312 | fn lt(a: T, b: T) -> bool {
1313 | a < b
1314 | }
1315 |
--------------------------------------------------------------------------------