├── 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 | --------------------------------------------------------------------------------