├── .editorconfig ├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs ├── lint.sh ├── rustfmt.toml ├── src ├── ast.rs ├── ast_walk_interpreter.rs ├── environment.rs ├── error.rs ├── function.rs ├── grammar.rustpeg ├── interpreter_test.rs ├── llvm_interpreter.rs ├── main.rs ├── operations.rs ├── repl.rs ├── runtime.rs ├── typechecker.rs └── value.rs └── tests ├── run-fail ├── arg-len.bl ├── arg-len.err ├── assert-eq-unequal.bl ├── assert-eq-unequal.err ├── assert-false.bl ├── assert-false.err ├── break-outside-loop.bl ├── break-outside-loop.err ├── break-return-outside.bl ├── break-return-outside.err ├── call-non-function.bl ├── call-non-function.err ├── continue-outside-loop.bl ├── continue-outside-loop.err ├── index-out-of-bounds.bl ├── index-out-of-bounds.err ├── non-integral-num-subscript.bl ├── non-integral-num-subscript.err ├── none-error.bl ├── none-error.err ├── subscript-on-non-subcriptable.bl └── subscript-on-non-subcriptable.err ├── run-pass ├── arithmetic.bl ├── basic-bindings.bl ├── block-env.bl ├── bool-cast.bl ├── closures.bl ├── comments.bl ├── comparisons.bl ├── compound-assign.bl ├── curried-add.bl ├── empty.bl ├── fib.bl ├── fn.bl ├── id-in-expr.bl ├── identifier-name.bl ├── if-else-if.bl ├── if-expr.bl ├── int-div-and-mod.bl ├── int-float-subscript.bl ├── len.bl ├── logical_and.bl ├── logical_or.bl ├── loop.bl ├── mixed-call-and-index.bl ├── mutual-recurse.bs ├── println-variadic.bl ├── reassign-with-type-change.bl ├── recursive-factorial.bl ├── string-concat.bl ├── tuple-add.bl ├── tuple-member-access.bl ├── value-identity.bl └── y-combinator.bl ├── typecheck-fail ├── add-type-error.bl ├── add-type-error.err ├── arg-len.bl ├── arg-len.err ├── bool-unary-minus.bl ├── bool-unary-minus.err ├── break-return-outside.bl ├── break-return-outside.err ├── call-any.bl ├── call-any.err ├── call-non-function.bl ├── call-non-function.err ├── closure-reference-error.bl ├── closure-reference-error.err ├── compound-assign-binary-error.bl ├── compound-assign-binary-error.err ├── continue-outside-loop.bl ├── continue-outside-loop.err ├── flow-test.bl ├── flow-test.err ├── fn-returns-multiple-types.bl ├── fn-returns-multiple-types.err ├── fn-type-error.bl ├── fn-type-error.err ├── if_rebind_type.bl ├── if_rebind_type.err ├── multiple-types-from-branch.bl ├── multiple-types-from-branch.err ├── non-integral-subscript.bl ├── non-integral-subscript.err ├── none-error-for-builtin.bl ├── none-error-for-builtin.err ├── none-ret.bl ├── none-ret.err ├── not-always-returning.bl ├── not-always-returning.err ├── reference-err.bl ├── reference-err.err ├── subscript-on-non-subcriptable.bl ├── subscript-on-non-subcriptable.err ├── undeclared-assignment.bl ├── undeclared-assignment.err ├── unreachable-code-return.bl └── unreachable-code-return.err └── typecheck-pass ├── arithmetic.bl ├── basic-bindings.bl ├── closure.bl ├── compound-assign.bl ├── fn-arg-match.bl ├── int-float-subscript.bl └── misc-binary-ops.bl /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | *.bk 6 | 7 | # Allow users to have special .ignore files for things like 8 | # setting $PATH 9 | *.ignore 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | # 14.04 needed to install llvm-4.0 3 | dist: trusty 4 | 5 | # Fast, container-based builds 6 | sudo: false 7 | 8 | rust: 9 | - stable 10 | - nightly-2017-07-30 11 | 12 | cache: cargo 13 | 14 | addons: 15 | apt: 16 | sources: 17 | - llvm-toolchain-trusty-4.0 18 | packages: 19 | - llvm-4.0 20 | - llvm-4.0-tools 21 | 22 | before_script: 23 | - mkdir -p .local/bin 24 | - ln -s /usr/bin/llvm-config-4.0 .local/bin/llvm-config 25 | - export PATH=$PWD/.local/bin/:$PATH 26 | - bash lint.sh 27 | 28 | script: 29 | - touch build.rs && cargo test --features "file-tests llvm-backend" 30 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributors' Guide 2 | 3 | First off, thanks a lot for contributing to Balloon! :D 4 | 5 | ## Setup 6 | 7 | Balloon is written in Rust. You'll need to get a working installation of Rust and Cargo to be able to work on Balloon. The [Rust websites's installation instructions page](https://www.rust-lang.org/en-US/install.html) should give you the necessary information. Remember to configure the `$PATH` environment variable (if necessary) as mentioned in the guide. You can get the latest nightly version of the compiler or the stable version - balloon should build fine in both. 8 | 9 | After this, you can run the REPL: 10 | 11 | ``` 12 | $ cargo run 13 | ``` 14 | 15 | You can also build the binary and use it directly as mentioned in the [readme](README.md). 16 | 17 | ``` 18 | $ cargo build 19 | $ ./target/debug/balloon [args] 20 | ``` 21 | 22 | If you want to work on the LLVM backend, you'll need to install LLVM (look at the apt packages listed in [.travis.yml](.travis.yml)) and then run cargo with the "llvm-backend" feature. 23 | 24 | ``` 25 | $ cargo build --features "llvm-backend" 26 | ``` 27 | 28 | ## Learning Rust 29 | 30 | The [Rust docs](https://www.rust-lang.org/en-US/documentation.html) are a great place to learn Rust from. 31 | 32 | ## Project structure for contributions 33 | 34 | Say you want to add a new language construct to Balloon. The process looks roughly as follows: 35 | 36 | 1. Make the necessary changes to the `struct`s and `enum`s in the [src/ast.rs](src/ast.rs) file. 37 | 1. Edit the PEG grammar file at [src/grammar.rustpeg](src/grammar.rustpeg). 38 | 1. Try building once using `cargo build`. If there's an error in the grammar or the AST, it'll show up now. If those parts are fine, you'll see an error because some case isn't handled in [src/ast_walk_interpreter.rs](src/ast_walk_interpreter.rs) and in [src/typechecker.rs](src/typechecker.rs). 39 | 1. Find the relevant cases in the AST-walk interpreter and the typechecker and add in the relevant code. 40 | 1. Build and test the feature manually to see if it works. (Unless you're doing something cool like TDD.) 41 | 1. Write tests. Look at the [tests/](tests/) directory for examples. 42 | - run-pass: Files that should run without errors 43 | - run-fail: Files that should given an error when run, specified in the respective .err file 44 | - typecheck-pass: Files that should typecheck without errors 45 | - typecheck-fail: Files that should give an error when typechecked, specified in the respective .err file 46 | 1. If everything looks good, commit and push your change. While there aren't any concrete rules yet, follow the commonly used imperative style, without a trailing period. Look at previous commits to get an idea of how your commit should look. 47 | 1. Send in a PR! 48 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "balloon" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "clippy 0.0.147 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "itertools 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 10 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 11 | "linear-map 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 12 | "llvm-sys 40.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 13 | "peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", 14 | "rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "aho-corasick" 19 | version = "0.6.3" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "ansi_term" 27 | version = "0.9.0" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | 30 | [[package]] 31 | name = "base64" 32 | version = "0.5.2" 33 | source = "registry+https://github.com/rust-lang/crates.io-index" 34 | dependencies = [ 35 | "byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 36 | ] 37 | 38 | [[package]] 39 | name = "bitflags" 40 | version = "0.4.0" 41 | source = "registry+https://github.com/rust-lang/crates.io-index" 42 | 43 | [[package]] 44 | name = "bitflags" 45 | version = "0.9.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | 48 | [[package]] 49 | name = "byteorder" 50 | version = "1.1.0" 51 | source = "registry+https://github.com/rust-lang/crates.io-index" 52 | 53 | [[package]] 54 | name = "cargo_metadata" 55 | version = "0.2.3" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | dependencies = [ 58 | "serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 60 | "serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 61 | ] 62 | 63 | [[package]] 64 | name = "clippy" 65 | version = "0.0.147" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | dependencies = [ 68 | "cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "clippy_lints 0.0.147 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "clippy_lints" 74 | version = "0.0.147" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "itertools 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 79 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 80 | "pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)", 81 | "quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", 82 | "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 83 | "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 84 | "serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "toml 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 88 | ] 89 | 90 | [[package]] 91 | name = "dtoa" 92 | version = "0.4.1" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | 95 | [[package]] 96 | name = "either" 97 | version = "1.1.0" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | 100 | [[package]] 101 | name = "encode_unicode" 102 | version = "0.1.3" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | 105 | [[package]] 106 | name = "fnv" 107 | version = "1.0.5" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | 110 | [[package]] 111 | name = "gcc" 112 | version = "0.3.51" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | 115 | [[package]] 116 | name = "getopts" 117 | version = "0.2.14" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | 120 | [[package]] 121 | name = "httparse" 122 | version = "1.2.3" 123 | source = "registry+https://github.com/rust-lang/crates.io-index" 124 | 125 | [[package]] 126 | name = "hyper" 127 | version = "0.10.12" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | dependencies = [ 130 | "base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 133 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 134 | "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 135 | "num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)", 136 | "time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "idna" 145 | version = "0.1.4" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 149 | "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 150 | "unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 151 | ] 152 | 153 | [[package]] 154 | name = "itertools" 155 | version = "0.6.1" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "itoa" 163 | version = "0.3.1" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | 166 | [[package]] 167 | name = "kernel32-sys" 168 | version = "0.2.2" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | dependencies = [ 171 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 172 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 173 | ] 174 | 175 | [[package]] 176 | name = "language-tags" 177 | version = "0.2.2" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | 180 | [[package]] 181 | name = "lazy_static" 182 | version = "0.2.8" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | 185 | [[package]] 186 | name = "libc" 187 | version = "0.2.28" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | 190 | [[package]] 191 | name = "linear-map" 192 | version = "1.2.0" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | 195 | [[package]] 196 | name = "llvm-sys" 197 | version = "40.0.4" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | dependencies = [ 200 | "gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)", 201 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 204 | "semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", 205 | ] 206 | 207 | [[package]] 208 | name = "log" 209 | version = "0.3.8" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | 212 | [[package]] 213 | name = "matches" 214 | version = "0.1.6" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | 217 | [[package]] 218 | name = "memchr" 219 | version = "1.0.1" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | dependencies = [ 222 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 223 | ] 224 | 225 | [[package]] 226 | name = "mime" 227 | version = "0.2.6" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | dependencies = [ 230 | "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 231 | ] 232 | 233 | [[package]] 234 | name = "nix" 235 | version = "0.5.1" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | dependencies = [ 238 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 239 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 240 | ] 241 | 242 | [[package]] 243 | name = "num-traits" 244 | version = "0.1.40" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | 247 | [[package]] 248 | name = "num_cpus" 249 | version = "1.6.2" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | dependencies = [ 252 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 253 | ] 254 | 255 | [[package]] 256 | name = "peg" 257 | version = "0.5.4" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | dependencies = [ 260 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 261 | ] 262 | 263 | [[package]] 264 | name = "percent-encoding" 265 | version = "1.0.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | 268 | [[package]] 269 | name = "pulldown-cmark" 270 | version = "0.0.15" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | dependencies = [ 273 | "bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", 274 | "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 275 | ] 276 | 277 | [[package]] 278 | name = "quine-mc_cluskey" 279 | version = "0.2.4" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | 282 | [[package]] 283 | name = "quote" 284 | version = "0.3.15" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | 287 | [[package]] 288 | name = "redox_syscall" 289 | version = "0.1.28" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | 292 | [[package]] 293 | name = "regex" 294 | version = "0.2.2" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | dependencies = [ 297 | "aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", 298 | "memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 299 | "regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 300 | "thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", 301 | "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 302 | ] 303 | 304 | [[package]] 305 | name = "regex-syntax" 306 | version = "0.4.1" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | 309 | [[package]] 310 | name = "rustyline" 311 | version = "1.0.0" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | dependencies = [ 314 | "encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 315 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 316 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 317 | "nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", 318 | "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 319 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 320 | ] 321 | 322 | [[package]] 323 | name = "semver" 324 | version = "0.6.0" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | dependencies = [ 327 | "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", 328 | ] 329 | 330 | [[package]] 331 | name = "semver-parser" 332 | version = "0.7.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | 335 | [[package]] 336 | name = "serde" 337 | version = "1.0.11" 338 | source = "registry+https://github.com/rust-lang/crates.io-index" 339 | 340 | [[package]] 341 | name = "serde_derive" 342 | version = "1.0.11" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | dependencies = [ 345 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 346 | "serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", 347 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 348 | ] 349 | 350 | [[package]] 351 | name = "serde_derive_internals" 352 | version = "0.15.1" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | dependencies = [ 355 | "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", 356 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 357 | ] 358 | 359 | [[package]] 360 | name = "serde_json" 361 | version = "1.0.2" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | dependencies = [ 364 | "dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", 365 | "itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", 366 | "num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", 367 | "serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 368 | ] 369 | 370 | [[package]] 371 | name = "syn" 372 | version = "0.11.11" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | dependencies = [ 375 | "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", 376 | "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", 377 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 378 | ] 379 | 380 | [[package]] 381 | name = "synom" 382 | version = "0.11.3" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | dependencies = [ 385 | "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 386 | ] 387 | 388 | [[package]] 389 | name = "thread_local" 390 | version = "0.3.4" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | dependencies = [ 393 | "lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 394 | "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 395 | ] 396 | 397 | [[package]] 398 | name = "time" 399 | version = "0.1.38" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | dependencies = [ 402 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 403 | "libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)", 404 | "redox_syscall 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", 405 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 406 | ] 407 | 408 | [[package]] 409 | name = "toml" 410 | version = "0.4.4" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | dependencies = [ 413 | "serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", 414 | ] 415 | 416 | [[package]] 417 | name = "traitobject" 418 | version = "0.1.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | 421 | [[package]] 422 | name = "typeable" 423 | version = "0.1.2" 424 | source = "registry+https://github.com/rust-lang/crates.io-index" 425 | 426 | [[package]] 427 | name = "unicase" 428 | version = "1.4.2" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | dependencies = [ 431 | "version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 432 | ] 433 | 434 | [[package]] 435 | name = "unicode-bidi" 436 | version = "0.3.4" 437 | source = "registry+https://github.com/rust-lang/crates.io-index" 438 | dependencies = [ 439 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 440 | ] 441 | 442 | [[package]] 443 | name = "unicode-normalization" 444 | version = "0.1.5" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | 447 | [[package]] 448 | name = "unicode-width" 449 | version = "0.1.4" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | 452 | [[package]] 453 | name = "unicode-xid" 454 | version = "0.0.4" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | 457 | [[package]] 458 | name = "unreachable" 459 | version = "1.0.0" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | dependencies = [ 462 | "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 463 | ] 464 | 465 | [[package]] 466 | name = "url" 467 | version = "1.5.1" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | dependencies = [ 470 | "idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", 471 | "matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 472 | "percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 473 | ] 474 | 475 | [[package]] 476 | name = "utf8-ranges" 477 | version = "1.0.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | 480 | [[package]] 481 | name = "version_check" 482 | version = "0.1.3" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | 485 | [[package]] 486 | name = "void" 487 | version = "1.0.2" 488 | source = "registry+https://github.com/rust-lang/crates.io-index" 489 | 490 | [[package]] 491 | name = "winapi" 492 | version = "0.2.8" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | 495 | [[package]] 496 | name = "winapi-build" 497 | version = "0.1.1" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | 500 | [metadata] 501 | "checksum aho-corasick 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "500909c4f87a9e52355b26626d890833e9e1d53ac566db76c36faa984b889699" 502 | "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" 503 | "checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557" 504 | "checksum bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8dead7461c1127cf637931a1e50934eb6eee8bff2f74433ac7909e9afcee04a3" 505 | "checksum bitflags 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5" 506 | "checksum byteorder 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ff81738b726f5d099632ceaffe7fb65b90212e8dce59d518729e7e8634032d3d" 507 | "checksum cargo_metadata 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "be1057b8462184f634c3a208ee35b0f935cfd94b694b26deadccd98732088d7b" 508 | "checksum clippy 0.0.147 (registry+https://github.com/rust-lang/crates.io-index)" = "a0fcf225c6695008fa72f52b275564b9556e42c5a7f559d637614148e5b46659" 509 | "checksum clippy_lints 0.0.147 (registry+https://github.com/rust-lang/crates.io-index)" = "6b84bd1f4374bc7c473efcb083a6a5cfca9107f9ea3463ad41a0f68ef3ebab72" 510 | "checksum dtoa 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "80c8b71fd71146990a9742fc06dcbbde19161a267e0ad4e572c35162f4578c90" 511 | "checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a" 512 | "checksum encode_unicode 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "28d65f1f5841ef7c6792861294b72beda34c664deb8be27970f36c306b7da1ce" 513 | "checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344" 514 | "checksum gcc 0.3.51 (registry+https://github.com/rust-lang/crates.io-index)" = "120d07f202dcc3f72859422563522b66fe6463a4c513df062874daad05f85f0a" 515 | "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" 516 | "checksum httparse 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "af2f2dd97457e8fb1ae7c5a420db346af389926e36f43768b96f101546b04a07" 517 | "checksum hyper 0.10.12 (registry+https://github.com/rust-lang/crates.io-index)" = "0f01e4a20f5dfa5278d7762b7bdb7cab96e24378b9eca3889fbd4b5e94dc7063" 518 | "checksum idna 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "014b298351066f1512874135335d62a789ffe78a9974f94b43ed5621951eaf7d" 519 | "checksum itertools 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e58359414720377f59889192f1ec0e726049ce5735bc21fdb0c4c8ae638305bb" 520 | "checksum itoa 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eb2f404fbc66fd9aac13e998248505e7ecb2ad8e44ab6388684c5fb11c6c251c" 521 | "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 522 | "checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a" 523 | "checksum lazy_static 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3b37545ab726dd833ec6420aaba8231c5b320814b9029ad585555d2a03e94fbf" 524 | "checksum libc 0.2.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb7b49972ee23d8aa1026c365a5b440ba08e35075f18c459980c7395c221ec48" 525 | "checksum linear-map 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" 526 | "checksum llvm-sys 40.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7c270841863ac4c4e7b1c968f5f7d3faac570e051831e51633d0c35ca75cfa37" 527 | "checksum log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "880f77541efa6e5cc74e76910c9884d9859683118839d6a1dc3b11e63512565b" 528 | "checksum matches 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "100aabe6b8ff4e4a7e32c1c13523379802df0772b82466207ac25b013f193376" 529 | "checksum memchr 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1dbccc0e46f1ea47b9f17e6d67c5a96bd27030519c519c9c91327e31275a47b4" 530 | "checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" 531 | "checksum nix 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bfb3ddedaa14746434a02041940495bf11325c22f6d36125d3bdd56090d50a79" 532 | "checksum num-traits 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "99843c856d68d8b4313b03a17e33c4bb42ae8f6610ea81b28abe076ac721b9b0" 533 | "checksum num_cpus 1.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aec53c34f2d0247c5ca5d32cca1478762f301740468ee9ee6dcb7a0dd7a0c584" 534 | "checksum peg 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "36a474cba42744afe0f223e9d4263594b3387f172e512259c72d2011e477c4fb" 535 | "checksum percent-encoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de154f638187706bde41d9b4738748933d64e6b37bdbffc0b47a97d16a6ae356" 536 | "checksum pulldown-cmark 0.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "378e941dbd392c101f2cb88097fa4d7167bc421d4b88de3ff7dbee503bc3233b" 537 | "checksum quine-mc_cluskey 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "07589615d719a60c8dd8a4622e7946465dfef20d1a428f969e3443e7386d5f45" 538 | "checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" 539 | "checksum redox_syscall 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "ddab7acd8e7bf3e49dfdf78ac1209b992329eb2f66e0bf672ab49c70a76d1d68" 540 | "checksum regex 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1731164734096285ec2a5ec7fea5248ae2f5485b3feeb0115af4fda2183b2d1b" 541 | "checksum regex-syntax 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad890a5eef7953f55427c50575c680c42841653abd2b028b68cd223d157f62db" 542 | "checksum rustyline 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "00b06ac9c8e8e3e83b33d175d39a9f7b6c2c930c82990593719c8e48788ae2d9" 543 | "checksum semver 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537" 544 | "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 545 | "checksum serde 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "f7726f29ddf9731b17ff113c461e362c381d9d69433f79de4f3dd572488823e9" 546 | "checksum serde_derive 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "cf823e706be268e73e7747b147aa31c8f633ab4ba31f115efb57e5047c3a76dd" 547 | "checksum serde_derive_internals 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37aee4e0da52d801acfbc0cc219eb1eda7142112339726e427926a6f6ee65d3a" 548 | "checksum serde_json 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "48b04779552e92037212c3615370f6bd57a40ebba7f20e554ff9f55e41a69a7b" 549 | "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" 550 | "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" 551 | "checksum thread_local 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1697c4b57aeeb7a536b647165a2825faddffb1d3bad386d507709bd51a90bb14" 552 | "checksum time 0.1.38 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d788d3aa77bc0ef3e9621256885555368b47bd495c13dd2e7413c89f845520" 553 | "checksum toml 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3e5e16033aacf7eead46cbcb62d06cf9d1c2aa1b12faa4039072f7ae5921103b" 554 | "checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" 555 | "checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887" 556 | "checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" 557 | "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" 558 | "checksum unicode-normalization 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "51ccda9ef9efa3f7ef5d91e8f9b83bbe6955f9bf86aec89d5cce2c874625920f" 559 | "checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" 560 | "checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" 561 | "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 562 | "checksum url 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eeb819346883532a271eb626deb43c4a1bb4c4dd47c519bd78137c3e72a4fe27" 563 | "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" 564 | "checksum version_check 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6b772017e347561807c1aa192438c5fd74242a670a6cffacc40f2defd1dc069d" 565 | "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 566 | "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 567 | "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 568 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "balloon" 3 | version = "0.1.0" 4 | authors = [ 5 | "Vivek Ghaisas ", 6 | "Siddharth Bhat " # LLVM JITing 7 | ] 8 | build = "build.rs" 9 | 10 | [build-dependencies] 11 | peg = { version = "0.5" } 12 | 13 | [features] 14 | cargo-clippy = [] 15 | file-tests = [] 16 | llvm-backend = ["llvm-sys", "libc", "itertools"] 17 | 18 | [dependencies] 19 | ansi_term = "0.9.0" 20 | rustyline = "1.0.0" 21 | fnv = "1.0.5" 22 | linear-map = "1.1.0" 23 | hyper = "0.10" 24 | llvm-sys = {version = "40.0.0", optional = true} 25 | itertools = {version = "0.6.0", optional = true} 26 | libc = {version = "0.2", optional = true} 27 | clippy = {version = "*", optional = true} 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Mozilla Public License Version 2.0 2 | ================================== 3 | 4 | 1. Definitions 5 | -------------- 6 | 7 | 1.1. "Contributor" 8 | means each individual or legal entity that creates, contributes to 9 | the creation of, or owns Covered Software. 10 | 11 | 1.2. "Contributor Version" 12 | means the combination of the Contributions of others (if any) used 13 | by a Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | means Covered Software of a particular Contributor. 17 | 18 | 1.4. "Covered Software" 19 | means Source Code Form to which the initial Contributor has attached 20 | the notice in Exhibit A, the Executable Form of such Source Code 21 | Form, and Modifications of such Source Code Form, in each case 22 | including portions thereof. 23 | 24 | 1.5. "Incompatible With Secondary Licenses" 25 | means 26 | 27 | (a) that the initial Contributor has attached the notice described 28 | in Exhibit B to the Covered Software; or 29 | 30 | (b) that the Covered Software was made available under the terms of 31 | version 1.1 or earlier of the License, but not also under the 32 | terms of a Secondary License. 33 | 34 | 1.6. "Executable Form" 35 | means any form of the work other than Source Code Form. 36 | 37 | 1.7. "Larger Work" 38 | means a work that combines Covered Software with other material, in 39 | a separate file or files, that is not Covered Software. 40 | 41 | 1.8. "License" 42 | means this document. 43 | 44 | 1.9. "Licensable" 45 | means having the right to grant, to the maximum extent possible, 46 | whether at the time of the initial grant or subsequently, any and 47 | all of the rights conveyed by this License. 48 | 49 | 1.10. "Modifications" 50 | means any of the following: 51 | 52 | (a) any file in Source Code Form that results from an addition to, 53 | deletion from, or modification of the contents of Covered 54 | Software; or 55 | 56 | (b) any new file in Source Code Form that contains any Covered 57 | Software. 58 | 59 | 1.11. "Patent Claims" of a Contributor 60 | means any patent claim(s), including without limitation, method, 61 | process, and apparatus claims, in any patent Licensable by such 62 | Contributor that would be infringed, but for the grant of the 63 | License, by the making, using, selling, offering for sale, having 64 | made, import, or transfer of either its Contributions or its 65 | Contributor Version. 66 | 67 | 1.12. "Secondary License" 68 | means either the GNU General Public License, Version 2.0, the GNU 69 | Lesser General Public License, Version 2.1, the GNU Affero General 70 | Public License, Version 3.0, or any later versions of those 71 | licenses. 72 | 73 | 1.13. "Source Code Form" 74 | means the form of the work preferred for making modifications. 75 | 76 | 1.14. "You" (or "Your") 77 | means an individual or a legal entity exercising rights under this 78 | License. For legal entities, "You" includes any entity that 79 | controls, is controlled by, or is under common control with You. For 80 | purposes of this definition, "control" means (a) the power, direct 81 | or indirect, to cause the direction or management of such entity, 82 | whether by contract or otherwise, or (b) ownership of more than 83 | fifty percent (50%) of the outstanding shares or beneficial 84 | ownership of such entity. 85 | 86 | 2. License Grants and Conditions 87 | -------------------------------- 88 | 89 | 2.1. Grants 90 | 91 | Each Contributor hereby grants You a world-wide, royalty-free, 92 | non-exclusive license: 93 | 94 | (a) under intellectual property rights (other than patent or trademark) 95 | Licensable by such Contributor to use, reproduce, make available, 96 | modify, display, perform, distribute, and otherwise exploit its 97 | Contributions, either on an unmodified basis, with Modifications, or 98 | as part of a Larger Work; and 99 | 100 | (b) under Patent Claims of such Contributor to make, use, sell, offer 101 | for sale, have made, import, and otherwise transfer either its 102 | Contributions or its Contributor Version. 103 | 104 | 2.2. Effective Date 105 | 106 | The licenses granted in Section 2.1 with respect to any Contribution 107 | become effective for each Contribution on the date the Contributor first 108 | distributes such Contribution. 109 | 110 | 2.3. Limitations on Grant Scope 111 | 112 | The licenses granted in this Section 2 are the only rights granted under 113 | this License. No additional rights or licenses will be implied from the 114 | distribution or licensing of Covered Software under this License. 115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 116 | Contributor: 117 | 118 | (a) for any code that a Contributor has removed from Covered Software; 119 | or 120 | 121 | (b) for infringements caused by: (i) Your and any other third party's 122 | modifications of Covered Software, or (ii) the combination of its 123 | Contributions with other software (except as part of its Contributor 124 | Version); or 125 | 126 | (c) under Patent Claims infringed by Covered Software in the absence of 127 | its Contributions. 128 | 129 | This License does not grant any rights in the trademarks, service marks, 130 | or logos of any Contributor (except as may be necessary to comply with 131 | the notice requirements in Section 3.4). 132 | 133 | 2.4. Subsequent Licenses 134 | 135 | No Contributor makes additional grants as a result of Your choice to 136 | distribute the Covered Software under a subsequent version of this 137 | License (see Section 10.2) or under the terms of a Secondary License (if 138 | permitted under the terms of Section 3.3). 139 | 140 | 2.5. Representation 141 | 142 | Each Contributor represents that the Contributor believes its 143 | Contributions are its original creation(s) or it has sufficient rights 144 | to grant the rights to its Contributions conveyed by this License. 145 | 146 | 2.6. Fair Use 147 | 148 | This License is not intended to limit any rights You have under 149 | applicable copyright doctrines of fair use, fair dealing, or other 150 | equivalents. 151 | 152 | 2.7. Conditions 153 | 154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted 155 | in Section 2.1. 156 | 157 | 3. Responsibilities 158 | ------------------- 159 | 160 | 3.1. Distribution of Source Form 161 | 162 | All distribution of Covered Software in Source Code Form, including any 163 | Modifications that You create or to which You contribute, must be under 164 | the terms of this License. You must inform recipients that the Source 165 | Code Form of the Covered Software is governed by the terms of this 166 | License, and how they can obtain a copy of this License. You may not 167 | attempt to alter or restrict the recipients' rights in the Source Code 168 | Form. 169 | 170 | 3.2. Distribution of Executable Form 171 | 172 | If You distribute Covered Software in Executable Form then: 173 | 174 | (a) such Covered Software must also be made available in Source Code 175 | Form, as described in Section 3.1, and You must inform recipients of 176 | the Executable Form how they can obtain a copy of such Source Code 177 | Form by reasonable means in a timely manner, at a charge no more 178 | than the cost of distribution to the recipient; and 179 | 180 | (b) You may distribute such Executable Form under the terms of this 181 | License, or sublicense it under different terms, provided that the 182 | license for the Executable Form does not attempt to limit or alter 183 | the recipients' rights in the Source Code Form under this License. 184 | 185 | 3.3. Distribution of a Larger Work 186 | 187 | You may create and distribute a Larger Work under terms of Your choice, 188 | provided that You also comply with the requirements of this License for 189 | the Covered Software. If the Larger Work is a combination of Covered 190 | Software with a work governed by one or more Secondary Licenses, and the 191 | Covered Software is not Incompatible With Secondary Licenses, this 192 | License permits You to additionally distribute such Covered Software 193 | under the terms of such Secondary License(s), so that the recipient of 194 | the Larger Work may, at their option, further distribute the Covered 195 | Software under the terms of either this License or such Secondary 196 | License(s). 197 | 198 | 3.4. Notices 199 | 200 | You may not remove or alter the substance of any license notices 201 | (including copyright notices, patent notices, disclaimers of warranty, 202 | or limitations of liability) contained within the Source Code Form of 203 | the Covered Software, except that You may alter any license notices to 204 | the extent required to remedy known factual inaccuracies. 205 | 206 | 3.5. Application of Additional Terms 207 | 208 | You may choose to offer, and to charge a fee for, warranty, support, 209 | indemnity or liability obligations to one or more recipients of Covered 210 | Software. However, You may do so only on Your own behalf, and not on 211 | behalf of any Contributor. You must make it absolutely clear that any 212 | such warranty, support, indemnity, or liability obligation is offered by 213 | You alone, and You hereby agree to indemnify every Contributor for any 214 | liability incurred by such Contributor as a result of warranty, support, 215 | indemnity or liability terms You offer. You may include additional 216 | disclaimers of warranty and limitations of liability specific to any 217 | jurisdiction. 218 | 219 | 4. Inability to Comply Due to Statute or Regulation 220 | --------------------------------------------------- 221 | 222 | If it is impossible for You to comply with any of the terms of this 223 | License with respect to some or all of the Covered Software due to 224 | statute, judicial order, or regulation then You must: (a) comply with 225 | the terms of this License to the maximum extent possible; and (b) 226 | describe the limitations and the code they affect. Such description must 227 | be placed in a text file included with all distributions of the Covered 228 | Software under this License. Except to the extent prohibited by statute 229 | or regulation, such description must be sufficiently detailed for a 230 | recipient of ordinary skill to be able to understand it. 231 | 232 | 5. Termination 233 | -------------- 234 | 235 | 5.1. The rights granted under this License will terminate automatically 236 | if You fail to comply with any of its terms. However, if You become 237 | compliant, then the rights granted under this License from a particular 238 | Contributor are reinstated (a) provisionally, unless and until such 239 | Contributor explicitly and finally terminates Your grants, and (b) on an 240 | ongoing basis, if such Contributor fails to notify You of the 241 | non-compliance by some reasonable means prior to 60 days after You have 242 | come back into compliance. Moreover, Your grants from a particular 243 | Contributor are reinstated on an ongoing basis if such Contributor 244 | notifies You of the non-compliance by some reasonable means, this is the 245 | first time You have received notice of non-compliance with this License 246 | from such Contributor, and You become compliant prior to 30 days after 247 | Your receipt of the notice. 248 | 249 | 5.2. If You initiate litigation against any entity by asserting a patent 250 | infringement claim (excluding declaratory judgment actions, 251 | counter-claims, and cross-claims) alleging that a Contributor Version 252 | directly or indirectly infringes any patent, then the rights granted to 253 | You by any and all Contributors for the Covered Software under Section 254 | 2.1 of this License shall terminate. 255 | 256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all 257 | end user license agreements (excluding distributors and resellers) which 258 | have been validly granted by You or Your distributors under this License 259 | prior to termination shall survive termination. 260 | 261 | ************************************************************************ 262 | * * 263 | * 6. Disclaimer of Warranty * 264 | * ------------------------- * 265 | * * 266 | * Covered Software is provided under this License on an "as is" * 267 | * basis, without warranty of any kind, either expressed, implied, or * 268 | * statutory, including, without limitation, warranties that the * 269 | * Covered Software is free of defects, merchantable, fit for a * 270 | * particular purpose or non-infringing. The entire risk as to the * 271 | * quality and performance of the Covered Software is with You. * 272 | * Should any Covered Software prove defective in any respect, You * 273 | * (not any Contributor) assume the cost of any necessary servicing, * 274 | * repair, or correction. This disclaimer of warranty constitutes an * 275 | * essential part of this License. No use of any Covered Software is * 276 | * authorized under this License except under this disclaimer. * 277 | * * 278 | ************************************************************************ 279 | 280 | ************************************************************************ 281 | * * 282 | * 7. Limitation of Liability * 283 | * -------------------------- * 284 | * * 285 | * Under no circumstances and under no legal theory, whether tort * 286 | * (including negligence), contract, or otherwise, shall any * 287 | * Contributor, or anyone who distributes Covered Software as * 288 | * permitted above, be liable to You for any direct, indirect, * 289 | * special, incidental, or consequential damages of any character * 290 | * including, without limitation, damages for lost profits, loss of * 291 | * goodwill, work stoppage, computer failure or malfunction, or any * 292 | * and all other commercial damages or losses, even if such party * 293 | * shall have been informed of the possibility of such damages. This * 294 | * limitation of liability shall not apply to liability for death or * 295 | * personal injury resulting from such party's negligence to the * 296 | * extent applicable law prohibits such limitation. Some * 297 | * jurisdictions do not allow the exclusion or limitation of * 298 | * incidental or consequential damages, so this exclusion and * 299 | * limitation may not apply to You. * 300 | * * 301 | ************************************************************************ 302 | 303 | 8. Litigation 304 | ------------- 305 | 306 | Any litigation relating to this License may be brought only in the 307 | courts of a jurisdiction where the defendant maintains its principal 308 | place of business and such litigation shall be governed by laws of that 309 | jurisdiction, without reference to its conflict-of-law provisions. 310 | Nothing in this Section shall prevent a party's ability to bring 311 | cross-claims or counter-claims. 312 | 313 | 9. Miscellaneous 314 | ---------------- 315 | 316 | This License represents the complete agreement concerning the subject 317 | matter hereof. If any provision of this License is held to be 318 | unenforceable, such provision shall be reformed only to the extent 319 | necessary to make it enforceable. Any law or regulation which provides 320 | that the language of a contract shall be construed against the drafter 321 | shall not be used to construe this License against a Contributor. 322 | 323 | 10. Versions of the License 324 | --------------------------- 325 | 326 | 10.1. New Versions 327 | 328 | Mozilla Foundation is the license steward. Except as provided in Section 329 | 10.3, no one other than the license steward has the right to modify or 330 | publish new versions of this License. Each version will be given a 331 | distinguishing version number. 332 | 333 | 10.2. Effect of New Versions 334 | 335 | You may distribute the Covered Software under the terms of the version 336 | of the License under which You originally received the Covered Software, 337 | or under the terms of any subsequent version published by the license 338 | steward. 339 | 340 | 10.3. Modified Versions 341 | 342 | If you create software not governed by this License, and you want to 343 | create a new license for such software, you may create and use a 344 | modified version of this License if you rename the license and remove 345 | any references to the name of the license steward (except to note that 346 | such modified license differs from this License). 347 | 348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 349 | Licenses 350 | 351 | If You choose to distribute Source Code Form that is Incompatible With 352 | Secondary Licenses under the terms of this version of the License, the 353 | notice described in Exhibit B of this License must be attached. 354 | 355 | Exhibit A - Source Code Form License Notice 356 | ------------------------------------------- 357 | 358 | This Source Code Form is subject to the terms of the Mozilla Public 359 | License, v. 2.0. If a copy of the MPL was not distributed with this 360 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 361 | 362 | If it is not possible or desirable to put the notice in a particular 363 | file, then You may include the notice in a location (such as a LICENSE 364 | file in a relevant directory) where a recipient would be likely to look 365 | for such a notice. 366 | 367 | You may add additional accurate notices of copyright ownership. 368 | 369 | Exhibit B - "Incompatible With Secondary Licenses" Notice 370 | --------------------------------------------------------- 371 | 372 | This Source Code Form is "Incompatible With Secondary Licenses", as 373 | defined by the Mozilla Public License, v. 2.0. 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The Balloon Programming Language 2 | 3 | [![Build Status](https://travis-ci.org/polybuildr/balloon-lang.svg?branch=master)](https://travis-ci.org/polybuildr/balloon-lang) 4 | 5 | **This is a very experimental project.** 6 | 7 | This is an attempt to build a general-purpose interpreted programming language (later with a focus on back-end web programming) that attempts to allow the programmer to choose their own guarantees for things like type safety. 8 | 9 | This project is written in Rust. If you're unfamiliar with Rust, you should take a look at the [Rust docs](https://www.rust-lang.org/en-US/documentation.html). 10 | 11 | Prospective contributors, please take a look at the [Contributors' Guide](CONTRIBUTING.md). 12 | 13 | (Also, for the motivation behind this language, take a look at the [introductory blog post](https://blog.vghaisas.com/introducing-balloon/), and a [more detailed blog post](https://blog.vghaisas.com/thoughts-about-balloon/).) 14 | 15 | ## Usage 16 | 17 | Balloon is currently not available on crates.io. You will need to build it yourself using Cargo. (You could also download one of the prebuilt binaries from this project's [release page](https://github.com/polybuildr/balloon-lang/releases).) 18 | 19 | ``` 20 | usage: balloon [[MODE] FILE] 21 | 22 | where MODE is one of: 23 | --run (default) runs the file 24 | --check type check the file 25 | --parse only parse the file, don't run it 26 | 27 | Not passing any arguments to balloon will start the REPL. 28 | ``` 29 | 30 | (There is also an (even more) experimental LLVM backend that is not documented here.) 31 | 32 | ## Code examples 33 | 34 | Examples of valid code can be found by looking at tests in the [tests/run-pass](tests/run-pass) directory. 35 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | extern crate peg; 6 | 7 | use std::io; 8 | use std::io::{Read, Write}; 9 | use std::fs; 10 | use std::fs::{DirEntry, File}; 11 | use std::path::PathBuf; 12 | use std::env; 13 | 14 | fn main() { 15 | peg::cargo_build("src/grammar.rustpeg"); 16 | if cfg!(feature = "file-tests") { 17 | generate_tests().unwrap(); 18 | } 19 | } 20 | 21 | fn generate_tests() -> io::Result<()> { 22 | let out_dir: PathBuf = env::var_os("OUT_DIR").unwrap().into(); 23 | let output_path = out_dir.join("file_tests").with_extension("rs"); 24 | let mut output_file = File::create(&output_path).unwrap(); 25 | output_file.write_all( 26 | b" 27 | use parser; 28 | use runtime::Interpreter; 29 | use ast_walk_interpreter::AstWalkInterpreter; 30 | use typechecker::TypeChecker; 31 | ", 32 | )?; 33 | let mut tests = Vec::new(); 34 | tests.append(&mut generate_run_pass_tests()?); 35 | tests.append(&mut generate_run_fail_tests()?); 36 | tests.append(&mut generate_typecheck_pass_tests()?); 37 | tests.append(&mut generate_typecheck_fail_tests()?); 38 | let test_fns_str = tests.concat(); 39 | output_file.write_all(test_fns_str.as_bytes())?; 40 | Ok(()) 41 | } 42 | 43 | fn generate_run_pass_tests() -> io::Result> { 44 | let mut tests = Vec::new(); 45 | for entry in fs::read_dir("tests/run-pass")? { 46 | let entry = entry?; 47 | let test_name = test_name_from_entry(&entry, "run_pass"); 48 | let mut file = File::open(entry.path()).unwrap(); 49 | let mut content = String::new(); 50 | file.read_to_string(&mut content)?; 51 | tests.push(make_run_pass_test_fn(&test_name, &content)); 52 | } 53 | Ok(tests) 54 | } 55 | 56 | fn make_run_pass_test_fn(name: &str, code: &str) -> String { 57 | format!( 58 | " 59 | #[test] 60 | fn {name}() {{ 61 | let code = r#\"{code}\"#; 62 | let ast = parser::program(code).unwrap(); 63 | let mut ast_walk_interpreter = AstWalkInterpreter::new(); 64 | ast_walk_interpreter 65 | .run_ast_as_program(&ast) 66 | .unwrap(); 67 | }} 68 | ", 69 | name = name, 70 | code = code 71 | ) 72 | } 73 | 74 | fn generate_run_fail_tests() -> io::Result> { 75 | let mut tests = Vec::new(); 76 | for entry in fs::read_dir("tests/run-fail")? { 77 | let entry = entry?; 78 | if entry.path().extension().unwrap() != "bl" { 79 | continue; 80 | } 81 | let content = read_file(entry.path()); 82 | let expected_err_to_str = read_file(entry.path().with_extension("err")); 83 | let test_name = test_name_from_entry(&entry, "run_fail"); 84 | tests.push(make_run_fail_test_fn( 85 | &test_name, 86 | &content, 87 | &expected_err_to_str.trim(), 88 | )); 89 | } 90 | Ok(tests) 91 | } 92 | 93 | fn make_run_fail_test_fn(name: &str, code: &str, expected_err_str: &str) -> String { 94 | format!( 95 | " 96 | #[test] 97 | fn {name}() {{ 98 | let code = r#\"{code}\"#; 99 | let ast = parser::program(code).unwrap(); 100 | let mut ast_walk_interpreter = AstWalkInterpreter::new(); 101 | let err = ast_walk_interpreter 102 | .run_ast_as_program(&ast) 103 | .unwrap_err(); 104 | assert_eq!( 105 | format!(\"{{:?}}\", err), 106 | r#\"{expected_err_str}\"# 107 | ); 108 | }} 109 | ", 110 | name = name, 111 | code = code, 112 | expected_err_str = expected_err_str 113 | ) 114 | } 115 | 116 | fn generate_typecheck_fail_tests() -> io::Result> { 117 | let mut tests = Vec::new(); 118 | for entry in fs::read_dir("tests/typecheck-fail")? { 119 | let entry = entry?; 120 | if entry.path().extension().unwrap() != "bl" { 121 | continue; 122 | } 123 | let content = read_file(entry.path()); 124 | let expected_err_to_str = read_file(entry.path().with_extension("err")); 125 | let test_name = test_name_from_entry(&entry, "typecheck_fail"); 126 | tests.push(make_typecheck_fail_test_fn( 127 | &test_name, 128 | &content, 129 | &expected_err_to_str.trim(), 130 | )); 131 | } 132 | Ok(tests) 133 | } 134 | 135 | fn make_typecheck_fail_test_fn(name: &str, code: &str, expected_err_str: &str) -> String { 136 | format!( 137 | " 138 | #[test] 139 | fn {name}() {{ 140 | let code = r#\"{code}\"#; 141 | let ast = parser::program(code).unwrap(); 142 | let mut checker = TypeChecker::new(); 143 | checker.check_program(&ast); 144 | let issues = checker.get_issues(); 145 | assert!(!issues.is_empty()); 146 | assert_eq!( 147 | format!(\"{{:?}}\", issues), 148 | r#\"{expected_err_str}\"# 149 | ); 150 | }} 151 | ", 152 | name = name, 153 | code = code, 154 | expected_err_str = expected_err_str 155 | ) 156 | } 157 | 158 | fn generate_typecheck_pass_tests() -> io::Result> { 159 | let mut tests = Vec::new(); 160 | for entry in fs::read_dir("tests/typecheck-pass")? { 161 | let entry = entry?; 162 | if entry.path().extension().unwrap() != "bl" { 163 | continue; 164 | } 165 | let content = read_file(entry.path()); 166 | let test_name = test_name_from_entry(&entry, "typecheck_pass"); 167 | tests.push(make_typecheck_pass_test_fn(&test_name, &content)); 168 | } 169 | Ok(tests) 170 | } 171 | 172 | fn make_typecheck_pass_test_fn(name: &str, code: &str) -> String { 173 | format!( 174 | " 175 | #[test] 176 | fn {name}() {{ 177 | let code = r#\"{code}\"#; 178 | let ast = parser::program(code).unwrap(); 179 | let mut checker = TypeChecker::new(); 180 | checker.check_program(&ast); 181 | let issues = checker.get_issues(); 182 | println!(\"{{:?}}\", issues); 183 | assert_eq!(issues, []); 184 | }} 185 | ", 186 | name = name, 187 | code = code 188 | ) 189 | } 190 | 191 | fn read_file(path: PathBuf) -> String { 192 | let mut file = File::open(path).unwrap(); 193 | let mut content = String::new(); 194 | file.read_to_string(&mut content).unwrap(); 195 | content 196 | } 197 | 198 | fn test_name_from_entry(entry: &DirEntry, prefix: &str) -> String { 199 | let path = entry.path(); 200 | let file_stem = path.file_stem(); 201 | let partial_test_name = file_stem 202 | .unwrap() 203 | .to_str() 204 | .unwrap() 205 | .to_owned() 206 | .replace("-", "_"); 207 | prefix.to_owned() + "_" + &partial_test_name 208 | } 209 | -------------------------------------------------------------------------------- /lint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This Source Code Form is subject to the terms of the Mozilla Public 4 | # License, v. 2.0. If a copy of the MPL was not distributed with this 5 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 | 7 | # Run cargo check (if not nightly), cargo clippy (if nightly) and 8 | # cargo fmt (if nightly) to lint code 9 | if rustc --version | grep "nightly" 10 | then 11 | echo "On nightly." 12 | echo "Running cargo clippy..." 13 | cargo rustc --features "clippy cargo-clippy llvm-backend" -- -Z no-trans -Z extra-plugins=clippy 14 | CARGO_EXIT_CODE=$? 15 | # Run rustfmt-nightly in diff mode, exits with error status if 16 | # there is any diff 17 | echo "Installing rustfmt-nightly" 18 | cargo install --force rustfmt-nightly 19 | echo "Running rustfmt..." 20 | cargo fmt -- --write-mode diff 21 | RUSTFMT_EXIT_CODE=$? 22 | else 23 | echo "Not on nightly." 24 | echo "Running only cargo check..." 25 | cargo check --features "llvm-backend" 26 | CARGO_EXIT_CODE=$? 27 | # Didn't run rustfmt, so claim there were no errors 28 | RUSTFMT_EXIT_CODE=0 29 | fi 30 | 31 | # Exit if either of the exit codes was non-zero 32 | if [[ $CARGO_EXIT_CODE -ne 0 ]]; then echo "Error!"; exit 1; fi 33 | if [[ $RUSTFMT_EXIT_CODE -ne 0 ]]; then echo "Error!"; exit 1; fi 34 | echo "Done." 35 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # Since switching to rustfmt-nightly and RFC style, no 2 | # special config here 3 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::fmt; 6 | 7 | pub type OffsetSpan = (usize, usize); 8 | 9 | #[derive(Debug, Clone, PartialEq)] 10 | pub struct Spanned { 11 | pub pos: OffsetSpan, 12 | pub data: T, 13 | } 14 | 15 | #[derive(Debug, Clone, PartialEq)] 16 | pub enum BinOp { 17 | Add, 18 | Sub, 19 | Mul, 20 | Div, 21 | Mod, 22 | Lt, 23 | Lte, 24 | Gt, 25 | Gte, 26 | Eq, 27 | } 28 | 29 | #[derive(Debug, Clone, PartialEq)] 30 | pub enum LogicalBinOp { 31 | And, 32 | Or, 33 | } 34 | 35 | #[derive(Debug, Clone, PartialEq)] 36 | pub enum UnOp { 37 | Neg, 38 | } 39 | 40 | #[derive(Debug, Clone, PartialEq)] 41 | pub enum LogicalUnOp { 42 | Not, 43 | } 44 | 45 | impl fmt::Display for BinOp { 46 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 47 | match *self { 48 | BinOp::Add => write!(f, "+"), 49 | BinOp::Sub => write!(f, "-"), 50 | BinOp::Mul => write!(f, "*"), 51 | BinOp::Div => write!(f, "/"), 52 | BinOp::Mod => write!(f, "%"), 53 | BinOp::Lt => write!(f, "<"), 54 | BinOp::Lte => write!(f, "<="), 55 | BinOp::Gt => write!(f, ">"), 56 | BinOp::Gte => write!(f, ">="), 57 | BinOp::Eq => write!(f, "=="), 58 | } 59 | } 60 | } 61 | 62 | impl fmt::Display for LogicalBinOp { 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 64 | match *self { 65 | LogicalBinOp::And => write!(f, "and"), 66 | LogicalBinOp::Or => write!(f, "or"), 67 | } 68 | } 69 | } 70 | 71 | impl fmt::Display for UnOp { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | match *self { 74 | UnOp::Neg => write!(f, "-"), 75 | } 76 | } 77 | } 78 | 79 | impl fmt::Display for LogicalUnOp { 80 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 81 | match *self { 82 | LogicalUnOp::Not => write!(f, "not"), 83 | } 84 | } 85 | } 86 | 87 | #[derive(Debug, Clone)] 88 | pub enum Literal { 89 | Integer(i64), 90 | Float(f64), 91 | Bool(bool), 92 | String(String), 93 | } 94 | 95 | pub type LiteralNode = Spanned; 96 | 97 | #[derive(Debug, Clone)] 98 | pub enum LhsExpr { 99 | Identifier(String), 100 | } 101 | 102 | pub type LhsExprNode = Spanned; 103 | 104 | #[derive(Debug, Clone)] 105 | pub enum Variable { 106 | Identifier(BindingType, String), 107 | } 108 | 109 | #[derive(Debug, Clone)] 110 | pub enum BindingType { 111 | Mutable, 112 | } 113 | 114 | #[derive(Debug, Clone)] 115 | pub struct FnDefExpr { 116 | pub maybe_id: Option, 117 | pub params: Vec, 118 | pub body: Box, 119 | } 120 | 121 | #[derive(Debug, Clone)] 122 | pub enum Expr { 123 | Literal(LiteralNode), 124 | Identifier(String), 125 | Binary(Box, BinOp, Box), 126 | BinaryLogical(Box, LogicalBinOp, Box), 127 | Unary(UnOp, Box), 128 | UnaryLogical(LogicalUnOp, Box), 129 | FnDef(FnDefExpr), 130 | FnCall(Box, Vec), 131 | Tuple(Vec), 132 | MemberByIdx(Box, Box), 133 | } 134 | 135 | // Only for parser convenience 136 | pub enum ExprSuffix { 137 | ListInParens(Vec), 138 | InSquareBrackets(ExprNode), 139 | } 140 | 141 | pub type ExprNode = Spanned; 142 | 143 | #[derive(Debug, Clone)] 144 | pub struct IfThenStmt { 145 | pub cond: ExprNode, 146 | pub then_block: Box, 147 | pub maybe_else_block: Option>, 148 | } 149 | 150 | #[derive(Debug, Clone)] 151 | pub enum Stmt { 152 | Assign(LhsExprNode, ExprNode), 153 | AssignOp(LhsExprNode, BinOp, ExprNode), 154 | VarDecl(Variable, ExprNode), 155 | Expr(ExprNode), 156 | Block(Vec), 157 | IfThen(IfThenStmt), 158 | Loop(Box), 159 | Return(Option), 160 | Break, 161 | Continue, 162 | Empty, 163 | } 164 | 165 | pub type StmtNode = Spanned; 166 | -------------------------------------------------------------------------------- /src/ast_walk_interpreter.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | use std::usize; 8 | 9 | use ast::*; 10 | use value::*; 11 | use operations; 12 | use environment::Environment; 13 | use function::*; 14 | use runtime::*; 15 | use typechecker::Type; 16 | 17 | #[derive(Clone)] 18 | struct Context { 19 | pub in_loop: bool, 20 | pub in_func: bool, 21 | } 22 | 23 | impl Context { 24 | pub fn root() -> Context { 25 | Context { 26 | in_loop: false, 27 | in_func: false, 28 | } 29 | } 30 | } 31 | 32 | pub struct AstWalkInterpreter { 33 | env: Rc>, 34 | context: Context, 35 | } 36 | 37 | impl AstWalkInterpreter { 38 | pub fn new() -> AstWalkInterpreter { 39 | AstWalkInterpreter { 40 | env: Environment::new_root(), 41 | context: Context::root(), 42 | } 43 | } 44 | 45 | fn with_environment_and_context( 46 | env: Rc>, 47 | context: Context, 48 | ) -> AstWalkInterpreter { 49 | AstWalkInterpreter { 50 | env: env, 51 | context: context, 52 | } 53 | } 54 | 55 | fn interpret_program( 56 | &mut self, 57 | program: &[StmtNode], 58 | ) -> Result, RuntimeErrorWithPosition> { 59 | let result = self.eval_stmts(program)?; 60 | Ok(result) 61 | } 62 | 63 | fn eval_stmts( 64 | &mut self, 65 | statements: &[StmtNode], 66 | ) -> Result, RuntimeErrorWithPosition> { 67 | let mut last_result = None; 68 | for statement in statements.iter() { 69 | last_result = Some(self.eval_stmt(statement)?); 70 | } 71 | Ok(last_result) 72 | } 73 | 74 | fn eval_stmt(&mut self, s: &StmtNode) -> Result { 75 | match s.data { 76 | Stmt::VarDecl(ref variable, ref expr) => self.eval_stmt_var_decl(variable, expr), 77 | Stmt::Assign(ref lhs_expr, ref expr) => self.eval_stmt_assign(lhs_expr, expr), 78 | Stmt::AssignOp(ref lhs_expr, ref op, ref expr) => { 79 | self.eval_stmt_assign_with_op(lhs_expr, op, expr, s) 80 | } 81 | Stmt::Block(ref statements) => self.eval_stmt_block(statements), 82 | Stmt::Expr(ref expr) => { 83 | let val = self.eval_expr(expr)?; 84 | match val { 85 | None => Ok(StmtResult::None), 86 | Some(x) => Ok(StmtResult::Value(x)), 87 | } 88 | } 89 | Stmt::IfThen(ref if_then_stmt) => self.eval_stmt_if_then(if_then_stmt), 90 | Stmt::Loop(ref block) => self.eval_stmt_loop(block), 91 | Stmt::Return(ref possible_expr) => self.eval_stmt_return(possible_expr, s), 92 | Stmt::Break => self.eval_stmt_break(s), 93 | Stmt::Continue => self.eval_stmt_continue(s), 94 | Stmt::Empty => Ok(StmtResult::None), 95 | } 96 | } 97 | 98 | fn eval_expr_as_value(&mut self, expr: &ExprNode) -> Result { 99 | let possible_val = self.eval_expr(expr)?; 100 | if possible_val.is_none() { 101 | if let Expr::FnCall(ref f_expr, _) = expr.data { 102 | if let Expr::Identifier(ref id) = f_expr.data { 103 | return Err((RuntimeError::NoneError(Some(id.clone())), expr.pos)); 104 | } 105 | return Err((RuntimeError::NoneError(None), expr.pos)); 106 | } else { 107 | unreachable!(); 108 | } 109 | } 110 | Ok(possible_val.clone().unwrap()) 111 | } 112 | 113 | fn eval_expr(&mut self, e: &ExprNode) -> Result, RuntimeErrorWithPosition> { 114 | match e.data { 115 | Expr::Literal(ref x) => Ok(Some(Value::from(x.data.clone()))), 116 | Expr::Identifier(ref id) => wrap(self.eval_expr_identifier(id, e)), 117 | Expr::Tuple(ref elems) => wrap(self.eval_expr_tuple(elems)), 118 | Expr::Unary(ref op, ref expr) => wrap(self.eval_expr_unary(op, expr, e)), 119 | Expr::UnaryLogical(ref op, ref expr) => wrap(self.eval_expr_unary_logical(op, expr)), 120 | Expr::Binary(ref expr1, ref op, ref expr2) => { 121 | wrap(self.eval_expr_binary(op, expr1, expr2, e)) 122 | } 123 | Expr::BinaryLogical(ref expr1, ref op, ref expr2) => { 124 | wrap(self.eval_expr_binary_logical(op, expr1, expr2)) 125 | } 126 | Expr::MemberByIdx(ref object_expr, ref index_expr) => { 127 | wrap(self.eval_expr_member_by_idx(object_expr, index_expr, e)) 128 | } 129 | Expr::FnDef(ref fn_def_expr) => wrap(self.eval_expr_fn_def(fn_def_expr)), 130 | Expr::FnCall(ref expr, ref args) => self.eval_expr_fn_call(expr, args, e), 131 | } 132 | } 133 | 134 | fn eval_stmt_var_decl( 135 | &mut self, 136 | variable: &Variable, 137 | expr: &ExprNode, 138 | ) -> Result { 139 | let val = self.eval_expr_as_value(expr)?; 140 | match *variable { 141 | Variable::Identifier(_, ref name) => { 142 | self.env.borrow_mut().declare(name, &val); 143 | } 144 | }; 145 | Ok(StmtResult::None) 146 | } 147 | 148 | fn eval_stmt_assign( 149 | &mut self, 150 | lhs_expr: &LhsExprNode, 151 | expr: &ExprNode, 152 | ) -> Result { 153 | let val = self.eval_expr_as_value(expr)?; 154 | match lhs_expr.data { 155 | LhsExpr::Identifier(ref id) => if !self.env.borrow_mut().set(id, val) { 156 | return Err(( 157 | RuntimeError::UndeclaredAssignment(id.clone()), 158 | lhs_expr.pos, 159 | )); 160 | }, 161 | }; 162 | Ok(StmtResult::None) 163 | } 164 | 165 | fn eval_stmt_assign_with_op( 166 | &mut self, 167 | lhs_expr: &LhsExprNode, 168 | op: &BinOp, 169 | expr: &ExprNode, 170 | stmt: &StmtNode, 171 | ) -> Result { 172 | let val = self.eval_expr_as_value(expr)?; 173 | match lhs_expr.data { 174 | LhsExpr::Identifier(ref id) => { 175 | let prev_expr_val = match self.env.borrow_mut().get_value(id) { 176 | Some(v) => v, 177 | None => { 178 | return Err((RuntimeError::ReferenceError(id.to_owned()), lhs_expr.pos)); 179 | } 180 | }; 181 | let retval = match *op { 182 | BinOp::Add => operations::add(prev_expr_val, val), 183 | BinOp::Sub => operations::subtract(prev_expr_val, val), 184 | BinOp::Mul => operations::multiply(prev_expr_val, val), 185 | BinOp::Div => operations::divide(prev_expr_val, val), 186 | BinOp::Mod => operations::modulo(prev_expr_val, val), 187 | BinOp::Lt | BinOp::Lte | BinOp::Gt | BinOp::Gte | BinOp::Eq => unreachable!(), 188 | }; 189 | let new_val = match retval { 190 | Ok(val) => val, 191 | Err(e) => { 192 | return Err((e, stmt.pos)); 193 | } 194 | }; 195 | // id must exist, because it was checked above 196 | self.env.borrow_mut().set(id, new_val); 197 | } 198 | }; 199 | Ok(StmtResult::None) 200 | } 201 | 202 | fn eval_stmt_block( 203 | &mut self, 204 | statements: &[StmtNode], 205 | ) -> Result { 206 | let child_env = Environment::create_child(self.env.clone()); 207 | let mut last_result = Ok(StmtResult::None); 208 | let current_env = self.env.clone(); 209 | self.env = child_env; 210 | for statement in statements.iter() { 211 | last_result = self.eval_stmt(statement); 212 | if last_result.is_err() || last_result.clone().unwrap().is_block_terminating() { 213 | break; 214 | } 215 | } 216 | self.env = current_env; 217 | last_result 218 | } 219 | 220 | fn eval_stmt_if_then( 221 | &mut self, 222 | if_then_stmt: &IfThenStmt, 223 | ) -> Result { 224 | let &IfThenStmt { 225 | ref cond, 226 | ref then_block, 227 | ref maybe_else_block, 228 | } = if_then_stmt; 229 | let val = self.eval_expr_as_value(cond)?; 230 | if val.is_truthy() { 231 | let result = self.eval_stmt(then_block)?; 232 | if let StmtResult::Break = result { 233 | return Ok(StmtResult::Break); 234 | } else if let StmtResult::Return(_) = result { 235 | return Ok(result); 236 | } 237 | } else if maybe_else_block.is_some() { 238 | let else_block = maybe_else_block.clone().unwrap(); 239 | let result = self.eval_stmt(&else_block)?; 240 | if let StmtResult::Break = result { 241 | return Ok(StmtResult::Break); 242 | } else if let StmtResult::Return(_) = result { 243 | return Ok(result); 244 | } 245 | } 246 | Ok(StmtResult::None) 247 | } 248 | 249 | fn eval_stmt_loop(&mut self, block: &StmtNode) -> Result { 250 | let old_in_loop = self.context.in_loop; 251 | self.context.in_loop = true; 252 | let mut last_result; 253 | loop { 254 | last_result = self.eval_stmt(block); 255 | if last_result.is_err() { 256 | break; 257 | } 258 | match last_result { 259 | Ok(StmtResult::None) | Ok(StmtResult::Value(_)) => {} 260 | Ok(StmtResult::Break) | Ok(StmtResult::Return(_)) | Err(_) => { 261 | break; 262 | } 263 | Ok(StmtResult::Continue) => { 264 | continue; 265 | } 266 | } 267 | } 268 | self.context.in_loop = old_in_loop; 269 | if let Ok(StmtResult::Break) = last_result { 270 | Ok(StmtResult::None) 271 | } else { 272 | last_result 273 | } 274 | } 275 | 276 | fn eval_stmt_return( 277 | &mut self, 278 | possible_expr: &Option, 279 | return_stmt: &StmtNode, 280 | ) -> Result { 281 | if !self.context.in_func { 282 | return Err((RuntimeError::ReturnOutsideFunction, return_stmt.pos)); 283 | } 284 | match *possible_expr { 285 | Some(ref expr) => { 286 | let val = self.eval_expr_as_value(expr)?; 287 | Ok(StmtResult::Return(Some(val))) 288 | } 289 | None => Ok(StmtResult::Return(None)), 290 | } 291 | } 292 | 293 | fn eval_stmt_break( 294 | &mut self, 295 | break_stmt: &StmtNode, 296 | ) -> Result { 297 | if !self.context.in_loop { 298 | return Err((RuntimeError::BreakOutsideLoop, break_stmt.pos)); 299 | } 300 | Ok(StmtResult::Break) 301 | } 302 | 303 | fn eval_stmt_continue( 304 | &mut self, 305 | continue_stmt: &StmtNode, 306 | ) -> Result { 307 | if !self.context.in_loop { 308 | return Err((RuntimeError::ContinueOutsideLoop, continue_stmt.pos)); 309 | } 310 | Ok(StmtResult::Continue) 311 | } 312 | 313 | fn eval_expr_identifier( 314 | &mut self, 315 | id: &str, 316 | id_expr: &ExprNode, 317 | ) -> Result { 318 | match self.env.borrow_mut().get_value(id) { 319 | Some(v) => Ok(v), 320 | None => Err((RuntimeError::ReferenceError(id.to_owned()), id_expr.pos)), 321 | } 322 | } 323 | 324 | fn eval_expr_tuple(&mut self, elems: &[ExprNode]) -> Result { 325 | let mut values = Vec::new(); 326 | for elem_expr in elems { 327 | let val = self.eval_expr_as_value(elem_expr)?; 328 | values.push(val); 329 | } 330 | Ok(Value::Tuple(values)) 331 | } 332 | 333 | fn eval_expr_unary( 334 | &mut self, 335 | op: &UnOp, 336 | expr: &ExprNode, 337 | unary_expr: &ExprNode, 338 | ) -> Result { 339 | let val = self.eval_expr_as_value(expr)?; 340 | match *op { 341 | UnOp::Neg => match operations::unary_minus(val) { 342 | Ok(v) => Ok(v), 343 | Err(err) => Err((err, unary_expr.pos)), 344 | }, 345 | } 346 | } 347 | 348 | fn eval_expr_unary_logical( 349 | &mut self, 350 | op: &LogicalUnOp, 351 | expr: &ExprNode, 352 | ) -> Result { 353 | let val = self.eval_expr_as_value(expr)?; 354 | match *op { 355 | LogicalUnOp::Not => Ok(Value::Bool(!val.is_truthy())), 356 | } 357 | } 358 | 359 | fn eval_expr_binary( 360 | &mut self, 361 | op: &BinOp, 362 | expr1: &ExprNode, 363 | expr2: &ExprNode, 364 | binary_expr: &ExprNode, 365 | ) -> Result { 366 | let val1 = self.eval_expr_as_value(expr1)?; 367 | let val2 = self.eval_expr_as_value(expr2)?; 368 | let retval = match *op { 369 | BinOp::Add => operations::add(val1, val2), 370 | BinOp::Sub => operations::subtract(val1, val2), 371 | BinOp::Mul => operations::multiply(val1, val2), 372 | BinOp::Div => operations::divide(val1, val2), 373 | BinOp::Mod => operations::modulo(val1, val2), 374 | BinOp::Lt => operations::less_than(val1, val2), 375 | BinOp::Lte => operations::less_than_or_equal(val1, val2), 376 | BinOp::Gt => operations::greater_than(val1, val2), 377 | BinOp::Gte => operations::greater_than_or_equal(val1, val2), 378 | BinOp::Eq => Ok(Value::Bool(val1 == val2)), 379 | }; 380 | match retval { 381 | Ok(v) => Ok(v), 382 | Err(err) => Err((err, binary_expr.pos)), 383 | } 384 | } 385 | 386 | fn eval_expr_binary_logical( 387 | &mut self, 388 | op: &LogicalBinOp, 389 | expr1: &ExprNode, 390 | expr2: &ExprNode, 391 | ) -> Result { 392 | match *op { 393 | LogicalBinOp::And => { 394 | let val1 = self.eval_expr_as_value(expr1)?; 395 | if !val1.is_truthy() { 396 | return Ok(Value::Bool(false)); 397 | } 398 | let val2 = self.eval_expr_as_value(expr2)?; 399 | Ok(Value::Bool(val2.is_truthy())) 400 | } 401 | LogicalBinOp::Or => { 402 | let val1 = self.eval_expr_as_value(expr1)?; 403 | if val1.is_truthy() { 404 | return Ok(Value::Bool(true)); 405 | } 406 | let val2 = self.eval_expr_as_value(expr2)?; 407 | Ok(Value::Bool(val2.is_truthy())) 408 | } 409 | } 410 | } 411 | 412 | fn eval_expr_member_by_idx( 413 | &mut self, 414 | object_expr: &ExprNode, 415 | index_expr: &ExprNode, 416 | member_access_expr: &ExprNode, 417 | ) -> Result { 418 | let object = self.eval_expr_as_value(object_expr)?; 419 | let index = self.eval_expr_as_value(index_expr)?; 420 | match object { 421 | Value::Tuple(ref v) => match index { 422 | Value::Number(n) => { 423 | let idx; 424 | if let Number::Float(f) = n { 425 | if f.fract() == 0.0 { 426 | idx = f.trunc() as i64; 427 | } else { 428 | return Err(( 429 | RuntimeError::NonIntegralSubscript(Type::Number), 430 | index_expr.pos, 431 | )); 432 | } 433 | } else if let Number::Integer(i) = n { 434 | idx = i; 435 | } else { 436 | unreachable!(); 437 | } 438 | if idx < 0 { 439 | return Err(( 440 | RuntimeError::IndexOutOfBounds(idx), 441 | member_access_expr.pos, 442 | )); 443 | } 444 | match v.get(idx as usize) { 445 | Some(x) => Ok(x.clone()), 446 | None => Err(( 447 | RuntimeError::IndexOutOfBounds(idx), 448 | member_access_expr.pos, 449 | )), 450 | } 451 | } 452 | non_num_index => Err(( 453 | RuntimeError::NonIntegralSubscript(non_num_index.get_type()), 454 | index_expr.pos, 455 | )), 456 | }, 457 | obj => Err(( 458 | RuntimeError::SubscriptOnNonSubscriptable(obj.get_type()), 459 | object_expr.pos, 460 | )), 461 | } 462 | } 463 | 464 | fn eval_expr_fn_def( 465 | &mut self, 466 | fn_def_expr: &FnDefExpr, 467 | ) -> Result { 468 | let &FnDefExpr { 469 | ref maybe_id, 470 | ref params, 471 | ref body, 472 | } = fn_def_expr; 473 | let func = Function::User { 474 | call_sign: CallSign { 475 | num_params: params.len(), 476 | variadic: false, 477 | }, 478 | param_names: params.clone(), 479 | body: body.clone(), 480 | env: self.env.clone(), 481 | }; 482 | let func_val = Value::Function(Box::new(func)); 483 | if let Some(ref id) = *maybe_id { 484 | self.env.borrow_mut().declare(id, &func_val); 485 | } 486 | Ok(func_val) 487 | } 488 | 489 | fn eval_expr_fn_call( 490 | &mut self, 491 | expr: &ExprNode, 492 | args: &[ExprNode], 493 | fn_call_expr: &ExprNode, 494 | ) -> Result, RuntimeErrorWithPosition> { 495 | let val = self.eval_expr_as_value(expr)?; 496 | let func = match val { 497 | Value::Function(f) => f, 498 | v => { 499 | if let Expr::Identifier(ref id) = expr.data { 500 | return Err(( 501 | RuntimeError::CallToNonFunction(Some(id.clone()), v.get_type()), 502 | expr.pos, 503 | )); 504 | } 505 | return Err(( 506 | RuntimeError::CallToNonFunction(None, v.get_type()), 507 | expr.pos, 508 | )); 509 | } 510 | }; 511 | let mut arg_vals = Vec::new(); 512 | for arg in args.iter() { 513 | let val = self.eval_expr_as_value(arg)?; 514 | arg_vals.push(val); 515 | } 516 | 517 | let call_sign = func.get_call_sign(); 518 | check_args_compat(&arg_vals, &call_sign, expr, fn_call_expr)?; 519 | 520 | let call_func_result = call_func(&func, &arg_vals); 521 | match call_func_result { 522 | Ok(possible_val) => Ok(possible_val), 523 | Err(runtime_error) => Err((runtime_error, fn_call_expr.pos)), 524 | } 525 | } 526 | } 527 | 528 | pub fn call_func(func: &Function, arg_vals: &[Value]) -> Result, RuntimeError> { 529 | match *func { 530 | Function::NativeVoid(_, ref native_fn) => { 531 | native_fn(arg_vals.to_vec())?; 532 | Ok(None) 533 | } 534 | Function::NativeReturning(_, ref native_fn) => Ok(Some(native_fn(arg_vals.to_vec())?)), 535 | Function::User { 536 | ref param_names, 537 | ref body, 538 | ref env, 539 | .. 540 | } => { 541 | // TODO: returning 542 | let function_env = Environment::create_child(env.clone()); 543 | for (param, arg) in param_names.iter().zip(arg_vals.iter()) { 544 | function_env.borrow_mut().declare(param, arg); 545 | } 546 | let inner_env = Environment::create_child(function_env); 547 | let fn_context = Context { 548 | in_func: true, 549 | in_loop: false, 550 | }; 551 | let mut machine = 552 | AstWalkInterpreter::with_environment_and_context(inner_env, fn_context); 553 | let result = machine.eval_stmt(body); 554 | match result { 555 | Err(error_with_position) => Err(RuntimeError::InsideFunctionCall( 556 | Box::new(error_with_position), 557 | )), 558 | Ok(statement_result) => { 559 | if let StmtResult::Return(possible_val) = statement_result { 560 | match possible_val { 561 | Some(val) => Ok(Some(val)), 562 | None => Ok(None), 563 | } 564 | } else { 565 | Ok(None) 566 | } 567 | } 568 | } 569 | } 570 | } 571 | } 572 | 573 | fn check_args_compat( 574 | arg_vals: &[Value], 575 | call_sign: &CallSign, 576 | expr: &ExprNode, 577 | full_expr: &ExprNode, 578 | ) -> Result<(), RuntimeErrorWithPosition> { 579 | if !call_sign.variadic && call_sign.num_params != arg_vals.len() { 580 | if let Expr::Identifier(ref id) = expr.data { 581 | return Err(( 582 | RuntimeError::ArgumentLength(Some(id.clone())), 583 | full_expr.pos, 584 | )); 585 | } 586 | return Err((RuntimeError::ArgumentLength(None), full_expr.pos)); 587 | } 588 | Ok(()) 589 | } 590 | 591 | fn wrap( 592 | result: Result, 593 | ) -> Result, RuntimeErrorWithPosition> { 594 | match result { 595 | Err(err) => Err(err), 596 | Ok(val) => Ok(Some(val)), 597 | } 598 | } 599 | 600 | impl Interpreter for AstWalkInterpreter { 601 | fn run_ast_as_statements( 602 | &mut self, 603 | statements: &[StmtNode], 604 | ) -> Result, RuntimeErrorWithPosition> { 605 | self.eval_stmts(statements) 606 | } 607 | 608 | fn run_ast_as_program( 609 | &mut self, 610 | program: &[StmtNode], 611 | ) -> Result, RuntimeErrorWithPosition> { 612 | self.interpret_program(program) 613 | } 614 | } 615 | -------------------------------------------------------------------------------- /src/environment.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | 8 | use fnv::FnvHashMap; 9 | 10 | use value::*; 11 | use function::*; 12 | 13 | trait Env { 14 | fn new_root() -> Rc> 15 | where 16 | Self: Sized; 17 | } 18 | 19 | #[derive(Debug)] 20 | pub struct Environment { 21 | parent: Option>>, 22 | symbol_table: FnvHashMap, 23 | } 24 | 25 | // Testing to see if linked list Env system works 26 | // impl Drop for Environment { 27 | // fn drop(&mut self) { 28 | // println!("{:?}", self.symbol_table); 29 | // } 30 | // } 31 | 32 | impl Environment { 33 | pub fn new_root() -> Rc> { 34 | let mut env = Environment::new(); 35 | let builtin_functions = &[ 36 | ( 37 | "println", 38 | Function::NativeVoid( 39 | CallSign { 40 | num_params: 0, 41 | variadic: true, 42 | }, 43 | native_println, 44 | ), 45 | ), 46 | ( 47 | "assert", 48 | Function::NativeVoid( 49 | CallSign { 50 | num_params: 1, 51 | variadic: false, 52 | }, 53 | native_assert, 54 | ), 55 | ), 56 | ( 57 | "assert_eq", 58 | Function::NativeVoid( 59 | CallSign { 60 | num_params: 2, 61 | variadic: false, 62 | }, 63 | native_assert_eq, 64 | ), 65 | ), 66 | ( 67 | "run_http_server", 68 | Function::NativeVoid( 69 | CallSign { 70 | num_params: 1, 71 | variadic: false, 72 | }, 73 | native_run_http_server, 74 | ), 75 | ), 76 | ( 77 | "len", 78 | Function::NativeReturning( 79 | CallSign { 80 | num_params: 1, 81 | variadic: false, 82 | }, 83 | native_len, 84 | ), 85 | ), 86 | ]; 87 | for item in builtin_functions.iter() { 88 | let (name, ref func) = *item; 89 | env.declare(&name.to_string(), &Value::Function(Box::new(func.clone()))); 90 | } 91 | Rc::new(RefCell::new(env)) 92 | } 93 | 94 | pub fn new() -> Environment { 95 | Environment { 96 | parent: None, 97 | symbol_table: FnvHashMap::default(), 98 | } 99 | } 100 | 101 | pub fn create_child(parent: Rc>) -> Rc> { 102 | let env = Environment { 103 | parent: Some(parent), 104 | symbol_table: FnvHashMap::default(), 105 | }; 106 | Rc::new(RefCell::new(env)) 107 | } 108 | 109 | pub fn declare(&mut self, identifier: &str, value: &Value) { 110 | self.symbol_table 111 | .insert(identifier.to_owned(), value.clone()); 112 | } 113 | 114 | pub fn set(&mut self, identifier: &str, value: Value) -> bool { 115 | // TODO: Entry API 116 | if self.symbol_table.contains_key(identifier) { 117 | self.symbol_table.insert(identifier.to_owned(), value); 118 | true 119 | } else { 120 | match self.parent { 121 | Some(ref parent) => parent.borrow_mut().set(identifier, value), 122 | None => false, 123 | } 124 | } 125 | } 126 | 127 | // TODO: Why &mut? 128 | pub fn get_value(&mut self, identifier: &str) -> Option { 129 | if let Some(val) = self.symbol_table.get(identifier) { 130 | return Some(val.clone()); 131 | } else { 132 | match self.parent { 133 | Some(ref parent) => parent.borrow_mut().get_value(identifier), 134 | None => None, 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::io::prelude::*; 6 | use std::io; 7 | use std::fs::File; 8 | use std::str; 9 | 10 | use ansi_term::Style; 11 | use ansi_term::Colour::{Red, Yellow}; 12 | 13 | use parser; 14 | #[derive(Debug)] 15 | pub enum ProcessingError { 16 | ParseError(parser::ParseError), 17 | IoError(io::Error), 18 | } 19 | 20 | impl From for ProcessingError { 21 | fn from(from: io::Error) -> Self { 22 | ProcessingError::IoError(from) 23 | } 24 | } 25 | 26 | impl From for ProcessingError { 27 | fn from(from: parser::ParseError) -> Self { 28 | ProcessingError::ParseError(from) 29 | } 30 | } 31 | 32 | pub fn get_error_and_line_for_file( 33 | parse_error: &parser::ParseError, 34 | file_name: &str, 35 | ) -> (parser::ParseError, String) { 36 | let mut parse_error = parse_error.clone(); 37 | let mut buf_reader = io::BufReader::new(File::open(file_name).unwrap()); 38 | let mut line = buf_reader.by_ref().lines().nth(parse_error.line - 1); 39 | 40 | let line_content; 41 | 42 | // error was in last line which was empty 43 | if line.is_none() { 44 | parse_error.line -= 1; 45 | buf_reader.seek(io::SeekFrom::Start(0)).unwrap(); 46 | // more helpful to point to end of previous line 47 | line = buf_reader.lines().nth(parse_error.line - 1); 48 | line_content = line.unwrap().unwrap(); 49 | parse_error.column = line_content.len() + 1; 50 | } else { 51 | line_content = line.unwrap().unwrap(); 52 | } 53 | (parse_error, line_content) 54 | } 55 | 56 | pub fn print_parse_error(file_name: &str, line_content: &str, parse_error: &parser::ParseError) { 57 | println!( 58 | "{}: {}: line {}, col {}: expected one of {:?}", 59 | Style::new().bold().paint((*file_name).to_owned()), 60 | Red.bold().paint("parse error"), 61 | parse_error.line, 62 | parse_error.column, 63 | parse_error.expected 64 | ); 65 | println!("{}", line_content); 66 | let mut pointer_string = String::from_utf8(vec![b' '; parse_error.column - 1]).unwrap(); 67 | pointer_string.push('^'); 68 | println!("{}", Style::new().bold().paint(pointer_string)); 69 | } 70 | 71 | use ast::OffsetSpan; 72 | 73 | #[derive(Debug)] 74 | pub struct SourceSpan { 75 | pub start_line: usize, 76 | pub start_col: usize, 77 | pub end_line: usize, 78 | pub end_col: usize, 79 | } 80 | 81 | pub fn offset_span_to_source_span(span: OffsetSpan, input: &str) -> SourceSpan { 82 | let (start_line, start_col) = offset_to_line_and_col(input, span.0); 83 | let (end_line, end_col) = offset_to_line_and_col(input, span.1 - 1); 84 | SourceSpan { 85 | start_line: start_line, 86 | start_col: start_col, 87 | end_line: end_line, 88 | end_col: end_col, 89 | } 90 | } 91 | 92 | fn offset_to_line_and_col(input: &str, pos: usize) -> (usize, usize) { 93 | let mut remaining = pos; 94 | let mut line_num: usize = 1; 95 | for line in input.lines() { 96 | let line_length = line.len() + 1; 97 | if remaining < line_length { 98 | return (line_num, remaining + 1); 99 | } 100 | remaining -= line_length; 101 | line_num += 1; 102 | } 103 | (line_num, remaining + 1) 104 | } 105 | 106 | use runtime::RuntimeError; 107 | use typechecker::TypeCheckerIssue; 108 | 109 | fn adjust_source_span(span: &mut SourceSpan, file_content: &str) { 110 | if (span.start_line != span.end_line) && span.end_col == 1 { 111 | span.end_col = 0; 112 | } 113 | while (span.end_col == 0) && span.end_line > span.start_line { 114 | span.end_line -= 1; 115 | span.end_col = file_content.lines().nth(span.end_line - 1).unwrap().len(); 116 | } 117 | } 118 | 119 | pub fn print_interpreter_error_for_file( 120 | err: RuntimeError, 121 | span: SourceSpan, 122 | file_content: &str, 123 | file_name: &str, 124 | ) { 125 | print_typechecker_error_for_file( 126 | TypeCheckerIssue::RuntimeError(err), 127 | span, 128 | file_content, 129 | file_name, 130 | ); 131 | } 132 | 133 | pub fn print_typechecker_error_for_file( 134 | err: TypeCheckerIssue, 135 | span: SourceSpan, 136 | file_content: &str, 137 | file_name: &str, 138 | ) { 139 | let mut span = span; 140 | let mut error_to_print_after_this = None; 141 | adjust_source_span(&mut span, file_content); 142 | if span.start_line == span.end_line { 143 | println!( 144 | "in {}, line {}, col {}:", 145 | Style::new().bold().paint(file_name.to_string()), 146 | span.start_line, 147 | span.start_col 148 | ); 149 | } else { 150 | println!( 151 | "in {}, starting on line {}, col {}:", 152 | Style::new().bold().paint(file_name.to_string()), 153 | span.start_line, 154 | span.start_col 155 | ); 156 | } 157 | let mut is_error = true; 158 | match err { 159 | TypeCheckerIssue::RuntimeError(e) => match e { 160 | RuntimeError::ReferenceError(id) => { 161 | println!( 162 | "{}: `{}` was not declared", 163 | Red.bold().paint("reference error"), 164 | id 165 | ); 166 | } 167 | RuntimeError::UndeclaredAssignment(id) => { 168 | println!( 169 | "{}: cannot assign to undeclared `{}`", 170 | Red.bold().paint("reference error"), 171 | id 172 | ); 173 | } 174 | RuntimeError::BinaryTypeError(binary_op, type1, type2) => { 175 | println!( 176 | "{}: `{}` cannot operate on types {} and {}", 177 | Red.bold().paint("type error"), 178 | binary_op, 179 | type1, 180 | type2 181 | ); 182 | } 183 | RuntimeError::UnaryTypeError(unary_op, typ) => { 184 | println!( 185 | "{}: `{}` cannot operate on type {}", 186 | Red.bold().paint("type error"), 187 | unary_op, 188 | typ 189 | ); 190 | } 191 | RuntimeError::NoneError(possible_id) => match possible_id { 192 | Some(id) => { 193 | println!( 194 | "{}: tried to use return value of non-returning function \ 195 | `{}`", 196 | Red.bold().paint("missing value error"), 197 | id 198 | ); 199 | } 200 | None => { 201 | println!( 202 | "{}: tried to use return value of non-returning function", 203 | Red.bold().paint("missing value error") 204 | ); 205 | } 206 | }, 207 | RuntimeError::CallToNonFunction(possible_id, other_type) => match possible_id { 208 | Some(id) => { 209 | println!( 210 | "{}: cannot call `{}` ({}) as Function", 211 | Red.bold().paint("type error"), 212 | id, 213 | other_type 214 | ); 215 | } 216 | None => { 217 | println!( 218 | "{}: cannot call {} as Function", 219 | Red.bold().paint("type error"), 220 | other_type 221 | ); 222 | } 223 | }, 224 | RuntimeError::ArgumentLength(possible_id) => match possible_id { 225 | Some(id) => { 226 | println!( 227 | "{}: function `{}` called with incorrect number of arguments", 228 | Red.bold().paint("arguments mismatch"), 229 | id 230 | ); 231 | } 232 | None => { 233 | println!( 234 | "{}: function called with incorrect number of arguments", 235 | Red.bold().paint("arguments mismatch") 236 | ); 237 | } 238 | }, 239 | RuntimeError::GeneralRuntimeError(message) => { 240 | println!("{}: {}", Red.bold().paint("runtime error"), message); 241 | } 242 | RuntimeError::InsideFunctionCall(error_with_position) => { 243 | println!("{}:", Red.bold().paint("error in function call")); 244 | let unboxed_error_with_position = *error_with_position; 245 | let (next_error, next_pos) = unboxed_error_with_position; 246 | error_to_print_after_this = 247 | Some((TypeCheckerIssue::RuntimeError(next_error), next_pos)); 248 | } 249 | RuntimeError::IndexOutOfBounds(index) => { 250 | println!( 251 | "{}: index `{}` is out of bounds of the tuple", 252 | Red.bold().paint("index of out bounds"), 253 | index 254 | ); 255 | } 256 | RuntimeError::SubscriptOnNonSubscriptable(typ) => { 257 | println!( 258 | "{}: cannot subscript type {}", 259 | Red.bold().paint("type error"), 260 | typ 261 | ); 262 | } 263 | RuntimeError::NonIntegralSubscript(typ) => { 264 | println!( 265 | "{}: cannot use non-integral {:?} as subscript", 266 | Red.bold().paint("non integral subscript"), 267 | typ 268 | ); 269 | } 270 | RuntimeError::BreakOutsideLoop => { 271 | println!( 272 | "{}: break statement appeared outside of a loop", 273 | Red.bold().paint("break outside loop") 274 | ); 275 | } 276 | RuntimeError::ContinueOutsideLoop => { 277 | println!( 278 | "{}: continue statement appeared outside of a loop", 279 | Red.bold().paint("continue outside loop") 280 | ); 281 | } 282 | RuntimeError::ReturnOutsideFunction => { 283 | println!( 284 | "{}: return statement appeared outside of a function", 285 | Red.bold().paint("return outside function") 286 | ); 287 | } 288 | }, 289 | TypeCheckerIssue::MultipleTypesFromBranchWarning(id) => { 290 | println!( 291 | "{}: `{}` gets different types in branches", 292 | Style::new().bold().paint("multiple types from branch"), 293 | id 294 | ); 295 | is_error = false; 296 | } 297 | TypeCheckerIssue::InsideFunctionCall(issue_with_position) => { 298 | println!("{}:", Red.bold().paint("issue in function call")); 299 | error_to_print_after_this = Some(*issue_with_position); 300 | } 301 | TypeCheckerIssue::FunctionReturnsMultipleTypes => { 302 | println!( 303 | "{}: different branches of the function return different types", 304 | Style::new().bold().paint("function returns multiple types") 305 | ); 306 | is_error = false; 307 | } 308 | TypeCheckerIssue::UnreachableCodeAfterReturn => { 309 | println!( 310 | "{}: code was found after a return statement, making it unreachable", 311 | Style::new().bold().paint("unreachable code") 312 | ); 313 | is_error = false; 314 | } 315 | TypeCheckerIssue::PossibleNoneError(possible_id) => match possible_id { 316 | Some(id) => { 317 | println!( 318 | "{}: tried to use return value of function `{}` that \ 319 | does not always return a value", 320 | Red.bold().paint("possibly missing value"), 321 | id 322 | ); 323 | } 324 | None => { 325 | println!( 326 | "{}: tried to use return value of function that \ 327 | does not always return a value", 328 | Red.bold().paint("possibly missing value") 329 | ); 330 | } 331 | }, 332 | } 333 | 334 | if span.start_line == span.end_line { 335 | let left_padding_size = span.start_line.to_string().len() + 3; 336 | println!( 337 | "{} | {}", 338 | span.start_line, 339 | file_content.lines().nth(span.start_line - 1).unwrap() 340 | ); 341 | let left_padding = 342 | String::from_utf8(vec![b' '; span.start_col + left_padding_size - 1]).unwrap(); 343 | let pointer_string = 344 | String::from_utf8(vec![b'^'; span.end_col + 1 - span.start_col]).unwrap(); 345 | println!("{}{}", left_padding, Yellow.bold().paint(pointer_string)); 346 | } else { 347 | let first_line_start_bytes = file_content 348 | .lines() 349 | .nth(span.start_line - 1) 350 | .unwrap() 351 | .bytes() 352 | .take(span.start_col - 1) 353 | .collect::>(); 354 | let first_line_start_string = str::from_utf8(&first_line_start_bytes).unwrap(); 355 | let first_line_rest_bytes = file_content 356 | .lines() 357 | .nth(span.start_line - 1) 358 | .unwrap() 359 | .bytes() 360 | .skip(span.start_col - 1) 361 | .collect::>(); 362 | let first_line_rest_string = str::from_utf8(&first_line_rest_bytes).unwrap(); 363 | 364 | let max_idx_width = span.end_line.to_string().len(); 365 | 366 | println!("{line_num:width$} |", line_num = "", width = max_idx_width); 367 | 368 | println!( 369 | "{line_num:width$} | {}{}", 370 | first_line_start_string, 371 | if is_error { 372 | Red.bold() 373 | } else { 374 | Style::new().bold() 375 | }.paint(first_line_rest_string), 376 | line_num = span.start_line, 377 | width = max_idx_width 378 | ); 379 | 380 | for line_num in span.start_line..span.end_line - 1 { 381 | println!( 382 | "{line_num:width$} | {}", 383 | if is_error { 384 | Red.bold() 385 | } else { 386 | Style::new().bold() 387 | }.paint(file_content.lines().nth(line_num).unwrap()), 388 | line_num = line_num + 1, 389 | width = max_idx_width 390 | ); 391 | } 392 | 393 | if span.end_col != 0 { 394 | let last_line_start_bytes = file_content 395 | .lines() 396 | .nth(span.end_line - 1) 397 | .unwrap() 398 | .bytes() 399 | .take(span.end_col) 400 | .collect::>(); 401 | let last_line_start_string = str::from_utf8(&last_line_start_bytes).unwrap(); 402 | let last_line_rest_bytes = file_content 403 | .lines() 404 | .nth(span.end_line - 1) 405 | .unwrap() 406 | .bytes() 407 | .skip(span.end_col) 408 | .collect::>(); 409 | let last_line_rest_string = str::from_utf8(&last_line_rest_bytes).unwrap(); 410 | println!( 411 | "{line_num:width$} | {}{}", 412 | if is_error { 413 | Red.bold() 414 | } else { 415 | Style::new().bold() 416 | }.paint(last_line_start_string), 417 | last_line_rest_string, 418 | line_num = span.end_line, 419 | width = max_idx_width 420 | ); 421 | } 422 | 423 | println!("{line_num:width$} |", line_num = "", width = max_idx_width); 424 | } 425 | if let Some(next_err) = error_to_print_after_this { 426 | let span = offset_span_to_source_span(next_err.1, file_content); 427 | print_typechecker_error_for_file( 428 | next_err.0, 429 | span, 430 | file_content, 431 | &("function in ".to_owned() + file_name), 432 | ); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | 8 | use value::*; 9 | use ast; 10 | use environment::Environment; 11 | use runtime::RuntimeError; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct CallSign { 15 | pub num_params: usize, 16 | pub variadic: bool, 17 | } 18 | 19 | #[derive(Clone, Debug)] 20 | pub enum Function { 21 | NativeVoid(CallSign, fn(Vec) -> Result<(), RuntimeError>), 22 | NativeReturning(CallSign, fn(Vec) -> Result), 23 | User { 24 | call_sign: CallSign, 25 | param_names: Vec, 26 | body: Box, 27 | env: Rc>, 28 | }, 29 | } 30 | 31 | impl Function { 32 | pub fn get_call_sign(&self) -> CallSign { 33 | match *self { 34 | Function::NativeVoid(ref call_sign, _) | 35 | Function::NativeReturning(ref call_sign, _) | 36 | Function::User { ref call_sign, .. } => call_sign.clone(), 37 | } 38 | } 39 | } 40 | 41 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 42 | pub fn native_println(args: Vec) -> Result<(), RuntimeError> { 43 | if args.is_empty() { 44 | return Ok(()); 45 | } 46 | if args.len() == 1 { 47 | println!("{}", args[0]); 48 | } else { 49 | print!("{}", args[0]); 50 | for arg in args.iter().skip(1) { 51 | print!(" {}", arg); 52 | } 53 | println!(""); 54 | } 55 | Ok(()) 56 | } 57 | 58 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 59 | pub fn native_assert(args: Vec) -> Result<(), RuntimeError> { 60 | let val = &args[0]; 61 | if !val.is_truthy() { 62 | Err(RuntimeError::GeneralRuntimeError( 63 | format!("assert: assertion failed for value {}", val), 64 | )) 65 | } else { 66 | Ok(()) 67 | } 68 | } 69 | 70 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 71 | pub fn native_assert_eq(args: Vec) -> Result<(), RuntimeError> { 72 | let (val1, val2) = (&args[0], &args[1]); 73 | 74 | if val1 != val2 { 75 | Err(RuntimeError::GeneralRuntimeError( 76 | format!("assert_eq: {} != {}", val1, val2), 77 | )) 78 | } else { 79 | Ok(()) 80 | } 81 | } 82 | 83 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 84 | pub fn native_len(args: Vec) -> Result { 85 | let val = &args[0]; 86 | match *val { 87 | Value::Tuple(ref v) => Ok(Value::Number(Number::Integer(v.len() as i64))), 88 | ref non_tuple_val => Err(RuntimeError::GeneralRuntimeError( 89 | format!("cannot get len of {:?}", non_tuple_val.get_type()), 90 | )), 91 | } 92 | } 93 | 94 | #[cfg_attr(feature = "cargo-clippy", allow(needless_pass_by_value))] 95 | pub fn native_run_http_server(args: Vec) -> Result<(), RuntimeError> { 96 | use hyper::server::{Request, Response, Server}; 97 | use hyper::header::ContentType; 98 | use hyper::uri::RequestUri; 99 | use std::sync::mpsc::channel; 100 | use std::sync::Mutex; 101 | use std::thread; 102 | use std::sync::PoisonError; 103 | 104 | use ast_walk_interpreter::call_func; 105 | 106 | let handler_val = &args[0]; 107 | 108 | let handler_func = match *handler_val { 109 | Value::Function(ref f) => f, 110 | _ => { 111 | return Err(RuntimeError::GeneralRuntimeError( 112 | "http_server: handler is not a Function".to_owned(), 113 | )) 114 | } 115 | }; 116 | 117 | let maybe_hyper_server = Server::http("0.0.0.0:8000"); 118 | 119 | if let Err(e) = maybe_hyper_server { 120 | return Err(RuntimeError::GeneralRuntimeError( 121 | format!("http_server: {}", e), 122 | )); 123 | } 124 | 125 | let server = maybe_hyper_server.unwrap(); 126 | // channel from server to interpreter 127 | let (sender, receiver) = channel(); 128 | let sender_mutex = Mutex::new(sender); 129 | thread::spawn(|| { 130 | println!("http_server: listening on 0.0.0.0:8000 on a new thread"); 131 | let handle_result = server.handle(move |req: Request, mut res: Response| { 132 | let sender = match sender_mutex.lock() { 133 | Ok(sender) => sender, 134 | Err(PoisonError { .. }) => panic!("http_server: threading error (lock poisoned)"), 135 | }; 136 | if let RequestUri::AbsolutePath(path) = req.uri { 137 | // channel from interpreter to server 138 | let (rev_sender, rev_receiver) = channel(); 139 | if sender.send((path, rev_sender)).is_err() { 140 | panic!("http_server: threading error (could not send on reverse channel)"); 141 | } 142 | let response_string: String = match rev_receiver.recv() { 143 | Ok(response_string) => response_string, 144 | Err(_) => { 145 | // assume some clean disconnect 146 | return; 147 | } 148 | }; 149 | res.headers_mut().set(ContentType::html()); 150 | if res.send(response_string.as_bytes()).is_err() { 151 | panic!("http_server: could not send response"); 152 | } 153 | } else { 154 | panic!("http_server: unknown kind of request"); 155 | } 156 | }); 157 | if handle_result.is_err() { 158 | panic!("http_server: could not handle requests"); 159 | } 160 | }); 161 | 162 | loop { 163 | match receiver.recv() { 164 | Err(_) => { 165 | // assume some clean disconnect 166 | break; 167 | } 168 | Ok(msg) => { 169 | let (path, sender) = msg; 170 | let possible_response_value = call_func(handler_func, &[Value::String(path)])?; 171 | let response_value = match possible_response_value { 172 | None => { 173 | return Err(RuntimeError::GeneralRuntimeError( 174 | "http_server: handler \ 175 | function did not return a \ 176 | value" 177 | .to_owned(), 178 | )); 179 | } 180 | Some(val) => val, 181 | }; 182 | if sender.send(response_value.to_string()).is_err() { 183 | return Err(RuntimeError::GeneralRuntimeError( 184 | "http_server: threading error \ 185 | (could not send on reverse \ 186 | channel)" 187 | .to_owned(), 188 | )); 189 | } 190 | } 191 | } 192 | } 193 | 194 | Ok(()) 195 | } 196 | -------------------------------------------------------------------------------- /src/grammar.rustpeg: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use ast::*; 6 | 7 | pub program -> Vec 8 | = __ terminators? s:statements { s } 9 | 10 | pub statements -> Vec 11 | = statement_node* 12 | 13 | statement_node -> StmtNode 14 | = __ lpos:#position s:statement rpos:#position __ { StmtNode { pos: (lpos, rpos), data: s } } 15 | 16 | statement -> Stmt 17 | = s:if_statement { s } 18 | / l:loop_statement { l } 19 | / BREAK __ TERMINATOR { Stmt::Break } 20 | / CONTINUE __ TERMINATOR { Stmt::Continue } 21 | / RETURN __ e:expr_node? __ TERMINATOR { Stmt::Return(e) } 22 | / a:assignment_statement { a } 23 | / v:variable_declaration { v } 24 | / e:expr_node __ TERMINATOR { Stmt::Expr(e) } 25 | / f:function_definition_node { Stmt::Expr(f) } 26 | / b:block { b } 27 | / TERMINATOR { Stmt::Empty } 28 | 29 | assignment_statement -> Stmt 30 | = lpos:#position i:identifier rpos:#position __ EQUALS __ e:expr_node __ TERMINATOR { 31 | Stmt::Assign(LhsExprNode { pos: (lpos, rpos), data: LhsExpr::Identifier(i) }, e) 32 | } 33 | / lpos:#position i:identifier rpos:#position __ op:assign_with_op __ e:expr_node __ TERMINATOR { 34 | Stmt::AssignOp(LhsExprNode { pos: (lpos, rpos), data: LhsExpr::Identifier(i) }, op, e) 35 | } 36 | 37 | assign_with_op -> BinOp 38 | = OP_PLUS __ EQUALS { BinOp::Add } 39 | / OP_MINUS __ EQUALS { BinOp::Sub } 40 | / OP_ASTERISK __ EQUALS { BinOp::Mul } 41 | / OP_SLASH __ EQUALS { BinOp::Div } 42 | / OP_MOD __ EQUALS { BinOp::Mod } 43 | 44 | variable_declaration -> Stmt 45 | = b:binding_type __ i:identifier __ EQUALS __ e:expr_node __ TERMINATOR { 46 | Stmt::VarDecl( 47 | Variable::Identifier(b, i), e 48 | ) 49 | } 50 | 51 | binding_type -> BindingType 52 | = VAR { BindingType::Mutable } 53 | 54 | loop_statement -> Stmt 55 | = LOOP __ lpos:#position b:block rpos:#position __ { Stmt::Loop(Box::new(StmtNode { pos: (lpos, rpos), data: b })) } 56 | 57 | if_statement -> Stmt 58 | = IF __ e:expr_node __ lpos1:#position b1:block rpos1:#position __ ELSE __ lpos2:#position b2:block rpos2:#position __ { 59 | Stmt::IfThen( 60 | IfThenStmt { 61 | cond: e, 62 | then_block: Box::new(StmtNode { pos: (lpos1, rpos1), data: b1 }), 63 | maybe_else_block: Some(Box::new(StmtNode { pos: (lpos2, rpos2), data: b2 })), 64 | } 65 | ) 66 | } 67 | / IF __ e:expr_node __ lpos1:#position b1:block rpos1:#position __ ELSE __ lpos2:#position elseif:if_statement rpos2:#position __ { 68 | Stmt::IfThen( 69 | IfThenStmt { 70 | cond: e, 71 | then_block: Box::new(StmtNode { pos: (lpos1, rpos1), data: b1 }), 72 | maybe_else_block: Some(Box::new(StmtNode { pos: (lpos2, rpos2), data: elseif })), 73 | } 74 | ) 75 | } 76 | / IF e:expr_node lpos:#position b:block rpos:#position { 77 | Stmt::IfThen( 78 | IfThenStmt { 79 | cond: e, 80 | then_block: Box::new(StmtNode { pos: (lpos, rpos), data: b }), 81 | maybe_else_block: None 82 | } 83 | ) 84 | } 85 | 86 | block -> Stmt 87 | = #quiet<_block> / #expected("block") 88 | 89 | _block -> Stmt 90 | = OPENING_BRACE __ terminators? __ s:statements __ terminators? __ CLOSING_BRACE { Stmt::Block(s) } 91 | 92 | function_definition_node -> ExprNode 93 | = __ lpos:#position f:function_definition rpos:#position __ { ExprNode { pos: (lpos, rpos), data: f } } 94 | 95 | function_definition -> Expr 96 | = FN __ i:identifier? __ OPEN_PAREN __ params:param_list __ COMMA? __ CLOSE_PAREN __ lpos:#position body:block rpos:#position __ { 97 | Expr::FnDef( 98 | FnDefExpr { 99 | maybe_id: i, 100 | params: params, 101 | body: Box::new(StmtNode { pos: (lpos, rpos), data: body } ), 102 | } 103 | ) 104 | } 105 | 106 | param_list -> Vec 107 | = identifier_with_whitespace ** COMMA 108 | 109 | identifier_with_whitespace -> String 110 | = __ id:identifier __ { id } 111 | 112 | expr_node -> ExprNode 113 | = e:binary_expr_node { e } 114 | / s:single_expr_node { s } 115 | 116 | comma_args -> Vec 117 | = expr_node ** COMMA 118 | 119 | binary_expr_node -> ExprNode 120 | = binary_expr 121 | // __ lpos:#position e:binary_expr rpos:#position __ { 122 | // // hackily check if e is a proper binary expr 123 | // if e.pos == (0, 0) { 124 | // ExprNode { pos: (lpos, rpos), data: e.data } 125 | // } else { 126 | // // e is actually a single_expr, so just propagate 127 | // e 128 | // } 129 | // } 130 | 131 | binary_expr -> ExprNode = #infix { 132 | #L x AND y { ExprNode { pos: (x.pos.0, y.pos.1), data: Expr::BinaryLogical(Box::new(x), LogicalBinOp::And, Box::new(y)) } } 133 | x OR y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::BinaryLogical(Box::new(x), LogicalBinOp::Or, Box::new(y)) } } 134 | #L x OP_STRICT_EQUALS y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Eq, Box::new(y)) } } 135 | #L x OP_LESS_THAN y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Lt, Box::new(y)) } } 136 | x OP_LESS_THAN_OR_EQUAL y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Lte, Box::new(y)) } } 137 | x OP_GREATER_THAN y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Gt, Box::new(y)) } } 138 | x OP_GREATER_THAN_OR_EQUAL y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Gte, Box::new(y)) } } 139 | #L x OP_PLUS y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Add, Box::new(y)) } } 140 | x OP_MINUS y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Sub, Box::new(y)) } } 141 | #L x OP_ASTERISK y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Mul, Box::new(y)) } } 142 | x OP_SLASH y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Div, Box::new(y)) } } 143 | x OP_MOD y { ExprNode { pos : (x.pos.0, y.pos.1), data: Expr::Binary(Box::new(x), BinOp::Mod, Box::new(y)) } } 144 | } 145 | 146 | single_expr_node -> ExprNode 147 | = __ OPEN_PAREN e:expr_node CLOSE_PAREN __ { e } 148 | / e:expr_with_suffix { e } 149 | 150 | expr_with_suffix -> ExprNode 151 | // TODO: This sort of messes up position annotation for the ExprNode 152 | = __ lpos:#position e:simple_expr_node suffixes:expr_suffix* rpos:#position __ { 153 | if suffixes.is_empty() { 154 | e 155 | } else { 156 | let mut expr = e; 157 | for suffix in suffixes { 158 | match suffix { 159 | ExprSuffix::InSquareBrackets(idx_expr) => { 160 | expr = ExprNode { 161 | pos: (lpos, rpos), 162 | data: Expr::MemberByIdx(Box::new(expr), Box::new(idx_expr)), 163 | } 164 | } 165 | ExprSuffix::ListInParens(args_list) => { 166 | expr = ExprNode { 167 | pos: (lpos, rpos), 168 | data: Expr::FnCall(Box::new(expr), args_list), 169 | } 170 | } 171 | } 172 | } 173 | expr 174 | } 175 | } 176 | 177 | expr_suffix -> ExprSuffix 178 | = member_access_suffix 179 | / function_call_suffix 180 | 181 | member_access_suffix -> ExprSuffix 182 | = OPEN_SQUARE_BRACKET idx:expr_node CLOSE_SQUARE_BRACKET { ExprSuffix::InSquareBrackets(idx) } 183 | 184 | function_call_suffix -> ExprSuffix 185 | = OPEN_PAREN __ args:comma_args __ COMMA? __ CLOSE_PAREN { ExprSuffix::ListInParens(args) } 186 | 187 | _tuple -> Expr 188 | = OPEN_PAREN __ args:comma_args __ COMMA? __ CLOSE_PAREN { 189 | Expr::Tuple(args) 190 | } 191 | 192 | simple_expr_node -> ExprNode 193 | = __ lpos:#position e:simple_expr rpos:#position __ { ExprNode { pos: (lpos, rpos), data: e } } 194 | 195 | simple_expr -> Expr 196 | = OP_MINUS __ e:single_expr_node { Expr::Unary(UnOp::Neg, Box::new(e)) } 197 | / NOT __ e:expr_node { Expr::UnaryLogical(LogicalUnOp::Not, Box::new(e)) } 198 | / t:tuple { t } 199 | / f:function_definition { f } 200 | / l:literal_node { Expr::Literal(l) } 201 | / i:identifier { Expr::Identifier(i) } 202 | 203 | literal_node -> LiteralNode 204 | = __ lpos:#position l:literal rpos:#position __ { 205 | LiteralNode { pos: (lpos, rpos), data: l } 206 | } 207 | 208 | literal -> Literal 209 | = f:float { Literal::Float(f) } 210 | / i:integer { Literal::Integer(i) } 211 | / b:boolean { Literal::Bool(b) } 212 | / s:doubleQuotedString { Literal::String(s) } 213 | 214 | tuple -> Expr 215 | = #quiet<_tuple> / #expected("tuple") 216 | 217 | doubleQuotedString -> String 218 | = #quiet<_doubleQuotedString> / #expected("string") 219 | 220 | _doubleQuotedString -> String 221 | = '"' s:$([^"]*) '"' { s.to_owned() } 222 | 223 | integer -> i64 224 | = #quiet<_integer> / #expected("number") 225 | 226 | _integer -> i64 227 | = n:$([+-]?[0-9]+) { n.parse().unwrap() } 228 | 229 | float -> f64 230 | = #quiet<_float> / #expected("number") 231 | 232 | _float -> f64 233 | = f:$([+-]?[0-9]+"."[0-9]+) { f.parse().unwrap() } 234 | 235 | boolean -> bool 236 | = #quiet<_boolean> / #expected("bool") 237 | 238 | _boolean -> bool 239 | = "true" { true } 240 | / "false" { false } 241 | 242 | identifier -> String 243 | = #quiet<_identifier> / #expected("identifier") 244 | 245 | _identifier -> String 246 | = !reserved_identifier i:$([a-zA-Z_][a-zA-Z0-9_]*[!?]?) { i.to_string() } 247 | 248 | __ = #quiet<(whitespace / comment)*> 249 | 250 | comment = "#" (!eol_char .)* 251 | 252 | eol_char = [\n\r] 253 | 254 | whitespace = [ \t\n\r] 255 | 256 | EQUALS = #quiet<"="> / #expected("equals") 257 | 258 | terminators = TERMINATOR+ 259 | 260 | TERMINATOR -> () 261 | = ";" 262 | 263 | OP_PLUS = "+" 264 | OP_MINUS = "-" 265 | OP_ASTERISK = "*" 266 | OP_SLASH = "/" 267 | OP_MOD = "%" 268 | OP_COLON = ":" 269 | OP_LESS_THAN = "<" 270 | OP_GREATER_THAN = ">" 271 | OP_LESS_THAN_OR_EQUAL = "<=" 272 | OP_GREATER_THAN_OR_EQUAL = ">=" 273 | 274 | OP_STRICT_EQUALS = "==" 275 | 276 | OPENING_BRACE = "{" 277 | CLOSING_BRACE = "}" 278 | OPEN_PAREN = "(" 279 | CLOSE_PAREN = ")" 280 | OPEN_SQUARE_BRACKET = "[" 281 | CLOSE_SQUARE_BRACKET = "]" 282 | 283 | COMMA = "," 284 | COLON = ":" 285 | 286 | reserved_identifier = VAR 287 | / IF 288 | / ELSE 289 | / AND 290 | / OR 291 | / NOT 292 | / "true" 293 | / "false" 294 | / LOOP 295 | / BREAK 296 | / CONTINUE 297 | / FN 298 | / RETURN; 299 | 300 | keyword = E 301 | 302 | VAR = keyword<"var"> 303 | IF = keyword<"if"> 304 | ELSE = keyword<"else"> 305 | AND = keyword<"and"> 306 | OR = keyword<"or"> 307 | NOT = keyword<"not"> 308 | LOOP = keyword<"loop"> 309 | BREAK = keyword<"break"> 310 | CONTINUE = keyword<"continue"> 311 | FN = keyword<"fn"> 312 | RETURN = keyword<"return"> 313 | NUMBER = keyword<"Number"> 314 | BOOL = keyword<"Bool"> 315 | STRING = keyword<"String"> 316 | FUNCTION = keyword<"Function"> 317 | TUPLE = keyword<"Tuple"> 318 | ANY = keyword<"any"> 319 | VOID = keyword<"void"> 320 | -------------------------------------------------------------------------------- /src/interpreter_test.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use parser; 6 | use runtime::Interpreter; 7 | use runtime::StmtResult; 8 | use ast_walk_interpreter::AstWalkInterpreter; 9 | use value::Value; 10 | use value::Number; 11 | 12 | fn run_and_get_last_value(code: &str) -> Value { 13 | match run_and_get_last_result(code) { 14 | StmtResult::Value(ref v) => v.clone(), 15 | _ => panic!("Cannot unwrap value"), 16 | } 17 | } 18 | 19 | fn run_and_get_last_result(code: &str) -> StmtResult { 20 | let ast = parser::program(code); 21 | match ast { 22 | Ok(ast) => { 23 | let mut ast_walk_interpreter = AstWalkInterpreter::new(); 24 | let reference_val = ast_walk_interpreter 25 | .run_ast_as_program(&ast) 26 | .unwrap() 27 | .unwrap(); 28 | return reference_val; 29 | // Test plan forward once LLVM backend is written 30 | // let mut llvm_interpreter = LLVMInterpreter::new() 31 | // let llvm_val = llvm_interpreter.run_ast_as_program(&ast) 32 | // .unwrap().unwrap(); 33 | // 34 | // if (llvm_val == reference_val) { 35 | // reference_val 36 | // } 37 | // else { 38 | // panic!("LLVM value: {:?} does not agree with Reference Value {:?}" 39 | // } 40 | // 41 | } 42 | Err(_) => panic!("{:?}", ast), 43 | } 44 | } 45 | 46 | #[test] 47 | fn i64_support() { 48 | assert_eq!( 49 | run_and_get_last_value("9223372036854775807;"), 50 | Value::Number(Number::Integer(9223372036854775807)) 51 | ); 52 | } 53 | 54 | #[test] 55 | fn f64_support() { 56 | assert_eq!( 57 | run_and_get_last_value("1234567890.012345678;"), 58 | Value::Number(Number::Float(1234567890.012345678)) 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::env; 6 | use std::io::prelude::*; 7 | use std::io; 8 | use std::fs::File; 9 | 10 | extern crate ansi_term; 11 | 12 | extern crate fnv; 13 | 14 | extern crate rustyline; 15 | 16 | extern crate linear_map; 17 | 18 | extern crate hyper; 19 | 20 | #[cfg(feature = "llvm-backend")] 21 | extern crate llvm_sys; 22 | #[cfg(feature = "llvm-backend")] 23 | extern crate itertools; 24 | #[cfg(feature = "llvm-backend")] 25 | extern crate libc; 26 | 27 | // include output of rust-peg given grammar.rustpeg 28 | mod parser { 29 | #![cfg_attr(feature = "cargo-clippy", allow(clippy))] 30 | include!(concat!(env!("OUT_DIR"), "/grammar.rs")); 31 | } 32 | 33 | mod ast; 34 | mod runtime; 35 | mod ast_walk_interpreter; 36 | #[cfg(feature = "llvm-backend")] 37 | mod llvm_interpreter; 38 | mod value; 39 | mod operations; 40 | mod environment; 41 | mod repl; 42 | mod error; 43 | mod typechecker; 44 | mod function; 45 | 46 | #[cfg(test)] 47 | mod interpreter_test; 48 | 49 | #[cfg(all(test, feature = "file-tests"))] 50 | mod file_test { 51 | include!(concat!(env!("OUT_DIR"), "/file_tests.rs")); 52 | } 53 | 54 | use runtime::*; 55 | use ast_walk_interpreter::AstWalkInterpreter; 56 | #[cfg(feature = "llvm-backend")] 57 | use llvm_interpreter::LLVMInterpreter; 58 | 59 | use error::*; 60 | 61 | // FIXME: How do you represent the usage style in POSIX notation? 62 | fn print_usage() { 63 | if cfg!(feature = "llvm-backend") { 64 | println!( 65 | "usage: balloon [--repl-llvm | [MODE] FILE] 66 | 67 | --repl-llvm launches the experimental REPL" 68 | ); 69 | } else { 70 | println!("usage: balloon [[MODE] FILE]"); 71 | } 72 | println!( 73 | " 74 | where MODE is one of: 75 | --run (default) runs the file [FILE] 76 | --check type check the file [FILE] 77 | --parse only parse the file [FILE], don't run it 78 | 79 | Not passing any arguments to balloon will start the REPL." 80 | ); 81 | } 82 | 83 | fn main() { 84 | let args: Vec = env::args().collect(); 85 | 86 | match args.len() { 87 | 1 => repl::run_repl(AstWalkInterpreter::new()), 88 | 2 => match args[1].as_str() { 89 | #[cfg(feature = "llvm-backend")] 90 | "--repl-llvm" => repl::run_repl(LLVMInterpreter::new()), 91 | filepath => run_file(filepath, AstWalkInterpreter::new()), 92 | }, 93 | 3 => { 94 | match args[1].as_str() { 95 | "--run" => run_file(&args[2], AstWalkInterpreter::new()), 96 | "--check" => typecheck_file(&args[2]), 97 | "--parse" => if let Some(ast) = parse_file(&args[2]) { 98 | println!("{:#?}", ast); 99 | }, 100 | _ => print_usage(), 101 | }; 102 | } 103 | _ => print_usage(), 104 | }; 105 | } 106 | 107 | fn parse_file(file_name: &str) -> Option> { 108 | match try_parse_file(file_name) { 109 | Err(err) => { 110 | match err { 111 | ProcessingError::ParseError(parse_error) => { 112 | let (parse_error, line_content) = 113 | get_error_and_line_for_file(&parse_error, file_name); 114 | print_parse_error(file_name, &line_content, &parse_error); 115 | } 116 | ProcessingError::IoError(io_error) => { 117 | match io_error.kind() { 118 | io::ErrorKind::NotFound => println!("{}", io_error), 119 | e => println!("An error occurred.\n{:?}", e), 120 | }; 121 | } 122 | } 123 | None 124 | } 125 | Ok(ast) => Some(ast), 126 | } 127 | } 128 | 129 | fn try_parse_file(file_name: &str) -> Result, ProcessingError> { 130 | let mut input_file = File::open(file_name)?; 131 | let mut input = String::new(); 132 | input_file.read_to_string(&mut input)?; 133 | let x = parser::program(&input); 134 | Ok(x?) 135 | } 136 | 137 | fn run_file(file_name: &str, mut machine: T) { 138 | if let Some(ast) = parse_file(file_name) { 139 | let result = machine.run_ast_as_program(&ast); 140 | if let Err(e) = result { 141 | let file_content = read_file(file_name); 142 | let span = offset_span_to_source_span(e.1, &file_content); 143 | print_interpreter_error_for_file(e.0, span, &file_content, file_name); 144 | } 145 | } 146 | } 147 | 148 | fn typecheck_file(file_name: &str) { 149 | if let Some(ast) = parse_file(file_name) { 150 | let mut checker = typechecker::TypeChecker::new(); 151 | checker.check_program(&ast); 152 | let issues = checker.get_issues(); 153 | 154 | if issues.is_empty() { 155 | println!("No problems detected in {}.", file_name); 156 | } else { 157 | let num_issues = issues.len(); 158 | let file_content = read_file(file_name); 159 | for issue in issues { 160 | let span = offset_span_to_source_span(issue.1, &file_content); 161 | print_typechecker_error_for_file(issue.0, span, &file_content, file_name); 162 | println!(""); 163 | } 164 | println!( 165 | "{} {} detected in {}.", 166 | num_issues, 167 | if num_issues > 1 { "issues" } else { "issue" }, 168 | file_name 169 | ); 170 | } 171 | } 172 | } 173 | 174 | fn read_file(file_name: &str) -> String { 175 | let mut buf_reader = io::BufReader::new(File::open(file_name).unwrap()); 176 | let mut file_content = String::new(); 177 | buf_reader.read_to_string(&mut file_content).unwrap(); 178 | file_content 179 | } 180 | -------------------------------------------------------------------------------- /src/operations.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use value::*; 6 | use ast::{BinOp, UnOp}; 7 | use runtime::RuntimeError; 8 | 9 | pub fn unary_minus(a: Value) -> Result { 10 | match a { 11 | Value::Number(x) => Ok(Value::Number(-x)), 12 | x => Err(RuntimeError::UnaryTypeError(UnOp::Neg, x.get_type())), 13 | } 14 | } 15 | 16 | pub fn add(a: Value, b: Value) -> Result { 17 | match (a, b) { 18 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)), 19 | (Value::Tuple(mut a), Value::Tuple(mut b)) => { 20 | a.append(&mut b); 21 | Ok(Value::Tuple(a)) 22 | } 23 | (Value::String(sa), Value::String(sb)) => Ok(Value::String(sa + &sb)), 24 | (Value::String(s), other) => Ok(Value::String(s + &other.to_string())), 25 | (other, Value::String(s)) => Ok(Value::String(other.to_string() + &s)), 26 | (a, b) => Err(RuntimeError::BinaryTypeError( 27 | BinOp::Add, 28 | a.get_type(), 29 | b.get_type(), 30 | )), 31 | } 32 | } 33 | 34 | pub fn subtract(a: Value, b: Value) -> Result { 35 | match (a, b) { 36 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a - b)), 37 | (a, b) => Err(RuntimeError::BinaryTypeError( 38 | BinOp::Sub, 39 | a.get_type(), 40 | b.get_type(), 41 | )), 42 | } 43 | } 44 | 45 | pub fn multiply(a: Value, b: Value) -> Result { 46 | match (a, b) { 47 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a * b)), 48 | (a, b) => Err(RuntimeError::BinaryTypeError( 49 | BinOp::Mul, 50 | a.get_type(), 51 | b.get_type(), 52 | )), 53 | } 54 | } 55 | 56 | pub fn divide(a: Value, b: Value) -> Result { 57 | match (a, b) { 58 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a / b)), 59 | (a, b) => Err(RuntimeError::BinaryTypeError( 60 | BinOp::Div, 61 | a.get_type(), 62 | b.get_type(), 63 | )), 64 | } 65 | } 66 | 67 | pub fn modulo(a: Value, b: Value) -> Result { 68 | match (a, b) { 69 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a.signum() * (a % b))), 70 | (a, b) => Err(RuntimeError::BinaryTypeError( 71 | BinOp::Mod, 72 | a.get_type(), 73 | b.get_type(), 74 | )), 75 | } 76 | } 77 | 78 | pub fn less_than(a: Value, b: Value) -> Result { 79 | match (a, b) { 80 | (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a < b)), 81 | (a, b) => Err(RuntimeError::BinaryTypeError( 82 | BinOp::Lt, 83 | a.get_type(), 84 | b.get_type(), 85 | )), 86 | } 87 | } 88 | 89 | pub fn less_than_or_equal(a: Value, b: Value) -> Result { 90 | match (a, b) { 91 | (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a <= b)), 92 | (a, b) => Err(RuntimeError::BinaryTypeError( 93 | BinOp::Lte, 94 | a.get_type(), 95 | b.get_type(), 96 | )), 97 | } 98 | } 99 | pub fn greater_than(a: Value, b: Value) -> Result { 100 | match (a, b) { 101 | (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a > b)), 102 | (a, b) => Err(RuntimeError::BinaryTypeError( 103 | BinOp::Gt, 104 | a.get_type(), 105 | b.get_type(), 106 | )), 107 | } 108 | } 109 | 110 | pub fn greater_than_or_equal(a: Value, b: Value) -> Result { 111 | match (a, b) { 112 | (Value::Number(a), Value::Number(b)) => Ok(Value::Bool(a >= b)), 113 | (a, b) => Err(RuntimeError::BinaryTypeError( 114 | BinOp::Gte, 115 | a.get_type(), 116 | b.get_type(), 117 | )), 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/repl.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use rustyline::error::ReadlineError; 6 | use rustyline::Editor; 7 | 8 | use runtime::*; 9 | use error::*; 10 | use parser; 11 | use runtime::StmtResult; 12 | 13 | pub fn run_repl(mut machine: T) { 14 | println!("Balloon REPL"); 15 | let mut rl = Editor::<()>::new(); 16 | // let mut machine = Interpreter::new(); 17 | 18 | let file_name = "repl".to_string(); 19 | loop { 20 | let readline = rl.readline("> "); 21 | match readline { 22 | Ok(line) => { 23 | rl.add_history_entry(&line); 24 | let orig_input = String::from(line.trim()); 25 | let mut input = orig_input.clone(); 26 | if !input.ends_with(';') && parser::program(&input).is_err() { 27 | input.push(';'); 28 | } 29 | match parser::program(&input) { 30 | Err(parse_error) => { 31 | print_parse_error(&file_name, &orig_input, &parse_error); 32 | } 33 | Ok(ast) => match machine.run_ast_as_statements(&ast) { 34 | Err(e) => { 35 | let span = offset_span_to_source_span(e.1, &input); 36 | print_interpreter_error_for_file(e.0, span, &input, &file_name); 37 | } 38 | Ok(possible_result) => if let Some(result) = possible_result { 39 | if let StmtResult::Value(v) = result { 40 | println!("{:?}", v); 41 | } 42 | }, 43 | }, 44 | } 45 | } 46 | Err(ReadlineError::Interrupted) => { 47 | println!("CTRL-C"); 48 | break; 49 | } 50 | Err(ReadlineError::Eof) => { 51 | println!("CTRL-D"); 52 | break; 53 | } 54 | Err(err) => { 55 | println!("Error: {:?}", err); 56 | break; 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/runtime.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use ast::*; 6 | use value::*; 7 | use typechecker::Type; 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub enum RuntimeError { 11 | /// When an undeclared identifier is used on the RHS 12 | ReferenceError(String), 13 | /// When an undeclared identifier is assigned to 14 | UndeclaredAssignment(String), 15 | /// When a binary op cannot be performed on the given types 16 | BinaryTypeError(BinOp, Type, Type), 17 | /// When a unary op cannot be performed on the given type 18 | UnaryTypeError(UnOp, Type), 19 | /// When a non-returning function's return value is used 20 | NoneError(Option), 21 | /// When a call is made to a non-function value 22 | CallToNonFunction(Option, Type), 23 | /// When a subscript access obj[i] is made on a non-subscriptable object 24 | SubscriptOnNonSubscriptable(Type), 25 | NonIntegralSubscript(Type), 26 | IndexOutOfBounds(i64), 27 | /// When the number of arguments don't match 28 | ArgumentLength(Option), 29 | /// When nothing else suits 30 | GeneralRuntimeError(String), 31 | /// When a runtime error occurs inside a function call 32 | /// and is getting propagated as a plain RuntimeError 33 | InsideFunctionCall(Box), 34 | BreakOutsideLoop, 35 | ContinueOutsideLoop, 36 | ReturnOutsideFunction, 37 | } 38 | 39 | pub type RuntimeErrorWithPosition = (RuntimeError, OffsetSpan); 40 | 41 | #[derive(Debug, PartialEq, Clone)] 42 | pub enum StmtResult { 43 | None, 44 | Break, 45 | Continue, 46 | Value(Value), 47 | Return(Option), 48 | } 49 | 50 | impl StmtResult { 51 | // When iterating through a sequence of statements in a block, does this StmtResult 52 | // mean the current iteration should end? 53 | pub fn is_block_terminating(&self) -> bool { 54 | match *self { 55 | StmtResult::None | StmtResult::Value(_) => false, 56 | StmtResult::Break | StmtResult::Continue | StmtResult::Return(_) => true, 57 | } 58 | } 59 | } 60 | 61 | pub trait Interpreter { 62 | fn run_ast_as_statements( 63 | &mut self, 64 | statements: &[StmtNode], 65 | ) -> Result, RuntimeErrorWithPosition>; 66 | fn run_ast_as_program( 67 | &mut self, 68 | program: &[StmtNode], 69 | ) -> Result, RuntimeErrorWithPosition>; 70 | } 71 | -------------------------------------------------------------------------------- /src/typechecker.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::collections::BTreeMap; 6 | use std::fmt; 7 | use std::rc::Rc; 8 | use std::cell::RefCell; 9 | use std::iter::Iterator; 10 | 11 | use linear_map::LinearMap; 12 | 13 | use ast::*; 14 | use ast; 15 | use runtime::RuntimeError; 16 | use function::*; 17 | 18 | #[derive(Clone, Debug)] 19 | pub enum FunctionType { 20 | NativeVoid(CallSign), 21 | NativeReturning(CallSign), 22 | User { 23 | call_sign: CallSign, 24 | param_names: Vec, 25 | body: Box, 26 | env: Rc>, 27 | already_checked_param_types: LinearMap, ()>, 28 | }, 29 | } 30 | 31 | impl FunctionType { 32 | pub fn get_call_sign(&self) -> CallSign { 33 | match *self { 34 | FunctionType::NativeVoid(ref call_sign) | 35 | FunctionType::NativeReturning(ref call_sign) | 36 | FunctionType::User { ref call_sign, .. } => call_sign.clone(), 37 | } 38 | } 39 | } 40 | 41 | #[derive(Clone, Debug)] 42 | pub enum Type { 43 | Number, 44 | Bool, 45 | Any, 46 | Function(Box>), 47 | Tuple, 48 | String, 49 | } 50 | 51 | impl PartialEq for Type { 52 | fn eq(&self, other: &Type) -> bool { 53 | match (self, other) { 54 | (&Type::Number, &Type::Number) | 55 | (&Type::Bool, &Type::Bool) | 56 | (&Type::Function(_), &Type::Function(_)) | // TODO 57 | (&Type::String, &Type::String) | 58 | (&Type::Tuple, &Type::Tuple) | 59 | (&Type::Any, &Type::Any) => true, 60 | _ => false, 61 | } 62 | } 63 | } 64 | 65 | impl Type { 66 | fn is_compatible_with(&self, other: &Type) -> bool { 67 | match (self, other) { 68 | (&Type::Number, &Type::Number) | 69 | (&Type::Bool, &Type::Bool) | 70 | (&Type::Function(_), &Type::Function(_)) | // TODO 71 | (&Type::String, &Type::String) | 72 | (&Type::Tuple, &Type::Tuple) | 73 | (&Type::Any, _) | 74 | (_, &Type::Any) => true, 75 | _ => false, 76 | } 77 | } 78 | } 79 | 80 | impl Eq for Type {} 81 | 82 | impl From for Type { 83 | fn from(from: ast::Literal) -> Self { 84 | match from { 85 | ast::Literal::Integer(_) | ast::Literal::Float(_) => Type::Number, 86 | ast::Literal::Bool(_) => Type::Bool, 87 | ast::Literal::String(_) => Type::String, 88 | } 89 | } 90 | } 91 | 92 | impl fmt::Display for Type { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | match *self { 95 | Type::Number => write!(f, "Number"), 96 | Type::Bool => write!(f, "Bool"), 97 | Type::Any => write!(f, "Any"), 98 | Type::Function(_) => write!(f, "Function"), 99 | Type::String => write!(f, "String"), 100 | Type::Tuple => write!(f, "Tuple"), 101 | } 102 | } 103 | } 104 | 105 | #[derive(Clone)] 106 | struct Context { 107 | pub in_loop: bool, 108 | pub in_func: bool, 109 | // Some(None) represents a non-returning function 110 | pub func_ret_type: Option>, 111 | } 112 | 113 | impl Context { 114 | fn root() -> Context { 115 | Context { 116 | in_loop: false, 117 | in_func: false, 118 | func_ret_type: None, 119 | } 120 | } 121 | } 122 | 123 | #[derive(Debug, PartialEq, Clone)] 124 | pub enum TypeCheckerIssue { 125 | RuntimeError(RuntimeError), 126 | MultipleTypesFromBranchWarning(String), 127 | InsideFunctionCall(Box), 128 | FunctionReturnsMultipleTypes, 129 | PossibleNoneError(Option), 130 | UnreachableCodeAfterReturn, 131 | } 132 | 133 | pub type TypeCheckerIssueWithPosition = (TypeCheckerIssue, OffsetSpan); 134 | 135 | impl From for TypeCheckerIssue { 136 | fn from(from: RuntimeError) -> Self { 137 | TypeCheckerIssue::RuntimeError(from) 138 | } 139 | } 140 | 141 | #[derive(Clone)] 142 | pub struct TypeEnvironment { 143 | pub symbol_table: BTreeMap, 144 | parent: Option>>, 145 | } 146 | 147 | impl fmt::Debug for TypeEnvironment { 148 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 149 | write!(f, "{:?}", self.symbol_table) 150 | } 151 | } 152 | 153 | impl TypeEnvironment { 154 | pub fn new_root() -> Rc> { 155 | let mut env = TypeEnvironment::new(); 156 | let builtin_functions = &[ 157 | ( 158 | "println", 159 | FunctionType::NativeVoid(CallSign { 160 | num_params: 0, 161 | variadic: true, 162 | }), 163 | ), 164 | ( 165 | "assert", 166 | FunctionType::NativeVoid(CallSign { 167 | num_params: 1, 168 | variadic: false, 169 | }), 170 | ), 171 | ( 172 | "assert_eq", 173 | FunctionType::NativeVoid(CallSign { 174 | num_params: 2, 175 | variadic: false, 176 | }), 177 | ), 178 | ( 179 | "len", 180 | FunctionType::NativeReturning(CallSign { 181 | num_params: 1, 182 | variadic: false, 183 | }), 184 | ), 185 | ( 186 | "run_http_server", 187 | FunctionType::NativeVoid(CallSign { 188 | num_params: 1, 189 | variadic: false, 190 | }), 191 | ), 192 | ]; 193 | for item in builtin_functions.iter() { 194 | let (name, ref func) = *item; 195 | env.declare( 196 | &name.to_string(), 197 | &Type::Function(Box::new(Some(func.clone()))), 198 | ); 199 | } 200 | Rc::new(RefCell::new(env)) 201 | } 202 | 203 | pub fn new() -> TypeEnvironment { 204 | TypeEnvironment { 205 | symbol_table: BTreeMap::new(), 206 | parent: None, 207 | } 208 | } 209 | 210 | pub fn create_clone(env: Rc>) -> Rc> { 211 | let cloned_env = env.borrow().clone(); 212 | Rc::new(RefCell::new(cloned_env)) 213 | } 214 | 215 | pub fn create_child(parent: Rc>) -> Rc> { 216 | let env = TypeEnvironment { 217 | parent: Some(parent), 218 | symbol_table: BTreeMap::default(), 219 | }; 220 | Rc::new(RefCell::new(env)) 221 | } 222 | 223 | pub fn declare(&mut self, id: &str, typ: &Type) { 224 | self.symbol_table.insert(id.to_owned(), typ.clone()); 225 | } 226 | 227 | pub fn set(&mut self, identifier: &str, typ: Type) -> bool { 228 | if self.symbol_table.contains_key(identifier) { 229 | self.symbol_table.insert(identifier.to_owned(), typ); 230 | true 231 | } else { 232 | match self.parent { 233 | Some(ref parent) => parent.borrow_mut().set(identifier, typ), 234 | None => false, 235 | } 236 | } 237 | } 238 | 239 | pub fn get_type(&self, identifier: &str) -> Option { 240 | if let Some(typ) = self.symbol_table.get(identifier) { 241 | return Some(typ.clone()); 242 | } else { 243 | match self.parent { 244 | Some(ref parent) => parent.borrow().get_type(identifier), 245 | None => None, 246 | } 247 | } 248 | } 249 | 250 | pub fn get_all_pairs(&self) -> Vec<(String, Type)> { 251 | let mut pairs = Vec::new(); 252 | for (key, value) in &self.symbol_table { 253 | pairs.push((key.clone(), value.clone())); 254 | } 255 | if let Some(ref parent) = self.parent { 256 | pairs.append(&mut parent.borrow().get_all_pairs()); 257 | } 258 | pairs 259 | } 260 | } 261 | 262 | #[derive(Debug)] 263 | pub enum StmtEffect { 264 | None, 265 | Return, 266 | } 267 | 268 | pub struct TypeChecker { 269 | context: Context, 270 | issues: Vec, 271 | env: Rc>, 272 | } 273 | 274 | impl TypeChecker { 275 | pub fn new() -> TypeChecker { 276 | TypeChecker { 277 | context: Context::root(), 278 | issues: Vec::new(), 279 | env: TypeEnvironment::new_root(), 280 | } 281 | } 282 | 283 | pub fn get_issues(&self) -> Vec { 284 | self.issues.clone() 285 | } 286 | 287 | pub fn check_program(&mut self, ast: &[StmtNode]) { 288 | self.check_statements(ast); 289 | } 290 | 291 | pub fn check_statements(&mut self, ast: &[StmtNode]) { 292 | for statement in ast.iter() { 293 | self.check_statement(statement); 294 | } 295 | } 296 | 297 | pub fn check_statement(&mut self, s: &StmtNode) -> StmtEffect { 298 | match s.data { 299 | Stmt::VarDecl(ref variable, ref expr) => { 300 | self.check_statement_variable_declaration(variable, expr); 301 | StmtEffect::None 302 | } 303 | Stmt::Assign(ref lhs_expr, ref expr) => { 304 | self.check_statement_assignment(lhs_expr, expr); 305 | StmtEffect::None 306 | } 307 | Stmt::AssignOp(ref lhs_expr, ref op, ref expr) => { 308 | self.check_statement_assignment_with_op(lhs_expr, op, expr, s); 309 | StmtEffect::None 310 | } 311 | Stmt::Block(ref statements) => { 312 | let current_env = self.env.clone(); 313 | self.env = TypeEnvironment::create_child(current_env.clone()); 314 | let mut last_effect = StmtEffect::None; 315 | for stmt in statements.iter() { 316 | if let StmtEffect::Return = last_effect { 317 | self.issues.push(( 318 | TypeCheckerIssue::UnreachableCodeAfterReturn, 319 | (stmt.pos.0, statements.last().unwrap().pos.1), 320 | )); 321 | // unreachable code, stop checking 322 | break; 323 | } 324 | last_effect = self.check_statement(stmt); 325 | } 326 | self.env = current_env; 327 | last_effect 328 | } 329 | Stmt::Expr(ref expr) => { 330 | self.check_expr(expr); 331 | StmtEffect::None 332 | } 333 | Stmt::IfThen(ref if_then_stmt) => self.check_statement_if_then_else(s, if_then_stmt), 334 | Stmt::Loop(ref block) => { 335 | let old_in_loop_value = self.context.in_loop; 336 | self.context.in_loop = true; 337 | let effect = self.check_statement(block); 338 | self.context.in_loop = old_in_loop_value; 339 | effect 340 | } 341 | Stmt::Break => { 342 | if self.context.in_loop != true { 343 | self.issues 344 | .push((RuntimeError::BreakOutsideLoop.into(), s.pos)); 345 | } 346 | StmtEffect::None 347 | } 348 | Stmt::Continue => { 349 | if self.context.in_loop != true { 350 | self.issues 351 | .push((RuntimeError::ContinueOutsideLoop.into(), s.pos)); 352 | } 353 | StmtEffect::None 354 | } 355 | Stmt::Empty => StmtEffect::None, 356 | Stmt::Return(ref possible_expr) => self.check_statement_return(possible_expr, s), 357 | } 358 | } 359 | 360 | fn check_expr(&mut self, expr: &ExprNode) -> Option { 361 | match expr.data { 362 | Expr::Literal(ref x) => Some(Type::from(x.data.clone())), 363 | Expr::Identifier(ref id) => match self.env.borrow().get_type(id) { 364 | Some(t) => Some(t), 365 | None => { 366 | self.issues 367 | .push((RuntimeError::ReferenceError(id.clone()).into(), expr.pos)); 368 | Some(Type::Any) 369 | } 370 | }, 371 | Expr::Tuple(ref elems) => Some(self.check_expr_tuple(elems)), 372 | Expr::Unary(ref op, ref expr) => Some(self.check_expr_unary_op(op, expr)), 373 | Expr::UnaryLogical(ref op, ref expr) => { 374 | Some(self.check_expr_unary_logical_op(op, expr)) 375 | } 376 | Expr::Binary(ref expr1, ref op, ref expr2) => { 377 | Some(self.check_expr_binary_expr(expr, expr1, op, expr2)) 378 | } 379 | Expr::BinaryLogical(ref expr1, ref op, ref expr2) => { 380 | Some(self.check_expr_binary_logical_expr(expr1, op, expr2)) 381 | } 382 | Expr::FnCall(ref f_expr, ref args) => self.check_expr_function_call(expr, f_expr, args), 383 | Expr::FnDef(ref fn_def_expr) => Some(self.check_expr_function_definition(fn_def_expr)), 384 | Expr::MemberByIdx(ref expr, ref index_expr) => { 385 | Some(self.check_expr_member_access_by_index(expr, index_expr)) 386 | } 387 | } 388 | } 389 | 390 | 391 | fn check_statement_variable_declaration(&mut self, variable: &Variable, expr: &ExprNode) { 392 | let checked_type = self.check_expr_as_value(expr); 393 | match *variable { 394 | Variable::Identifier(_, ref id) => { 395 | self.env.borrow_mut().declare(id, &checked_type); 396 | } 397 | }; 398 | } 399 | 400 | fn check_statement_assignment(&mut self, lhs_expr: &LhsExprNode, expr: &ExprNode) { 401 | let checked_type = self.check_expr_as_value(expr); 402 | match lhs_expr.data { 403 | LhsExpr::Identifier(ref id) => if !self.env.borrow_mut().set(id, checked_type) { 404 | self.issues.push(( 405 | RuntimeError::UndeclaredAssignment(id.clone()).into(), 406 | lhs_expr.pos, 407 | )); 408 | }, 409 | }; 410 | } 411 | 412 | fn check_statement_assignment_with_op( 413 | &mut self, 414 | lhs_expr: &LhsExprNode, 415 | op: &BinOp, 416 | expr: &ExprNode, 417 | stmt: &StmtNode, 418 | ) { 419 | let checked_type = self.check_expr_as_value(expr); 420 | match lhs_expr.data { 421 | LhsExpr::Identifier(ref id) => { 422 | let prev_type = match self.env.borrow_mut().get_type(id) { 423 | Some(t) => t, 424 | None => { 425 | self.issues.push(( 426 | RuntimeError::ReferenceError(id.to_owned()).into(), 427 | lhs_expr.pos, 428 | )); 429 | Type::Any 430 | } 431 | }; 432 | let retval = match *op { 433 | BinOp::Add => check_add_for_types(&prev_type, &checked_type), 434 | ref op @ BinOp::Sub | 435 | ref op @ BinOp::Mul | 436 | ref op @ BinOp::Div | 437 | ref op @ BinOp::Mod => { 438 | check_binary_arithmetic_for_types(op.clone(), &prev_type, &checked_type) 439 | } 440 | _ => unreachable!(), 441 | }; 442 | let new_type = match retval { 443 | Ok(t) => t, 444 | Err(issue) => { 445 | self.issues.push((issue, stmt.pos)); 446 | Type::Any 447 | } 448 | }; 449 | 450 | // if id does not exist, then error was reported above 451 | self.env.borrow_mut().set(id, new_type); 452 | } 453 | }; 454 | } 455 | 456 | fn check_statement_if_then_else( 457 | &mut self, 458 | statement: &StmtNode, 459 | if_then_stmt: &IfThenStmt, 460 | ) -> StmtEffect { 461 | let &IfThenStmt { 462 | ref cond, 463 | ref then_block, 464 | ref maybe_else_block, 465 | } = if_then_stmt; 466 | 467 | let else_block = match *maybe_else_block { 468 | None => { 469 | StmtNode { 470 | data: Stmt::Block(vec![]), 471 | pos: (0, 0), // dummy span 472 | } 473 | } 474 | Some(ref block) => *block.clone(), 475 | }; 476 | 477 | self.check_expr_as_value(cond); 478 | 479 | let current_env = self.env.clone(); 480 | let then_env = TypeEnvironment::create_clone(current_env.clone()); 481 | let else_env = TypeEnvironment::create_clone(current_env.clone()); 482 | 483 | self.env = then_env; 484 | let then_effect = self.check_statement(then_block); 485 | 486 | let then_pairs = self.env.borrow().get_all_pairs(); 487 | 488 | self.env = else_env; 489 | let else_effect = self.check_statement(&else_block); 490 | 491 | let else_pairs = self.env.borrow().get_all_pairs(); 492 | 493 | self.env = current_env; 494 | 495 | for (then_pair, else_pair) in then_pairs.iter().zip(else_pairs.iter()) { 496 | let &(ref then_name, ref then_type) = then_pair; 497 | let &(ref else_name, ref else_type) = else_pair; 498 | if then_name != else_name { 499 | panic!("Unexpected behaviour when iterating through environments!"); 500 | } 501 | if !else_type.is_compatible_with(then_type) { 502 | self.issues.push(( 503 | TypeCheckerIssue::MultipleTypesFromBranchWarning(then_name.clone()), 504 | statement.pos, 505 | )); 506 | self.env.borrow_mut().set(then_name, Type::Any); 507 | } else { 508 | self.env.borrow_mut().set(then_name, then_type.clone()); 509 | } 510 | } 511 | 512 | if let (StmtEffect::Return, StmtEffect::Return) = (then_effect, else_effect) { 513 | StmtEffect::Return 514 | } else { 515 | StmtEffect::None 516 | } 517 | } 518 | 519 | fn check_statement_return( 520 | &mut self, 521 | possible_expr: &Option, 522 | return_statement: &StmtNode, 523 | ) -> StmtEffect { 524 | if self.context.in_func != true { 525 | self.issues.push(( 526 | RuntimeError::ReturnOutsideFunction.into(), 527 | return_statement.pos, 528 | )); 529 | // if the return is outside a function, don't typecheck anything else wrt the return 530 | return StmtEffect::None; 531 | } 532 | match *possible_expr { 533 | // represents `return foo;` 534 | Some(ref expr) => { 535 | let actual_type = self.check_expr_as_value(expr); 536 | self.context.func_ret_type = match self.context.func_ret_type { 537 | None => Some(Some(actual_type)), 538 | Some(ref maybe_type /* : Option */) => { 539 | match *maybe_type { 540 | // Some(None), non-returning 541 | None => { 542 | // If the function didn't return a value at some point, 543 | // then that's the case we'll stick with because the function 544 | // does not *always* return a value. 545 | 546 | // But we will complain that this time it did return a value. 547 | self.issues.push(( 548 | TypeCheckerIssue::FunctionReturnsMultipleTypes, 549 | return_statement.pos, 550 | )); 551 | None 552 | } 553 | // Some(Some(typ)), returning typ 554 | Some(ref typ) => if !actual_type.is_compatible_with(typ) { 555 | self.issues.push(( 556 | TypeCheckerIssue::FunctionReturnsMultipleTypes, 557 | return_statement.pos, 558 | )); 559 | Some(Some(Type::Any)) 560 | } else { 561 | Some(Some(typ.clone())) 562 | }, 563 | } 564 | } 565 | }; 566 | StmtEffect::Return 567 | } 568 | // represents `return;` 569 | None => { 570 | // If the function did return a value previously, then it is returning 571 | // "multiple types". 572 | self.issues.push(( 573 | TypeCheckerIssue::FunctionReturnsMultipleTypes, 574 | return_statement.pos, 575 | )); 576 | 577 | // No matter what the value was before, if it is ever non-returning, 578 | // we have to remember that 579 | self.context.func_ret_type = Some(None); 580 | StmtEffect::Return 581 | } 582 | } 583 | } 584 | 585 | fn check_expr_tuple(&mut self, elems: &[ExprNode]) -> Type { 586 | for elem_expr in elems { 587 | self.check_expr_as_value(elem_expr); 588 | } 589 | Type::Tuple 590 | } 591 | 592 | fn check_expr_unary_op(&mut self, op: &UnOp, expr: &ExprNode) -> Type { 593 | let typ = self.check_expr_as_value(expr); 594 | match *op { 595 | UnOp::Neg => match check_unary_minus_for_type(typ) { 596 | Ok(t) => t, 597 | Err(e) => { 598 | self.issues.push((e, expr.pos)); 599 | Type::Any 600 | } 601 | }, 602 | } 603 | } 604 | 605 | fn check_expr_unary_logical_op(&mut self, op: &LogicalUnOp, expr: &ExprNode) -> Type { 606 | self.check_expr_as_value(expr); 607 | match *op { 608 | LogicalUnOp::Not => Type::Bool, 609 | } 610 | } 611 | 612 | fn check_expr_binary_expr( 613 | &mut self, 614 | binary_expr: &ExprNode, 615 | expr1: &ExprNode, 616 | op: &BinOp, 617 | expr2: &ExprNode, 618 | ) -> Type { 619 | let checked_type_1 = self.check_expr_as_value(expr1); 620 | let checked_type_2 = self.check_expr_as_value(expr2); 621 | use ast::BinOp::*; 622 | let result = match *op { 623 | Add => check_add_for_types(&checked_type_1, &checked_type_2), 624 | ref op @ Sub | ref op @ Mul | ref op @ Div | ref op @ Mod => { 625 | check_binary_arithmetic_for_types(op.clone(), &checked_type_1, &checked_type_2) 626 | } 627 | ref op @ Lt | ref op @ Lte | ref op @ Gt | ref op @ Gte => { 628 | check_binary_comparison_for_types(op.clone(), &checked_type_1, &checked_type_2) 629 | } 630 | Eq => Ok(Type::Bool), 631 | }; 632 | match result { 633 | Err(e) => { 634 | self.issues.push((e, binary_expr.pos)); 635 | Type::Any 636 | } 637 | Ok(t) => t, 638 | } 639 | } 640 | 641 | fn check_expr_binary_logical_expr( 642 | &mut self, 643 | expr1: &ExprNode, 644 | op: &LogicalBinOp, 645 | expr2: &ExprNode, 646 | ) -> Type { 647 | match *op { 648 | LogicalBinOp::And | LogicalBinOp::Or => { 649 | self.check_expr_as_value(expr1); 650 | self.check_expr_as_value(expr2); 651 | } 652 | } 653 | Type::Bool 654 | } 655 | 656 | 657 | fn check_expr_function_call( 658 | &mut self, 659 | expr: &ExprNode, 660 | f_expr: &ExprNode, 661 | args: &[ExprNode], 662 | ) -> Option { 663 | let checked_type = self.check_expr_as_value(f_expr); 664 | 665 | let mut arg_types = Vec::new(); 666 | for arg in args.iter() { 667 | arg_types.push(self.check_expr_as_value(arg)); 668 | } 669 | 670 | let func_type = match checked_type { 671 | Type::Function(possible_func) => match *possible_func { 672 | None => unreachable!(), 673 | Some(func_type) => func_type, 674 | }, 675 | Type::Any => { 676 | // Don't know anything about this type. Allow it to be called 677 | // as func, and then assume the return type is Any. 678 | return Some(Type::Any); 679 | } 680 | v => { 681 | self.issues.push(( 682 | RuntimeError::CallToNonFunction(try_get_name_of_fn(f_expr), v).into(), 683 | expr.pos, 684 | )); 685 | return Some(Type::Any); 686 | } 687 | }; 688 | 689 | let func_call_sign = func_type.get_call_sign(); 690 | if !func_call_sign.variadic && args.len() != func_type.get_call_sign().num_params { 691 | self.issues.push(( 692 | RuntimeError::ArgumentLength(try_get_name_of_fn(f_expr)).into(), 693 | expr.pos, 694 | )); 695 | return Some(Type::Any); 696 | } 697 | match func_type { 698 | FunctionType::NativeVoid(_) => None, 699 | FunctionType::NativeReturning(_) => Some(Type::Any), 700 | FunctionType::User { 701 | ref param_names, 702 | ref body, 703 | ref env, 704 | ref already_checked_param_types, 705 | .. 706 | } => { 707 | let function_env = TypeEnvironment::create_child(self.env.clone()); 708 | for (param, arg) in param_names.iter().zip(arg_types.iter()) { 709 | function_env.borrow_mut().declare(param, arg); 710 | } 711 | let inner_env = TypeEnvironment::create_child(function_env); 712 | 713 | let constraint_types = arg_types.iter().map(|typ| typ.clone().into()).collect(); 714 | if !already_checked_param_types.contains_key(&constraint_types) { 715 | if let Some(id) = try_get_name_of_fn(f_expr) { 716 | let mut new_checked_param_types = already_checked_param_types.clone(); 717 | new_checked_param_types.insert(constraint_types, ()); 718 | let new_func_type = get_function_type_with_updated_already_checked( 719 | &func_type, 720 | new_checked_param_types, 721 | ); 722 | env.borrow_mut() 723 | .set(&id, Type::Function(Box::new(Some(new_func_type)))); 724 | 725 | let old_context = self.context.clone(); 726 | self.context = Context { 727 | in_loop: false, 728 | in_func: true, 729 | func_ret_type: None, 730 | }; 731 | let mut outer_issues = self.issues.clone(); 732 | self.issues = Vec::new(); 733 | let current_env = self.env.clone(); 734 | self.env = inner_env; 735 | let fn_body_effect = self.check_statement(body); 736 | self.env = current_env; 737 | for inner_issue in &self.issues { 738 | outer_issues.push(( 739 | TypeCheckerIssue::InsideFunctionCall(Box::new(inner_issue.clone())), 740 | expr.pos, 741 | )); 742 | } 743 | self.issues = outer_issues; 744 | let ret_type; 745 | if let StmtEffect::None = fn_body_effect { 746 | self.context = old_context; 747 | None 748 | } else { 749 | match self.context.func_ret_type { 750 | // non-returning 751 | None | Some(None) => { 752 | ret_type = None; 753 | } 754 | Some(ref typ) => { 755 | ret_type = typ.clone(); 756 | } 757 | } 758 | 759 | self.context = old_context; 760 | ret_type 761 | } 762 | } else { 763 | // TODO 764 | // If the function is anonymous, no typechecking is performed, 765 | // but probably could and should be. 766 | Some(Type::Any) 767 | } 768 | } else { 769 | // TODO 770 | // If it's been previously checked, remember that and 771 | // use the return type 772 | Some(Type::Any) 773 | } 774 | } 775 | } 776 | } 777 | 778 | fn check_expr_function_definition(&mut self, fn_def_expr: &FnDefExpr) -> Type { 779 | let &FnDefExpr { 780 | ref maybe_id, 781 | ref params, 782 | ref body, 783 | } = fn_def_expr; 784 | let func = FunctionType::User { 785 | call_sign: CallSign { 786 | num_params: params.len(), 787 | variadic: false, 788 | }, 789 | param_names: params.to_vec(), 790 | body: body.clone(), 791 | env: self.env.clone(), 792 | already_checked_param_types: LinearMap::new(), 793 | }; 794 | let func_type = Type::Function(Box::new(Some(func))); 795 | if let Some(ref id) = *maybe_id { 796 | self.env.borrow_mut().declare(id, &func_type); 797 | } 798 | func_type 799 | } 800 | 801 | fn check_expr_member_access_by_index( 802 | &mut self, 803 | expr: &ExprNode, 804 | index_expr: &ExprNode, 805 | ) -> Type { 806 | let object_type = self.check_expr_as_value(expr); 807 | match object_type { 808 | Type::Tuple | Type::Any => {} 809 | typ => { 810 | self.issues.push(( 811 | RuntimeError::SubscriptOnNonSubscriptable(typ).into(), 812 | expr.pos, 813 | )); 814 | } 815 | }; 816 | let typ = self.check_expr_as_value(index_expr); 817 | match typ { 818 | Type::Number | Type::Any => {} 819 | non_integral_type => { 820 | self.issues.push(( 821 | RuntimeError::NonIntegralSubscript(non_integral_type).into(), 822 | index_expr.pos, 823 | )); 824 | } 825 | }; 826 | Type::Any 827 | } 828 | 829 | fn check_expr_as_value(&mut self, expr: &ExprNode) -> Type { 830 | let possible_type = self.check_expr(expr); 831 | if possible_type.is_none() { 832 | if let Expr::FnCall(ref f_expr, _) = expr.data { 833 | if let Expr::Identifier(ref id) = f_expr.data { 834 | self.issues.push(( 835 | TypeCheckerIssue::PossibleNoneError(Some(id.clone())), 836 | expr.pos, 837 | )); 838 | } else { 839 | self.issues 840 | .push((TypeCheckerIssue::PossibleNoneError(None), expr.pos)); 841 | } 842 | } else { 843 | unreachable!(); 844 | } 845 | Type::Any 846 | } else { 847 | possible_type.clone().unwrap() 848 | } 849 | } 850 | } 851 | 852 | fn check_unary_minus_for_type(typ: Type) -> Result { 853 | match typ { 854 | Type::Number => Ok(Type::Number), 855 | Type::Any => Ok(Type::Any), 856 | _ => Err(RuntimeError::UnaryTypeError(UnOp::Neg, typ).into()), 857 | } 858 | } 859 | 860 | fn check_add_for_types(t1: &Type, t2: &Type) -> Result { 861 | match (t1, t2) { 862 | (&Type::Number, &Type::Number) => Ok(Type::Number), 863 | (&Type::String, _) | (_, &Type::String) => Ok(Type::String), 864 | (&Type::Any, _) | (_, &Type::Any) => Ok(Type::Any), 865 | _ => Err( 866 | RuntimeError::BinaryTypeError(BinOp::Add, t1.clone(), t2.clone()).into(), 867 | ), 868 | } 869 | } 870 | 871 | fn check_binary_arithmetic_for_types( 872 | op: BinOp, 873 | t1: &Type, 874 | t2: &Type, 875 | ) -> Result { 876 | match (t1, t2) { 877 | (&Type::Number, &Type::Number) => Ok(Type::Number), 878 | (&Type::Any, _) | (_, &Type::Any) => Ok(Type::Any), 879 | _ => Err( 880 | RuntimeError::BinaryTypeError(op, t1.clone(), t2.clone()).into(), 881 | ), 882 | } 883 | } 884 | 885 | fn check_binary_comparison_for_types( 886 | op: BinOp, 887 | t1: &Type, 888 | t2: &Type, 889 | ) -> Result { 890 | match (t1, t2) { 891 | (&Type::Number, &Type::Number) => Ok(Type::Bool), 892 | (&Type::Any, _) | (_, &Type::Any) => Ok(Type::Any), 893 | _ => Err( 894 | RuntimeError::BinaryTypeError(op, t1.clone(), t2.clone()).into(), 895 | ), 896 | } 897 | } 898 | 899 | fn try_get_name_of_fn(expr: &ExprNode) -> Option { 900 | if let Expr::Identifier(ref id) = expr.data { 901 | Some(id.to_string()) 902 | } else { 903 | None 904 | } 905 | } 906 | 907 | fn get_function_type_with_updated_already_checked( 908 | old_fn_type: &FunctionType, 909 | new_already_checked: LinearMap, ()>, 910 | ) -> FunctionType { 911 | 912 | if let FunctionType::User { 913 | ref param_names, 914 | ref body, 915 | ref env, 916 | ref call_sign, 917 | .. 918 | } = *old_fn_type 919 | { 920 | FunctionType::User { 921 | param_names: param_names.clone(), 922 | body: body.clone(), 923 | env: env.clone(), 924 | already_checked_param_types: new_already_checked, 925 | call_sign: call_sign.clone(), 926 | } 927 | } else { 928 | panic!("Not a user function"); 929 | } 930 | } 931 | -------------------------------------------------------------------------------- /src/value.rs: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | use std::fmt; 6 | use std::ops; 7 | use std::cmp; 8 | 9 | use ast; 10 | use typechecker::Type; 11 | use function::*; 12 | 13 | #[derive(Clone)] 14 | pub enum Value { 15 | Number(Number), 16 | Bool(bool), 17 | Function(Box), 18 | String(String), 19 | Tuple(Vec), 20 | } 21 | 22 | #[derive(Debug, Copy, Clone)] 23 | pub enum Number { 24 | Integer(i64), 25 | Float(f64), 26 | } 27 | 28 | impl fmt::Debug for Value { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | match *self { 31 | Value::Bool(b) => write!(f, "{}", b), 32 | Value::Number(n) => write!(f, "{}", n), 33 | Value::Function(_) => write!(f, ""), 34 | Value::String(ref s) => write!(f, "\"{}\"", s), 35 | Value::Tuple(ref t) => if t.is_empty() { 36 | write!(f, "()") 37 | } else { 38 | let mut output = "(".to_owned(); 39 | for elem in &t[0..t.len() - 1] { 40 | output.push_str(&format!("{:?}", elem)); 41 | output.push_str(", "); 42 | } 43 | output.push_str(&format!("{:?}", &t[t.len() - 1])); 44 | output.push_str(")"); 45 | write!(f, "{}", output) 46 | }, 47 | } 48 | } 49 | } 50 | 51 | impl fmt::Display for Value { 52 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 53 | match *self { 54 | Value::String(ref s) => write!(f, "{}", s), 55 | Value::Tuple(ref t) => if t.is_empty() { 56 | write!(f, "()") 57 | } else { 58 | let mut output = "(".to_owned(); 59 | for elem in &t[0..t.len() - 1] { 60 | output.push_str(&format!("{}", elem)); 61 | output.push_str(", "); 62 | } 63 | output.push_str(&format!("{}", &t[t.len() - 1])); 64 | output.push_str(")"); 65 | write!(f, "{}", output) 66 | }, 67 | ref value => write!(f, "{:?}", value), 68 | } 69 | } 70 | } 71 | 72 | impl Number { 73 | pub fn signum(&self) -> Number { 74 | match *self { 75 | Number::Integer(x) => Number::Integer(x.signum()), 76 | Number::Float(x) => Number::Float(x.signum()), 77 | } 78 | } 79 | } 80 | 81 | impl fmt::Display for Number { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | match *self { 84 | Number::Integer(x) => write!(f, "{}", x), 85 | Number::Float(x) => write!(f, "{}", x), 86 | } 87 | } 88 | } 89 | 90 | impl ops::Add for Number { 91 | type Output = Number; 92 | 93 | fn add(self, other: Number) -> Number { 94 | match (self, other) { 95 | (Number::Integer(x), Number::Integer(y)) => Number::Integer(x + y), 96 | (Number::Float(x), Number::Float(y)) => Number::Float(x + y), 97 | (Number::Float(x), Number::Integer(y)) => Number::Float(x + (y as f64)), 98 | (Number::Integer(x), Number::Float(y)) => Number::Float((x as f64) + y), 99 | } 100 | } 101 | } 102 | 103 | impl ops::Sub for Number { 104 | type Output = Number; 105 | 106 | fn sub(self, other: Number) -> Number { 107 | match (self, other) { 108 | (Number::Integer(x), Number::Integer(y)) => Number::Integer(x - y), 109 | (Number::Float(x), Number::Float(y)) => Number::Float(x - y), 110 | (Number::Float(x), Number::Integer(y)) => Number::Float(x - (y as f64)), 111 | (Number::Integer(x), Number::Float(y)) => Number::Float((x as f64) - y), 112 | } 113 | } 114 | } 115 | 116 | impl ops::Mul for Number { 117 | type Output = Number; 118 | 119 | fn mul(self, other: Number) -> Number { 120 | match (self, other) { 121 | (Number::Integer(x), Number::Integer(y)) => Number::Integer(x * y), 122 | (Number::Float(x), Number::Float(y)) => Number::Float(x * y), 123 | (Number::Float(x), Number::Integer(y)) => Number::Float(x * (y as f64)), 124 | (Number::Integer(x), Number::Float(y)) => Number::Float((x as f64) * y), 125 | } 126 | } 127 | } 128 | 129 | impl ops::Div for Number { 130 | type Output = Number; 131 | 132 | fn div(self, other: Number) -> Number { 133 | match (self, other) { 134 | (Number::Integer(x), Number::Integer(y)) => if x % y == 0 { 135 | Number::Integer(x / y) 136 | } else { 137 | Number::Float((x as f64) / (y as f64)) 138 | }, 139 | (Number::Float(x), Number::Float(y)) => Number::Float(x / y), 140 | (Number::Float(x), Number::Integer(y)) => Number::Float(x / (y as f64)), 141 | (Number::Integer(x), Number::Float(y)) => Number::Float((x as f64) / y), 142 | } 143 | } 144 | } 145 | 146 | impl ops::Rem for Number { 147 | type Output = Number; 148 | 149 | fn rem(self, other: Number) -> Number { 150 | match (self, other) { 151 | (Number::Integer(x), Number::Integer(y)) => Number::Integer(x % y), 152 | (Number::Float(x), Number::Float(y)) => Number::Float(x % y), 153 | (Number::Float(x), Number::Integer(y)) => Number::Float(x % (y as f64)), 154 | (Number::Integer(x), Number::Float(y)) => Number::Float((x as f64) % y), 155 | } 156 | } 157 | } 158 | 159 | impl ops::Neg for Number { 160 | type Output = Number; 161 | 162 | fn neg(self) -> Number { 163 | match self { 164 | Number::Integer(x) => Number::Integer(-x), 165 | Number::Float(x) => Number::Float(-x), 166 | } 167 | } 168 | } 169 | 170 | impl PartialEq for Value { 171 | fn eq(&self, other: &Value) -> bool { 172 | match (self, other) { 173 | (&Value::Number(a), &Value::Number(b)) => a == b, 174 | (&Value::Bool(a), &Value::Bool(b)) => a == b, 175 | (&Value::String(ref sa), &Value::String(ref sb)) => sa == sb, 176 | (&Value::Tuple(ref ta), &Value::Tuple(ref tb)) => ta == tb, 177 | _ => false, 178 | } 179 | } 180 | } 181 | 182 | impl PartialEq for Number { 183 | fn eq(&self, other: &Number) -> bool { 184 | match (*self, *other) { 185 | (Number::Integer(x), Number::Integer(y)) => x == y, 186 | (Number::Float(x), Number::Float(y)) => x == y, 187 | (Number::Float(x), Number::Integer(y)) => (x == x.trunc()) && (x as i64) == y, 188 | (Number::Integer(x), Number::Float(y)) => (y == y.trunc()) && (y as i64) == x, 189 | } 190 | } 191 | } 192 | 193 | impl PartialOrd for Number { 194 | fn partial_cmp(&self, other: &Number) -> Option { 195 | match (*self, *other) { 196 | (Number::Integer(x), Number::Integer(y)) => x.partial_cmp(&y), 197 | (Number::Float(x), Number::Float(y)) => x.partial_cmp(&y), 198 | (Number::Float(x), Number::Integer(y)) => x.partial_cmp(&(y as f64)), 199 | (Number::Integer(x), Number::Float(y)) => (x as f64).partial_cmp(&y), 200 | } 201 | } 202 | } 203 | 204 | impl Value { 205 | pub fn get_type(&self) -> Type { 206 | match *self { 207 | Value::Number(_) => Type::Number, 208 | Value::Bool(_) => Type::Bool, 209 | Value::Function(_) => Type::Function(Box::new(None)), 210 | Value::String(_) => Type::String, 211 | Value::Tuple(_) => Type::Tuple, 212 | } 213 | } 214 | 215 | pub fn is_truthy(&self) -> bool { 216 | match *self { 217 | Value::Number(n) => match n { 218 | Number::Integer(i) => i != 0, 219 | Number::Float(f) => f != 0.0, 220 | }, 221 | Value::Bool(b) => b, 222 | Value::String(ref s) => s != "", 223 | Value::Function(_) => true, 224 | Value::Tuple(ref t) => !t.is_empty(), 225 | } 226 | } 227 | } 228 | 229 | impl From for Value { 230 | fn from(from: ast::Literal) -> Self { 231 | match from { 232 | ast::Literal::Integer(x) => Value::Number(Number::Integer(x)), 233 | ast::Literal::Float(x) => Value::Number(Number::Float(x)), 234 | ast::Literal::Bool(x) => Value::Bool(x), 235 | ast::Literal::String(s) => Value::String(s), 236 | } 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /tests/run-fail/arg-len.bl: -------------------------------------------------------------------------------- 1 | fn foo(x) {} 2 | foo(); 3 | -------------------------------------------------------------------------------- /tests/run-fail/arg-len.err: -------------------------------------------------------------------------------- 1 | (ArgumentLength(Some("foo")), (13, 18)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/assert-eq-unequal.bl: -------------------------------------------------------------------------------- 1 | assert_eq(1, 2); 2 | -------------------------------------------------------------------------------- /tests/run-fail/assert-eq-unequal.err: -------------------------------------------------------------------------------- 1 | (GeneralRuntimeError("assert_eq: 1 != 2"), (0, 15)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/assert-false.bl: -------------------------------------------------------------------------------- 1 | assert(false); 2 | -------------------------------------------------------------------------------- /tests/run-fail/assert-false.err: -------------------------------------------------------------------------------- 1 | (GeneralRuntimeError("assert: assertion failed for value false"), (0, 13)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/break-outside-loop.bl: -------------------------------------------------------------------------------- 1 | { 2 | break; 3 | } 4 | -------------------------------------------------------------------------------- /tests/run-fail/break-outside-loop.err: -------------------------------------------------------------------------------- 1 | (BreakOutsideLoop, (6, 12)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/break-return-outside.bl: -------------------------------------------------------------------------------- 1 | fn test() { 2 | break; 3 | } 4 | 5 | return 5; 6 | -------------------------------------------------------------------------------- /tests/run-fail/break-return-outside.err: -------------------------------------------------------------------------------- 1 | (ReturnOutsideFunction, (26, 35)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/call-non-function.bl: -------------------------------------------------------------------------------- 1 | var f = 5; 2 | f(); 3 | -------------------------------------------------------------------------------- /tests/run-fail/call-non-function.err: -------------------------------------------------------------------------------- 1 | (CallToNonFunction(Some("f"), Number), (11, 12)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/continue-outside-loop.bl: -------------------------------------------------------------------------------- 1 | { 2 | continue; 3 | } 4 | -------------------------------------------------------------------------------- /tests/run-fail/continue-outside-loop.err: -------------------------------------------------------------------------------- 1 | (ContinueOutsideLoop, (6, 15)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/index-out-of-bounds.bl: -------------------------------------------------------------------------------- 1 | (1, 2)[10]; 2 | -------------------------------------------------------------------------------- /tests/run-fail/index-out-of-bounds.err: -------------------------------------------------------------------------------- 1 | (IndexOutOfBounds(10), (0, 10)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/non-integral-num-subscript.bl: -------------------------------------------------------------------------------- 1 | (1, 2, 3)[1.1]; 2 | -------------------------------------------------------------------------------- /tests/run-fail/non-integral-num-subscript.err: -------------------------------------------------------------------------------- 1 | (NonIntegralSubscript(Number), (10, 13)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/none-error.bl: -------------------------------------------------------------------------------- 1 | 1 + println(); 2 | -------------------------------------------------------------------------------- /tests/run-fail/none-error.err: -------------------------------------------------------------------------------- 1 | (NoneError(Some("println")), (4, 13)) 2 | -------------------------------------------------------------------------------- /tests/run-fail/subscript-on-non-subcriptable.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | x[1]; 3 | -------------------------------------------------------------------------------- /tests/run-fail/subscript-on-non-subcriptable.err: -------------------------------------------------------------------------------- 1 | (SubscriptOnNonSubscriptable(Number), (11, 12)) 2 | -------------------------------------------------------------------------------- /tests/run-pass/arithmetic.bl: -------------------------------------------------------------------------------- 1 | assert_eq((1 + 3 / (1 + 1)) * 12 - 1, 29.0); 2 | assert_eq(3 - 5 + 12 / 4 - 1, 0.0); 3 | assert_eq(-10 / 4 + -5 - 12, -19.5); 4 | -------------------------------------------------------------------------------- /tests/run-pass/basic-bindings.bl: -------------------------------------------------------------------------------- 1 | var a = 5; 2 | assert_eq(a, 5); 3 | var b = 22.5; 4 | assert_eq(b, 22.5); 5 | var c = true; 6 | assert_eq(c, true); 7 | var d = "Check string."; 8 | assert_eq(d, "Check string."); 9 | var e = ("A", 1, false, 12.109); 10 | assert_eq(e, ("A", 1, false, 12.109)); 11 | -------------------------------------------------------------------------------- /tests/run-pass/block-env.bl: -------------------------------------------------------------------------------- 1 | var x = 3; 2 | var y = 10; 3 | { 4 | var x = 6; 5 | assert_eq(x, 6); 6 | y = 20; 7 | } 8 | assert_eq(x, 3); 9 | assert_eq(y, 20); 10 | -------------------------------------------------------------------------------- /tests/run-pass/bool-cast.bl: -------------------------------------------------------------------------------- 1 | assert(1); 2 | assert(not 0); 3 | assert(1.1); 4 | assert(not 0.0); 5 | assert("Hello!"); 6 | assert(not ""); 7 | assert((1, 2)); 8 | assert(not (,)); 9 | -------------------------------------------------------------------------------- /tests/run-pass/closures.bl: -------------------------------------------------------------------------------- 1 | fn foo(thing) { 2 | fn bar() { 3 | return thing + 1; 4 | } 5 | thing = thing + 1; 6 | var x = bar(); 7 | thing = thing + 1; 8 | var y = bar(); 9 | return (x, y); 10 | } 11 | 12 | assert_eq(foo(5), (7, 8)); 13 | -------------------------------------------------------------------------------- /tests/run-pass/comments.bl: -------------------------------------------------------------------------------- 1 | # comment 2 | fn f() {#comment 3 | var x = 5; # comment 4 | if true { #comment 5 | # comment 6 | x = 20;#comment 7 | } # comment 8 | else { 9 | x = 10; 10 | } # comment 11 | return x; #comment 12 | # comment 13 | } #comment 14 | # comment 15 | assert_eq(f(), 20); # comment 16 | -------------------------------------------------------------------------------- /tests/run-pass/comparisons.bl: -------------------------------------------------------------------------------- 1 | assert(1 < 2); 2 | assert(not 2 < 1); 3 | assert(1.0 < 2.0); 4 | assert(not 2.0 < 1.0); 5 | assert(4 <= 4); 6 | assert(4 >= 4); 7 | assert(5.0 <= 5.0); 8 | assert(5.0 >= 5.0); 9 | assert(7 == 7.0); 10 | assert(not 7 == 7.1); 11 | assert(not 1 == true); 12 | -------------------------------------------------------------------------------- /tests/run-pass/compound-assign.bl: -------------------------------------------------------------------------------- 1 | var s = "Hello"; 2 | s += ", world!"; 3 | assert_eq(s, "Hello, world!"); 4 | 5 | var x = 5; 6 | x += 5; 7 | assert_eq(x, 10); 8 | x -= 5; 9 | assert_eq(x, 5); 10 | x *= 2; 11 | assert_eq(x, 10); 12 | x /= 2; 13 | assert_eq(x, 5); 14 | -------------------------------------------------------------------------------- /tests/run-pass/curried-add.bl: -------------------------------------------------------------------------------- 1 | fn add_x(x) { 2 | return fn(y) { 3 | return x + y; 4 | }; 5 | } 6 | 7 | assert_eq(add_x("Hello,")(" world!"), "Hello, world!"); 8 | -------------------------------------------------------------------------------- /tests/run-pass/empty.bl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/polybuildr/balloon-lang/7580baa303546675c19fc3ef9c1202f179209bce/tests/run-pass/empty.bl -------------------------------------------------------------------------------- /tests/run-pass/fib.bl: -------------------------------------------------------------------------------- 1 | fn fib(n) { 2 | if n < 2 { 3 | return 0; 4 | } 5 | if n == 2 { 6 | return 1; 7 | } 8 | return fib(n - 1) + fib(n - 2); 9 | } 10 | 11 | assert_eq(fib(12), 89); 12 | -------------------------------------------------------------------------------- /tests/run-pass/fn.bl: -------------------------------------------------------------------------------- 1 | fn foo(x) { 2 | return x + 1; 3 | } 4 | 5 | assert_eq(foo(10), 11); 6 | -------------------------------------------------------------------------------- /tests/run-pass/id-in-expr.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | assert_eq(x / 2 + x, 7.5); 3 | -------------------------------------------------------------------------------- /tests/run-pass/identifier-name.bl: -------------------------------------------------------------------------------- 1 | var _x_X123_ = false; 2 | assert_eq(_x_X123_, false); 3 | -------------------------------------------------------------------------------- /tests/run-pass/if-else-if.bl: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | if false { 3 | x = 1; 4 | } else if false { 5 | x = 2; 6 | } else { 7 | x = 3; 8 | } 9 | assert_eq(x, 3); 10 | 11 | var y = 0; 12 | if false { 13 | y = 1; 14 | } else if true { 15 | y = 2; 16 | } else { 17 | y = 3; 18 | } 19 | assert_eq(y, 2); 20 | -------------------------------------------------------------------------------- /tests/run-pass/if-expr.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | if true { 3 | x = 12; 4 | } 5 | assert_eq(x, 12); 6 | if false { 7 | x = 22; 8 | } 9 | assert_eq(x, 12); 10 | if false { 11 | x = 22; 12 | } else { 13 | x = 42; 14 | } 15 | assert_eq(x, 42); 16 | -------------------------------------------------------------------------------- /tests/run-pass/int-div-and-mod.bl: -------------------------------------------------------------------------------- 1 | # will fail if numbers are converted to float 2 | assert_eq(9223372036854775806 / 2, 4611686018427387903); 3 | assert_eq(9223372036854775806 % 9, 6); 4 | -------------------------------------------------------------------------------- /tests/run-pass/int-float-subscript.bl: -------------------------------------------------------------------------------- 1 | assert_eq((1, 2, 3)[1.0], 2); 2 | -------------------------------------------------------------------------------- /tests/run-pass/len.bl: -------------------------------------------------------------------------------- 1 | assert_eq(len((1, 2, 3, true, "!")), 5); 2 | -------------------------------------------------------------------------------- /tests/run-pass/logical_and.bl: -------------------------------------------------------------------------------- 1 | assert_eq(false and false, false); 2 | assert_eq(false and true, false); 3 | assert_eq(true and false, false); 4 | assert_eq(true and true, true); 5 | -------------------------------------------------------------------------------- /tests/run-pass/logical_or.bl: -------------------------------------------------------------------------------- 1 | assert_eq(false or false, false); 2 | assert_eq(false or true, true); 3 | assert_eq(true or false, true); 4 | assert_eq(true or true, true); 5 | -------------------------------------------------------------------------------- /tests/run-pass/loop.bl: -------------------------------------------------------------------------------- 1 | var x = 0; 2 | loop { 3 | if x > 20 { break; } 4 | x = x + 5; 5 | } 6 | assert_eq(x, 25); 7 | -------------------------------------------------------------------------------- /tests/run-pass/mixed-call-and-index.bl: -------------------------------------------------------------------------------- 1 | assert_eq(((fn() { return false; },), 2, 3)[0][0](), false); 2 | assert_eq(fn() { return fn() { return (1, 2, true); }; }()()[2], true); 3 | -------------------------------------------------------------------------------- /tests/run-pass/mutual-recurse.bs: -------------------------------------------------------------------------------- 1 | fn f1(n) { 2 | if n > 0 { 3 | return f2(n - 1); 4 | } 5 | return 0; 6 | } 7 | 8 | fn f2(n) { 9 | if n > 0 { 10 | return f1(n - 1); 11 | } 12 | return -1; 13 | } 14 | 15 | assert_eq(f1(11), -1); 16 | -------------------------------------------------------------------------------- /tests/run-pass/println-variadic.bl: -------------------------------------------------------------------------------- 1 | println(); 2 | println(5); 3 | println(5, 10); 4 | -------------------------------------------------------------------------------- /tests/run-pass/reassign-with-type-change.bl: -------------------------------------------------------------------------------- 1 | var x = 10; 2 | x = false; 3 | assert_eq(x, false); 4 | -------------------------------------------------------------------------------- /tests/run-pass/recursive-factorial.bl: -------------------------------------------------------------------------------- 1 | fn factorial(n) { 2 | if n < 2 { 3 | return 1; 4 | } 5 | return n * factorial(n - 1); 6 | } 7 | 8 | assert_eq(factorial(5), 120); 9 | assert_eq(factorial(15), 1307674368000); 10 | -------------------------------------------------------------------------------- /tests/run-pass/string-concat.bl: -------------------------------------------------------------------------------- 1 | assert_eq("abc" + "def", "abcdef"); 2 | assert_eq("abc" + 123, "abc123"); 3 | assert_eq(123 + "abc", "123abc"); 4 | assert_eq("abc" + true, "abctrue"); 5 | assert_eq(false + "abc", "falseabc"); 6 | -------------------------------------------------------------------------------- /tests/run-pass/tuple-add.bl: -------------------------------------------------------------------------------- 1 | assert_eq((1, 2) + (3, 4), (1, 2, 3, 4)); 2 | -------------------------------------------------------------------------------- /tests/run-pass/tuple-member-access.bl: -------------------------------------------------------------------------------- 1 | assert_eq((1, 2, 3)[0], 1); 2 | -------------------------------------------------------------------------------- /tests/run-pass/value-identity.bl: -------------------------------------------------------------------------------- 1 | assert_eq(2, 2); 2 | assert_eq(-3, -3); 3 | assert_eq(3.14159, 3.14159); 4 | assert_eq(-40.2, -40.2); 5 | assert_eq(true, true); 6 | assert_eq(false, false); 7 | assert_eq("", ""); 8 | assert_eq("Hello!", "Hello!"); 9 | assert_eq((1, 1.1, true, "ping?"), (1, 1.1, true, "ping?")); 10 | -------------------------------------------------------------------------------- /tests/run-pass/y-combinator.bl: -------------------------------------------------------------------------------- 1 | fn Y(f) { 2 | var lazy_wrapper = fn () { return Y(f); }; 3 | return f(lazy_wrapper); 4 | } 5 | 6 | fn factorial_wrap(lazy_wrap_fact) { 7 | fn factorial(i) { 8 | if (i == 0) { 9 | return 1; 10 | } else { 11 | return i * lazy_wrap_fact()(i - 1); 12 | } 13 | } 14 | return factorial; 15 | } 16 | 17 | var fact = Y(factorial_wrap); 18 | 19 | assert_eq(fact(5), 120); 20 | -------------------------------------------------------------------------------- /tests/typecheck-fail/add-type-error.bl: -------------------------------------------------------------------------------- 1 | 1 + true; 2 | true + 1; 3 | false + true; 4 | -------------------------------------------------------------------------------- /tests/typecheck-fail/add-type-error.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(BinaryTypeError(Add, Number, Bool)), (0, 8)), (RuntimeError(BinaryTypeError(Add, Bool, Number)), (10, 18)), (RuntimeError(BinaryTypeError(Add, Bool, Bool)), (20, 32))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/arg-len.bl: -------------------------------------------------------------------------------- 1 | fn foo() {} 2 | foo(1); 3 | foo(1, 2); 4 | 5 | fn bar(x, y) {} 6 | bar(); 7 | bar(1); 8 | bar(1, 2, 3); 9 | -------------------------------------------------------------------------------- /tests/typecheck-fail/arg-len.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(ArgumentLength(Some("foo"))), (12, 18)), (RuntimeError(ArgumentLength(Some("foo"))), (20, 29)), (RuntimeError(ArgumentLength(Some("bar"))), (48, 53)), (RuntimeError(ArgumentLength(Some("bar"))), (55, 61)), (RuntimeError(ArgumentLength(Some("bar"))), (63, 75))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/bool-unary-minus.bl: -------------------------------------------------------------------------------- 1 | -false; 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/bool-unary-minus.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(UnaryTypeError(Neg, Bool)), (1, 6))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/break-return-outside.bl: -------------------------------------------------------------------------------- 1 | fn test() { 2 | break; 3 | } 4 | test(); 5 | return 5; 6 | -------------------------------------------------------------------------------- /tests/typecheck-fail/break-return-outside.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((RuntimeError(BreakOutsideLoop), (16, 22))), (25, 31)), (RuntimeError(ReturnOutsideFunction), (33, 42))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/call-any.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | if true { 3 | x = fn () {}; 4 | } else { 5 | x = 10; 6 | } 7 | 8 | x(); 9 | -------------------------------------------------------------------------------- /tests/typecheck-fail/call-any.err: -------------------------------------------------------------------------------- 1 | [(MultipleTypesFromBranchWarning("x"), (11, 63))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/call-non-function.bl: -------------------------------------------------------------------------------- 1 | var f = 5; 2 | f(); 3 | -------------------------------------------------------------------------------- /tests/typecheck-fail/call-non-function.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(CallToNonFunction(Some("f"), Number)), (11, 14))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/closure-reference-error.bl: -------------------------------------------------------------------------------- 1 | fn f() { 2 | return x; 3 | } 4 | f() + 1; 5 | -------------------------------------------------------------------------------- /tests/typecheck-fail/closure-reference-error.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((RuntimeError(ReferenceError("x")), (20, 21))), (25, 28))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/compound-assign-binary-error.bl: -------------------------------------------------------------------------------- 1 | var x = true; 2 | x += 1; 3 | -------------------------------------------------------------------------------- /tests/typecheck-fail/compound-assign-binary-error.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(BinaryTypeError(Add, Bool, Number)), (14, 21))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/continue-outside-loop.bl: -------------------------------------------------------------------------------- 1 | { 2 | continue; 3 | } 4 | -------------------------------------------------------------------------------- /tests/typecheck-fail/continue-outside-loop.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(ContinueOutsideLoop), (6, 15))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/flow-test.bl: -------------------------------------------------------------------------------- 1 | fn square(n) { 2 | return n * n; 3 | } 4 | 5 | square("2"); 6 | -------------------------------------------------------------------------------- /tests/typecheck-fail/flow-test.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((RuntimeError(BinaryTypeError(Mul, String, String)), (24, 29))), (34, 45))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/fn-returns-multiple-types.bl: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | return 5; 3 | } 4 | 5 | foo() + 1; 6 | 7 | fn bar(x) { 8 | if x { 9 | return 10; 10 | } else { 11 | return false; 12 | } 13 | } 14 | 15 | bar(1) + 1; 16 | 17 | fn foobar(x) { 18 | if x { 19 | return 10; 20 | } 21 | return false; 22 | } 23 | 24 | foobar(10); 25 | -------------------------------------------------------------------------------- /tests/typecheck-fail/fn-returns-multiple-types.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((FunctionReturnsMultipleTypes, (103, 116))), (126, 132)), (InsideFunctionCall((FunctionReturnsMultipleTypes, (194, 207))), (211, 221))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/fn-type-error.bl: -------------------------------------------------------------------------------- 1 | fn add(a, b) { 2 | return a + b; 3 | } 4 | 5 | add(true, true); 6 | add(1, 1); 7 | add(true, 1); 8 | add(false, false); 9 | -------------------------------------------------------------------------------- /tests/typecheck-fail/fn-type-error.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((RuntimeError(BinaryTypeError(Add, Bool, Bool)), (26, 31))), (36, 51)), (InsideFunctionCall((RuntimeError(BinaryTypeError(Add, Bool, Number)), (26, 31))), (64, 76))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/if_rebind_type.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | if true { 3 | x = false; 4 | } 5 | -------------------------------------------------------------------------------- /tests/typecheck-fail/if_rebind_type.err: -------------------------------------------------------------------------------- 1 | [(MultipleTypesFromBranchWarning("x"), (11, 37))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/multiple-types-from-branch.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | if true { 3 | x = true; 4 | } else { 5 | x = 10; 6 | } 7 | var x = 1; 8 | if 1 { 9 | if 2 { 10 | x = true; 11 | } else { 12 | x = 1; 13 | } 14 | } else { 15 | x = 5; 16 | } 17 | -------------------------------------------------------------------------------- /tests/typecheck-fail/multiple-types-from-branch.err: -------------------------------------------------------------------------------- 1 | [(MultipleTypesFromBranchWarning("x"), (11, 58)), (MultipleTypesFromBranchWarning("x"), (80, 139))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/non-integral-subscript.bl: -------------------------------------------------------------------------------- 1 | (1, 2, 3)[true]; 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/non-integral-subscript.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(NonIntegralSubscript(Bool)), (10, 14))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/none-error-for-builtin.bl: -------------------------------------------------------------------------------- 1 | -println(10); 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/none-error-for-builtin.err: -------------------------------------------------------------------------------- 1 | [(PossibleNoneError(Some("println")), (1, 12))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/none-ret.bl: -------------------------------------------------------------------------------- 1 | var x = println(); 2 | println(println()) + println(println(5)); 3 | not println(); 4 | -println(); 5 | -------------------------------------------------------------------------------- /tests/typecheck-fail/none-ret.err: -------------------------------------------------------------------------------- 1 | [(PossibleNoneError(Some("println")), (8, 17)), (PossibleNoneError(Some("println")), (27, 36)), (PossibleNoneError(Some("println")), (19, 37)), (PossibleNoneError(Some("println")), (48, 58)), (PossibleNoneError(Some("println")), (40, 59)), (PossibleNoneError(Some("println")), (65, 74)), (PossibleNoneError(Some("println")), (77, 86))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/not-always-returning.bl: -------------------------------------------------------------------------------- 1 | fn hello_sometimes(x) { 2 | if x { 3 | return "Hello"; 4 | } 5 | } 6 | println(hello_sometimes(true)); 7 | -------------------------------------------------------------------------------- /tests/typecheck-fail/not-always-returning.err: -------------------------------------------------------------------------------- 1 | [(PossibleNoneError(Some("hello_sometimes")), (75, 96))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/reference-err.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | x; 3 | y; 4 | { 5 | var z = 5; 6 | z; 7 | x; 8 | y; 9 | } 10 | z; 11 | -------------------------------------------------------------------------------- /tests/typecheck-fail/reference-err.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(ReferenceError("y")), (14, 15)), (RuntimeError(ReferenceError("y")), (52, 53)), (RuntimeError(ReferenceError("z")), (57, 58))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/subscript-on-non-subcriptable.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | x[1]; 3 | -------------------------------------------------------------------------------- /tests/typecheck-fail/subscript-on-non-subcriptable.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(SubscriptOnNonSubscriptable(Number)), (11, 12))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/undeclared-assignment.bl: -------------------------------------------------------------------------------- 1 | x = 5; 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/undeclared-assignment.err: -------------------------------------------------------------------------------- 1 | [(RuntimeError(UndeclaredAssignment("x")), (0, 1))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-fail/unreachable-code-return.bl: -------------------------------------------------------------------------------- 1 | fn f1() { 2 | { 3 | var x = 1; 4 | return 5; 5 | x = 2; 6 | } 7 | } 8 | f1(); 9 | 10 | fn f2() { 11 | var x = 2; 12 | return 10; 13 | println("After return!"); 14 | } 15 | f2(); 16 | -------------------------------------------------------------------------------- /tests/typecheck-fail/unreachable-code-return.err: -------------------------------------------------------------------------------- 1 | [(InsideFunctionCall((UnreachableCodeAfterReturn, (61, 67))), (76, 80)), (InsideFunctionCall((UnreachableCodeAfterReturn, (127, 152))), (155, 159))] 2 | -------------------------------------------------------------------------------- /tests/typecheck-pass/arithmetic.bl: -------------------------------------------------------------------------------- 1 | (1 + 3 / (1 + 1)) * 12 - 1; 2 | 3 - 5 + 12 / 4 - 1; 3 | -10 / 4 + -5 - 12; 4 | -------------------------------------------------------------------------------- /tests/typecheck-pass/basic-bindings.bl: -------------------------------------------------------------------------------- 1 | var a = 5; 2 | a; 3 | var b = 22.5; 4 | b; 5 | var c = true; 6 | c; 7 | var d = "Check string."; 8 | d; 9 | var e = ("A", 1, false, 12.109); 10 | e; 11 | -------------------------------------------------------------------------------- /tests/typecheck-pass/closure.bl: -------------------------------------------------------------------------------- 1 | var x = 5; 2 | fn f() { 3 | return x; 4 | } 5 | f() + 1; 6 | -------------------------------------------------------------------------------- /tests/typecheck-pass/compound-assign.bl: -------------------------------------------------------------------------------- 1 | var s = "Hello"; 2 | s += ", world!"; 3 | 4 | var x = 5; 5 | x += 5; 6 | x -= 5; 7 | x *= 2; 8 | x /= 2; 9 | -------------------------------------------------------------------------------- /tests/typecheck-pass/fn-arg-match.bl: -------------------------------------------------------------------------------- 1 | fn f0() {} 2 | f0(); 3 | fn f1(x) {} 4 | f1(1); 5 | fn f2(x, y) {} 6 | f2(1, 2); 7 | -------------------------------------------------------------------------------- /tests/typecheck-pass/int-float-subscript.bl: -------------------------------------------------------------------------------- 1 | (1, 2, 3)[1.0]; 2 | -------------------------------------------------------------------------------- /tests/typecheck-pass/misc-binary-ops.bl: -------------------------------------------------------------------------------- 1 | 5 + 5.5 < 10 - 2 and 5; 2 | 1 < 2 and 3 > 2 or false; 3 | 1 < 2 and 3 + 1 or -5 - 1 and "Hello" + "world"; 4 | --------------------------------------------------------------------------------