├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.org ├── examples ├── printing.scm ├── tail_call_optimization.scm └── threads.scm ├── src ├── core │ ├── mod.rs │ └── repl.rs ├── interpreter │ ├── ast_walk_interpreter.rs │ ├── cps_interpreter.rs │ ├── interpreter.rs │ └── mod.rs ├── lib.rs ├── main.rs └── reader │ ├── lexer.rs │ ├── mod.rs │ └── parser.rs └── watch /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /tmp 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "getopts" 3 | version = "0.2.11" 4 | source = "registry+https://github.com/rust-lang/crates.io-index" 5 | dependencies = [ 6 | "log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "libc" 11 | version = "0.1.6" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | 14 | [[package]] 15 | name = "libc" 16 | version = "0.2.43" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | 19 | [[package]] 20 | name = "log" 21 | version = "0.3.1" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | dependencies = [ 24 | "libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "rusty_scheme" 29 | version = "0.0.2" 30 | dependencies = [ 31 | "getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [metadata] 36 | "checksum getopts 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "45798d13443a1efeff35f7eba8f83f1e46dcaf7e1abb78b5dcfa7124223ce615" 37 | "checksum libc 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4e643e4dfa31fd135586df9da9999e8e5eb8e753bc3ce0765aef85469b6db1da" 38 | "checksum libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)" = "76e3a3ef172f1a0b9a9ff0dd1491ae5e6c948b94479a3021819ba7d860c8645d" 39 | "checksum log 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d2ee3324e161fe61168b08bcd096f1e023d14f0777a076ba14d1825ea3b99d40" 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusty_scheme" 3 | version = "0.0.2" 4 | 5 | [dependencies] 6 | getopts = "^0.2" 7 | libc = "^0.2" 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Ken Pratt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RustyScheme 2 | =========== 3 | 4 | A toy Scheme interpreter written in Rust, loosely based on the [R5RS Specification](http://www.schemers.org/Documents/Standards/R5RS/HTML/) with a bit of [Racket](http://docs.racket-lang.org/reference/index.html) thrown in as well. 5 | 6 | It supports a small number of standard library functions, as well as: 7 | 8 | * Function and variable definition 9 | * Quote, Quasiquote/unquote 10 | * Apply & Eval 11 | * Macros (not hygenic yet) 12 | * Let expressions 13 | * Tail-call optimization 14 | * Continuations, [Call-with-current-continuation](http://en.wikipedia.org/wiki/Call-with-current-continuation) 15 | * Unicode 16 | * REPL, with history 17 | 18 | There are two versions of the interpreter: 19 | 20 | * A straight-forward AST-walking interpreter, which uses the Rust stack and heap, and uses vectors to represent Scheme lists. 21 | * A [continuation-passing style](http://en.wikipedia.org/wiki/Continuation-passing_style) interpreter, which supports tail-call optimization and continuations, uses the Rust stack and heap, and uses a linked list to represent Scheme lists. 22 | 23 | In the future, I may develop an interpreter that manages its own stack and/or heap, and possibly a bytecode VM & compiler as well for comparison. 24 | 25 | Requirements 26 | ------------ 27 | 28 | * Rust 1.0-beta or later 29 | 30 | Usage 31 | ----- 32 | 33 | Download and install Rust 1.0 from http://www.rust-lang.org/install.html. 34 | 35 | To start a REPL using the default CPS interpreter: 36 | 37 | cargo run 38 | 39 | To execute a Scheme file using the default CPS interpreter: 40 | 41 | cargo run examples/printing.scm 42 | 43 | To start a REPL using the AST-walking interpreter: 44 | 45 | cargo run -- -t ast_walk 46 | 47 | To execute a Scheme file using the AST-walking interpreter: 48 | 49 | cargo run -- -t ast_walk examples/printing.scm 50 | 51 | To run the test suite: 52 | 53 | cargo test 54 | 55 | To watch for changes and auto-rebuild (on OS X): 56 | 57 | gem install kicker -s http://gemcutter.org 58 | ./watch 59 | -------------------------------------------------------------------------------- /TODO.org: -------------------------------------------------------------------------------- 1 | * Actual todo 2 | ** DONE See if I can get the tokenizer to return lifetime strs instead of Strings -> NOPE http://www.reddit.com/r/rust/comments/28waq4/how_do_i_create_and_return_a_str_dynamically/ 3 | ** DONE Parse to AST! 4 | ** DONE Add functions 5 | ** DONE Add nil 6 | ** DONE Print result to string 7 | ** DONE Implement set! 8 | ** DONE Conditionals 9 | ** DONE Boolean logic/operations 10 | ** DONE Symbols 11 | ** DONE Add quote operator 12 | ** DONE Add quote syntax sugar 13 | ** DONE Add quasiquote/unquote operators 14 | ** DONE Add quasiquote/unquote syntax sugar 15 | ** DONE Lists as data 16 | ** DONE Apply 17 | ** DONE Eval 18 | ** DONE REPL 19 | ** DONE Add up/down arrows to REPL 20 | ** TODO Unify AST and Intepreter enums? -> hard, have to include procedures somehow :( 21 | ** DONE Print statement 22 | ** DONE Run files from file system 23 | ** DONE Let expressions 24 | ** DONE Shortcut syntax for defining functions (define (myfunc x) (+ x x)) 25 | ** DONE Try to replace most for loops with iterators 26 | ** TODO See if I can internalize the RefCell contract and expose something simpler for Envirnoment (probably not) 27 | ** TODO Tab completion in REPL (based on defined functions and constants, and maybe even local vars?) 28 | ** DONE Add macros 29 | ** TODO Hygenic macros 30 | ** TODO call/cc (implement with workers? (probably not possible) or manual stack/instruction pointer?) 31 | ** TODO Bytecode VM (stack, or register based? -> stack is probably easier) 32 | ** TODO JIT 33 | 34 | * Unimplemented/maybe TODO 35 | ** TODO Floats 36 | ** TODO Ecaping doubles quotes and backslashes in strings 37 | ** TODO Restricting non-global defines? (seems like there's mixed implementations on this, but should at least be conistent) 38 | ** TODO Tail call optimization 39 | ** TODO Nested quasiquotes 40 | ** TODO unquote-splicing in quasiquote 41 | ** TODO quote-syntax 42 | 43 | * Interpreters: Existing languages 44 | ** Ruby 1.8: normal interpreter, no precompilation, no VM. 45 | ** Ruby 1.9: no precompilation, compiles to bytecode, runs on VM (YARV), VM is interpreted. 46 | ** JVM: precompiles to bytecode, runs on VM, VM is interpreted with a tracing JIT (or static JIT? depends in the VM?) 47 | ** V8: no precomplation, no VM, no interpreter, static JIT ("full compiler") compiles JS to machine code when it's run for the first time, tracing JIT ("optimizing compiler") watches for hot functions and re-compiles with assumptions & guards baked in, and backs out to static JIT if it breaks. Both JITs are stack machines. 48 | ** Firefox: no precompilation, compiles to bytecode, VM interpreter runs, then first tracing "baseline" JIT, then second optimizing tracing JIT ("Ion") kicks in. VM and JITs are all stack machines, VM interpreter stack and JITs native C stacks. 49 | ** Safari: no precompilation, VM interpreter, first tracing JIT, second optimizing JIT. Both are register machines, not stack machines. Or actually, maybe most platforms ship with interpreter turned off, so it's just a baseline JIT and an optimizing JIT, like V8 but operating on intermediate bytecode. 50 | ** Rust: precompiled to machine code (obviously, I guess). 51 | ** Python (CPython): no precompilation, compiles to bytecode on first run, VM & VM interpreter. 52 | ** PyPy: JIT'ed interpreter written in RPython. 53 | 54 | * Interpreters: My options 55 | ** Static compilation (generate machine code statically) 56 | ** Vanilla interpreter 57 | ** Static JIT (generate machine code on first run) 58 | ** Vanilla interpreter + tracing JIT (profile & generate machine code for hot loops/functions) 59 | ** Bytecode + VM interpreter 60 | ** Bytecode + VM w/ static JIT (generate machine code on first execution of each operation) 61 | ** Bytecode + VM w/ interpreter & tracing JIT (profile & generate machine code for hot loops) 62 | ** JVM bytecode compiler (or other VM to target) 63 | ** LLVM backend compiler 64 | ** Plan: do the non-machine code ones first (vanilla interpreter, bytecode + VM interpreter), then try static compilation, then static JITs, then tracing JITs? Or if it's too hard to do a full static compile, just do VM interpreter + tracing JIT, as that's probably the least amount of machine code. 65 | 66 | * Resources 67 | http://home.pipeline.com/~hbaker1/LinearLisp.html 68 | http://blog.reverberate.org/2012/12/hello-jit-world-joy-of-simple-jits.html?m=1 69 | -------------------------------------------------------------------------------- /examples/printing.scm: -------------------------------------------------------------------------------- 1 | (define (dump what s) 2 | (displayln what) 3 | (write s) 4 | (newline) 5 | (display s) 6 | (newline) 7 | (print s) 8 | (newline) 9 | (newline)) 10 | 11 | (dump "symbol" 'blah) 12 | (dump "int" 23) 13 | (dump "bool" #t) 14 | (dump "string" "blah") 15 | (dump "list" '(blah 23 #t "blah" (blah 23 #t "blah"))) 16 | (dump "proc" dump) 17 | 18 | ;; 19 | ;; Expected output: 20 | ;; 21 | ;; symbol 22 | ;; blah 23 | ;; blah 24 | ;; 'blah 25 | ;; 26 | ;; int 27 | ;; 23 28 | ;; 23 29 | ;; 23 30 | ;; 31 | ;; bool 32 | ;; #t 33 | ;; #t 34 | ;; #t 35 | ;; 36 | ;; string 37 | ;; "blah" 38 | ;; blah 39 | ;; "blah" 40 | ;; 41 | ;; list 42 | ;; (blah 23 #t "blah" (blah 23 #t "blah")) 43 | ;; (blah 23 #t blah (blah 23 #t blah)) 44 | ;; '(blah 23 #t "blah" (blah 23 #t "blah")) 45 | ;; 46 | ;; proc 47 | ;; # 48 | ;; # 49 | ;; # 50 | -------------------------------------------------------------------------------- /examples/tail_call_optimization.scm: -------------------------------------------------------------------------------- 1 | (define (tco-test) 2 | (define (f i) 3 | (if (= i 100000) 4 | (displayln "made it to 100000") 5 | (f (+ i 1)))) 6 | (f 1) 7 | (displayln "done")) 8 | 9 | (tco-test) 10 | -------------------------------------------------------------------------------- /examples/threads.scm: -------------------------------------------------------------------------------- 1 | ;; FIFO queue. 2 | (define thread-pool '()) 3 | 4 | ;; Push to end of queue. 5 | (define (push-thread t) 6 | (set! thread-pool (append thread-pool (list t)))) 7 | 8 | ;; Pop from front of queue. 9 | (define (pop-thread) 10 | (if (null? thread-pool) 11 | '() 12 | (let ((t (car thread-pool))) 13 | (set! thread-pool (cdr thread-pool)) 14 | t))) 15 | 16 | ;; To start, set the exit function to the point. 17 | (define (start) 18 | (call/cc 19 | (lambda (cc) 20 | (set! exit cc) 21 | (run-next-thread)))) 22 | 23 | ;; Exit point will be defined when start is run. 24 | (define exit '()) 25 | 26 | ;; Run the next thread in line. If no more, call exit. 27 | (define (run-next-thread) 28 | (let ((t (pop-thread))) 29 | (if (null? t) 30 | (exit) 31 | (t)))) 32 | 33 | ;; Create a new thread 34 | (define (spawn fn) 35 | (push-thread 36 | (lambda () 37 | (fn) 38 | (run-next-thread)))) 39 | 40 | ;; Yield saves the running state of the current thread, 41 | ;; and then runs the next one. 42 | (define (yield) 43 | (call/cc 44 | (lambda (cc) 45 | (push-thread cc) 46 | (run-next-thread)))) 47 | 48 | (spawn 49 | (lambda () 50 | (displayln "Hello from thread #1") 51 | (yield) 52 | (displayln "Hello again from thread #1") 53 | (yield) 54 | (displayln "Hello once more from thread #1"))) 55 | 56 | (spawn 57 | (lambda () 58 | (displayln "Hello from thread #2") 59 | (yield) 60 | (displayln "Hello again from thread #2") 61 | (yield) 62 | (displayln "Hello once more from thread #2"))) 63 | 64 | (displayln "Starting...") 65 | (start) 66 | (displayln "Done") 67 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod repl; -------------------------------------------------------------------------------- /src/core/repl.rs: -------------------------------------------------------------------------------- 1 | extern crate libc; 2 | use std::ffi::CString; 3 | use std::ffi::CStr; 4 | 5 | #[link(name = "readline")] 6 | extern { 7 | fn readline(prompt: *const libc::c_char) -> *const libc::c_char; 8 | fn add_history(entry: *const libc::c_char); 9 | } 10 | 11 | fn prompt_for_input(prompt: &str) -> Option { 12 | let prompt_c_str = CString::new(prompt).unwrap(); 13 | 14 | unsafe { 15 | // wait for enter/CTRL-C/CTRL-D 16 | let raw = readline(prompt_c_str.as_ptr()); 17 | if raw.is_null() { 18 | return None; 19 | } 20 | 21 | // parse into String and return 22 | let buf = CStr::from_ptr(raw).to_bytes(); 23 | let cs = String::from_utf8(buf.to_vec()).unwrap(); 24 | 25 | // add to shell history unless it's an empty string 26 | if cs.len() > 0 { 27 | add_history(raw); 28 | } 29 | 30 | // return Option 31 | Some(cs) 32 | } 33 | } 34 | 35 | pub fn start Result>(prompt: &str, f: F) { 36 | loop { 37 | match prompt_for_input(prompt) { 38 | Some(input) => { 39 | if input.len() > 0 { 40 | let result = f(input); 41 | println!("{}", result.unwrap_or_else(|e| e)); 42 | } 43 | }, 44 | None => return 45 | }; 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/interpreter/ast_walk_interpreter.rs: -------------------------------------------------------------------------------- 1 | use crate::reader::parser::*; 2 | 3 | use std::fmt; 4 | use std::collections::HashMap; 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | 8 | pub fn new() -> Interpreter { 9 | Interpreter::new() 10 | } 11 | 12 | #[derive(Clone)] 13 | pub struct Interpreter { 14 | root: Rc> 15 | } 16 | 17 | impl Interpreter { 18 | pub fn new() -> Interpreter { 19 | Interpreter { root: Environment::new_root() } 20 | } 21 | 22 | pub fn run(&self, nodes: &[Node]) -> Result { 23 | let values = Value::from_nodes(nodes); 24 | evaluate_values(&values, self.root.clone()) 25 | } 26 | } 27 | 28 | #[derive(PartialEq, Clone)] 29 | pub enum Value { 30 | Symbol(String), 31 | Integer(i64), 32 | Boolean(bool), 33 | String(String), 34 | List(Vec), 35 | Procedure(Function), 36 | Macro(Vec, Vec), 37 | } 38 | 39 | // null == empty list 40 | macro_rules! null { () => (Value::List(vec![])) } 41 | 42 | pub enum Function { 43 | Native(ValueOperation), 44 | Scheme(Vec, Vec, Rc>), 45 | } 46 | 47 | // type signature for all native functions 48 | pub type ValueOperation = fn(&[Value], Rc>) -> Result; 49 | 50 | impl Value { 51 | fn from_nodes(nodes: &[Node]) -> Vec { 52 | nodes.iter().map(Value::from_node).collect() 53 | } 54 | 55 | fn from_node(node: &Node) -> Value { 56 | match *node { 57 | Node::Identifier(ref val) => Value::Symbol(val.clone()), 58 | Node::Integer(val) => Value::Integer(val), 59 | Node::Boolean(val) => Value::Boolean(val), 60 | Node::String(ref val) => Value::String(val.clone()), 61 | Node::List(ref nodes) => Value::List(Value::from_nodes(&nodes)) 62 | } 63 | } 64 | } 65 | 66 | impl fmt::Display for Value { 67 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 | match *self { 69 | Value::Symbol(ref val) => write!(f, "{}", val), 70 | Value::Integer(val) => write!(f, "{}", val), 71 | Value::Boolean(val) => write!(f, "#{}", if val { "t" } else { "f" }), 72 | Value::String(ref val) => write!(f, "{}", val), 73 | Value::List(ref list) => { 74 | let strs: Vec = list.iter().map(|v| format!("{}", v)).collect(); 75 | write!(f, "({})", &strs.join(" ")) 76 | }, 77 | Value::Procedure(_) => write!(f, "#"), 78 | Value::Macro(_,_) => write!(f, "#"), 79 | } 80 | } 81 | } 82 | 83 | impl fmt::Debug for Value { 84 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 85 | match *self { 86 | Value::String(ref val) => write!(f, "\"{}\"", val), 87 | Value::List(ref list) => { 88 | let strs: Vec = list.iter().map(|v| format!("{:?}", v)).collect(); 89 | write!(f, "({})", &strs.join(" ")) 90 | }, 91 | _ => write!(f, "{}", self) 92 | } 93 | } 94 | } 95 | 96 | impl PartialEq for Function { 97 | fn eq(&self, other: &Function) -> bool { 98 | self == other 99 | } 100 | } 101 | 102 | impl Clone for Function { 103 | fn clone(&self) -> Function { 104 | match *self { 105 | Function::Native(ref func) => Function::Native(*func), 106 | Function::Scheme(ref a, ref b, ref env) => Function::Scheme(a.clone(), b.clone(), env.clone()) 107 | } 108 | } 109 | } 110 | 111 | pub struct RuntimeError { 112 | message: String, 113 | } 114 | 115 | impl fmt::Display for RuntimeError { 116 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 117 | write!(f, "RuntimeError: {}", self.message) 118 | } 119 | } 120 | impl fmt::Debug for RuntimeError { 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | write!(f, "RuntimeError: {}", self.message) 123 | } 124 | } 125 | 126 | macro_rules! runtime_error { 127 | ($($arg:tt)*) => ( 128 | return Err(RuntimeError { message: format!($($arg)*)}) 129 | ) 130 | } 131 | 132 | pub struct Environment { 133 | parent: Option>>, 134 | values: HashMap, 135 | } 136 | 137 | /** 138 | * 139 | */ 140 | impl Environment { 141 | fn new_root() -> Rc> { 142 | let mut env = Environment { parent: None, values: HashMap::new() }; 143 | let predefined_functions = &[ 144 | ("define", Function::Native(native_define)), 145 | ("define-syntax-rule", Function::Native(native_define_syntax_rule)), 146 | ("begin", Function::Native(native_begin)), 147 | ("let", Function::Native(native_let)), 148 | ("set!", Function::Native(native_set)), 149 | ("lambda", Function::Native(native_lambda)), 150 | ("λ", Function::Native(native_lambda)), 151 | ("if", Function::Native(native_if)), 152 | ("+", Function::Native(native_plus)), 153 | ("-", Function::Native(native_minus)), 154 | ("*", Function::Native(native_multiply)), 155 | ("/", Function::Native(native_divide)), 156 | ("<", Function::Native(native_lessthan)), 157 | (">", Function::Native(native_greaterthan)), 158 | ("=", Function::Native(native_equal)), 159 | ("and", Function::Native(native_and)), 160 | ("or", Function::Native(native_or)), 161 | ("null?", Function::Native(native_null)), 162 | ("list", Function::Native(native_list)), 163 | ("car", Function::Native(native_car)), 164 | ("cdr", Function::Native(native_cdr)), 165 | ("cons", Function::Native(native_cons)), 166 | ("append", Function::Native(native_append)), 167 | ("quote", Function::Native(native_quote)), 168 | ("quasiquote", Function::Native(native_quasiquote)), 169 | ("error", Function::Native(native_error)), 170 | ("apply", Function::Native(native_apply)), 171 | ("eval", Function::Native(native_eval)), 172 | ("write", Function::Native(native_write)), 173 | ("display", Function::Native(native_display)), 174 | ("displayln", Function::Native(native_displayln)), 175 | ("print", Function::Native(native_print)), 176 | ("newline", Function::Native(native_newline)), 177 | ]; 178 | for item in predefined_functions.iter() { 179 | let (name, ref func) = *item; 180 | env.define(name.to_string(), Value::Procedure(func.clone())).unwrap(); 181 | } 182 | Rc::new(RefCell::new(env)) 183 | } 184 | 185 | fn new_child(parent: Rc>) -> Rc> { 186 | let env = Environment { parent: Some(parent), values: HashMap::new() }; 187 | Rc::new(RefCell::new(env)) 188 | } 189 | 190 | // Define a variable at the current level 191 | // If key is not defined in the current env, set it 192 | // If key is already defined in the current env, return runtime error 193 | // (So if key is defined at a higher level, still define it at the current level) 194 | fn define(&mut self, key: String, value: Value) -> Result<(), RuntimeError> { 195 | if self.values.contains_key(&key) { 196 | runtime_error!("Duplicate define: {:?}", key) 197 | } else { 198 | self.values.insert(key, value); 199 | Ok(()) 200 | } 201 | } 202 | 203 | // Set a variable to a value, at any level in the env, or throw a runtime error if it isn't defined at all 204 | fn set(&mut self, key: String, value: Value) -> Result<(), RuntimeError> { 205 | if self.values.contains_key(&key) { 206 | self.values.insert(key, value); 207 | Ok(()) 208 | } else { 209 | // recurse up the environment tree until a value is found or the end is reached 210 | match self.parent { 211 | Some(ref parent) => parent.borrow_mut().set(key, value), 212 | None => runtime_error!("Can't set! an undefined variable: {:?}", key) 213 | } 214 | } 215 | } 216 | 217 | fn get(&self, key: &String) -> Option { 218 | match self.values.get(key) { 219 | Some(val) => Some(val.clone()), 220 | None => { 221 | // recurse up the environment tree until a value is found or the end is reached 222 | match self.parent { 223 | Some(ref parent) => parent.borrow().get(key), 224 | None => None 225 | } 226 | } 227 | } 228 | } 229 | 230 | fn get_root(env_ref: Rc>) -> Rc> { 231 | let env = env_ref.borrow(); 232 | match env.parent { 233 | Some(ref parent) => Environment::get_root(parent.clone()), 234 | None => env_ref.clone() 235 | } 236 | } 237 | } 238 | 239 | fn evaluate_values(values: &[Value], env: Rc>) -> Result { 240 | let mut res = null!(); 241 | for v in values.iter() { 242 | res = try!(evaluate_value(v, env.clone())); 243 | } 244 | Ok(res) 245 | } 246 | 247 | fn evaluate_value(value: &Value, env: Rc>) -> Result { 248 | match value { 249 | &Value::Symbol(ref v) => { 250 | match env.borrow().get(v) { 251 | Some(val) => Ok(val), 252 | None => runtime_error!("Identifier not found: {:?}", value) 253 | } 254 | }, 255 | &Value::Integer(v) => Ok(Value::Integer(v)), 256 | &Value::Boolean(v) => Ok(Value::Boolean(v)), 257 | &Value::String(ref v) => Ok(Value::String(v.clone())), 258 | &Value::List(ref vec) => { 259 | if vec.len() > 0 { 260 | evaluate_expression(vec, env.clone()) 261 | } else { 262 | Ok(null!()) 263 | } 264 | }, 265 | &Value::Procedure(ref v) => Ok(Value::Procedure(v.clone())), 266 | &Value::Macro(ref a, ref b) => Ok(Value::Macro(a.clone(), b.clone())), 267 | } 268 | } 269 | 270 | fn quote_value(value: &Value, quasi: bool, env: Rc>) -> Result { 271 | match value { 272 | &Value::Symbol(ref v) => Ok(Value::Symbol(v.clone())), 273 | &Value::Integer(v) => Ok(Value::Integer(v)), 274 | &Value::Boolean(v) => Ok(Value::Boolean(v)), 275 | &Value::String(ref v) => Ok(Value::String(v.clone())), 276 | &Value::List(ref vec) => { 277 | // check if we are unquoting inside a quasiquote 278 | if quasi && vec.len() > 0 && vec[0] == Value::Symbol("unquote".to_string()) { 279 | if vec.len() != 2 { 280 | runtime_error!("Must supply exactly one argument to unquote: {:?}", vec); 281 | } 282 | evaluate_value(&vec[1], env.clone()) 283 | } else { 284 | let res: Result, RuntimeError> = vec.iter().map(|v| quote_value(v, quasi, env.clone())).collect(); 285 | let new_vec = try!(res); 286 | Ok(Value::List(new_vec)) 287 | } 288 | }, 289 | &Value::Procedure(ref v) => Ok(Value::Procedure(v.clone())), 290 | &Value::Macro(ref a, ref b) => Ok(Value::Macro(a.clone(), b.clone())), 291 | } 292 | } 293 | 294 | fn evaluate_expression(values: &Vec, env: Rc>) -> Result { 295 | if values.len() == 0 { 296 | runtime_error!("Can't evaluate an empty expression: {:?}", values); 297 | } 298 | let first = try!(evaluate_value(&values[0], env.clone())); 299 | match first { 300 | Value::Procedure(f) => apply_function(&f, &values[1..], env.clone()), 301 | Value::Macro(a, b) => expand_macro(a, b, &values[1..], env.clone()), 302 | _ => runtime_error!("First element in an expression must be a procedure: {:?}", first) 303 | } 304 | } 305 | 306 | fn apply_function(func: &Function, args: &[Value], env: Rc>) -> Result { 307 | match func { 308 | &Function::Native(native_fn) => { 309 | native_fn(args, env) 310 | }, 311 | &Function::Scheme(ref arg_names, ref body, ref func_env) => { 312 | if arg_names.len() != args.len() { 313 | runtime_error!("Must supply exactly {} arguments to function: {:?}", arg_names.len(), args); 314 | } 315 | 316 | // create a new, child environment for the procedure and define the arguments as local variables 317 | let proc_env = Environment::new_child(func_env.clone()); 318 | for (name, arg) in arg_names.iter().zip(args.iter()) { 319 | let val = try!(evaluate_value(arg, env.clone())); 320 | try!(proc_env.borrow_mut().define(name.clone(), val)); 321 | } 322 | 323 | // evaluate procedure body with new environment with procedure environment as parent 324 | let inner_env = Environment::new_child(proc_env); 325 | evaluate_values(&body, inner_env) 326 | } 327 | } 328 | } 329 | 330 | fn expand_macro(arg_names: Vec, body: Vec, args: &[Value], env: Rc>) -> Result { 331 | let mut substitutions = HashMap::new(); 332 | for (name, arg) in arg_names.iter().zip(args.iter()) { 333 | substitutions.insert(name.clone(), arg.clone()); 334 | } 335 | let expanded = try!(expand_macro_substitute_values(&body, substitutions)); 336 | evaluate_values(&expanded, env) 337 | } 338 | 339 | fn expand_macro_substitute_values(values: &[Value], substitutions: HashMap) -> Result, RuntimeError> { 340 | values.iter().map(|n| expand_macro_substitute_value(n, substitutions.clone())).collect() 341 | } 342 | 343 | fn expand_macro_substitute_value(value: &Value, substitutions: HashMap) -> Result { 344 | let res = match value { 345 | &Value::Symbol(ref s) => { 346 | if substitutions.contains_key(s) { 347 | substitutions.get(s).unwrap().clone() 348 | } else { 349 | Value::Symbol(s.clone()) 350 | } 351 | }, 352 | &Value::List(ref l) => { 353 | Value::List(try!(expand_macro_substitute_values(&l, substitutions))) 354 | }, 355 | other => other.clone() 356 | }; 357 | Ok(res) 358 | } 359 | 360 | fn native_define(args: &[Value], env: Rc>) -> Result { 361 | if args.len() < 2 { 362 | runtime_error!("Must supply at least two arguments to define: {:?}", args); 363 | } 364 | let (name, val) = match args[0] { 365 | Value::Symbol(ref name) => { 366 | let val = try!(evaluate_value(&args[1], env.clone())); 367 | (name, val) 368 | }, 369 | Value::List(ref list) => { 370 | // if a list is the second argument, it's shortcut for defining a procedure 371 | // (define ( ) ) == (define (lambda () ) 372 | if list.len() < 1 { 373 | runtime_error!("Must supply at least one argument in list part of define: {:?}", list); 374 | } 375 | match list[0] { 376 | Value::Symbol(ref name) => { 377 | let res: Result, RuntimeError> = (&list[1..]).iter().map(|i| match *i { 378 | Value::Symbol(ref s) => Ok(s.clone()), 379 | _ => runtime_error!("Unexpected argument in define arguments: {:?}", i) 380 | }).collect(); 381 | let arg_names = try!(res); 382 | let body = (&args[1..]).to_vec(); 383 | let val = Value::Procedure(Function::Scheme(arg_names, body, env.clone())); 384 | (name, val) 385 | }, 386 | _ => runtime_error!("Must supply a symbol in list part of define: {:?}", list) 387 | } 388 | }, 389 | _ => runtime_error!("Unexpected value for name in define: {:?}", args) 390 | }; 391 | 392 | try!(env.borrow_mut().define(name.clone(), val)); 393 | Ok(null!()) 394 | } 395 | 396 | fn native_define_syntax_rule(args: &[Value], env: Rc>) -> Result { 397 | if args.len() != 2 { 398 | runtime_error!("Must supply exactly two arguments to define-syntax-rule: {:?}", args); 399 | } 400 | let (name, val) = match args[0] { 401 | Value::List(ref list) => { 402 | // (define-syntax-rule ( )