├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── bench.sh ├── benches └── example.rs ├── clippy.sh ├── examples ├── kitchen_sink.risp ├── simple_song.risp └── song.risp ├── src ├── convert.rs ├── core.rs ├── environment.rs ├── eval.rs ├── lib.rs ├── main.rs ├── parse.rs ├── tokenize.rs └── types.rs └── tests ├── risp.rs ├── risp_examples.rs ├── risp_readme.rs └── risp_readme_convert.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .idea 3 | .private -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | matrix: 7 | allow_failures: 8 | - rust: nightly 9 | 10 | before_install: 11 | - sudo apt-get update 12 | 13 | addons: 14 | apt: 15 | packages: 16 | - libcurl4-openssl-dev 17 | - libelf-dev 18 | - libdw-dev 19 | - cmake 20 | - gcc 21 | - binutils-dev 22 | 23 | after_success: | 24 | wget https://github.com/SimonKagstrom/kcov/archive/master.tar.gz && 25 | tar xzf master.tar.gz && 26 | cd kcov-master && 27 | mkdir build && 28 | cd build && 29 | cmake .. && 30 | make && 31 | sudo make install && 32 | cd ../.. && 33 | rm -rf kcov-master && 34 | for file in $(find . -regex "./target/debug/risp-[0-9a-f]*" ); do mkdir -p "target/cov/$(basename $file)"; kcov --exclude-pattern=/.cargo,/usr/lib --verify "target/cov/$(basename $file)" "$file"; done && 35 | bash <(curl -s https://codecov.io/bash) && 36 | echo "Uploaded code coverage" -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "risp" 3 | version = "0.7.0" 4 | dependencies = [ 5 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 7 | ] 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.6.3" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "kernel32-sys" 19 | version = "0.2.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "lazy_static" 28 | version = "0.2.8" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "libc" 33 | version = "0.2.21" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "memchr" 38 | version = "1.0.1" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 42 | ] 43 | 44 | [[package]] 45 | name = "regex" 46 | version = "0.2.1" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | dependencies = [ 49 | "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 50 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 51 | "regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 52 | "thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", 53 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 54 | ] 55 | 56 | [[package]] 57 | name = "regex-syntax" 58 | version = "0.4.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | 61 | [[package]] 62 | name = "thread-id" 63 | version = "3.0.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | dependencies = [ 66 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 67 | "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", 68 | ] 69 | 70 | [[package]] 71 | name = "thread_local" 72 | version = "0.3.3" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "unreachable" 81 | version = "0.1.1" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | dependencies = [ 84 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 85 | ] 86 | 87 | [[package]] 88 | name = "utf8-ranges" 89 | version = "1.0.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | 92 | [[package]] 93 | name = "void" 94 | version = "1.0.2" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | 97 | [[package]] 98 | name = "winapi" 99 | version = "0.2.8" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | 102 | [[package]] 103 | name = "winapi-build" 104 | version = "0.1.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [metadata] 108 | "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" 109 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 110 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 111 | "checksum libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)" = "88ee81885f9f04bff991e306fea7c1c60a5f0f9e409e99f6b40e3311a3363135" 112 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 113 | "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" 114 | "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" 115 | "checksum thread-id 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" 116 | "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7" 117 | "checksum unreachable 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1f2ae5ddb18e1c92664717616dd9549dde73f539f01bd7b77c2edb2446bdff91" 118 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 119 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 120 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 121 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 122 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "risp" 3 | version = "0.7.0" 4 | description = "A rusty Lisp inspired by Clojure for usage as simple configuration language" 5 | repository = "https://github.com/shybyte/risp" 6 | license = "MIT" 7 | authors = ["shybyte@gmail.com"] 8 | 9 | [dependencies] 10 | regex = "^0.2" 11 | lazy_static = "^0.2" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Marco Stahl 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 | # Risp [![Build Status](https://travis-ci.org/shybyte/risp.svg?branch=master)](https://travis-ci.org/shybyte/risp) [![codecov](https://codecov.io/gh/shybyte/risp/branch/master/graph/badge.svg)](https://codecov.io/gh/shybyte/risp) [![Crate Version](https://img.shields.io/crates/v/risp.svg)](https://crates.io/crates/risp) 2 | 3 | A rusty lisp inspired by Clojure for usage as simple configuration language. 4 | 5 | ## Usage in Rust 6 | 7 | ```rust 8 | extern crate risp; 9 | 10 | use risp::eval_risp_script; 11 | use risp::types::RispType::Int; 12 | use risp::core::create_core_environment; 13 | 14 | #[test] 15 | fn test_minimal_example() { 16 | let mut env = create_core_environment(); 17 | env.set("var", Int(2)); 18 | 19 | let risp_script = "(+ 40 var)"; 20 | let result = eval_risp_script(risp_script, &mut env); 21 | 22 | assert_eq!(result, Ok(Int(42))); 23 | } 24 | ``` 25 | 26 | ## Risp example showing every existing language feature 27 | 28 | ```clojure 29 | ; The risp kitchen sink - yes this line is a single line comment. 30 | 31 | (def my_int 2) 32 | 33 | (def my_vector [1 my_int 3]) 34 | 35 | ; repeat [1 2 3] 2 times => [1 2 3 1 2 3] 36 | (def repeated (rep 2 1 2 3)) 37 | 38 | ; => [11 21] 39 | (def vector_sum1 (+ 1 [10 20])) 40 | 41 | ; => [21 22] 42 | (def vector_sum2 (+ [1 2] [10 20])) 43 | 44 | ; => [11 12 21 22] (it wraps!) 45 | (def vector_sum3 (+ [1 2] [10 10 20 20])) 46 | 47 | (comment 48 | (this is not evaluated) 49 | (it can have multiple lines) 50 | (but must have valid risp syntax)) 51 | 52 | ; Define a function 53 | (defn double [x] (* x 2)) 54 | 55 | ; Function which returns a function (some call it a closure), which adds x1 to its single argument 56 | (defn create_adder [x1] 57 | (fn [x2] (+ x1 x2))) 58 | 59 | (def add_20 (create_adder 20)) 60 | 61 | ; variadic function, notes is a vector of all remaining arguments after name 62 | (defn create_song [name & notes] 63 | {:name name :notes notes}) 64 | 65 | ; This last expression (it's a map in this case) will be returned. 66 | {:yes true 67 | :no false 68 | :added (+ my_int 20) 69 | :multiplied (* my_int 20) 70 | :divided (* 10 2) 71 | :substracted (- 10 2) 72 | :doubled (double 21) 73 | :added_20 (add_20 3) 74 | :vector_sum1 vector_sum1 75 | :vector_sum2 vector_sum2 76 | :vector_sum3 vector_sum3 77 | :repeated repeated 78 | :my_vector my_vector 79 | :my_map {:key my_int} 80 | :my_string "Hello" 81 | :my_do_result (do 82 | (def my_int_2 20) 83 | (+ my_int my_int_2)) 84 | :song (create_song "Sweet Dreams" 1 2 3 4)} 85 | ``` 86 | 87 | 88 | ## Convert evaluated Risp to Rust 89 | ```rust 90 | extern crate risp; 91 | 92 | use risp::eval_risp_script; 93 | use risp::core::create_core_environment; 94 | 95 | struct SimpleSong { 96 | name: String, 97 | speed: i64, 98 | notes: Vec 99 | } 100 | 101 | #[test] 102 | fn test_convert_to_struct_example() { 103 | let mut env = create_core_environment(); 104 | 105 | let risp_script = r#" 106 | { 107 | :name "Name" 108 | :speed 220 109 | :notes [1 2 3] 110 | }"#; 111 | 112 | let result = eval_risp_script(risp_script, &mut env).unwrap(); 113 | 114 | let simple_song = SimpleSong { 115 | name: result.get("name").unwrap().unwrap(), 116 | speed: result.get("speed").unwrap().unwrap(), 117 | notes: result.get("notes").unwrap().unwrap() 118 | }; 119 | 120 | assert_eq!(simple_song.name, "Name"); 121 | assert_eq!(simple_song.speed, 220); 122 | assert_eq!(simple_song.notes, vec![1, 2, 3]); 123 | } 124 | 125 | ``` 126 | 127 | ## Goals 128 | * Simple configuration language 129 | * Subset of Clojure, well... a kind of 130 | 131 | ## Secret Real Goal 132 | * Usable for configuring patches in my pet project [https://github.com/shybyte/rust-midi-patcher](https://github.com/shybyte/rust-midi-patcher) 133 | 134 | ## Non-Goals 135 | * Performance 136 | * Completeness 137 | 138 | ## License 139 | 140 | MIT 141 | 142 | ## Copyright 143 | 144 | Copyright (c) 2017 Marco Stahl 145 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rustup run nightly cargo bench -------------------------------------------------------------------------------- /benches/example.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | extern crate test; 4 | extern crate risp; 5 | 6 | use test::Bencher; 7 | 8 | use risp::eval_risp_script; 9 | use risp::types::RispType::Int; 10 | use risp::core::create_core_environment; 11 | 12 | use risp::tokenize::tokenize; 13 | 14 | 15 | static RISP_SCRIPT: &str = r#" 16 | (def chorus_notes 17 | (rep 6 18 | (rep 4 45 57) 19 | (rep 4 48 60) 20 | (rep 4 43 55) 21 | (rep 4 43 55))) 22 | 23 | (def wild_notes (rep 50 45 47 53 57 60 67 60 57 53 47)) 24 | 25 | {:name "Amazon" 26 | :program 42 27 | :time_per_note 220 28 | :effects [{:trigger 43 :noteSequencer {:notes chorus_notes}} 29 | {:trigger 45 :noteSequencer {:notes [38 50 38 50 chorus_notes] :beat_offset 4}} 30 | {:trigger 36 :noteSequencer {:notes wild_notes}} 31 | {:trigger 38 :noteSequencer {:notes []}}]} 32 | 33 | (+ 40 2) 34 | 35 | "#; 36 | 37 | 38 | fn run_eval_risp_script_example() { 39 | let mut env = create_core_environment(); 40 | let result = eval_risp_script(RISP_SCRIPT, &mut env); 41 | assert_eq!(result, Ok(Int(42))); 42 | } 43 | 44 | 45 | fn run_tokenize_risp_script_example() { 46 | let result = tokenize(RISP_SCRIPT); 47 | assert!(result.is_empty()); 48 | } 49 | 50 | #[bench] 51 | fn bench_tokenize_example(b: &mut Bencher) { 52 | b.iter(run_tokenize_risp_script_example); 53 | } 54 | 55 | #[bench] 56 | fn bench_eval_risp_script_example(b: &mut Bencher) { 57 | b.iter(run_eval_risp_script_example); 58 | } 59 | 60 | -------------------------------------------------------------------------------- /clippy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | rustup run nightly cargo clippy -------------------------------------------------------------------------------- /examples/kitchen_sink.risp: -------------------------------------------------------------------------------- 1 | ; The risp kitchen sink - yes this line is a single line comment. 2 | 3 | (def my_int 2) 4 | 5 | (def my_vector [1 my_int 3]) 6 | 7 | ; repeat [1 2 3] 2 times => [1 2 3 1 2 3] 8 | (def repeated (rep 2 1 2 3)) 9 | 10 | ; => [11 21] 11 | (def vector_sum1 (+ 1 [10 20])) 12 | 13 | ; => [21 22] 14 | (def vector_sum2 (+ [1 2] [10 20])) 15 | 16 | ; => [11 12 21 22] (it wraps!) 17 | (def vector_sum3 (+ [1 2] [10 10 20 20])) 18 | 19 | (comment 20 | (this is not evaluated) 21 | (it can have multiple lines) 22 | (but must have valid risp syntax)) 23 | 24 | ; Define a function 25 | (defn double [x] (* x 2)) 26 | 27 | ; Function which returns a function (some call it a closure), which adds x1 to its single argument 28 | (defn create_adder [x1] 29 | (fn [x2] (+ x1 x2))) 30 | 31 | (def add_20 (create_adder 20)) 32 | 33 | ; variadic function, notes is a vector of all remaining arguments after name 34 | (defn create_song [name & notes] 35 | {:name name :notes notes}) 36 | 37 | ; This last expression (it's a map in this case) will be returned. 38 | {:yes true 39 | :no false 40 | :added (+ my_int 20) 41 | :multiplied (* my_int 20) 42 | :divided (* 10 2) 43 | :substracted (- 10 2) 44 | :doubled (double 21) 45 | :added_20 (add_20 3) 46 | :vector_sum1 vector_sum1 47 | :vector_sum2 vector_sum2 48 | :vector_sum3 vector_sum3 49 | :repeated repeated 50 | :my_vector my_vector 51 | :my_map {:key my_int} 52 | :my_string "Hello" 53 | :my_do_result (do 54 | (def my_int_2 20) 55 | (+ my_int my_int_2)) 56 | :song (create_song "Sweet Dreams" 1 2 3 4)} -------------------------------------------------------------------------------- /examples/simple_song.risp: -------------------------------------------------------------------------------- 1 | {:name "Name" :speed 220 :notes [1 2 3]} -------------------------------------------------------------------------------- /examples/song.risp: -------------------------------------------------------------------------------- 1 | (def chorus_notes 2 | (rep 6 3 | (rep 4 45 57) 4 | (rep 4 48 60) 5 | (rep 4 43 55) 6 | (rep 4 43 55))) 7 | 8 | (def wild_notes (rep 50 45 47 53 57 60 67 60 57 53 47)) 9 | 10 | {:name "Amazon" 11 | :program 42 12 | :time_per_note 220 13 | :effects [{:trigger 43 :noteSequencer {:notes chorus_notes}} 14 | {:trigger 45 :noteSequencer {:notes [38 50 38 50 chorus_notes] :beat_offset 4}} 15 | {:trigger 36 :noteSequencer {:notes wild_notes}} 16 | {:trigger 38 :noteSequencer {:notes []}}]} 17 | -------------------------------------------------------------------------------- /src/convert.rs: -------------------------------------------------------------------------------- 1 | use types::RispType; 2 | use types::RispType::*; 3 | use types::RispError; 4 | use types::*; 5 | use std::collections::HashMap; 6 | 7 | 8 | impl Into> for RispType { 9 | fn into(self) -> Result { 10 | Ok(self) 11 | } 12 | } 13 | 14 | impl Into> for RispType { 15 | fn into(self) -> Result { 16 | match self { 17 | Bool(b) => Ok(b), 18 | _ => Err(error(format!("Expected Bool but got {:?}", self))), 19 | } 20 | } 21 | } 22 | 23 | 24 | impl Into> for RispType { 25 | fn into(self) -> Result { 26 | match self { 27 | Int(int) => Ok(int), 28 | _ => Err(error(format!("Expected Int but got {:?}", self))), 29 | } 30 | } 31 | } 32 | 33 | impl Into> for RispType { 34 | fn into(self) -> Result { 35 | match self { 36 | Str(s) => Ok(s), 37 | _ => Err(error(format!("Expected String but got {:?}", self))), 38 | } 39 | } 40 | } 41 | 42 | impl Into, RispError>> for RispType { 43 | fn into(self) -> Result, RispError> { 44 | match self { 45 | Map(map) => Ok(map), 46 | _ => Err(error(format!("Expected Map but got {:?}", self))) 47 | } 48 | } 49 | } 50 | 51 | impl Into, RispError>> for RispType { 52 | fn into(self) -> Result, RispError> { 53 | match self { 54 | Vector(vector) => Ok(vector), 55 | _ => Err(error(format!("Expected Vector but got {:?}", self))) 56 | } 57 | } 58 | } 59 | 60 | impl Into, RispError>> for RispType { 61 | fn into(self) -> Result, RispError> { 62 | let vec_of_risp: Result, _> = self.into(); 63 | vec_of_risp?.iter().cloned() 64 | .map(|el| el.into()) 65 | .collect::, _>>() 66 | } 67 | } 68 | 69 | 70 | impl RispType { 71 | pub fn get(&self, key: &str) -> Result, RispError> where RispType: Into> { 72 | match *self { 73 | Map(ref map) => { 74 | match map.get(key).cloned() { 75 | Some(risp_value) => { 76 | Ok(Some(risp_value.into()?)) 77 | } 78 | None => Ok(None) 79 | } 80 | } 81 | _ => Err(error(format!("Expected Map but got {:?}", self))) 82 | } 83 | } 84 | } 85 | 86 | 87 | pub fn flatten_into(risp_vec_input: RispType) -> Result, RispError> 88 | where RispType: Into> { 89 | match risp_vec_input { 90 | Vector(vector) => { 91 | let flat = flatten_vec(vector); 92 | flat.iter().cloned() 93 | .map(|el| el.into()) 94 | .collect() 95 | } 96 | _ => Err(error(format!("Expected Vector but got {:?}", risp_vec_input))) 97 | } 98 | } 99 | 100 | pub fn flatten_vec(risp_vec: Vec) -> Vec { 101 | let mut result = vec![]; 102 | for el in risp_vec { 103 | if let RispType::Vector(vector) = el { 104 | for child_el in flatten_vec(vector) { 105 | result.push(child_el) 106 | } 107 | } else { 108 | result.push(el); 109 | } 110 | } 111 | result 112 | } 113 | 114 | 115 | /* ------------------------------ Tests ----------------------------------------------- */ 116 | 117 | 118 | #[test] 119 | fn test_convert_int() { 120 | let result: Result = Int(3).into(); 121 | assert_eq!(result, Ok(3)); 122 | } 123 | 124 | #[test] 125 | fn test_convert_int_error() { 126 | let result: Result = List(vec![]).into(); 127 | assert!(result.is_err()); 128 | } 129 | 130 | 131 | #[test] 132 | fn test_convert_string() { 133 | let result: Result = string("string").into(); 134 | assert_eq!(result, Ok("string".to_string())); 135 | } 136 | 137 | #[test] 138 | fn test_convert_string_error() { 139 | let result: Result = List(vec![]).into(); 140 | assert!(result.is_err()); 141 | } 142 | 143 | #[test] 144 | fn test_convert_vector() { 145 | let result: Result, RispError> = Vector(vec![Int(23)]).into(); 146 | assert_eq!(result, Ok(vec![Int(23)])); 147 | } 148 | 149 | #[test] 150 | fn test_convert_vector_error() { 151 | let result: Result, RispError> = Int(1).into(); 152 | assert!(result.is_err()); 153 | } 154 | 155 | #[test] 156 | fn test_convert_vector_int() { 157 | let result: Result, RispError> = Vector(vec![Int(23)]).into(); 158 | assert_eq!(result, Ok(vec![23])); 159 | } 160 | 161 | #[test] 162 | fn test_convert_map() { 163 | let input_map = map(vec![ 164 | ("key", Int(23)) 165 | ]); 166 | let result: Result, RispError> = input_map.into(); 167 | assert_eq!(result.unwrap().get("key").unwrap().clone(), Int(23)); 168 | } 169 | 170 | #[test] 171 | fn test_convert_map_error() { 172 | let result: Result, RispError> = List(vec![]).into(); 173 | assert_eq!(result, Err(error(format!("Expected Map but got List([])")))); 174 | } 175 | 176 | #[test] 177 | fn test_get() { 178 | let input_map = map(vec![ 179 | ("key", Int(23)) 180 | ]); 181 | let int_option = input_map.get("key").unwrap(); 182 | assert_eq!(int_option, Some(23)); 183 | } 184 | 185 | #[test] 186 | fn test_get_risptype() { 187 | let input_map = map(vec![ 188 | ("key", Int(23)) 189 | ]); 190 | let int_option: Option = input_map.get("key").unwrap(); 191 | assert_eq!(int_option, Some(Int(23))); 192 | } 193 | 194 | #[test] 195 | fn test_get_none() { 196 | let input_map = map(vec![ 197 | ("key", Int(23)) 198 | ]); 199 | let int_option: Option = input_map.get("unknown_key").unwrap(); 200 | assert_eq!(int_option, None); 201 | } 202 | 203 | #[test] 204 | fn test_get_error_expected_int() { 205 | let input_map = map(vec![ 206 | ("key", string("string")) 207 | ]); 208 | let int_result: Result, _> = input_map.get("key"); 209 | assert_eq!(int_result, Err(error(format!("Expected Int but got {:?}", string("string"))))); 210 | } 211 | 212 | #[test] 213 | fn test_get_error_expected_map() { 214 | let input = Int(123); 215 | let int_result: Result, _> = input.get("key"); 216 | assert_eq!(int_result, Err(error(format!("Expected Map but got Int(123)")))); 217 | } 218 | 219 | 220 | #[test] 221 | fn test_flatten_vec() { 222 | let input = vec![ 223 | Int(1), 224 | Vector(vec![Int(2), Int(3)]) 225 | ]; 226 | let flat_result = flatten_vec(input); 227 | assert_eq!(flat_result, vec![Int(1), Int(2), Int(3)] ); 228 | } 229 | 230 | #[test] 231 | fn test_flatten_into() { 232 | let input = Vector(vec![ 233 | Int(1), 234 | Vector(vec![Int(2), Int(3)]) 235 | ]); 236 | let flat_result = flatten_into(input); 237 | assert_eq!(flat_result, Ok(vec![1, 2, 3])); 238 | } 239 | 240 | #[test] 241 | fn test_flatten_error_outer() { 242 | let flat_result: Result, _> = flatten_into(Int(1)); 243 | assert_eq!(flat_result, Err(error("Expected Vector but got Int(1)"))); 244 | } 245 | 246 | #[test] 247 | fn test_flatten_error_inner() { 248 | let input = Vector(vec![ 249 | Int(1), 250 | Vector(vec![string("string"), Int(3)]) 251 | ]); 252 | let flat_result: Result, _> = flatten_into(input); 253 | assert_eq!(flat_result, Err(error("Expected Int but got Str(\"string\")"))); 254 | } 255 | -------------------------------------------------------------------------------- /src/core.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 2 | 3 | use std::iter; 4 | use environment::*; 5 | use types::*; 6 | use types::RispType::*; 7 | use std::cmp; 8 | use std::ops::{Add, Div, Mul, Sub}; 9 | 10 | type IntOperation = fn(i64, i64) -> i64; 11 | 12 | fn sum(args: Vec) -> RispResult { 13 | apply_to_vector(i64::add, &args) 14 | } 15 | 16 | fn apply_to_vector(op: IntOperation, vec: &[RispType]) -> RispResult { 17 | let s: RispResult = vec.get(0).ok_or_else(|| error("Missing first argument")).map(|x| x.clone()); 18 | vec.iter().skip(1).fold(s, |acc, x| apply_to(op, &acc?, x)) 19 | } 20 | 21 | fn apply_to(op: IntOperation, x1: &RispType, x2: &RispType) -> RispResult { 22 | match *x1 { 23 | Int(x1_int) => match *x2 { 24 | Int(x2_int) => Ok(Int(op(x1_int, x2_int))), 25 | Vector(ref x2_vec) => apply_to_number_and_vector(op, x1_int, x2_vec), 26 | _ => error_result(format!("Operation wants an Int or a Vector as second argument but got {:?}", x2)) 27 | }, 28 | Vector(ref x1_vec) => match *x2 { 29 | Int(x2_int) => apply_to_vector_and_number(op, x1_vec, x2_int), 30 | Vector(ref x2_vec) => apply_to_vector_and_vector(op, x1_vec, x2_vec), 31 | _ => error_result(format!("Operation wants an Int or a Vector as second argument but got {:?}", x2)) 32 | }, 33 | _ => error_result(format!("Operation wants an Int or a Vector as first argument but got {:?}", x1)) 34 | } 35 | } 36 | 37 | fn apply_to_number_and_vector(op: IntOperation, x_int: i64, xs: &[RispType]) -> RispResult { 38 | let x = Int(x_int); 39 | xs.iter().map(|x2| apply_to(op, &x, x2)) 40 | .collect::>() 41 | .map(Vector) 42 | } 43 | 44 | fn apply_to_vector_and_number(op: IntOperation, xs: &[RispType], x_int: i64) -> RispResult { 45 | let x = Int(x_int); 46 | xs.iter().map(|x2| apply_to(op, x2, &x)) 47 | .collect::>() 48 | .map(Vector) 49 | } 50 | 51 | fn apply_to_vector_and_vector(op: IntOperation, xs1: &[RispType], xs2: &[RispType]) -> RispResult { 52 | let result_len = cmp::max(xs1.len(), xs2.len()); 53 | (0..result_len) 54 | .map(|i| apply_to(op, &xs1[i % xs1.len()], &xs2[i % xs2.len()])) 55 | .collect::>() 56 | .map(Vector) 57 | } 58 | 59 | fn mul(vec: Vec) -> RispResult { 60 | apply_to_vector(i64::mul, &vec) 61 | } 62 | 63 | fn div(vec: Vec) -> RispResult { 64 | apply_to_vector(i64::div, &vec) 65 | } 66 | 67 | fn sub(vec: Vec) -> RispResult { 68 | apply_to_vector(i64::sub, &vec) 69 | } 70 | 71 | fn rep(args: Vec) -> RispResult { 72 | if let Some((n, elements)) = args.split_first() { 73 | match *n { 74 | Int(n2) => { 75 | Ok(Vector(repeated(elements, n2 as usize))) 76 | } 77 | _ => error_result("rep needs an int as first argument") 78 | } 79 | } else { 80 | error_result("rep needs 2 arguments but got 0") 81 | } 82 | } 83 | 84 | fn repeated(pattern: &[T], times: usize) -> Vec { 85 | concat(iter::repeat(pattern.to_vec()).take(times).collect()) 86 | } 87 | 88 | fn concat(input: Vec>) -> Vec { 89 | input.into_iter().flat_map(|x| x).collect() 90 | } 91 | 92 | 93 | pub fn create_core_environment() -> Environment { 94 | let mut env = Environment::new(); 95 | env.set("+", Function(sum)); 96 | env.set("*", Function(mul)); 97 | env.set("/", Function(div)); 98 | env.set("-", Function(sub)); 99 | env.set("rep", Function(rep)); 100 | env 101 | } 102 | 103 | 104 | /* ------------------------------ Tests ----------------------------------------------- */ 105 | 106 | #[allow(dead_code)] 107 | fn sum2(x1: &RispType, x2: &RispType) -> RispResult { 108 | apply_to(i64::add, x1, x2) 109 | } 110 | 111 | 112 | #[test] 113 | fn test_sum() { 114 | assert_eq!(sum(vec![Int(1), Int(2)]), Ok(Int(3))); 115 | } 116 | 117 | #[test] 118 | fn test_sum_number_vector() { 119 | assert_eq!(sum(vec![Int(20), Vector(vec![Int(1), Int(2)])]), Ok(Vector(vec![Int(21), Int(22)]))); 120 | } 121 | 122 | #[test] 123 | fn test_sum_number_vector_vector() { 124 | assert_eq!(sum(vec![Int(20), Vector(vec![Int(1), Int(2)]), Vector(vec![Int(100), Int(200)])]), Ok(Vector(vec![Int(121), Int(222)]))); 125 | } 126 | 127 | #[test] 128 | fn test_sum_number_nested_vector() { 129 | assert_eq!(sum(vec![ 130 | Int(20), 131 | Vector(vec![Int(1), Int(2), Vector(vec![Int(100), Int(200)])]), 132 | ]) 133 | , Ok(Vector(vec![Int(21), Int(22), Vector(vec![Int(120), Int(220)])]))); 134 | } 135 | 136 | #[test] 137 | fn test_sum2_number_vector() { 138 | assert_eq!(sum2(&Int(20), &Vector(vec![Int(1), Int(2)])), Ok(Vector(vec![Int(21), Int(22)]))); 139 | } 140 | 141 | #[test] 142 | fn test_sum2_vector_number() { 143 | assert_eq!(sum2(&Vector(vec![Int(1), Int(2)]), &Int(20)), Ok(Vector(vec![Int(21), Int(22)]))); 144 | } 145 | 146 | #[test] 147 | fn test_sum2_vector_vector() { 148 | assert_eq!(sum2( 149 | &Vector(vec![Int(1), Int(2)]), 150 | &Vector(vec![Int(10), Int(20)]) 151 | ), Ok(Vector(vec![Int(11), Int(22)]))); 152 | } 153 | 154 | #[test] 155 | fn test_sum2_vector_longer_vector() { 156 | assert_eq!(sum2( 157 | &Vector(vec![Int(1), Int(2)]), 158 | &Vector(vec![Int(10), Int(20), Int(30)]) 159 | ), Ok(Vector(vec![Int(11), Int(22), Int(31)]))); 160 | } 161 | 162 | #[test] 163 | fn test_sum2_errors() { 164 | assert_eq!(sum2(&string("string"), &Int(1)), error_result("Operation wants an Int or a Vector as first argument but got Str(\"string\")")); 165 | assert_eq!(sum2(&Int(1), &string("string")), error_result("Operation wants an Int or a Vector as second argument but got Str(\"string\")")); 166 | assert_eq!(sum2(&Vector(vec![Int(1)]), &string("string")), error_result("Operation wants an Int or a Vector as second argument but got Str(\"string\")")); 167 | } 168 | 169 | 170 | #[test] 171 | fn test_add_number_to_vector() { 172 | assert_eq!(apply_to_number_and_vector(i64::add, 20, &vec![Int(1), Int(2)]), Ok(Vector(vec![Int(21), Int(22)]))); 173 | } 174 | 175 | 176 | #[test] 177 | fn test_sum_errors() { 178 | assert_eq!(sum(vec![Nil, Nil]), error_result("Operation wants an Int or a Vector as first argument but got Nil")); 179 | } 180 | 181 | #[test] 182 | fn test_mul_errors() { 183 | assert!(mul(vec![List(vec![]), Int(1)]).is_err()); 184 | } 185 | 186 | #[test] 187 | fn test_div() { 188 | assert_eq!(div(vec![Int(10), Int(2)]), Ok(Int(5))); 189 | } 190 | 191 | #[test] 192 | fn test_sub() { 193 | assert_eq!(sub(vec![Int(10), Int(2), Int(1)]), Ok(Int(7))); 194 | } 195 | 196 | 197 | #[test] 198 | fn test_rep() { 199 | assert_eq!( 200 | rep(vec![Int(2), Int(3), Int(4)]), 201 | Ok(Vector(vec![Int(3), Int(4), Int(3), Int(4)])) 202 | ); 203 | } 204 | 205 | #[test] 206 | fn test_rep_nested() { 207 | assert_eq!( 208 | rep(vec![Int(2), Vector(vec![Int(3), Int(4)])]), 209 | Ok(Vector(vec![Vector(vec![Int(3), Int(4)]), Vector(vec![Int(3), Int(4)])])) 210 | ); 211 | } 212 | 213 | #[test] 214 | fn test_rep_missing_arguments() { 215 | assert_eq!(rep(vec![]), error_result("rep needs 2 arguments but got 0")); 216 | } 217 | 218 | #[test] 219 | fn test_rep_needs_int_as_first_argument() { 220 | assert_eq!(rep(vec![string("23")]), error_result("rep needs an int as first argument")); 221 | } -------------------------------------------------------------------------------- /src/environment.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use types::*; 3 | 4 | #[derive(Default, Debug, PartialEq, Clone)] 5 | pub struct Environment { 6 | data: HashMap, 7 | } 8 | 9 | impl Environment { 10 | pub fn new() -> Self { 11 | Environment { data: HashMap::new() } 12 | } 13 | 14 | pub fn set(&mut self, key: &str, value: RispType) { 15 | self.data.insert(key.to_string(), value); 16 | } 17 | 18 | pub fn get(&self, key: &str) -> Option { 19 | self.data.get(key).cloned() 20 | } 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/eval.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use types::*; 3 | use types::RispType::*; 4 | use environment::*; 5 | use core::create_core_environment; 6 | use parse; 7 | use std::rc::Rc; 8 | 9 | pub fn eval(ast: RispType, env: &mut Environment) -> RispResult { 10 | match ast { 11 | List(list) => { 12 | let first_element = list.first().ok_or_else(|| error("Empty List"))?; 13 | match *first_element { 14 | Symbol(ref symbol_ref) => { 15 | match symbol_ref.as_ref() { 16 | "def" => { 17 | let var = list.get(1).ok_or_else(|| error("Missing variable in def"))?; 18 | match *var { 19 | Symbol(ref sym_var) => { 20 | let value_ast = list.get(2).ok_or_else(|| error("Missing value in def"))?; 21 | let value = eval(value_ast.clone(), env)?; 22 | env.set(sym_var, value.clone()); 23 | Ok(value) 24 | } 25 | _ => error_result(format!("Expected symbol in def but got {:?}", var)) 26 | } 27 | } 28 | "defn" => { 29 | let name = list.get(1).ok_or_else(|| error("Missing function name in defn"))?; 30 | let args = list.get(2).ok_or_else(|| error("Missing args in defn"))?; 31 | let body = list.get(3).ok_or_else(|| error("Missing body in defn"))?; 32 | eval(List(vec![symbol("def"), name.clone(), List(vec![symbol("fn"), args.clone(), body.clone()])]), env) 33 | } 34 | "do" => { 35 | if let Some((last, elements)) = (&list[1..]).split_last() { 36 | for child_ast in elements.iter() { 37 | eval(child_ast.clone(), env)?; 38 | } 39 | eval(last.clone(), env) 40 | } else { 41 | error_result("Empty do block") 42 | } 43 | } 44 | "comment" => { 45 | Ok(Nil) 46 | } 47 | "fn" => { 48 | let args_risp = list.get(1).ok_or_else(|| error("Missing args in fn"))?; 49 | match *args_risp { 50 | Vector(ref args_vec) => { 51 | let body = list.get(2).ok_or_else(|| error("Missing body in fn"))?; 52 | if let Some(variadic_marker_pos) = args_vec.iter().position(is_variadic_marker) { 53 | if let Some(&Symbol(ref variadic_arg)) = args_vec.get(variadic_marker_pos + 1) { 54 | Ok(RispFunction(RispFunc { 55 | args: args_vec[..variadic_marker_pos].to_vec(), 56 | variadic_arg: Some(variadic_arg.to_string()), 57 | body: Rc::new(body.clone()), 58 | env: env.clone() 59 | })) 60 | } else { 61 | error_result(format!("Missing variadic arg after & in {:?}", args_vec)) 62 | } 63 | } else { 64 | Ok(RispFunction(RispFunc { 65 | args: args_vec.clone(), 66 | variadic_arg: None, 67 | body: Rc::new(body.clone()), 68 | env: env.clone() 69 | })) 70 | } 71 | } 72 | _ => error_result(format!("Expected args vector in fn but got {:?}", args_risp)) 73 | } 74 | } 75 | _ => { 76 | let evaluated_tail = list[1..].iter() 77 | .map(|el| eval(el.clone(), env)) 78 | .collect::, _>>()?; 79 | let env_value = env.get(symbol_ref).ok_or_else(|| error(format!("Undefined symbol{:?}", symbol_ref)))?; 80 | match env_value { 81 | Function(function) => function(evaluated_tail.to_vec()), 82 | RispFunction(risp_function) => { 83 | let mut inner_env = risp_function.env.clone(); 84 | put_args_into_env(&risp_function, &evaluated_tail, &mut inner_env)?; 85 | eval((*risp_function.body).clone(), &mut inner_env) 86 | } 87 | _ => error_result(format!("Expected function but got {:?}", env_value)) 88 | } 89 | } 90 | } 91 | } 92 | _ => error_result(format!("Expected symbol but got {:?}", first_element)) 93 | } 94 | } 95 | Vector(vector) => { 96 | let evaluated_vector = vector.iter() 97 | .map(|el| eval(el.clone(), env)) 98 | .collect::, _>>()?; 99 | Ok(Vector(evaluated_vector)) 100 | } 101 | 102 | Map(map_value) => { 103 | let evaluated_map = map_value.iter() 104 | .map(|(key, val)| 105 | eval(val.clone(), env).map(|evaluated_value| 106 | (key.to_string(), evaluated_value))) 107 | .collect::, _>>()?; 108 | Ok(Map(evaluated_map)) 109 | } 110 | 111 | Symbol(symbol) => { 112 | env.get(&symbol).ok_or_else(|| error(format!("symbol '{:?}' is undefined", symbol))) 113 | } 114 | other => Ok(other) 115 | } 116 | } 117 | 118 | fn is_variadic_marker(risp: &RispType) -> bool { 119 | *risp == symbol("&") 120 | } 121 | 122 | fn put_args_into_env(risp_func: &RispFunc, values: &[RispType], env: &mut Environment) -> Result<(), RispError> { 123 | for (arg, value) in risp_func.args.iter().zip(values.iter()) { 124 | match *arg { 125 | Symbol(ref arg_string) => { 126 | env.set(arg_string, value.clone()) 127 | } 128 | _ => { 129 | return Err(error(format!("Expected symbol in args list got {:?}", arg))); 130 | } 131 | } 132 | } 133 | if let Some(ref variadic_arg) = risp_func.variadic_arg { 134 | let variadic_args: Vec = values[risp_func.args.len()..].to_vec(); 135 | env.set(variadic_arg, Vector(variadic_args)); 136 | } 137 | Ok(()) 138 | } 139 | 140 | /* ------------------------------ Tests ----------------------------------------------- */ 141 | 142 | #[allow(dead_code)] 143 | fn eval_test(ast: RispType) -> RispResult { 144 | eval(ast, &mut create_core_environment()) 145 | } 146 | 147 | #[allow(dead_code)] 148 | fn eval_str(risp: &str) -> RispResult { 149 | let ast = parse::parse(risp)?; 150 | eval(ast, &mut create_core_environment()) 151 | } 152 | 153 | #[test] 154 | fn test_eval_number() { 155 | assert_eq!(eval_test(Int(23)), Ok(Int(23))); 156 | } 157 | 158 | #[test] 159 | fn test_eval_math() { 160 | assert_eq!(eval_test(List(vec![Symbol("+".to_string()), Int(1), Int(2)])), Ok(Int(3))); 161 | } 162 | 163 | #[test] 164 | fn test_nested_math() { 165 | assert_eq!(eval_test(List(vec![ 166 | Symbol("+".to_string()), Int(1), 167 | List(vec![Symbol("+".to_string()), Int(10), Int(100)]) 168 | ])), Ok(Int(111))); 169 | } 170 | 171 | #[test] 172 | fn test_mul() { 173 | assert_eq!(eval_test(List(vec![ 174 | Symbol("+".to_string()), Int(1), 175 | List(vec![Symbol("*".to_string()), Int(10), Int(23)]) 176 | ])), Ok(Int(231))); 177 | } 178 | 179 | 180 | #[test] 181 | fn test_def() { 182 | let variable = "variable"; 183 | let variable_value = Int(23); 184 | let mut env = create_core_environment(); 185 | 186 | assert_eq!(eval(List(vec![ 187 | symbol("def"), 188 | symbol(variable), 189 | variable_value.clone() 190 | ]), &mut env), Ok(variable_value.clone())); 191 | 192 | assert_eq!(env.get(variable), Some(variable_value)); 193 | } 194 | 195 | #[test] 196 | fn test_def_evaluated() { 197 | let variable = "variable"; 198 | let mut env = create_core_environment(); 199 | 200 | assert_eq!(eval(List(vec![ 201 | symbol("def"), 202 | symbol(variable), 203 | List(vec![symbol("+"), Int(1), Int(2)]) 204 | ]), &mut env), Ok(Int(3))); 205 | 206 | assert_eq!(env.get(variable), Some(Int(3))); 207 | } 208 | 209 | #[test] 210 | fn test_eval_simple_vector() { 211 | let simple_vector = Vector(vec![Int(1), Int(2)]); 212 | assert_eq!(eval_test(simple_vector.clone()), Ok(simple_vector)); 213 | } 214 | 215 | #[test] 216 | fn test_eval_nested_vector() { 217 | let simple_vector = Vector(vec![Int(1), List(vec![symbol("+"), Int(1), Int(2)])]); 218 | assert_eq!(eval_test(simple_vector.clone()), Ok(Vector(vec![Int(1), Int(3)]))); 219 | } 220 | 221 | 222 | #[test] 223 | fn test_eval_simple_map() { 224 | let simple_map = map(vec![ 225 | ("key1", Int(1)), 226 | ("key2", Int(2)) 227 | ]); 228 | assert_eq!(eval_test(simple_map.clone()), Ok(simple_map)); 229 | } 230 | 231 | #[test] 232 | fn test_eval_nested_map() { 233 | let input_map = map(vec![ 234 | ("key", List(vec![symbol("+"), Int(1), Int(2)])) 235 | ]); 236 | let expected_output_map = map(vec![ 237 | ("key", Int(3)) 238 | ]); 239 | assert_eq!(eval_test(input_map.clone()), Ok(expected_output_map)); 240 | } 241 | 242 | #[test] 243 | fn test_ignore_comments() { 244 | assert_eq!(eval_str("(comment)"), Ok(Nil)); 245 | assert_eq!(eval_str("(comment bla)"), Ok(Nil)); 246 | assert_eq!(eval_str("(comment (bla 1 2))"), Ok(Nil)); 247 | } 248 | 249 | #[test] 250 | fn test_risp_function_no_args() { 251 | assert_eq!(eval_str("(fn [] 23)"), Ok(RispFunction(RispFunc { 252 | args: vec![], 253 | variadic_arg: None, 254 | body: Rc::new(Int(23)), 255 | env: create_core_environment() 256 | }))); 257 | 258 | assert_eq!(eval_str("(fn [] (+ 40 2))"), Ok(RispFunction(RispFunc { 259 | args: vec![], 260 | variadic_arg: None, 261 | body: Rc::new(List(vec![symbol("+"), Int(40), Int(2)])), 262 | env: create_core_environment() 263 | }))); 264 | } 265 | 266 | #[test] 267 | fn test_risp_function_error() { 268 | assert_eq!(eval_str("(fn)"), error_result("Missing args in fn")); 269 | assert_eq!(eval_str("(fn 23)"), error_result("Expected args vector in fn but got Int(23)")); 270 | assert_eq!(eval_str("(fn [])"), error_result("Missing body in fn")); 271 | } 272 | 273 | #[test] 274 | fn test_eval_risp_function_no_args() { 275 | assert_eq!(eval_str(r" 276 | (do 277 | (def f23 (fn [] 23)) 278 | (f23) 279 | ) 280 | "), Ok(Int(23))); 281 | 282 | assert_eq!(eval_str(r" 283 | (do 284 | (def f23 (fn [] (+ 20 3))) 285 | (f23) 286 | ) 287 | "), Ok(Int(23))); 288 | } 289 | 290 | #[test] 291 | fn test_eval_risp_function_with_args() { 292 | assert_eq!(eval_str(r" 293 | (do 294 | (def plus20 (fn [x y] (+ x y 20))) 295 | (plus20 1 2) 296 | ) 297 | "), Ok(Int(23))); 298 | } 299 | 300 | #[test] 301 | fn test_eval_risp_function_with_args_error() { 302 | assert_eq!(eval_str(r#" 303 | (do 304 | (def plus20 (fn ["12" y] (+ x y 20))) 305 | (plus20 1 2) 306 | ) 307 | "#), error_result("Expected symbol in args list got Str(\"12\")")); 308 | } 309 | 310 | 311 | #[test] 312 | fn test_eval_risp_function_does_not_change_surrounding_env() { 313 | assert_eq!(eval_str(r" 314 | (do 315 | (def x 1234) 316 | (def plus20 (fn [x y] (+ x y 20))) 317 | (plus20 1 2) 318 | x 319 | ) 320 | "), Ok(Int(1234))); 321 | } 322 | 323 | 324 | #[test] 325 | fn test_eval_defn() { 326 | assert_eq!(eval_str(r" 327 | (do 328 | (defn plus20 [x y] (+ x y 20)) 329 | (plus20 1 2) 330 | ) 331 | "), Ok(Int(23))); 332 | } 333 | 334 | #[test] 335 | fn test_eval_defn_errors() { 336 | assert_eq!(eval_str("(defn)"), error_result("Missing function name in defn")); 337 | assert_eq!(eval_str("(defn name)"), error_result("Missing args in defn")); 338 | assert_eq!(eval_str("(defn name [])"), error_result("Missing body in defn")); 339 | } 340 | 341 | #[test] 342 | fn test_eval_extra_variadic_args() { 343 | assert_eq!(eval_str(r" 344 | (do 345 | (defn plus [x & more] (+ x more)) 346 | (plus 10 1 2) 347 | ) 348 | "), Ok(Vector(vec![Int(11), Int(12)]))); 349 | } 350 | 351 | 352 | #[test] 353 | fn test_eval_only_variadic_args() { 354 | assert_eq!(eval_str(r" 355 | (do 356 | (defn plus64 [& more] (+ 64 more)) 357 | (plus64 -1 0 1) 358 | ) 359 | "), Ok(Vector(vec![Int(63), Int(64), Int(65)]))); 360 | } 361 | 362 | #[test] 363 | fn test_eval_variadic_error() { 364 | assert_eq!(eval_str("(fn [&] 23)"), error_result("Missing variadic arg after & in [Symbol(\"&\")]")); 365 | } 366 | 367 | 368 | #[test] 369 | fn test_return_closures() { 370 | assert_eq!(eval_str(r" 371 | (do 372 | (defn create_adder [x1] 373 | (fn [x2] (+ x1 x2))) 374 | 375 | (def add_20 (create_adder 20)) 376 | 377 | (add_20 3) 378 | 379 | ) 380 | "), Ok(Int(23))); 381 | } -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate lazy_static; 2 | extern crate regex; 3 | 4 | pub mod convert; 5 | pub mod core; 6 | pub mod environment; 7 | pub mod eval; 8 | pub mod parse; 9 | pub mod tokenize; 10 | pub mod types; 11 | 12 | use types::RispResult; 13 | use parse::parse; 14 | use eval::eval; 15 | use environment::Environment; 16 | use core::create_core_environment; 17 | 18 | pub fn eval_risp(risp_code: &str) -> RispResult { 19 | eval_risp_for_env(risp_code, &mut create_core_environment()) 20 | } 21 | 22 | pub fn eval_risp_for_env(risp_code: &str, env: &mut Environment) -> RispResult { 23 | let ast = parse(risp_code)?; 24 | eval(ast, env) 25 | } 26 | 27 | pub fn eval_risp_script(risp_code: &str, env: &mut Environment) -> RispResult { 28 | let ast = parse(&("(do ".to_string() + risp_code + ")"))?; 29 | eval(ast, env) 30 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate lazy_static; 2 | extern crate regex; 3 | 4 | mod core; 5 | mod environment; 6 | mod eval; 7 | mod parse; 8 | mod types; 9 | mod tokenize; 10 | 11 | use std::fs::File; 12 | use std::io::prelude::*; 13 | use parse::parse; 14 | use eval::eval; 15 | use core::create_core_environment; 16 | 17 | 18 | fn main() { 19 | let mut file = File::open("examples/kitchen_sink.risp").unwrap(); 20 | let mut risp_code = String::new(); 21 | file.read_to_string(&mut risp_code).unwrap(); 22 | 23 | let ast = parse(&("(do ".to_string() + &risp_code + ")")).unwrap(); 24 | let result = eval(ast, &mut create_core_environment()); 25 | println!("{:?}", result); 26 | } 27 | 28 | -------------------------------------------------------------------------------- /src/parse.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use types::*; 3 | use types::RispType::*; 4 | use tokenize::*; 5 | 6 | fn parse_internal(tokenizer: &mut Iterator) -> Result { 7 | let mut tokenizer = tokenizer.peekable(); 8 | if let Some(token) = tokenizer.next() { 9 | return match token { 10 | (TokenType::Number, token_string) => { 11 | Ok(Int(token_string.parse().unwrap())) 12 | } 13 | 14 | (TokenType::Symbol, token_string) => { 15 | match &token_string[..] { 16 | "true" => Ok(Bool(true)), 17 | "false" => Ok(Bool(false)), 18 | _ => Ok(symbol(token_string)) 19 | } 20 | } 21 | 22 | (TokenType::Keyword, token_string) => { 23 | Ok(keyword(&token_string[1..])) 24 | } 25 | 26 | (TokenType::Str, token_string) => { 27 | Ok(string(&token_string[1..(token_string.len() - 1)])) 28 | } 29 | 30 | (TokenType::ListStart, _token_string) => { 31 | let mut list = vec![]; 32 | loop { 33 | let token_option = tokenizer.peek().cloned(); 34 | if let Some(element_token) = token_option { 35 | match element_token { 36 | (TokenType::ListEnd, _) => { 37 | break; 38 | } 39 | _ => { 40 | let parsed_element = parse_internal(&mut tokenizer)?; 41 | list.push(parsed_element); 42 | } 43 | } 44 | } else { 45 | return error_result("Unexpected end of list"); 46 | } 47 | } 48 | Ok(List(list)) 49 | } 50 | 51 | (TokenType::ListEnd, _token_string) => { 52 | error_result("Unexpected end of list") 53 | } 54 | 55 | (TokenType::VectorStart, _token_string) => { 56 | let mut vector = vec![]; 57 | loop { 58 | let token_option = tokenizer.peek().cloned(); 59 | if let Some(element_token) = token_option { 60 | match element_token { 61 | (TokenType::VectorEnd, _) => { 62 | break; 63 | } 64 | _ => { 65 | let parsed_element = parse_internal(&mut tokenizer)?; 66 | vector.push(parsed_element); 67 | } 68 | } 69 | } else { 70 | return error_result("Vector should end with ] but just ends"); 71 | } 72 | } 73 | Ok(Vector(vector)) 74 | } 75 | 76 | (TokenType::VectorEnd, _token_string) => { 77 | error_result("Unexpected ]") 78 | } 79 | 80 | (TokenType::HashMapStart, _token_string) => { 81 | let mut map = HashMap::::new(); 82 | loop { 83 | let token_option = tokenizer.peek().cloned(); 84 | if let Some(element_token) = token_option { 85 | match element_token { 86 | (TokenType::HashMapEnd, _) => { 87 | break; 88 | } 89 | 90 | (TokenType::Keyword, keyword) => { 91 | tokenizer.next(); 92 | let parsed_element = parse_internal(&mut tokenizer)?; 93 | map.insert((&keyword[1..]).to_string(), parsed_element); 94 | } 95 | 96 | (_, token_string) => { 97 | return error_result(format!("Expected keyword but got {:?}", token_string)); 98 | } 99 | } 100 | } else { 101 | return error_result("HashMap should end with } but just ends"); 102 | } 103 | } 104 | Ok(Map(map)) 105 | } 106 | 107 | (TokenType::HashMapEnd, _token_string) => { 108 | error_result("Unexpected }") 109 | } 110 | } 111 | } 112 | 113 | error_result("Error") 114 | } 115 | 116 | pub fn parse(input: &str) -> Result { 117 | let mut tokenizer = Tokenizer::new(input); 118 | parse_internal(&mut tokenizer) 119 | } 120 | 121 | 122 | /* ------------------------------ Tests ----------------------------------------------- */ 123 | 124 | #[test] 125 | fn test_parse_number() { 126 | assert_eq!(parse("0"), Ok(Int(0))); 127 | assert_eq!(parse("1"), Ok(Int(1))); 128 | assert_eq!(parse("42"), Ok(Int(42))); 129 | assert_eq!(parse("-42"), Ok(Int(-42))); 130 | } 131 | 132 | 133 | #[test] 134 | fn test_list() { 135 | assert_eq!(parse("()"), Ok(List(vec![]))); 136 | assert_eq!(parse("(42)"), Ok(List(vec![Int(42)]))); 137 | assert_eq!(parse("(42 23)"), Ok(List(vec![Int(42), Int(23)]))); 138 | assert_eq!(parse("(42 (23))"), Ok(List(vec![Int(42), List(vec![Int(23)])]))); 139 | } 140 | 141 | #[test] 142 | fn test_parse_symbols() { 143 | assert_eq!(parse("symbol"), Ok(Symbol("symbol".to_string()))); 144 | assert_eq!(parse("(+ 1 2)"), Ok(List(vec![Symbol("+".to_string()), Int(1), Int(2)]))); 145 | } 146 | 147 | #[test] 148 | fn test_parse_vector() { 149 | assert_eq!(parse("[]"), Ok(Vector(vec![]))); 150 | assert_eq!(parse("[42]"), Ok(Vector(vec![Int(42)]))); 151 | assert_eq!(parse("[42 23]"), Ok(Vector(vec![Int(42), Int(23)]))); 152 | assert_eq!(parse("[42 [23]]"), Ok(Vector(vec![Int(42), Vector(vec![Int(23)])]))); 153 | } 154 | 155 | #[test] 156 | fn test_parse_vector_errors() { 157 | assert_eq!(parse("["), error_result("Vector should end with ] but just ends")); 158 | assert_eq!(parse("]"), error_result("Unexpected ]")); 159 | assert_eq!(parse("(]"), error_result("Unexpected ]")); 160 | } 161 | 162 | 163 | #[test] 164 | fn test_keyword() { 165 | assert_eq!(parse(":key"), Ok(keyword("key"))); 166 | } 167 | 168 | 169 | #[test] 170 | fn test_hash_map_empty() { 171 | assert_eq!(parse("{}"), Ok(Map(HashMap::new()))); 172 | } 173 | 174 | #[test] 175 | fn test_hash_map_with_1_key() { 176 | assert_eq!(parse("{:key 123}"), Ok(map(vec![("key", Int(123))]))); 177 | } 178 | 179 | #[test] 180 | fn test_hash_map_with_2_keys() { 181 | let expected_map = map(vec![ 182 | ("key1", Int(1)), 183 | ("key2", Int(2)) 184 | ]); 185 | assert_eq!(parse("{:key1 1 :key2 2}"), Ok(expected_map)); 186 | } 187 | 188 | #[test] 189 | fn test_map_with_var() { 190 | assert_eq!(parse("{:key1 var}"), Ok(map(vec![ 191 | ("key1", symbol("var")) 192 | ]))); 193 | } 194 | 195 | 196 | #[test] 197 | fn test_hash_map_errors() { 198 | assert_eq!(parse("{"), error_result("HashMap should end with } but just ends")); 199 | assert_eq!(parse("}"), error_result("Unexpected }")); 200 | assert_eq!(parse("{123}"), error_result("Expected keyword but got \"123\"")); 201 | } 202 | 203 | #[test] 204 | fn test_str() { 205 | assert_eq!(parse("\"string\""), Ok(string("string"))); 206 | } 207 | 208 | 209 | #[test] 210 | fn test_bool() { 211 | assert_eq!(parse("true"), Ok(Bool(true))); 212 | assert_eq!(parse("false"), Ok(Bool(false))); 213 | } 214 | -------------------------------------------------------------------------------- /src/tokenize.rs: -------------------------------------------------------------------------------- 1 | use regex::Regex; 2 | 3 | #[derive(Debug, PartialEq, Copy, Clone)] 4 | pub enum TokenType { 5 | Number, 6 | ListStart, 7 | ListEnd, 8 | VectorStart, 9 | VectorEnd, 10 | HashMapStart, 11 | HashMapEnd, 12 | Symbol, 13 | Keyword, 14 | Str 15 | } 16 | 17 | lazy_static! { 18 | static ref COMMENT_REGEXP: Regex = Regex::new("^(\\s+|;.*?(\n|$))+").unwrap(); 19 | static ref STR_REGEXP: Regex = Regex::new("^\".*?\"").unwrap(); 20 | static ref SYMBOL_REGEXP: Regex = Regex::new(r"^[^\s\{\}()\[\]]+").unwrap(); 21 | static ref NUMBER_REGEXP: Regex = Regex::new(r"^-?\d+").unwrap(); 22 | } 23 | 24 | pub type Token = (TokenType, String); 25 | 26 | pub struct Tokenizer { 27 | input: String, 28 | pos: usize 29 | } 30 | 31 | impl Tokenizer { 32 | pub fn new(input: &str) -> Tokenizer { 33 | Tokenizer { input: input.to_string(), pos: 0 } 34 | } 35 | } 36 | 37 | impl Iterator for Tokenizer { 38 | type Item = Token; 39 | 40 | fn next(&mut self) -> Option { 41 | // Skip white space and comments 42 | if let Some(cap) = COMMENT_REGEXP.captures(&self.input[self.pos..]) { 43 | self.pos += cap[0].len(); 44 | } 45 | 46 | let input = &self.input[self.pos..]; 47 | 48 | 49 | if input.starts_with('(') { 50 | self.pos += 1; 51 | return Some(token(TokenType::ListStart, "(")) 52 | } 53 | 54 | if input.starts_with(')') { 55 | self.pos += 1; 56 | return Some(token(TokenType::ListEnd, ")")) 57 | } 58 | 59 | if input.starts_with('[') { 60 | self.pos += 1; 61 | return Some(token(TokenType::VectorStart, "[")) 62 | } 63 | 64 | if input.starts_with(']') { 65 | self.pos += 1; 66 | return Some(token(TokenType::VectorEnd, "]")); 67 | } 68 | 69 | if input.starts_with('{') { 70 | self.pos += 1; 71 | return Some(token(TokenType::HashMapStart, "{")) 72 | } 73 | 74 | if input.starts_with('}') { 75 | self.pos += 1; 76 | return Some(token(TokenType::HashMapEnd, "}")); 77 | } 78 | 79 | if let Some(cap) = STR_REGEXP.captures(input) { 80 | self.pos += cap[0].len(); 81 | return Some(token(TokenType::Str, cap[0].to_string())) 82 | } 83 | 84 | if let Some(cap) = NUMBER_REGEXP.captures(input) { 85 | self.pos += cap[0].len(); 86 | return Some(token(TokenType::Number, cap[0].to_string())) 87 | } 88 | 89 | if let Some(cap) = SYMBOL_REGEXP.captures(input) { 90 | self.pos += cap[0].len(); 91 | let cap_string = cap[0].to_string(); 92 | if cap_string.starts_with(':') { 93 | return Some((TokenType::Keyword, cap_string)) 94 | } else { 95 | return Some((TokenType::Symbol, cap_string)) 96 | } 97 | } 98 | 99 | None 100 | } 101 | } 102 | 103 | 104 | fn token>(token_type: TokenType, s: S) -> Token { 105 | (token_type, s.into()) 106 | } 107 | 108 | 109 | /* ------------------------------ Tests ----------------------------------------------- */ 110 | 111 | #[allow(dead_code)] 112 | pub fn tokenize(input: &str) -> Vec { 113 | Tokenizer::new(input).collect() 114 | } 115 | 116 | #[test] 117 | fn test_tokenizer() { 118 | assert_eq!(tokenize("42"), vec![(TokenType::Number, "42".to_string())]); 119 | assert_eq!(tokenize("("), vec![(TokenType::ListStart, "(".to_string())]); 120 | assert_eq!(tokenize(")"), vec![(TokenType::ListEnd, ")".to_string())]); 121 | assert_eq!(tokenize("( )"), vec![ 122 | (TokenType::ListStart, "(".to_string()), 123 | (TokenType::ListEnd, ")".to_string()) 124 | ]); 125 | assert_eq!(tokenize("(\n)"), vec![ 126 | (TokenType::ListStart, "(".to_string()), 127 | (TokenType::ListEnd, ")".to_string()) 128 | ]); 129 | assert_eq!(tokenize("(42)"), vec![ 130 | (TokenType::ListStart, "(".to_string()), 131 | (TokenType::Number, "42".to_string()), 132 | (TokenType::ListEnd, ")".to_string()) 133 | ]); 134 | } 135 | 136 | #[test] 137 | fn test_tokenizer_symbol() { 138 | assert_eq!(tokenize("symbol"), vec![(TokenType::Symbol, "symbol".to_string())]); 139 | assert_eq!(tokenize("(+ 42)"), vec![ 140 | (TokenType::ListStart, "(".to_string()), 141 | (TokenType::Symbol, "+".to_string()), 142 | (TokenType::Number, "42".to_string()), 143 | (TokenType::ListEnd, ")".to_string()) 144 | ]); 145 | } 146 | 147 | #[test] 148 | fn test_tokenizer_vector() { 149 | assert_eq!(tokenize("[]"), vec![ 150 | token(TokenType::VectorStart, "["), 151 | token(TokenType::VectorEnd, "]") 152 | ]); 153 | assert_eq!(tokenize("[23 42]"), vec![ 154 | token(TokenType::VectorStart, "["), 155 | token(TokenType::Number, "23"), 156 | token(TokenType::Number, "42"), 157 | token(TokenType::VectorEnd, "]") 158 | ]); 159 | } 160 | 161 | #[test] 162 | fn test_empty_map() { 163 | assert_eq!(tokenize("{}"), vec![ 164 | token(TokenType::HashMapStart, "{"), 165 | token(TokenType::HashMapEnd, "}") 166 | ]); 167 | } 168 | 169 | #[test] 170 | fn test_map() { 171 | assert_eq!(tokenize("{:keyword 123}"), vec![ 172 | token(TokenType::HashMapStart, "{"), 173 | token(TokenType::Keyword, ":keyword"), 174 | token(TokenType::Number, "123"), 175 | token(TokenType::HashMapEnd, "}") 176 | ]); 177 | } 178 | 179 | #[test] 180 | fn test_map_ending_with_symbol_as_value() { 181 | assert_eq!(tokenize("{:keyword var}"), vec![ 182 | token(TokenType::HashMapStart, "{"), 183 | token(TokenType::Keyword, ":keyword"), 184 | token(TokenType::Symbol, "var"), 185 | token(TokenType::HashMapEnd, "}") 186 | ]); 187 | } 188 | 189 | #[test] 190 | fn test_list_ending_with_symbol() { 191 | assert_eq!(tokenize("(symbol)"), vec![ 192 | token(TokenType::ListStart, "("), 193 | token(TokenType::Symbol, "symbol"), 194 | token(TokenType::ListEnd, ")") 195 | ]); 196 | } 197 | 198 | #[test] 199 | fn test_vector_ending_with_symbol() { 200 | assert_eq!(tokenize("[symbol]"), vec![ 201 | token(TokenType::VectorStart, "["), 202 | token(TokenType::Symbol, "symbol"), 203 | token(TokenType::VectorEnd, "]") 204 | ]); 205 | } 206 | 207 | #[test] 208 | fn test_string() { 209 | assert_eq!(tokenize("\"string\" \"\""), vec![ 210 | token(TokenType::Str, "\"string\""), 211 | token(TokenType::Str, "\"\""), 212 | ]); 213 | } 214 | 215 | #[test] 216 | fn test_ignore_single_line_comment() { 217 | assert_eq!(tokenize("; comment"), vec![]); 218 | assert_eq!(tokenize("; comment\n"), vec![]); 219 | assert_eq!(tokenize("; comment\n 23"), vec![token(TokenType::Number, "23")]); 220 | } 221 | 222 | #[test] 223 | fn test_negative_int() { 224 | assert_eq!(tokenize("-23"), vec![token(TokenType::Number, "-23")]); 225 | } -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use std::rc::Rc; 4 | 5 | use environment::Environment; 6 | 7 | #[derive(Debug, PartialEq)] 8 | pub struct RispError(String); 9 | 10 | pub type RispResult = Result; 11 | 12 | 13 | #[derive(Debug, PartialEq, Clone)] 14 | pub enum RispType { 15 | Nil, 16 | Bool(bool), 17 | Int(i64), 18 | Str(String), 19 | List(Vec), 20 | Vector(Vec), 21 | Map(HashMap), 22 | Keyword(String), 23 | Symbol(String), 24 | Function(fn(Vec) -> RispResult), 25 | RispFunction(RispFunc), 26 | } 27 | 28 | #[derive(Debug, PartialEq, Clone)] 29 | pub struct RispFunc { 30 | pub args: Vec, 31 | pub variadic_arg: Option, 32 | pub body: Rc, 33 | pub env: Environment 34 | } 35 | 36 | 37 | pub fn error>(message: S) -> RispError { 38 | RispError(message.into()) 39 | } 40 | 41 | pub fn error_result>(message: S) -> RispResult { 42 | Err(error(message)) 43 | } 44 | 45 | pub fn symbol>(s: S) -> RispType { 46 | RispType::Symbol(s.into()) 47 | } 48 | 49 | pub fn keyword>(s: S) -> RispType { 50 | RispType::Keyword(s.into()) 51 | } 52 | 53 | pub fn string>(s: S) -> RispType { 54 | RispType::Str(s.into()) 55 | } 56 | 57 | #[allow(dead_code)] 58 | pub fn map>(pairs: Vec<(S, RispType)>) -> RispType { 59 | let result: HashMap = pairs.into_iter() 60 | .map(|(s, r)| (s.into(), r)) 61 | .collect(); 62 | RispType::Map(result) 63 | } -------------------------------------------------------------------------------- /tests/risp.rs: -------------------------------------------------------------------------------- 1 | extern crate risp; 2 | 3 | use risp::*; 4 | use risp::types::RispType::*; 5 | use risp::types::*; 6 | use risp::types::error_result; 7 | use risp::core::create_core_environment; 8 | 9 | #[test] 10 | fn test_sum() { 11 | assert_eq!(eval_risp(r" 12 | (+ 1 13 | (* 2 3) 14 | ) 15 | "), Ok(Int(7))); 16 | } 17 | 18 | #[test] 19 | fn test_def() { 20 | let mut env = create_core_environment(); 21 | 22 | assert_eq!(eval_risp_for_env(r" 23 | (def variable 24 | (* 2 3) 25 | ) 26 | ", &mut env), Ok(Int(6))); 27 | 28 | assert_eq!(env.get("variable"), Some(Int(6))); 29 | 30 | } 31 | 32 | #[test] 33 | fn test_parsing_errors() { 34 | assert!(eval_risp("(").is_err()); 35 | assert!(eval_risp(")").is_err()); 36 | assert!(eval_risp("").is_err()); 37 | } 38 | 39 | #[test] 40 | fn test_eval_errors() { 41 | assert!(eval_risp("()").is_err()); 42 | assert!(eval_risp("(1 2)").is_err()); 43 | assert!(eval_risp("(a 2)").is_err()); 44 | assert!(eval_risp("(def)").is_err()); 45 | assert!(eval_risp("(def a)").is_err()); 46 | assert!(eval_risp("(def 1 2)").is_err()); 47 | } 48 | 49 | #[test] 50 | fn test_eval_error_expected_function() { 51 | let mut env = create_core_environment(); 52 | env.set("var", Int(1)); 53 | let result = eval_risp_for_env("(var 1 2 3)", &mut env); 54 | assert_eq!(result, error_result("Expected function but got Int(1)")); 55 | } 56 | 57 | #[test] 58 | fn test_eval_vector() { 59 | let mut env = create_core_environment(); 60 | env.set("var", Int(1)); 61 | let result = eval_risp_for_env("[var 2 (+ 3 4)]", &mut env); 62 | assert_eq!(result, Ok(Vector(vec![Int(1), Int(2), Int(7)]))); 63 | } 64 | 65 | #[test] 66 | fn test_eval_map() { 67 | let mut env = create_core_environment(); 68 | env.set("var", Int(1)); 69 | let result = eval_risp_for_env("{:key1 var :key2 (+ var 4)}", &mut env); 70 | assert_eq!(result, Ok(map(vec![ 71 | ("key1", Int(1)), 72 | ("key2", Int(5)) 73 | ]))); 74 | } 75 | 76 | #[test] 77 | fn test_eval_do() { 78 | let mut env = create_core_environment(); 79 | let result = eval_risp_for_env(r" 80 | (do 81 | (def var 23) 82 | {:key var} 83 | ) 84 | ", &mut env); 85 | assert_eq!(result, Ok(map(vec![ 86 | ("key", Int(23)), 87 | ]))); 88 | } 89 | 90 | #[test] 91 | fn test_eval_do_empty() { 92 | let result = eval_risp("(do)"); 93 | assert_eq!(result, error_result("Empty do block")); 94 | } -------------------------------------------------------------------------------- /tests/risp_examples.rs: -------------------------------------------------------------------------------- 1 | extern crate risp; 2 | 3 | use std::fs::File; 4 | use std::io::prelude::*; 5 | use risp::eval_risp_script; 6 | use risp::core::create_core_environment; 7 | 8 | #[test] 9 | fn test_example_song() { 10 | let mut file = File::open("examples/song.risp").unwrap(); 11 | let mut risp_code = String::new(); 12 | file.read_to_string(&mut risp_code).unwrap(); 13 | 14 | let mut env = create_core_environment(); 15 | let result = eval_risp_script(&risp_code, &mut env); 16 | assert!(result.is_ok()); 17 | } 18 | -------------------------------------------------------------------------------- /tests/risp_readme.rs: -------------------------------------------------------------------------------- 1 | extern crate risp; 2 | 3 | use risp::eval_risp_script; 4 | use risp::types::RispType::*; 5 | use risp::types::*; 6 | use risp::core::create_core_environment; 7 | use std::fs::File; 8 | use std::io::prelude::*; 9 | 10 | #[test] 11 | fn test_minimal_example() { 12 | let mut env = create_core_environment(); 13 | env.set("var", Int(2)); 14 | 15 | let risp_script = "(+ 40 var)"; 16 | let result = eval_risp_script(risp_script, &mut env); 17 | 18 | assert_eq!(result, Ok(Int(42))); 19 | } 20 | 21 | #[test] 22 | fn test_kitchen_sink() { 23 | let mut file = File::open("examples/kitchen_sink.risp").unwrap(); 24 | let mut risp_code = String::new(); 25 | file.read_to_string(&mut risp_code).unwrap(); 26 | 27 | let mut env = create_core_environment(); 28 | let result = eval_risp_script(&risp_code, &mut env); 29 | 30 | if result.is_err() { 31 | println!("Error = {:?}", result); 32 | } 33 | assert!(result.is_ok()); 34 | 35 | let result_map = result.unwrap(); 36 | assert_eq!(result_map.get("yes").unwrap(), Some(true)); 37 | assert_eq!(result_map.get("no").unwrap(), Some(false)); 38 | assert_eq!(result_map.get("vector_sum1").unwrap(), Some(vec![11, 21])); 39 | assert_eq!(result_map.get("vector_sum2").unwrap(), Some(vec![11, 22])); 40 | assert_eq!(result_map.get("vector_sum3").unwrap(), Some(vec![11, 12, 21, 22])); 41 | assert_eq!(result_map.get("doubled").unwrap(), Some(Int(42))); 42 | assert_eq!(result_map.get("added_20").unwrap(), Some(Int(23))); 43 | 44 | let song: RispType = result_map.get("song").unwrap().unwrap(); 45 | assert_eq!(song.get("name").unwrap(), Some(string("Sweet Dreams"))); 46 | assert_eq!(song.get("notes").unwrap(), Some(Vector(vec![Int(1), Int(2), Int(3), Int(4)]))); 47 | } -------------------------------------------------------------------------------- /tests/risp_readme_convert.rs: -------------------------------------------------------------------------------- 1 | extern crate risp; 2 | 3 | use risp::eval_risp_script; 4 | use risp::core::create_core_environment; 5 | 6 | struct SimpleSong { 7 | name: String, 8 | speed: i64, 9 | notes: Vec 10 | } 11 | 12 | #[test] 13 | fn test_convert_to_struct_example() { 14 | let mut env = create_core_environment(); 15 | 16 | let risp_script = r#" 17 | { 18 | :name "Name" 19 | :speed 220 20 | :notes [1 2 3] 21 | }"#; 22 | 23 | let result = eval_risp_script(risp_script, &mut env).unwrap(); 24 | 25 | let simple_song = SimpleSong { 26 | name: result.get("name").unwrap().unwrap(), 27 | speed: result.get("speed").unwrap().unwrap(), 28 | notes: result.get("notes").unwrap().unwrap() 29 | }; 30 | 31 | assert_eq!(simple_song.name, "Name"); 32 | assert_eq!(simple_song.speed, 220); 33 | assert_eq!(simple_song.notes, vec![1, 2, 3]); 34 | } 35 | --------------------------------------------------------------------------------