├── .all-contributorsrc ├── .cargo └── config.toml ├── .gitignore ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── DOCS.md ├── Makefile ├── README.md ├── assets └── code.png ├── examples ├── comeinspinner.aussie ├── dreamtime.aussie ├── fibonacci.aussie ├── fizzbuzz.aussie ├── time.aussie └── upsidedown.aussie ├── gxx_personality_v0_stub.o ├── lib.js ├── rust-toolchain ├── site ├── .eslintignore ├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── components │ ├── Code.tsx │ ├── CodeDropdown.tsx │ ├── Features.tsx │ └── Head.tsx ├── lib │ ├── AussieWorker.ts │ ├── create_aussie_worker.ts │ ├── docs.md │ ├── docs.ts │ ├── example.ts │ └── syntax.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages │ ├── _app.ts │ └── index.tsx ├── postcss.config.js ├── public │ ├── FiraCode-Light.woff │ ├── FiraCode-Light.woff2 │ ├── FiraCode-Regular.woff │ ├── FiraCode-Regular.woff2 │ ├── aussie_plus_plus.js │ ├── aussie_plus_plus.wasm │ └── image.png ├── styles │ └── global.css ├── tailwind.config.js ├── tsconfig.json ├── type │ └── types.ts └── yarn.lock ├── src ├── ast │ ├── conditional.rs │ ├── expression.rs │ ├── function.rs │ ├── loops.rs │ ├── mod.rs │ ├── op.rs │ ├── statement.rs │ └── var.rs ├── lexer │ ├── lexer.rs │ ├── mod.rs │ └── source.rs ├── lib.rs ├── main.rs ├── parser │ ├── error.rs │ ├── mod.rs │ └── parser.rs ├── resolver.rs ├── runtime │ ├── callable │ │ ├── builtin.rs │ │ ├── callable.rs │ │ ├── function.rs │ │ └── mod.rs │ ├── environment.rs │ ├── eq.rs │ ├── error.rs │ ├── exit.rs │ ├── interpreter.rs │ ├── mod.rs │ └── value.rs ├── token.rs └── upside_down.rs └── tests ├── interpreter_test.rs ├── lexer_test.rs └── parser_test.rs /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "jwfxpr", 10 | "name": "jwfxpr", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/20788820?v=4", 12 | "profile": "https://github.com/jwfxpr", 13 | "contributions": [ 14 | "code", 15 | "example", 16 | "doc" 17 | ] 18 | }, 19 | { 20 | "login": "bbrk24", 21 | "name": "bbrk24", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/25109429?v=4", 23 | "profile": "https://github.com/bbrk24", 24 | "contributions": [ 25 | "ideas", 26 | "bug" 27 | ] 28 | } 29 | ], 30 | "contributorsPerLine": 7, 31 | "projectName": "aussieplusplus", 32 | "projectOwner": "zackradisic", 33 | "repoType": "github", 34 | "repoHost": "https://github.com", 35 | "skipCi": true 36 | } 37 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.wasm32-unknown-emscripten] 2 | rustflags = ["-C", "link-args=gxx_personality_v0_stub.o", "-C", "link-args=--js-library lib.js -O3 -s MODULARIZE=1 -s EXPORT_NAME=aussiepp -s -s EXPORTED_RUNTIME_METHODS=[FS,intArrayFromString,writeArrayToMemory,_malloc,UTF8ToString]"] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | OI, FUCKWIT! SO YA WANNA CONTRIBUTE TO THE PROJECT? LISTEN UP! 3 | 4 | ## Style 5 | All commit messages must be Aussie: 6 | 7 | ❌ CARN `fixed a bug` 8 | 9 | ✅ FAIR DINKUM `FIXED A FUCKUP!` 10 | 11 | ## Development workflow 12 | Fork the repo and make your changes. Make sure your code compiles and passes the tests: 13 | ```bash 14 | # Run tests 15 | cargo test 16 | 17 | # Build executable 18 | cargo build 19 | 20 | # Build wasm for site 21 | make wasm 22 | ``` 23 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.7.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 10 | dependencies = [ 11 | "getrandom", 12 | "once_cell", 13 | "version_check", 14 | ] 15 | 16 | [[package]] 17 | name = "ansi_term" 18 | version = "0.11.0" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 21 | dependencies = [ 22 | "winapi", 23 | ] 24 | 25 | [[package]] 26 | name = "anyhow" 27 | version = "1.0.44" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1" 30 | 31 | [[package]] 32 | name = "arrayvec" 33 | version = "0.7.1" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd" 36 | 37 | [[package]] 38 | name = "atty" 39 | version = "0.2.14" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 42 | dependencies = [ 43 | "hermit-abi", 44 | "libc", 45 | "winapi", 46 | ] 47 | 48 | [[package]] 49 | name = "aussie_plus_plus" 50 | version = "0.1.0" 51 | dependencies = [ 52 | "ahash", 53 | "anyhow", 54 | "arrayvec", 55 | "chrono", 56 | "chrono-tz", 57 | "itertools", 58 | "rand", 59 | "structopt", 60 | "thiserror", 61 | ] 62 | 63 | [[package]] 64 | name = "autocfg" 65 | version = "1.0.1" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 68 | 69 | [[package]] 70 | name = "bitflags" 71 | version = "1.3.2" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 74 | 75 | [[package]] 76 | name = "cfg-if" 77 | version = "1.0.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 80 | 81 | [[package]] 82 | name = "chrono" 83 | version = "0.4.19" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 86 | dependencies = [ 87 | "libc", 88 | "num-integer", 89 | "num-traits", 90 | "time", 91 | "winapi", 92 | ] 93 | 94 | [[package]] 95 | name = "chrono-tz" 96 | version = "0.6.0" 97 | source = "registry+https://github.com/rust-lang/crates.io-index" 98 | checksum = "64c01c1c607d25c71bbaa67c113d6c6b36c434744b4fd66691d711b5b1bc0c8b" 99 | dependencies = [ 100 | "chrono", 101 | "chrono-tz-build", 102 | "phf", 103 | ] 104 | 105 | [[package]] 106 | name = "chrono-tz-build" 107 | version = "0.0.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "db058d493fb2f65f41861bfed7e3fe6335264a9f0f92710cab5bdf01fef09069" 110 | dependencies = [ 111 | "parse-zoneinfo", 112 | "phf", 113 | "phf_codegen", 114 | ] 115 | 116 | [[package]] 117 | name = "clap" 118 | version = "2.33.3" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" 121 | dependencies = [ 122 | "ansi_term", 123 | "atty", 124 | "bitflags", 125 | "strsim", 126 | "textwrap", 127 | "unicode-width", 128 | "vec_map", 129 | ] 130 | 131 | [[package]] 132 | name = "either" 133 | version = "1.6.1" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 136 | 137 | [[package]] 138 | name = "getrandom" 139 | version = "0.2.3" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" 142 | dependencies = [ 143 | "cfg-if", 144 | "libc", 145 | "wasi", 146 | ] 147 | 148 | [[package]] 149 | name = "heck" 150 | version = "0.3.3" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" 153 | dependencies = [ 154 | "unicode-segmentation", 155 | ] 156 | 157 | [[package]] 158 | name = "hermit-abi" 159 | version = "0.1.19" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 162 | dependencies = [ 163 | "libc", 164 | ] 165 | 166 | [[package]] 167 | name = "itertools" 168 | version = "0.10.1" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 171 | dependencies = [ 172 | "either", 173 | ] 174 | 175 | [[package]] 176 | name = "lazy_static" 177 | version = "1.4.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 180 | 181 | [[package]] 182 | name = "libc" 183 | version = "0.2.103" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 186 | 187 | [[package]] 188 | name = "num-integer" 189 | version = "0.1.44" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 192 | dependencies = [ 193 | "autocfg", 194 | "num-traits", 195 | ] 196 | 197 | [[package]] 198 | name = "num-traits" 199 | version = "0.2.14" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 202 | dependencies = [ 203 | "autocfg", 204 | ] 205 | 206 | [[package]] 207 | name = "once_cell" 208 | version = "1.8.0" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" 211 | 212 | [[package]] 213 | name = "parse-zoneinfo" 214 | version = "0.3.0" 215 | source = "registry+https://github.com/rust-lang/crates.io-index" 216 | checksum = "c705f256449c60da65e11ff6626e0c16a0a0b96aaa348de61376b249bc340f41" 217 | dependencies = [ 218 | "regex", 219 | ] 220 | 221 | [[package]] 222 | name = "phf" 223 | version = "0.10.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "b9fc3db1018c4b59d7d582a739436478b6035138b6aecbce989fc91c3e98409f" 226 | dependencies = [ 227 | "phf_shared", 228 | ] 229 | 230 | [[package]] 231 | name = "phf_codegen" 232 | version = "0.10.0" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" 235 | dependencies = [ 236 | "phf_generator", 237 | "phf_shared", 238 | ] 239 | 240 | [[package]] 241 | name = "phf_generator" 242 | version = "0.10.0" 243 | source = "registry+https://github.com/rust-lang/crates.io-index" 244 | checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" 245 | dependencies = [ 246 | "phf_shared", 247 | "rand", 248 | ] 249 | 250 | [[package]] 251 | name = "phf_shared" 252 | version = "0.10.0" 253 | source = "registry+https://github.com/rust-lang/crates.io-index" 254 | checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" 255 | dependencies = [ 256 | "siphasher", 257 | "uncased", 258 | ] 259 | 260 | [[package]] 261 | name = "ppv-lite86" 262 | version = "0.2.10" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 265 | 266 | [[package]] 267 | name = "proc-macro-error" 268 | version = "1.0.4" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 271 | dependencies = [ 272 | "proc-macro-error-attr", 273 | "proc-macro2", 274 | "quote", 275 | "syn", 276 | "version_check", 277 | ] 278 | 279 | [[package]] 280 | name = "proc-macro-error-attr" 281 | version = "1.0.4" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 284 | dependencies = [ 285 | "proc-macro2", 286 | "quote", 287 | "version_check", 288 | ] 289 | 290 | [[package]] 291 | name = "proc-macro2" 292 | version = "1.0.29" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 295 | dependencies = [ 296 | "unicode-xid", 297 | ] 298 | 299 | [[package]] 300 | name = "quote" 301 | version = "1.0.9" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 304 | dependencies = [ 305 | "proc-macro2", 306 | ] 307 | 308 | [[package]] 309 | name = "rand" 310 | version = "0.8.4" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" 313 | dependencies = [ 314 | "libc", 315 | "rand_chacha", 316 | "rand_core", 317 | "rand_hc", 318 | ] 319 | 320 | [[package]] 321 | name = "rand_chacha" 322 | version = "0.3.1" 323 | source = "registry+https://github.com/rust-lang/crates.io-index" 324 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 325 | dependencies = [ 326 | "ppv-lite86", 327 | "rand_core", 328 | ] 329 | 330 | [[package]] 331 | name = "rand_core" 332 | version = "0.6.3" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 335 | dependencies = [ 336 | "getrandom", 337 | ] 338 | 339 | [[package]] 340 | name = "rand_hc" 341 | version = "0.3.1" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" 344 | dependencies = [ 345 | "rand_core", 346 | ] 347 | 348 | [[package]] 349 | name = "regex" 350 | version = "1.5.4" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" 353 | dependencies = [ 354 | "regex-syntax", 355 | ] 356 | 357 | [[package]] 358 | name = "regex-syntax" 359 | version = "0.6.25" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 362 | 363 | [[package]] 364 | name = "siphasher" 365 | version = "0.3.7" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "533494a8f9b724d33625ab53c6c4800f7cc445895924a8ef649222dcb76e938b" 368 | 369 | [[package]] 370 | name = "strsim" 371 | version = "0.8.0" 372 | source = "registry+https://github.com/rust-lang/crates.io-index" 373 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 374 | 375 | [[package]] 376 | name = "structopt" 377 | version = "0.3.23" 378 | source = "registry+https://github.com/rust-lang/crates.io-index" 379 | checksum = "bf9d950ef167e25e0bdb073cf1d68e9ad2795ac826f2f3f59647817cf23c0bfa" 380 | dependencies = [ 381 | "clap", 382 | "lazy_static", 383 | "structopt-derive", 384 | ] 385 | 386 | [[package]] 387 | name = "structopt-derive" 388 | version = "0.4.16" 389 | source = "registry+https://github.com/rust-lang/crates.io-index" 390 | checksum = "134d838a2c9943ac3125cf6df165eda53493451b719f3255b2a26b85f772d0ba" 391 | dependencies = [ 392 | "heck", 393 | "proc-macro-error", 394 | "proc-macro2", 395 | "quote", 396 | "syn", 397 | ] 398 | 399 | [[package]] 400 | name = "syn" 401 | version = "1.0.77" 402 | source = "registry+https://github.com/rust-lang/crates.io-index" 403 | checksum = "5239bc68e0fef57495900cfea4e8dc75596d9a319d7e16b1e0a440d24e6fe0a0" 404 | dependencies = [ 405 | "proc-macro2", 406 | "quote", 407 | "unicode-xid", 408 | ] 409 | 410 | [[package]] 411 | name = "textwrap" 412 | version = "0.11.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 415 | dependencies = [ 416 | "unicode-width", 417 | ] 418 | 419 | [[package]] 420 | name = "thiserror" 421 | version = "1.0.29" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" 424 | dependencies = [ 425 | "thiserror-impl", 426 | ] 427 | 428 | [[package]] 429 | name = "thiserror-impl" 430 | version = "1.0.29" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" 433 | dependencies = [ 434 | "proc-macro2", 435 | "quote", 436 | "syn", 437 | ] 438 | 439 | [[package]] 440 | name = "time" 441 | version = "0.1.44" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 444 | dependencies = [ 445 | "libc", 446 | "wasi", 447 | "winapi", 448 | ] 449 | 450 | [[package]] 451 | name = "uncased" 452 | version = "0.9.6" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "5baeed7327e25054889b9bd4f975f32e5f4c5d434042d59ab6cd4142c0a76ed0" 455 | dependencies = [ 456 | "version_check", 457 | ] 458 | 459 | [[package]] 460 | name = "unicode-segmentation" 461 | version = "1.8.0" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" 464 | 465 | [[package]] 466 | name = "unicode-width" 467 | version = "0.1.9" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 470 | 471 | [[package]] 472 | name = "unicode-xid" 473 | version = "0.2.2" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 476 | 477 | [[package]] 478 | name = "vec_map" 479 | version = "0.8.2" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 482 | 483 | [[package]] 484 | name = "version_check" 485 | version = "0.9.3" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 488 | 489 | [[package]] 490 | name = "wasi" 491 | version = "0.10.0+wasi-snapshot-preview1" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 494 | 495 | [[package]] 496 | name = "winapi" 497 | version = "0.3.9" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 500 | dependencies = [ 501 | "winapi-i686-pc-windows-gnu", 502 | "winapi-x86_64-pc-windows-gnu", 503 | ] 504 | 505 | [[package]] 506 | name = "winapi-i686-pc-windows-gnu" 507 | version = "0.4.0" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 510 | 511 | [[package]] 512 | name = "winapi-x86_64-pc-windows-gnu" 513 | version = "0.4.0" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 516 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "aussie_plus_plus" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [[bin]] 7 | name = "aussie_plus_plus" 8 | 9 | [dependencies] 10 | ahash = "0.7.6" 11 | anyhow = "1.0.44" 12 | arrayvec = "0.7.1" 13 | itertools = "0.10.1" 14 | rand = "0.8.4" 15 | thiserror = "1.0.29" 16 | 17 | [target.'cfg(not(target_os = "emscripten"))'.dependencies] 18 | structopt = "0.3.23" 19 | chrono = "0.4.19" 20 | chrono-tz = "0.6.0" 21 | -------------------------------------------------------------------------------- /DOCS.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | `aussie++` is a dynamically-typed and interpreted language inspired by [this](https://www.reddit.com/r/ProgrammerHumor/comments/oa8chw/australian_programming_language/) Reddit post. 3 | 4 | ## General 5 | All keywords are case-insensitive, 6 | meaning `CHEERS C***!` is equivalent to `cheers c***!`, but all caps is strongly recommended. 7 | 8 | We use boomerangs (`<` `>`) instead of curly braces (`{` `}`) 9 | 10 | ```aussie 11 | // Programs must start with `G'DAY MATE!` 12 | G'DAY MATE! 13 | 14 | // Prints "crikey mate!" to console 15 | GIMME "crikey mate!"; 16 | 17 | // Boomerangs for blocks/scopes 18 | < 19 | I RECKON x = 5; 20 | > 21 | 22 | // Use this to indicate end of program 23 | CHEERS C***! 24 | ``` 25 | 26 | 27 | ## Types / Variables 28 | Booleans are any sequence of `NAH`s and `YEAH`s separated by whitespace, a comma, or `\n` and followed by a terminal `!` denoting the end of the boolean. The last `NAH` or `YEAH` determines the truthiness of the boolean. The following are all valid booleans: 29 | ```aussie 30 | // Booleans 31 | I RECKON thisIsFalse = YEAH, NAH!; 32 | I RECKON thisIsTrue = NAH, YEAH!; 33 | I RECKON alsoTrue = NAH YEAH YEAH YEAH YEAH YEAH YEAH! 34 | I RECKON wow = NAH YEAH NAH 35 | NAH YEAH NAH NAH YEAH NAH NAH YEAH NAH! 36 | ``` 37 | 38 | Numbers, strings and `nil/null` are like other languages: 39 | ```aussie 40 | // Numbers 41 | I RECKON regularInteger = 42069; 42 | I RECKON tinyNum = 0.00001; 43 | I RECKON negativeNum = -1; 44 | 45 | // Strings 46 | I RECKON goodStr = "fair dinkum mate!"; 47 | 48 | // Nil/Null 49 | I RECKON emptiness = BUGGER ALL; 50 | ``` 51 | ## Operators 52 | 53 | Most mathematical operators are familiar from other languages. 54 | ```aussie 55 | I RECKON a = 1; 56 | I RECKON b = 2; 57 | I RECKON sum = a + b; 58 | I RECKON diff = b - a; 59 | I RECKON product = a * b; 60 | I RECKON ratio = a / b; 61 | ``` 62 | 63 | However, the increment and decrement expressions are unique. To increment a variable `var`, use `GOOD ON YA var`; to decrement, use `PULL YA HEAD IN var`. These expressions are pre-increment and pre-decrement expressions: the value attached to the variable is modified, then returned. 64 | ```aussie 65 | I RECKON a = 0; 66 | GOOD ON YA a; // a == 1 67 | I RECKON b = 10 + GOOD ON YA a; // a == 2, b == 12 68 | I RECKON c = PULL YA HEAD IN b + GOOD ON YA a; // a == 3, b == 11, c == 14 69 | PULL YA HEAD IN c; // c == 13 70 | ``` 71 | 72 | ## Control flow 73 | `aussie++` supports if statements and basic pattern matching: 74 | ```aussie 75 | // If/else statemets 76 | YA RECKON 1 == 2 ? < 77 | GIMME "fark we broke maths!"; 78 | > WHATABOUT NAH, YEAH! == YEAH, NAH! ? < 79 | GIMME "strewth we broke boolean logic!"; 80 | > WHATABOUT ? < 81 | GIMME "the universe is okay"; 82 | > 83 | 84 | // Pattern matching 85 | YA RECKON randomBeer() IS A < 86 | "Fosters" ~ GIMME "Flamin' hell!"; 87 | "Coopers" ~ GIMME "You Beauty!"; 88 | somethinElse ~ GIMME "Yeah, dunno that one: " + somethinElse; 89 | > 90 | ``` 91 | 92 | ## Loops 93 | `aussie++` has for and while loops. With for loops the main thing to note is that the ranges are specified using interval notation (`[` or `]` is inclusive, and `(` or `)` is exclusive). You can mix and match. You can break out of a loop by saying `MATE FUCK THIS`: 94 | ```aussie 95 | // From 0-100 96 | I RECKON x IS A WALKABOUT FROM [0 TO 100] < 97 | GIMME x; 98 | > 99 | 100 | // From 0-99 101 | I RECKON x IS A WALKABOUT FROM [0 TO 100) < 102 | GIMME x; 103 | > 104 | 105 | // Breaking with `MATE FUCK THIS` 106 | I RECKON x IS A WALKABOUT FROM [0 TO 999999] < 107 | YA RECKON x > 1000 ? MATE FUCK THIS; 108 | > 109 | ``` 110 | 111 | While loops are similar to those you would find in other languages, except that the loop only executes if the condition is false. 112 | 113 | ```aussie 114 | // OI MATE, PAY ATTENTION! THIS LOOP STOPS WHEN I'VE WALKED OVER 3 KM! 115 | 116 | I RECKON kmWalked = 0; 117 | I RECKON I'LL HAVE A WALKABOUT UNTIL (kmWalked > 3) < 118 | GIMME "i walked 1 km!"; 119 | kmWalked = kmWalked + 1; 120 | > 121 | GIMME "BLOODY OATH I'M TIRED!"; 122 | ``` 123 | 124 | ## Functions 125 | Define functions like so, using `BAIL ` to return values: 126 | ```aussie 127 | THE HARD YAKKA FOR greeting() IS < 128 | BAIL "G'day mate!"; 129 | > 130 | 131 | GIMME greeting(); 132 | ``` 133 | 134 | ## Standard library / Built-ins 135 | Use `IMPOHT ME FUNC ` to import built-in functions. The language currently comes with two built-ins, `ChuckSomeDice(start, end)` and `HitTheSack(ms)`: 136 | 137 | ```aussie 138 | IMPOHT ME FUNC ChuckSomeDice; 139 | IMPOHT ME FUNC HitTheSack; 140 | 141 | THE HARD YAKKA FOR goIntoAComa() IS < 142 | // Return a random integer from 0-99 143 | I RECKON duration = ChuckSomeDice(0, 100); 144 | 145 | // Sleep for `duration` seconds 146 | HitTheSack(duration * 1000); 147 | 148 | GIMME "strewth! i went into a coma!"; 149 | > 150 | 151 | goIntoAComa(); 152 | ``` 153 | 154 | ## Comments 155 | All lines before `G'DAY MATE!` and after `CHEERS C***!` are ignored, and can be used to document your module. 156 | 157 | `//` marks the start of documentation until the end of that line. 158 | 159 | Block comments can be opened with `OI MATE!` and closed with `GOT IT?`. 160 | 161 | ```aussie 162 | You bloody bewdy, cobbadiggamate, this is a rippa module! 163 | 164 | G'DAY MATE! 165 | 166 | THE HARD YAKKA FOR YOU_CAN_GET_IT_DOING(NOTHING_AT_ALL) IS < 167 | I RECKON A_HARD_EARNED_THIRST = "A BIG COLD BEER"; // And the best cold beer is Vic, Victoria Bitter. 168 | OI MATE! 169 | But I drink to get p*ssed! 170 | GOT IT? 171 | BAIL A_HARD_EARNED_THIRST; 172 | > 173 | 174 | CHEERS C***! 175 | 176 | References: 177 | - https://www.youtube.com/watch?v=0uPCi_KnCiQ 178 | - https://www.youtube.com/watch?v=7n9IE2jRtgs 179 | ``` 180 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: 2 | wasm: 3 | cargo build --release --target wasm32-unknown-emscripten -Z build-std=panic_abort,std 4 | cp target/wasm32-unknown-emscripten/release/aussie_plus_plus.js site/public/aussie_plus_plus.js 5 | cp target/wasm32-unknown-emscripten/release/aussie_plus_plus.wasm site/public/aussie_plus_plus.wasm -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![aussie_plus_plus](assets/code.png) 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | # aussie++ 6 | 7 | Programming language from down under, inspired by [this](https://www.reddit.com/r/ProgrammerHumor/comments/oa8chw/australian_programming_language/) Reddit post. 8 | 9 | View live demo [here](http://aussieplusplus.vercel.app/). 10 | 11 | Special thanks to [MarkWhyBird](https://github.com/MarkWhybird), [louis100](https://github.com/louis1001), and others who came up with the language [spec](https://github.com/louis1001/c---/issues/5). 12 | 13 | ## Key Features 14 | * 🇦🇺 Syntax entirely comprised of Australian lingo and slang 15 | * 🪃 Wield an Australian's greatest weapon: use boomerangs (angle-brackets) instead of curly braces 16 | * **True aussie mode**, where uʍop ǝpᴉsdn characters become valid code 17 | 18 | ## Example 19 | ``` 20 | G'DAY MATE! 21 | 22 | THE HARD YAKKA FOR fibonacci IS ( x ) < 23 | YA RECKON x <= 1 ? BAIL x; 24 | 25 | BAIL fibonacci(x - 1) + fibonacci(x - 2); 26 | > 27 | 28 | GIMME fibonacci(30); 29 | ``` 30 | 31 | # Docs 32 | Check out [DOCS.md](DOCS.md) 33 | 34 | # Contributing 35 | Check out [CONTRIBUTING.md](CONTRIBUTING.md) 36 | 37 | ## Contributors ✨ 38 | 39 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |

jwfxpr

💻 💡 📖

bbrk24

🤔 🐛
50 | 51 | 52 | 53 | 54 | 55 | 56 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 57 | -------------------------------------------------------------------------------- /assets/code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/assets/code.png -------------------------------------------------------------------------------- /examples/comeinspinner.aussie: -------------------------------------------------------------------------------- 1 | G'DAY MATE! 2 | 3 | IMPOHT ME FUNC ChuckSomeDice; 4 | 5 | // Tails are odd numbers & true values; heads are even numbers & false values. Unexpected values resolve to Heads. 6 | // Returns values according to this table, where T == tails/true, H == heads/false 7 | // 8 | // coin1 T H 9 | // +-----+-----+ 10 | // T |Tails|Odds | 11 | // coin2 +-----+-----+ 12 | // H |Odds |Heads| 13 | // +-----+-----+ 14 | THE HARD YAKKA FOR comeInSpinner IS (coin1, coin2) < 15 | I RECKON coin1isTails = YEAH, NAH!; 16 | I RECKON coin2isTails = YEAH, NAH!; 17 | 18 | YA RECKON coin1 IS A < 19 | YEAH, NAH! ~ coin1isTails = YEAH, NAH!; 20 | NAH, YEAH! ~ coin1isTails = NAH, YEAH!; 21 | buggeredIfIKnow ~ coin1isTails = (buggeredIfIKnow % 2) == 1; 22 | > 23 | 24 | YA RECKON coin2 IS A < 25 | YEAH, NAH! ~ coin2isTails = YEAH, NAH!; 26 | NAH, YEAH! ~ coin2isTails = NAH, YEAH!; 27 | buggeredIfIKnow ~ coin2isTails = (buggeredIfIKnow % 2) == 1; 28 | > 29 | 30 | YA RECKON coin1isTails ? < 31 | YA RECKON coin2isTails ? < BAIL "Tails"; > 32 | WHATABOUT ? < BAIL "Odds"; > 33 | > WHATABOUT ? < 34 | YA RECKON coin2isTails ? < BAIL "Odds"; > 35 | WHATABOUT ? < BAIL "Heads"; > 36 | > 37 | > 38 | 39 | THE HARD YAKKA FOR tossACoin IS () < 40 | BAIL ChuckSomeDice(0, 2) == 1; 41 | > 42 | 43 | GIMME comeInSpinner(tossACoin(), tossACoin()); 44 | 45 | CHEERS C***! 46 | -------------------------------------------------------------------------------- /examples/dreamtime.aussie: -------------------------------------------------------------------------------- 1 | G'DAY MATE! 2 | 3 | IMPOHT ME FUNC HitTheSack; 4 | IMPOHT ME FUNC ChuckSomeDice; 5 | 6 | THE HARD YAKKA FOR dreamtime IS () < 7 | GIMME "'boutta get some winks mate"; 8 | 9 | I RECKON I'LL HAVE A WALKABOUT UNTIL (Yeah, nah!) < 10 | GIMME "zZz..."; 11 | 12 | HitTheSack(1000); 13 | 14 | YA RECKON ChuckSomeDice(0, 6) == 0 ? MATE FUCK THIS; 15 | > 16 | 17 | GIMME "that nap was bonza mate!"; 18 | > 19 | 20 | dreamtime(); 21 | 22 | CHEERS C***! 23 | -------------------------------------------------------------------------------- /examples/fibonacci.aussie: -------------------------------------------------------------------------------- 1 | G'DAY MATE! 2 | 3 | THE HARD YAKKA FOR fibonacci IS ( x ) < 4 | YA RECKON x <= 1 ? BAIL x; 5 | 6 | BAIL fibonacci(x - 1) + fibonacci(x - 2); 7 | > 8 | 9 | GIMME fibonacci(5); 10 | -------------------------------------------------------------------------------- /examples/fizzbuzz.aussie: -------------------------------------------------------------------------------- 1 | G'DAY MATE! 2 | 3 | THE HARD YAKKA FOR FuckWit IS (n) < 4 | I RECKON x IS A WALKABOUT FROM [0 TO n) < 5 | YA RECKON x % 15 == 0 ? GIMME "FuckWit"; 6 | WHATABOUT x % 3 == 0 ? GIMME "Fuck"; 7 | WHATABOUT x % 5 == 0 ? GIMME "Wit"; 8 | > 9 | > 10 | 11 | FuckWit(100); -------------------------------------------------------------------------------- /examples/time.aussie: -------------------------------------------------------------------------------- 1 | G'DAY MATE! 2 | 3 | IMPOHT ME FUNC GimmeTime; 4 | 5 | THE HARD YAKKA FOR gimmeTimeMate IS () < 6 | GIMME "Oi mate, the time in Melbourne is: " + GimmeTime(); 7 | > 8 | 9 | gimmeTimeMate(); 10 | -------------------------------------------------------------------------------- /examples/upsidedown.aussie: -------------------------------------------------------------------------------- 1 | 2 | ¡***Ɔ SɹƎƎHƆ 3 | 4 | ;„uʍop ǝpᴉsdn sᴉ sᴉɥʇ„ ƎWWIפ 5 | 6 | ¡Ǝ┴∀W ⅄∀p,פ 7 | -------------------------------------------------------------------------------- /gxx_personality_v0_stub.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/gxx_personality_v0_stub.o -------------------------------------------------------------------------------- /lib.js: -------------------------------------------------------------------------------- 1 | mergeInto(LibraryManager.library, { 2 | aussie_time: function() { 3 | const str = new Date().toLocaleString("en-AU", {timeZone: "Australia/Sydney"}); 4 | const intArray = Module.intArrayFromString(str) 5 | const ptr = Module._malloc(intArray.length) 6 | Module.writeArrayToMemory(intArray, ptr) 7 | 8 | return ptr 9 | } 10 | }) -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly -------------------------------------------------------------------------------- /site/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next/ 3 | out_publish/ 4 | out_function/ -------------------------------------------------------------------------------- /site/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "react-app", 9 | "plugin:react/recommended", 10 | "plugin:react-hooks/recommended", 11 | "prettier-standard" 12 | ], 13 | "parserOptions": { 14 | "project": "./tsconfig.json" 15 | }, 16 | "plugins": [ 17 | "react", 18 | "@typescript-eslint", 19 | "react-hooks", 20 | "prettier", 21 | "simple-import-sort" 22 | ], 23 | "rules": { 24 | "react/jsx-no-undef": ["error", { "allowGlobals": true }], 25 | "no-use-before-define": "off", 26 | "react/no-children-prop": "off", 27 | "prettier/prettier": [ 28 | "error", 29 | { 30 | "endOfLine": "auto" 31 | } 32 | ], 33 | "jsx-a11y/anchor-is-valid": "off", 34 | "simple-import-sort/imports": "error", 35 | "simple-import-sort/exports": "error" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /site/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | lib/DOCS.md 4 | 5 | # dependencies 6 | /node_modules 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # next.js 14 | /.next/ 15 | /out/ 16 | 17 | # production 18 | /build 19 | 20 | # misc 21 | .DS_Store 22 | *.pem 23 | 24 | # debug 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | 29 | # local env files 30 | .env.local 31 | .env.development.local 32 | .env.test.local 33 | .env.production.local 34 | 35 | # vercel 36 | .vercel 37 | /out_publish/ 38 | /out_functions/ 39 | 40 | # Local Netlify folder 41 | .netlify 42 | 43 | .vscode/ -------------------------------------------------------------------------------- /site/.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next/ 3 | out/ 4 | out_publish/ 5 | out_functions/ 6 | .netlify/ 7 | .vercel/ -------------------------------------------------------------------------------- /site/.prettierrc: -------------------------------------------------------------------------------- 1 | "prettier-config-standard" 2 | -------------------------------------------------------------------------------- /site/components/Code.tsx: -------------------------------------------------------------------------------- 1 | import 'prismjs/themes/prism-tomorrow.css' 2 | 3 | import classNames from 'classnames' 4 | import Prism, { highlight } from 'prismjs' 5 | import React, { useEffect, useRef, useState } from 'react' 6 | import Editor from 'react-simple-code-editor' 7 | import useBreakpoint from 'use-breakpoint' 8 | 9 | import { AussieWorker } from '../lib/AussieWorker' 10 | import { createAussieWorker } from '../lib/create_aussie_worker' 11 | import { examples } from '../lib/example' 12 | import { aussieSyntax } from '../lib/syntax' 13 | import CodeDropdown from './CodeDropdown' 14 | 15 | const BREAKPOINTS = { mobile: 0, tablet: 768, desktop: 1280 } 16 | 17 | const defaultText = 18 | '

G'DAY MATE! HIT 'Run' TO GET GOING

' 19 | 20 | const hightlightWithLineNumbers = (input: string) => 21 | highlight(input, aussieSyntax, 'aussie') 22 | .split('\n') 23 | .map((line, i) => `${i + 1}${line}`) 24 | .join('\n') 25 | 26 | const Code = () => { 27 | const [code, setCode] = useState(examples.dreamtime) 28 | const [terminalText, setTerminalText] = useState(defaultText) 29 | const { breakpoint } = useBreakpoint(BREAKPOINTS, 'mobile') 30 | 31 | const [loaded, setLoaded] = useState(false) 32 | const [running, setRunning] = useState(false) 33 | const [isUpsideDown, setIsUpsideDown] = useState(false) 34 | 35 | const aussieRef = useRef() 36 | const rightsideUpCached = useRef('') 37 | 38 | const flipOrientation = async () => { 39 | const upsidededness = !isUpsideDown 40 | if (!isUpsideDown) { 41 | rightsideUpCached.current = code 42 | const flipped = await aussieRef.current!.flip(code, upsidededness) 43 | 44 | setCode(flipped) 45 | } else { 46 | setCode(rightsideUpCached.current) 47 | } 48 | 49 | setIsUpsideDown(upsidededness) 50 | } 51 | 52 | const switchExample = (code: string) => { 53 | rightsideUpCached.current = code 54 | setIsUpsideDown(false) 55 | setCode(code) 56 | } 57 | 58 | useEffect(() => { 59 | const run = async () => { 60 | const [aussiePlusPlus, worker] = await createAussieWorker() 61 | worker.onmessage = e => { 62 | switch (e.data.type) { 63 | case 'stderr': { 64 | setTerminalText( 65 | text => 66 | text + 67 | `${e.data.data}` + 68 | '
' 69 | ) 70 | break 71 | } 72 | case 'stdout': { 73 | setTerminalText(text => text + e.data.data + '
') 74 | break 75 | } 76 | } 77 | } 78 | aussieRef.current = aussiePlusPlus 79 | setLoaded(true) 80 | } 81 | run() 82 | Prism.highlightAll() 83 | }, []) 84 | return ( 85 |
86 |
87 |
88 |
89 | hightlightWithLineNumbers(code)} 108 | onValueChange={(v: string) => setCode(v || '')} 109 | value={code} 110 | /> 111 |
112 |
113 |
114 |
115 | 134 | 140 |
141 | 147 | 148 |
149 |
150 |
153 |
154 |
155 |
156 |
157 |
158 | ) 159 | } 160 | 161 | export default Code 162 | -------------------------------------------------------------------------------- /site/components/CodeDropdown.tsx: -------------------------------------------------------------------------------- 1 | import { Menu, Transition } from '@headlessui/react' 2 | import { ChevronDownIcon } from '@heroicons/react/solid' 3 | import classNames from 'classnames' 4 | import React, { Fragment, useState } from 'react' 5 | 6 | import { examples } from '../lib/example' 7 | 8 | const exampleList = [ 9 | 'dreamtime.aussie', 10 | 'fibonacci.aussie', 11 | 'time.aussie', 12 | 'random_beer.aussie' 13 | ] 14 | 15 | const getExample = (name: string) => { 16 | return examples[name.split('.')[0]] 17 | } 18 | 19 | const Item = ({ 20 | active, 21 | name, 22 | onClick 23 | }: { 24 | active: boolean 25 | name: string 26 | onClick: () => void 27 | }) => { 28 | return ( 29 | 37 | ) 38 | } 39 | 40 | type Props = { 41 | setCode: (s: string) => void 42 | } 43 | 44 | const CodeDropdown = ({ setCode }: Props) => { 45 | const [activeExample, setActiveExample] = useState(exampleList[0]) 46 | return ( 47 | 48 |
49 | 50 | {activeExample} 51 | 53 |
54 | 55 | 63 | 64 |
65 | {exampleList.map(name => { 66 | return ( 67 | 68 | {() => ( 69 | { 73 | setCode(getExample(name)) 74 | setActiveExample(name) 75 | }} 76 | /> 77 | )} 78 | 79 | ) 80 | })} 81 |
82 |
83 |
84 |
85 | ) 86 | } 87 | 88 | export default CodeDropdown 89 | -------------------------------------------------------------------------------- /site/components/Features.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const features = [ 4 | { 5 | name: 'Aussie Syntax', 6 | description: 'Oi mate! All your strayan lingo is valid syntax!', 7 | icon: '🇦🇺' 8 | }, 9 | { 10 | name: 'Boomerangs > Curly braces', 11 | description: 12 | 'Curly braces are for daft buggers, wield dangerous boomerangs as your block delimiters.', 13 | icon: '🪃' 14 | }, 15 | { 16 | name: 'True aussie mode', 17 | description: 'Where uʍop ǝpᴉsdn characters become valid code', 18 | icon: '🙃' 19 | } 20 | ] 21 | 22 | const Features = () => { 23 | return ( 24 |
25 |
26 |
27 | {features.map((feature, i) => ( 28 |
29 |
30 |
31 | {feature.icon} 32 |
33 |

34 |

35 | {feature.name}{' '} 36 | {i === 2 && ( 37 |
38 |
39 |
40 | Some characters don't have upside-down 41 | counterparts so this may break identifiers. 42 | 48 | 52 | 53 |
54 |
55 | 60 | 65 | 66 |
67 | )} 68 |
69 |

70 |
71 |
72 | {feature.description} 73 |
74 |
75 | ))} 76 |
77 |
78 |
79 | ) 80 | } 81 | 82 | export default Features 83 | -------------------------------------------------------------------------------- /site/components/Head.tsx: -------------------------------------------------------------------------------- 1 | import NextHead from 'next/head' 2 | import React from 'react' 3 | 4 | type MetaTag = { 5 | name: string 6 | content: string 7 | } 8 | 9 | const metaTag = (name: string, content: string) => ({ name, content }) 10 | 11 | const title = 'Aussie++' 12 | const description = 'The programming language from down under.' 13 | const imageUrl = '/image.png' 14 | const author = '@zack_overflow' 15 | 16 | const metaTags: MetaTag[] = [ 17 | metaTag('description', description), 18 | metaTag('og:title', title), 19 | metaTag('og:description', description), 20 | metaTag('og:image', imageUrl), 21 | metaTag('twitter:card', 'summary'), 22 | metaTag('twitter:site', title), 23 | metaTag('twitter:title', title), 24 | metaTag('twitter:creator', author), 25 | metaTag('twitter:description', description), 26 | metaTag('twitter:image', imageUrl) 27 | ] 28 | 29 | const Head = () => ( 30 | 31 | {title} 32 | {metaTags.map(({ name, content }) => ( 33 | 34 | ))} 35 | 36 | ) 37 | 38 | export default Head 39 | -------------------------------------------------------------------------------- /site/lib/AussieWorker.ts: -------------------------------------------------------------------------------- 1 | import * as Comlink from 'comlink' 2 | 3 | // @ts-ignore 4 | // eslint-disable-next-line no-undef 5 | importScripts('/aussie_plus_plus.js') 6 | 7 | export class AussieWorker { 8 | module: any 9 | 10 | async initWasm() { 11 | // @ts-ignore 12 | // eslint-disable-next-line no-undef 13 | this.module = await aussiepp({ 14 | mainScriptUrlOrBlob: '/aussie_plus_plus.js', 15 | locateFile: (path: string) => { 16 | if (path === 'aussie_plus_plus.wasm') { 17 | return `/${path}` 18 | } 19 | }, 20 | print: (t: string) => { 21 | // @ts-ignore 22 | postMessage({ type: 'stdout', data: t }) 23 | }, 24 | printErr: (t: string) => { 25 | // @ts-ignore 26 | postMessage({ type: 'stderr', data: t }) 27 | } 28 | }) 29 | 30 | console.log(this.module) 31 | } 32 | 33 | run(code: string, isUpsideDown: boolean) { 34 | const ptr = this.passStringToWasm(code) 35 | 36 | // `_interpret()` will take ownership of `ptr` and deallocate it 37 | this.module._interpret(ptr, isUpsideDown) 38 | } 39 | 40 | flip(code: string, upsideDown: boolean): string { 41 | const ptr = this.passStringToWasm(code) 42 | 43 | const lenPtr = this.module._alloc(4) 44 | const outputPtr = this.module._flip_text(ptr, lenPtr, upsideDown) 45 | const outputLen = this.getLen(lenPtr) 46 | 47 | const str = this.module.UTF8ToString(outputPtr, outputLen) 48 | 49 | this.module._dealloc(lenPtr, 4) 50 | this.module._dealloc(outputPtr, outputLen) 51 | 52 | return str 53 | } 54 | 55 | passStringToWasm(str: string) { 56 | const intArray = this.module.intArrayFromString(str) 57 | const ptr = this.module._alloc(intArray.length) 58 | this.module.writeArrayToMemory(intArray, ptr) 59 | 60 | return ptr 61 | } 62 | 63 | getLen(lenPtr: number) { 64 | const buf = new Uint8Array(this.module.HEAPU8, lenPtr, 4) 65 | return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0] 66 | } 67 | } 68 | 69 | Comlink.expose(AussieWorker) 70 | -------------------------------------------------------------------------------- /site/lib/create_aussie_worker.ts: -------------------------------------------------------------------------------- 1 | import * as Comlink from 'comlink' 2 | 3 | import type { AussieWorker } from './AussieWorker' 4 | 5 | export const createAussieWorker = async (): Promise< 6 | [wrapped: AussieWorker, underlying: Worker] 7 | > => { 8 | // @ts-ignore 9 | const worker = new Worker(new URL('./AussieWorker.ts', import.meta.url)) 10 | 11 | // @ts-ignore 12 | const WorkerClass: new ( 13 | ..._args: ConstructorParameters 14 | ) => // @ts-ignore 15 | InstanceType> = Comlink.wrap(worker) 16 | 17 | const aussieWorker: AussieWorker = await new WorkerClass() 18 | await aussieWorker.initWasm() 19 | 20 | return [aussieWorker, worker] 21 | } 22 | -------------------------------------------------------------------------------- /site/lib/docs.md: -------------------------------------------------------------------------------- 1 | # Docs 2 | `aussie++` is a dynamically-typed and interpreted language inspired by [this](https://www.reddit.com/r/ProgrammerHumor/comments/oa8chw/australian_programming_language/) Reddit post. 3 | 4 | ## General 5 | All keywords are case-insensitive, 6 | meaning `CHEERS C***!` is equivalent to `cheers c***!`, but all caps is strongly recommended. 7 | 8 | We use boomerangs (`<` `>`) instead of curly braces (`{` `}`) 9 | 10 | ```aussie 11 | // Programs must start with `G'DAY MATE!` 12 | G'DAY MATE! 13 | 14 | // Prints "crikey mate!" to console 15 | GIMME "crikey mate!"; 16 | 17 | // Boomerangs for blocks/scopes 18 | < 19 | I RECKON x = 5; 20 | > 21 | 22 | // Use this to indicate end of program 23 | CHEERS C***! 24 | ``` 25 | 26 | 27 | ## Types / Variables 28 | Booleans are any sequence of `NAH`s and `YEAH`s separated by whitespace, a comma, or `\n` and followed by a terminal `!` denoting the end of the boolean. The last `NAH` or `YEAH` determines the truthiness of the boolean. The following are all valid booleans: 29 | ```aussie 30 | // Booleans 31 | I RECKON thisIsFalse = YEAH, NAH!; 32 | I RECKON thisIsTrue = NAH, YEAH!; 33 | I RECKON alsoTrue = NAH YEAH YEAH YEAH YEAH YEAH NAH! 34 | I RECKON wow = NAH YEAH NAH 35 | NAH YEAH NAH NAH YEAH NAH NAH YEAH NAH! 36 | ``` 37 | 38 | Numbers, strings and `nil/null` are like other languages: 39 | ```aussie 40 | // Numbers 41 | I RECKON regularInteger = 42069; 42 | I RECKON tinyNum = 0.00001; 43 | I RECKON negativeNum = -1; 44 | 45 | // Strings 46 | I RECKON goodStr = "fair dinkum mate!"; 47 | 48 | // Nil/Null 49 | I RECKON emptiness = BUGGER ALL; 50 | ``` 51 | 52 | ## Control flow 53 | `aussie++` supports if statements and basic pattern matching: 54 | ```aussie 55 | // If/else statemets 56 | YA RECKON 1 == 2 ? < 57 | GIMME "fark we broke maths!"; 58 | > WHATABOUT NAH, YEAH! == YEAH, NAH! ? < 59 | GIMME "strewth we broke boolean logic!"; 60 | > WHATABOUT ? < 61 | GIMME "the universe is okay"; 62 | > 63 | 64 | // Pattern matching 65 | YA RECKON randomBeer() IS A < 66 | "Fosters" ~ GIMME "Flamin' hell!"; 67 | "Coopers" ~ GIMME "You Beauty!"; 68 | somethinElse ~ GIMME "Yeah, dunno that one: " + somethinElse; 69 | > 70 | ``` 71 | 72 | ## Loops 73 | `aussie++` has for and while loops. With for loops the main thing to note is that the ranges are specified using interval notation (`[` or `]` is inclusive, and `(` or `)` is exclusive). You can mix and match. You can break out of a loop by saying `MATE FUCK THIS`: 74 | ```aussie 75 | // From 0-100 76 | I RECKON x IS A WALKABOUT FROM [0 TO 100] < 77 | GIMME x; 78 | > 79 | 80 | // From 0-99 81 | I RECKON x IS A WALKABOUT FROM [0 TO 100) < 82 | GIMME x; 83 | > 84 | 85 | // Breaking with `MATE FUCK THIS` 86 | I RECKON x IS A WALKABOUT FROM [0 TO 999999] < 87 | YA RECKON x > 1000 ? MATE FUCK THIS; 88 | > 89 | ``` 90 | 91 | While loops are similar to those you would find in other languages, except that the loop only executes if the condition is false. 92 | 93 | ```aussie 94 | // OI MATE, PAY ATTENTION! THIS LOOP STOPS WHEN I'VE WALKED OVER 3 KM! 95 | 96 | I RECKON kmWalked = 0; 97 | I RECKON I'LL HAVE A WALKABOUT UNTIL (kmWalked > 3) < 98 | GIMME "i walked 1 km!"; 99 | kmWalked = kmWalked + 1; 100 | > 101 | GIMME "BLOODY OATH I'M TIRED!"; 102 | ``` 103 | 104 | ## Functions 105 | Define functions like so, using `BAIL ` to return values: 106 | ```aussie 107 | THE HARD YAKKA FOR greeting() IS < 108 | BAIL "G'day mate!"; 109 | > 110 | 111 | GIMME greeting(); 112 | ``` 113 | 114 | ## Standard library / Built-ins 115 | Use `IMPOHT ME FUNC ` to import built-in functions. The language currently comes with two built-ins, `ChuckSomeDice(start, end)` and `HitTheSack(ms)`: 116 | 117 | ```aussie 118 | IMPOHT ME FUNC ChuckSomeDice; 119 | IMPOHT ME FUNC HitTheSack; 120 | 121 | THE HARD YAKKA FOR goIntoAComa() IS < 122 | // Return a random integer from 0-99 123 | I RECKON duration = ChuckSomeDice(0, 100); 124 | 125 | // Sleep for `duration` seconds 126 | HitTheSack(duration * 1000); 127 | 128 | GIMME "strewth! i went into a coma!"; 129 | > 130 | 131 | goIntoAComa(); 132 | ``` 133 | -------------------------------------------------------------------------------- /site/lib/docs.ts: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs' 2 | import { join } from 'path' 3 | 4 | const docs = join(process.cwd(), 'lib', 'docs.md') 5 | 6 | export const readDocsFile = async () => { 7 | return new Promise((resolve, reject) => { 8 | readFile( 9 | docs, 10 | { 11 | encoding: 'utf8' 12 | }, 13 | (err, data) => { 14 | if (err) return reject(err) 15 | return resolve(data) 16 | } 17 | ) 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /site/lib/example.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable camelcase */ 2 | const time = `G'DAY MATE! 3 | 4 | IMPOHT ME FUNC GimmeTime; 5 | 6 | GIMME "the time in sydney is: " + GimmeTime(); 7 | ` 8 | 9 | const fibonacci = `G'DAY MATE! 10 | 11 | THE HARD YAKKA FOR fibonacci IS ( x ) < 12 | YA RECKON x <= 1 ? BAIL x; 13 | 14 | BAIL fibonacci(x - 1) + fibonacci(x - 2); 15 | > 16 | 17 | GIMME fibonacci(10); 18 | 19 | CHEERS C***!` 20 | 21 | const dreamtime = `G'DAY MATE! 22 | 23 | IMPOHT ME FUNC HitTheSack; 24 | IMPOHT ME FUNC ChuckSomeDice; 25 | 26 | THE HARD YAKKA FOR dreamtime IS () < 27 | GIMME "'boutta get some winks mate"; 28 | 29 | I RECKON I'LL HAVE A WALKABOUT UNTIL (YEAH, NAH!) < 30 | GIMME "zZz..."; 31 | 32 | HitTheSack(1000); 33 | 34 | YA RECKON ChuckSomeDice(0, 6) == 0 ? MATE FUCK THIS; 35 | > 36 | 37 | GIMME "that nap was bonza mate!"; 38 | > 39 | 40 | dreamtime(); 41 | 42 | CHEERS C***! 43 | ` 44 | 45 | const random_beer = `G'DAY MATE! 46 | 47 | IMPOHT ME FUNC ChuckSomeDice; 48 | 49 | THE HARD YAKKA FOR randomBeer IS () < 50 | YA RECKON ChuckSomeDice(0, 3) IS A < 51 | 0 ~ BAIL "Fosters"; 52 | 1 ~ BAIL "Coopers"; 53 | 2 ~ BAIL "Pilsner"; 54 | > 55 | > 56 | 57 | YA RECKON randomBeer() IS A < 58 | "Fosters" ~ GIMME "Flamin' hell!"; 59 | "Coopers" ~ GIMME "You Beauty!"; 60 | somethinElse ~ GIMME "Yeah, dunno that one: " + somethinElse; 61 | > 62 | 63 | CHEERS C***!` 64 | 65 | export const examples: Record = { 66 | fibonacci, 67 | dreamtime, 68 | time, 69 | random_beer 70 | } 71 | -------------------------------------------------------------------------------- /site/lib/syntax.ts: -------------------------------------------------------------------------------- 1 | import Prism, { languages } from 'prismjs' 2 | 3 | export const aussieSyntax = languages.extend('clike', { 4 | comment: [ 5 | { 6 | pattern: /OI MATE[\s\S]*?GOT IT\?/ 7 | }, 8 | { 9 | pattern: /(^|[^\\:])\/\/.*/, 10 | lookbehind: true, 11 | greedy: true 12 | } 13 | ], 14 | string: { 15 | pattern: /(["`])(?:\\[\s\S]|(?!\1)[^\\])*\1/, 16 | greedy: true 17 | }, 18 | keyword: 19 | /\b(?:PULL YA HEAD IN|GOOD ON YA|I FULLY RECKON|GIMME|G'DAY MATE!|CHEERS C\*\*\*!|I RECKON|YA RECKON|WHATABOUT|IS A|IS|WALKABOUT|FROM|MATE FUCK THIS|I'LL HAVE A|UNTIL|THE HARD YAKKA FOR|IMPOHT ME FUNC|BAIL|OI MATE|GOT IT)\b/, 20 | boolean: /\b(?:BUGGER ALL|NAH|YEAH)\b/, 21 | number: /(?:(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[-+]?\d+)?)i?/i, 22 | operator: 23 | /[*/%^!=]=?|~|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./ 24 | }) 25 | 26 | Prism.languages.aussie = aussieSyntax 27 | -------------------------------------------------------------------------------- /site/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | /// 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/basic-features/typescript for more information. 7 | -------------------------------------------------------------------------------- /site/next.config.js: -------------------------------------------------------------------------------- 1 | const SSRPlugin = 2 | require('next/dist/build/webpack/plugins/nextjs-ssr-import').default 3 | const { dirname, relative, resolve, join } = require('path') 4 | 5 | module.exports = { 6 | reactStrictMode: true, 7 | // https://github.com/vercel/next.js/issues/22581 8 | webpack(config) { 9 | const ssrPlugin = config.plugins.find(plugin => plugin instanceof SSRPlugin) 10 | 11 | if (ssrPlugin) { 12 | patchSsrPlugin(ssrPlugin) 13 | } 14 | 15 | return config 16 | }, 17 | // Target must be serverless 18 | target: 'serverless', 19 | async headers() { 20 | // return [] 21 | return [ 22 | { 23 | source: '/', 24 | headers: [ 25 | { 26 | key: 'Cross-Origin-Opener-Policy', 27 | value: 'unsafe-none' 28 | }, 29 | { 30 | key: 'Cross-Origin-Embedder-Policy', 31 | value: 'unsafe-none' 32 | }, 33 | { 34 | key: 'Cross-Origin-Resource-Policy', 35 | value: 'cross-origin' 36 | } 37 | ] 38 | } 39 | ] 40 | } 41 | } 42 | 43 | // Unfortunately there isn't an easy way to override the replacement function body, so we 44 | // have to just replace the whole plugin `apply` body. 45 | function patchSsrPlugin(plugin) { 46 | plugin.apply = function apply(compiler) { 47 | compiler.hooks.compilation.tap('NextJsSSRImport', compilation => { 48 | compilation.mainTemplate.hooks.requireEnsure.tap( 49 | 'NextJsSSRImport', 50 | (code, chunk) => { 51 | // This is the block that fixes https://github.com/vercel/next.js/issues/22581 52 | if (!chunk.name) { 53 | return 54 | } 55 | 56 | // Update to load chunks from our custom chunks directory 57 | const outputPath = resolve('/') 58 | const pagePath = join('/', dirname(chunk.name)) 59 | const relativePathToBaseDir = relative(pagePath, outputPath) 60 | // Make sure even in windows, the path looks like in unix 61 | // Node.js require system will convert it accordingly 62 | const relativePathToBaseDirNormalized = relativePathToBaseDir.replace( 63 | /\\/g, 64 | '/' 65 | ) 66 | return code 67 | .replace( 68 | 'require("./"', 69 | `require("${relativePathToBaseDirNormalized}/"` 70 | ) 71 | .replace( 72 | 'readFile(join(__dirname', 73 | `readFile(join(__dirname, "${relativePathToBaseDirNormalized}"` 74 | ) 75 | } 76 | ) 77 | }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aussieplusplus", 3 | "author": "zackradisic", 4 | "version": "1.0.0", 5 | "scripts": { 6 | "dev": "next", 7 | "build": "cp ../DOCS.md ./lib/DOCS.md && next build", 8 | "start": "next start", 9 | "type-check": "tsc", 10 | "lint": "eslint . --ext .ts,.tsx", 11 | "lint:fix": "yarn lint --fix", 12 | "prettier": "prettier --check **/*.ts*", 13 | "prettier:fix": "prettier --write **/*.ts*", 14 | "verify": "yarn prettier && yarn lint", 15 | "verify:fix": "yarn prettier:fix && yarn lint:fix" 16 | }, 17 | "dependencies": { 18 | "@headlessui/react": "^1.4.1", 19 | "@heroicons/react": "^1.0.1", 20 | "@monaco-editor/react": "^4.3.1", 21 | "@tailwindcss/aspect-ratio": "^0.2.1", 22 | "@wasmer/wasi": "^0.12.0", 23 | "@wasmer/wasm-terminal": "^0.12.0", 24 | "@wasmer/wasm-transformer": "^0.12.0", 25 | "@wasmer/wasmfs": "^0.12.0", 26 | "autoprefixer": "^10.2.4", 27 | "classnames": "^2.3.1", 28 | "fflate": "^0.7.1", 29 | "isomorphic-path": "^2.0.1", 30 | "next": "latest", 31 | "postcss": "^8.2.4", 32 | "prismjs": "^1.25.0", 33 | "react": "^17.0.1", 34 | "react-dom": "^16.12.0", 35 | "react-markdown": "^7.0.1", 36 | "react-simple-code-editor": "^0.11.0", 37 | "tailwindcss": "^2.2.9", 38 | "use-breakpoint": "^2.0.2" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "^12.12.21", 42 | "@types/prismjs": "^1.16.6", 43 | "@types/react": "^16.9.16", 44 | "@types/react-dom": "^16.9.4", 45 | "@typescript-eslint/eslint-plugin": "^4.14.2", 46 | "@typescript-eslint/parser": "^4.14.2", 47 | "babel-eslint": "^10.1.0", 48 | "eslint": "^7.19.0", 49 | "eslint-config-prettier": "^7.2.0", 50 | "eslint-config-prettier-standard": "^3.0.1", 51 | "eslint-config-react-app": "^6.0.0", 52 | "eslint-config-standard": "^16.0.2", 53 | "eslint-plugin-flowtype": "^5.2.0", 54 | "eslint-plugin-import": "^2.22.1", 55 | "eslint-plugin-jsx-a11y": "^6.4.1", 56 | "eslint-plugin-node": "^11.1.0", 57 | "eslint-plugin-prettier": "^3.3.1", 58 | "eslint-plugin-promise": "^4.2.1", 59 | "eslint-plugin-react": "^7.22.0", 60 | "eslint-plugin-react-hooks": "^4.2.0", 61 | "eslint-plugin-simple-import-sort": "^7.0.0", 62 | "eslint-plugin-standard": "^5.0.0", 63 | "husky": "^4.3.8", 64 | "prettier": "^2.2.1", 65 | "prettier-config-standard": "^1.0.1", 66 | "slugify": "^1.6.0", 67 | "typescript": "~4.3.5" 68 | }, 69 | "license": "MIT", 70 | "husky": { 71 | "hooks": { 72 | "pre-commit": "yarn prettier", 73 | "pre-push": "yarn verify" 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /site/pages/_app.ts: -------------------------------------------------------------------------------- 1 | import '@styles/global.css' 2 | 3 | import App from 'next/app' 4 | 5 | export default App 6 | -------------------------------------------------------------------------------- /site/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Code from '@components/Code' 2 | import Features from '@components/Features' 3 | import Head from '@components/Head' 4 | import { GetStaticProps } from 'next' 5 | import React from 'react' 6 | import ReactMarkdown from 'react-markdown' 7 | 8 | import { readDocsFile } from '../lib/docs' 9 | 10 | type Props = { 11 | docs: string 12 | } 13 | 14 | const Landing = ({ docs }: Props) => { 15 | return ( 16 | <> 17 | 18 |
19 |
20 |
21 |
22 |

23 | Aussie++ 24 |

25 |

26 | The programming language from down under. 27 |

28 |
29 | 36 | 43 |
44 |
45 |

46 | Made by{' '} 47 | 50 | @zack_overflow 51 | 52 | , inspired by{' '} 53 | 56 | Reddit 57 | 58 |

59 |
60 |
61 |
62 |
63 | 64 | 65 |
66 |
67 | 68 |
69 |
70 |
71 | 72 | ) 73 | } 74 | 75 | export default Landing 76 | 77 | export const getStaticProps: GetStaticProps = async () => { 78 | const docs = await readDocsFile() 79 | return { 80 | props: { 81 | docs 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /site/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {} 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /site/public/FiraCode-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/FiraCode-Light.woff -------------------------------------------------------------------------------- /site/public/FiraCode-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/FiraCode-Light.woff2 -------------------------------------------------------------------------------- /site/public/FiraCode-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/FiraCode-Regular.woff -------------------------------------------------------------------------------- /site/public/FiraCode-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/FiraCode-Regular.woff2 -------------------------------------------------------------------------------- /site/public/aussie_plus_plus.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/aussie_plus_plus.wasm -------------------------------------------------------------------------------- /site/public/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zackradisic/aussieplusplus/9522366ef0602946420fee19fa2ad6769b773c07/site/public/image.png -------------------------------------------------------------------------------- /site/styles/global.css: -------------------------------------------------------------------------------- 1 | /* ./styles/globals.css */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | html, 6 | body, 7 | body > div:first-child, 8 | div#__next, 9 | div#__next > div { 10 | height: 100%; 11 | background-color: #1E2130; 12 | scroll-behavior: smooth; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Fira Code'; 17 | src: url('/FiraCode-Light.woff2') format('woff2'), 18 | url("/FiraCode-Light.woff") format("woff"); 19 | font-weight: 300; 20 | font-style: normal; 21 | } 22 | 23 | @font-face { 24 | font-family: 'Fira Code'; 25 | src: url('/FiraCode-Regular.woff2') format('woff2'), 26 | url("/FiraCode-Regular.woff") format("woff"); 27 | font-weight: 400; 28 | font-style: normal; 29 | } 30 | 31 | .docs { 32 | @apply text-gray-50; 33 | } 34 | 35 | .docs h1 { 36 | @apply text-2xl pt-8 font-bold underline; 37 | } 38 | 39 | .docs h2 { 40 | @apply text-lg pt-16 font-bold underline; 41 | } 42 | 43 | .docs p { 44 | @apply pt-8 text-gray-300; 45 | } 46 | 47 | .docs pre { 48 | @apply mt-8 bg-[#282C42] p-4 rounded-md overflow-auto; 49 | } 50 | 51 | .docs p code { 52 | @apply text-gray-50 bg-[#1A1C26]; 53 | } 54 | 55 | .docs a { 56 | @apply text-blue-400 underline font-bold; 57 | } 58 | 59 | .editor { 60 | counter-reset: line; 61 | overflow-x: scroll !important; 62 | overflow-y: scroll !important; 63 | } 64 | 65 | #codeArea { 66 | overflow-wrap: normal !important; 67 | word-break: keep-all !important; 68 | outline: none; 69 | padding-top: 1rem; 70 | padding-left: 60px !important; 71 | z-index: 999999999; 72 | white-space: nowrap; 73 | overflow-x: hidden !important; 74 | overflow-y: scroll !important; 75 | } 76 | 77 | .editor pre { 78 | padding-left: 60px !important; 79 | } 80 | 81 | .editor .editorLineNumber { 82 | position: absolute; 83 | left: 0px; 84 | color: #8a8a8a; 85 | text-align: right; 86 | width: 40px; 87 | } 88 | 89 | .tooltip { 90 | @apply invisible absolute; 91 | transform: translateY(-100%); 92 | } 93 | 94 | .has-tooltip:hover .tooltip { 95 | @apply visible z-50; 96 | } 97 | -------------------------------------------------------------------------------- /site/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const colors = require('tailwindcss/colors') 2 | 3 | module.exports = { 4 | purge: ['**/*.tsx'], 5 | mode: 'jit', 6 | darkMode: false, // or 'media' or 'class' 7 | theme: { 8 | extend: { 9 | colors: { 10 | orange: colors.orange, 11 | coolGray: colors.coolGray, 12 | blueGray: colors.blueGray, 13 | red: colors.red, 14 | bg: '#1E2130' 15 | } 16 | }, 17 | fontFamily: { 18 | sans: ['Fira code', 'Fira Mono', 'monospace'] 19 | } 20 | }, 21 | variants: { 22 | extend: {} 23 | }, 24 | plugins: [require('@tailwindcss/aspect-ratio')] 25 | } 26 | -------------------------------------------------------------------------------- /site/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "alwaysStrict": true, 5 | "esModuleInterop": true, 6 | "forceConsistentCasingInFileNames": true, 7 | "isolatedModules": true, 8 | "jsx": "preserve", 9 | "lib": ["dom", "es2017"], 10 | "module": "esnext", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noFallthroughCasesInSwitch": true, 14 | "noUnusedLocals": true, 15 | "noUnusedParameters": true, 16 | "resolveJsonModule": true, 17 | "skipLibCheck": true, 18 | "strict": true, 19 | "target": "esnext", 20 | "baseUrl": ".", 21 | "paths": { 22 | "@styles*": ["styles*"], 23 | "@components*": ["components*"], 24 | "@type*": ["type*"], 25 | "@utils*": ["utils*"] 26 | } 27 | }, 28 | "exclude": ["node_modules", "goscript/"], 29 | "include": ["**/*.ts", "**/*.tsx", "components/CodeEditor.js"] 30 | } 31 | -------------------------------------------------------------------------------- /site/type/types.ts: -------------------------------------------------------------------------------- 1 | export type WasmRunFn = (strPtr: number) => number 2 | export type WasmAlloc = (size: number) => number 3 | export type WasmDealloc = (ptr: number, len: number) => number 4 | -------------------------------------------------------------------------------- /src/ast/conditional.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | runtime::{RuntimePartialEq, Value}, 3 | token::{Kind, Token}, 4 | }; 5 | 6 | use super::{ExprNode, Ident, Stmt, Var}; 7 | 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub struct If { 10 | pub cond: ExprNode, 11 | pub then: Box, 12 | pub else_: Option>, 13 | } 14 | 15 | impl If { 16 | pub fn new(cond: ExprNode, then: Box, else_: Option>) -> Self { 17 | Self { cond, then, else_ } 18 | } 19 | } 20 | 21 | #[derive(Clone, Debug, PartialEq)] 22 | pub struct Match { 23 | pub val: ExprNode, 24 | pub branches: Vec, 25 | pub default: Option, 26 | } 27 | 28 | impl Match { 29 | pub fn new(val: ExprNode, branches: Vec, default: Option) -> Match { 30 | Match { 31 | val, 32 | branches, 33 | default, 34 | } 35 | } 36 | } 37 | 38 | #[derive(Clone, Debug, PartialEq)] 39 | pub struct MatchBranch { 40 | pub pat: Pattern, 41 | pub body: Vec, 42 | line: usize, 43 | } 44 | 45 | impl MatchBranch { 46 | pub fn new(pat: Pattern, body: Vec, line: usize) -> Self { 47 | Self { pat, body, line } 48 | } 49 | 50 | pub fn line(&self) -> usize { 51 | self.line 52 | } 53 | } 54 | 55 | #[derive(Clone, Debug, PartialEq)] 56 | pub enum Pattern { 57 | Var(Var), 58 | String(String), 59 | Number(f64), 60 | Bool(bool), 61 | Nil, 62 | } 63 | 64 | impl RuntimePartialEq for Pattern { 65 | fn runtime_eq(&self, other: &Value) -> bool { 66 | match (self, other) { 67 | (Self::String(l), Value::String(r)) => l == r, 68 | (Self::Number(l), Value::Number(r)) => l == r, 69 | (Self::Bool(l), Value::Bool(r)) => l == r, 70 | (Self::Nil, Value::Nil) => true, 71 | _ => false, 72 | } 73 | } 74 | 75 | fn runtime_ne(&self, other: &Value) -> bool { 76 | !self.runtime_eq(other) 77 | } 78 | } 79 | 80 | impl From for Option { 81 | fn from(kind: Kind) -> Self { 82 | match &kind { 83 | Kind::Number(n) => Some(Pattern::Number(*n)), 84 | Kind::String(s) => Some(Pattern::String(s.clone())), 85 | Kind::True => Some(Pattern::Bool(true)), 86 | Kind::False => Some(Pattern::Bool(false)), 87 | Kind::BuggerAll => Some(Pattern::Nil), 88 | Kind::Ident(ident) => Some(Pattern::Var(Var::new( 89 | Ident::new(ident.clone(), 0), 90 | usize::MAX, 91 | ))), 92 | _ => None, 93 | } 94 | } 95 | } 96 | 97 | impl From for Option { 98 | fn from(tok: Token) -> Self { 99 | match &tok.kind { 100 | Kind::Number(n) => Some(Pattern::Number(*n)), 101 | Kind::String(s) => Some(Pattern::String(s.clone())), 102 | Kind::True => Some(Pattern::Bool(true)), 103 | Kind::False => Some(Pattern::Bool(false)), 104 | Kind::BuggerAll => Some(Pattern::Nil), 105 | Kind::Ident(ident) => Some(Pattern::Var(Var::new( 106 | Ident::new(ident.clone(), tok.line()), 107 | usize::MAX, 108 | ))), 109 | _ => None, 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/ast/expression.rs: -------------------------------------------------------------------------------- 1 | use crate::{runtime::Value, token::Token}; 2 | 3 | use super::{ 4 | op::{BinaryOp, UnaryOp}, 5 | LogicalOp, Var, 6 | }; 7 | 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub struct ExprNode { 10 | expr: Expr, 11 | line: usize, 12 | } 13 | 14 | impl ExprNode { 15 | pub fn new(expr: Expr, line: usize) -> ExprNode { 16 | Self { expr, line } 17 | } 18 | 19 | pub fn line(&self) -> usize { 20 | self.line 21 | } 22 | 23 | pub fn expr(&self) -> &Expr { 24 | &self.expr 25 | } 26 | 27 | pub fn expr_mut(&mut self) -> &mut Expr { 28 | &mut self.expr 29 | } 30 | 31 | pub fn literal(&self) -> Option<&Value> { 32 | self.expr.literal() 33 | } 34 | } 35 | 36 | #[derive(Clone, Debug, PartialEq)] 37 | pub enum Expr { 38 | Unary(UnaryOp, Box), 39 | Binary(Box, BinaryOp, Box), 40 | Logical(Box, LogicalOp, Box), 41 | Grouping(Box), 42 | Literal(Value), 43 | Var(Var), 44 | Assign(Var, Box), 45 | Call(Box, Token, Vec), 46 | } 47 | 48 | impl Expr { 49 | pub fn literal(&self) -> Option<&Value> { 50 | match self { 51 | Self::Literal(val) => Some(val), 52 | _ => None, 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/ast/function.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, rc::Rc}; 2 | 3 | use super::{Ident, Stmt}; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct FnDecl { 7 | pub ident: Ident, 8 | pub params: Vec, 9 | pub body: Vec, 10 | } 11 | 12 | impl FnDecl { 13 | pub fn new(ident: Ident, params: Vec, body: Vec) -> Self { 14 | Self { 15 | ident, 16 | params, 17 | body, 18 | } 19 | } 20 | 21 | pub fn name(&self) -> &Rc { 22 | &self.ident.name 23 | } 24 | } 25 | 26 | impl Display for FnDecl { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | write!( 29 | f, 30 | "{}({})", 31 | self.ident, 32 | self.params 33 | .iter() 34 | .map(|ident| ident.to_string()) 35 | .collect::>() 36 | .join(", ") 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/ast/loops.rs: -------------------------------------------------------------------------------- 1 | use super::{ExprNode, Stmt, Var}; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct ForLoop { 5 | pub var: Var, 6 | pub range: (RangeBound, RangeBound), 7 | pub body: Vec, 8 | } 9 | 10 | impl ForLoop { 11 | pub fn new( 12 | var: Var, 13 | range: (RangeBound, RangeBound), 14 | body: Vec, 15 | ) -> Self { 16 | Self { var, range, body } 17 | } 18 | } 19 | 20 | #[derive(Clone, Debug, PartialEq)] 21 | pub struct WhileLoop { 22 | pub cond: ExprNode, 23 | pub body: Vec, 24 | } 25 | 26 | impl WhileLoop { 27 | pub fn new(cond: ExprNode, body: Vec) -> Self { 28 | Self { cond, body } 29 | } 30 | } 31 | 32 | #[derive(Clone, Debug, PartialEq)] 33 | pub enum RangeBound { 34 | Inclusive(T), 35 | Exclusive(T), 36 | } 37 | 38 | impl RangeBound { 39 | pub fn expr(&self) -> &ExprNode { 40 | match self { 41 | Self::Inclusive(expr) => expr, 42 | Self::Exclusive(expr) => expr, 43 | } 44 | } 45 | 46 | pub fn expr_mut(&mut self) -> &mut ExprNode { 47 | match self { 48 | Self::Inclusive(expr) => expr, 49 | Self::Exclusive(expr) => expr, 50 | } 51 | } 52 | 53 | pub fn to_evaluated(&self, val: f64) -> RangeBound { 54 | match self { 55 | Self::Inclusive(_) => RangeBound::Inclusive(val), 56 | Self::Exclusive(_) => RangeBound::Exclusive(val), 57 | } 58 | } 59 | } 60 | 61 | impl RangeBound { 62 | pub fn value(&self) -> f64 { 63 | match self { 64 | Self::Inclusive(val) => *val, 65 | Self::Exclusive(val) => *val, 66 | } 67 | } 68 | } 69 | 70 | pub trait Range { 71 | fn satisfied(&self, i: T) -> bool; 72 | fn values(&self) -> (T, T); 73 | fn iterate(&self, i: &mut T); 74 | } 75 | 76 | impl Range for (RangeBound, RangeBound) { 77 | fn iterate(&self, val: &mut f64) { 78 | if self.0.value() < self.1.value() { 79 | *val += 1f64; 80 | } else if self.0.value() > self.1.value() { 81 | *val -= 1f64; 82 | } 83 | } 84 | 85 | fn satisfied(&self, i: f64) -> bool { 86 | match self { 87 | (RangeBound::Inclusive(start), RangeBound::Inclusive(end)) => { 88 | if start < end { 89 | i >= *start && i <= *end 90 | } else if start > end { 91 | i >= *end && i <= *start 92 | } else { 93 | let error_margin = f64::EPSILON; 94 | (i - *start).abs() < error_margin 95 | } 96 | } 97 | (RangeBound::Inclusive(start), RangeBound::Exclusive(end)) => { 98 | if start < end { 99 | i >= *start && i < *end 100 | } else if start > end { 101 | i < *end && i >= *start 102 | } else { 103 | let error_margin = f64::EPSILON; 104 | (i - *start).abs() < error_margin 105 | } 106 | } 107 | (RangeBound::Exclusive(start), RangeBound::Inclusive(end)) => { 108 | if start < end { 109 | i > *start && i <= *end 110 | } else if end < start { 111 | i >= *end && i < *start 112 | } else { 113 | let error_margin = f64::EPSILON; 114 | (i - *end).abs() < error_margin 115 | } 116 | } 117 | (RangeBound::Exclusive(start), RangeBound::Exclusive(end)) => { 118 | if start < end { 119 | i > *start && i < *end 120 | } else if end < start { 121 | i > *end && i < *start 122 | } else { 123 | false 124 | } 125 | } 126 | } 127 | } 128 | 129 | fn values(&self) -> (f64, f64) { 130 | match self { 131 | (RangeBound::Inclusive(a), RangeBound::Inclusive(b)) => (*a, *b), 132 | (RangeBound::Inclusive(a), RangeBound::Exclusive(b)) => { 133 | if a < b { 134 | (*a, *b + 1f64) 135 | } else { 136 | (*a, *b - 1f64) 137 | } 138 | } 139 | (RangeBound::Exclusive(a), RangeBound::Exclusive(b)) => { 140 | if a < b { 141 | (*a + 1f64, *b - 1f64) 142 | } else { 143 | (*a - 1f64, *b + 1f64) 144 | } 145 | } 146 | (RangeBound::Exclusive(a), RangeBound::Inclusive(b)) => { 147 | if a < b { 148 | (*a + 1f64, *b) 149 | } else { 150 | (*a - 1f64, *b) 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | pub use conditional::*; 2 | pub use expression::*; 3 | pub use function::*; 4 | pub use loops::*; 5 | pub use op::*; 6 | pub use statement::*; 7 | pub use var::*; 8 | 9 | mod conditional; 10 | mod expression; 11 | mod function; 12 | mod loops; 13 | mod op; 14 | mod statement; 15 | mod var; 16 | -------------------------------------------------------------------------------- /src/ast/op.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use crate::token::Kind; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq)] 6 | pub enum UnaryOp { 7 | Bang, 8 | Minus, 9 | Incr, 10 | Decr, 11 | } 12 | 13 | impl From for Option { 14 | fn from(kind: Kind) -> Self { 15 | match kind { 16 | Kind::Bang => Some(UnaryOp::Bang), 17 | Kind::Minus => Some(UnaryOp::Minus), 18 | Kind::GoodOnYa => Some(UnaryOp::Incr), 19 | Kind::PullYaHeadIn => Some(UnaryOp::Decr), 20 | _ => None, 21 | } 22 | } 23 | } 24 | 25 | #[derive(Copy, Clone, Debug, PartialEq)] 26 | pub enum BinaryOp { 27 | Plus, 28 | Minus, 29 | Multiply, 30 | Divide, 31 | Equal, 32 | NotEqual, 33 | Less, 34 | LessEqual, 35 | Greater, 36 | GreaterEqual, 37 | Modulo, 38 | } 39 | 40 | impl From for Option { 41 | fn from(kind: Kind) -> Self { 42 | match kind { 43 | Kind::Plus => Some(BinaryOp::Plus), 44 | Kind::Minus => Some(BinaryOp::Minus), 45 | Kind::Asterisk => Some(BinaryOp::Multiply), 46 | Kind::Slash => Some(BinaryOp::Divide), 47 | Kind::Equals => Some(BinaryOp::Equal), 48 | Kind::BangEqual => Some(BinaryOp::NotEqual), 49 | Kind::LeftBoomerang => Some(BinaryOp::Less), 50 | Kind::LTE => Some(BinaryOp::LessEqual), 51 | Kind::RightBoomerang => Some(BinaryOp::Greater), 52 | Kind::GTE => Some(BinaryOp::GreaterEqual), 53 | Kind::Modulo => Some(BinaryOp::Modulo), 54 | _ => None, 55 | } 56 | } 57 | } 58 | 59 | impl Display for BinaryOp { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | match self { 62 | Self::Plus => write!(f, "+"), 63 | Self::Minus => write!(f, "-"), 64 | Self::Multiply => write!(f, "*"), 65 | Self::Divide => write!(f, "/"), 66 | Self::Equal => write!(f, "=="), 67 | Self::NotEqual => write!(f, "!="), 68 | Self::Less => write!(f, "<"), 69 | Self::LessEqual => write!(f, "<="), 70 | Self::Greater => write!(f, ">"), 71 | Self::GreaterEqual => write!(f, ">="), 72 | Self::Modulo => write!(f, "%"), 73 | } 74 | } 75 | } 76 | 77 | #[derive(Copy, Clone, Debug, PartialEq)] 78 | pub enum LogicalOp { 79 | And, 80 | Or, 81 | } 82 | 83 | impl From for Option { 84 | fn from(kind: Kind) -> Self { 85 | match kind { 86 | Kind::And => Some(LogicalOp::And), 87 | Kind::Or => Some(LogicalOp::Or), 88 | _ => None, 89 | } 90 | } 91 | } 92 | 93 | impl Display for LogicalOp { 94 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 95 | match self { 96 | Self::And => write!(f, "&&"), 97 | Self::Or => write!(f, "||"), 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/ast/statement.rs: -------------------------------------------------------------------------------- 1 | use crate::token::Token; 2 | 3 | use super::{ExprNode, FnDecl, ForLoop, Ident, If, Match, VarDecl, WhileLoop}; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub enum Stmt { 7 | Expr(ExprNode), 8 | Block(Vec), 9 | If(If), 10 | Match(Match), 11 | VarDecl(VarDecl), 12 | FnDecl(FnDecl), 13 | Print(ExprNode), 14 | For(Box), 15 | Break(Token), 16 | While(Box), 17 | Return(Token, Option), 18 | Import(Ident), 19 | Exit(bool), 20 | } 21 | 22 | impl From for Vec { 23 | fn from(s: Stmt) -> Self { 24 | match s { 25 | Stmt::Block(stmts) => stmts, 26 | stmt => vec![stmt], 27 | } 28 | } 29 | } 30 | 31 | impl Stmt { 32 | pub fn kind(&self) -> String { 33 | match self { 34 | Self::Expr(_) => "expr", 35 | Self::Block(_) => "block", 36 | Self::If(_) => "if", 37 | Self::Match(_) => "match", 38 | Self::VarDecl(_) => "var decl", 39 | Self::FnDecl(_) => "fn decl", 40 | Self::Print(_) => "print", 41 | Self::For(_) => "for", 42 | Self::Break(_) => "rbreak", 43 | Self::While(_) => "while", 44 | Self::Return(_, _) => "return", 45 | Self::Import(_) => "import", 46 | Self::Exit(_) => "exit", 47 | } 48 | .into() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ast/var.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, rc::Rc}; 2 | 3 | use super::ExprNode; 4 | 5 | #[derive(Clone, Debug, PartialEq)] 6 | pub struct VarDecl { 7 | pub ident: Ident, 8 | pub initializer: Option, 9 | pub immutable: bool, 10 | } 11 | 12 | #[derive(Clone, Debug, PartialEq)] 13 | pub struct Var { 14 | pub ident: Ident, 15 | // is defined. 0 means it belongs in the current environment 16 | pub scope_distance: usize, 17 | } 18 | 19 | impl Var { 20 | pub fn new(ident: Ident, scope_distance: usize) -> Self { 21 | Self { 22 | ident, 23 | scope_distance, 24 | } 25 | } 26 | 27 | pub fn ident(&self) -> Ident { 28 | self.ident.clone() 29 | } 30 | 31 | pub fn name(&self) -> &Rc { 32 | &self.ident.name 33 | } 34 | 35 | pub fn line(&self) -> usize { 36 | self.ident.line 37 | } 38 | } 39 | 40 | impl From<(Ident, usize)> for Var { 41 | fn from(val: (Ident, usize)) -> Self { 42 | Self { 43 | ident: val.0, 44 | scope_distance: val.1, 45 | } 46 | } 47 | } 48 | 49 | impl From<(String, usize, usize)> for Var { 50 | fn from(tup: (String, usize, usize)) -> Self { 51 | Self { 52 | ident: (tup.0, tup.1).into(), 53 | scope_distance: tup.2, 54 | } 55 | } 56 | } 57 | 58 | impl From<(&str, usize, usize)> for Var { 59 | fn from(tup: (&str, usize, usize)) -> Self { 60 | (tup.0.to_string(), tup.1, tup.2).into() 61 | } 62 | } 63 | 64 | #[derive(Clone, Debug, PartialEq)] 65 | pub struct Ident { 66 | pub name: Rc, 67 | line: usize, 68 | } 69 | 70 | impl Ident { 71 | pub fn new(name: String, line: usize) -> Self { 72 | Self { 73 | name: Rc::from(name), 74 | line, 75 | } 76 | } 77 | 78 | pub fn line(&self) -> usize { 79 | self.line 80 | } 81 | } 82 | 83 | impl From<(String, usize)> for Ident { 84 | fn from(tup: (String, usize)) -> Self { 85 | Self { 86 | name: Rc::from(tup.0), 87 | line: tup.1, 88 | } 89 | } 90 | } 91 | 92 | impl From<(&str, usize)> for Ident { 93 | fn from(tup: (&str, usize)) -> Self { 94 | (tup.0.to_string(), tup.1).into() 95 | } 96 | } 97 | 98 | impl Display for Ident { 99 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 100 | write!(f, "{}", self.name) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/lexer/lexer.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use thiserror::Error; 3 | 4 | use crate::token::{Kind, Token}; 5 | 6 | use super::source::Source; 7 | 8 | pub struct Lexer 9 | where 10 | T: Source, 11 | { 12 | src: T, 13 | line: usize, 14 | } 15 | 16 | impl<'a, T: Source> Lexer { 17 | pub fn new(src: T) -> Self { 18 | Self { src, line: 1 } 19 | } 20 | 21 | pub fn lex(&mut self) -> (Vec, bool) { 22 | let mut tokens: Vec = Vec::new(); 23 | let mut had_error = false; 24 | 25 | let mut last_nah_yeah: Option = None; 26 | let mut nah_yeah_count = 0; 27 | 28 | loop { 29 | match self.next_token() { 30 | Ok(tok) => { 31 | match tok.kind() { 32 | Kind::EOF => { 33 | tokens.push(tok); 34 | break; 35 | } 36 | Kind::Cheers => { 37 | tokens.push(Token::new(Kind::Cheers, tok.line())); 38 | tokens.push(Token::new(Kind::EOF, tok.line() + 1)); 39 | break; 40 | } 41 | Kind::Nah | Kind::Yeah => { 42 | last_nah_yeah = Some(tok.kind()); 43 | nah_yeah_count += 1; 44 | // Don't fall through 45 | continue; 46 | } 47 | Kind::Bang => { 48 | if let Some(k) = last_nah_yeah { 49 | let tok = match k { 50 | Kind::Yeah => Token::new(Kind::True, tok.line()), 51 | Kind::Nah => Token::new(Kind::False, tok.line()), 52 | _ => panic!("This should not happen"), 53 | }; 54 | 55 | if nah_yeah_count < 2 { 56 | had_error = true; 57 | eprintln!("{}", LexError::TooLittleNahYeahs(tok.line())); 58 | } 59 | 60 | last_nah_yeah = None; 61 | nah_yeah_count = 0; 62 | tokens.push(tok); 63 | 64 | continue; 65 | } 66 | } 67 | _ => {} 68 | } 69 | 70 | if last_nah_yeah.is_some() { 71 | had_error = true; 72 | eprintln!( 73 | "{}", 74 | LexError::Expected("!".into(), tok.kind().literal(), tok.line()) 75 | ); 76 | } 77 | 78 | tokens.push(tok); 79 | } 80 | Err(e) => { 81 | had_error = true; 82 | eprintln!("{}", e); 83 | } 84 | } 85 | } 86 | 87 | (tokens, had_error) 88 | } 89 | 90 | fn next_token(&mut self) -> Result { 91 | self.eat_whitespace(); 92 | 93 | let ch = match self.next() { 94 | Some(ch) => ch, 95 | None => return Ok(Token::new(Kind::EOF, self.line)), 96 | }; 97 | 98 | let kind: Kind = match ch { 99 | '%' => Kind::Modulo, 100 | '~' => Kind::Tilde, 101 | '?' => Kind::QuestionMark, 102 | '[' => Kind::LeftBracket, 103 | ']' => Kind::RightBracket, 104 | '(' => Kind::LeftParen, 105 | ')' => Kind::RightParen, 106 | ',' => Kind::Comma, 107 | '+' => Kind::Plus, 108 | '-' => Kind::Minus, 109 | '*' => Kind::Asterisk, 110 | ';' => Kind::Semicolon, 111 | 112 | '=' => { 113 | if self.peek_adv('=') { 114 | Kind::Equals 115 | } else { 116 | Kind::Assign 117 | } 118 | } 119 | '/' => { 120 | if self.peek_adv('/') { 121 | self.eat_line(); 122 | return self.next_token(); 123 | } else { 124 | Kind::Slash 125 | } 126 | } 127 | '!' => { 128 | if self.peek_adv('=') { 129 | Kind::BangEqual 130 | } else { 131 | Kind::Bang 132 | } 133 | } 134 | '<' => { 135 | if self.peek_adv('=') { 136 | Kind::LTE 137 | } else { 138 | Kind::LeftBoomerang 139 | } 140 | } 141 | '>' => { 142 | if self.peek_adv('=') { 143 | Kind::GTE 144 | } else { 145 | Kind::RightBoomerang 146 | } 147 | } 148 | '&' => { 149 | if self.peek_adv('&') { 150 | Kind::And 151 | } else { 152 | return Err(LexError::ExpectedCharacter( 153 | '&', 154 | self.peek().unwrap_or_default(), 155 | self.line, 156 | ) 157 | .into()); 158 | } 159 | } 160 | '|' => { 161 | if self.peek_adv('|') { 162 | Kind::Or 163 | } else { 164 | return Err(LexError::ExpectedCharacter( 165 | '|', 166 | self.peek().unwrap_or_default(), 167 | self.line, 168 | ) 169 | .into()); 170 | } 171 | } 172 | c => match c.to_ascii_lowercase() { 173 | // Please keep these alphabetical 174 | 'b' if self.peek_is('a') => self.eat_keyword_or_ident(c, Kind::Bail)?, 175 | 'b' if self.peek_is('u') => self.eat_keyword_or_ident(c, Kind::BuggerAll)?, 176 | 'c' => self.eat_keyword_or_ident(c, Kind::Cheers)?, 177 | 'f' if self.peek_is('u') => self.eat_keyword_or_ident(c, Kind::FuckinPiker)?, 178 | 'f' if self.peek_is('r') => self.eat_keyword_or_ident(c, Kind::From)?, 179 | 'g' if self.peek_is('i') => self.eat_keyword_or_ident(c, Kind::Gimme)?, 180 | 'g' if self.peek_is('o') => self.eat_keyword_or_ident(c, Kind::GoodOnYa)?, 181 | 'g' if self.peek_is('\'') => self.eat_keyword_or_ident(c, Kind::GdayMate)?, 182 | 'i' if self.peek_is('m') => self.eat_keyword_or_ident(c, Kind::Import)?, 183 | 'i' if self.peek_is('\'') => self.eat_keyword_or_ident(c, Kind::IllHaveA)?, 184 | 'i' if self.peek_is(' ') => self.eat_reckon_or_fully_reckon(c)?, 185 | 'i' if self.peek_is('s') => self.eat_is_or_isa(c)?, 186 | 'm' => self.eat_keyword_or_ident(c, Kind::MateFuckThis)?, 187 | 'n' => self.eat_nah_or_yeah_or_ident(c, Kind::Nah)?, 188 | 'o' => { 189 | if let Some(tok) = self.eat_block_comment_or_ident(c)? { 190 | tok 191 | } else { 192 | return self.next_token(); 193 | } 194 | } 195 | 'p' => self.eat_keyword_or_ident(c, Kind::PullYaHeadIn)?, 196 | 't' if self.peek_is('o') => self.eat_keyword_or_ident(c, Kind::To)?, 197 | 't' if self.peek_is('h') => self.eat_keyword_or_ident(c, Kind::HardYakkaFor)?, 198 | 'u' => self.eat_keyword_or_ident(c, Kind::Until)?, 199 | 'w' if self.peek_is('a') => self.eat_keyword_or_ident(c, Kind::Walkabout)?, 200 | 'w' if self.peek_is('h') => self.eat_keyword_or_ident(c, Kind::Whatabout)?, 201 | 'y' if self.peek_is('a') => self.eat_keyword_or_ident(c, Kind::YaReckon)?, 202 | 'y' if self.peek_is('e') => self.eat_nah_or_yeah_or_ident(c, Kind::Yeah)?, 203 | '"' => self.eat_string()?, 204 | 205 | _ => { 206 | if c.is_digit(10) { 207 | self.eat_number(c)? 208 | } else if c == '_' || c.is_alphabetic() { 209 | self.eat_identifier(c)? 210 | } else { 211 | return Err(LexError::UnexpectedCharacter(c, self.line).into()); 212 | } 213 | } 214 | }, 215 | }; 216 | 217 | Ok(Token::new(kind, self.line)) 218 | } 219 | 220 | fn eat_number(&mut self, first: char) -> Result { 221 | let mut s = String::from(first); 222 | let mut has_decimal = false; 223 | 224 | while let Some(peek) = self.peek() { 225 | if peek.is_digit(10) { 226 | let _ = self.next(); 227 | s.push(peek); 228 | } else if peek == '.' { 229 | if has_decimal { 230 | return Err(LexError::InvalidNumber(self.line).into()); 231 | } 232 | has_decimal = true; 233 | s.push(peek); 234 | let _ = self.next(); 235 | } else { 236 | break; 237 | } 238 | } 239 | 240 | if let Ok(f) = s.parse::() { 241 | return Ok(Kind::Number(f)); 242 | } 243 | 244 | Err(LexError::InvalidNumber(self.line).into()) 245 | } 246 | 247 | fn eat_string(&mut self) -> Result { 248 | let mut s = String::new(); 249 | let mut ended = false; 250 | 251 | while let Some(next) = self.next() { 252 | match next { 253 | '"' => { 254 | ended = true; 255 | break; 256 | } 257 | ch => { 258 | if ch == '\n' { 259 | self.line += 1; 260 | } 261 | s.push(ch); 262 | } 263 | } 264 | } 265 | 266 | if !ended { 267 | return Err(LexError::UnterminatedString(self.line).into()); 268 | } 269 | 270 | Ok(Kind::String(s)) 271 | } 272 | 273 | fn eat_identifier(&mut self, first: char) -> Result { 274 | let mut s: String = first.into(); 275 | loop { 276 | match self.peek() { 277 | None => break, 278 | Some(c) => { 279 | if c.is_digit(10) || c.is_alphabetic() || c == '_' { 280 | s.push(c); 281 | let _ = self.next(); 282 | } else { 283 | break; 284 | } 285 | } 286 | } 287 | } 288 | 289 | // Space, new-line, or semi-colon must separate token 290 | self.expect_separator()?; 291 | 292 | Ok(Kind::Ident(s)) 293 | } 294 | 295 | fn is_separator(c: Option) -> bool { 296 | matches!( 297 | c, 298 | Some(' ' | '\n' | ';' | ',' | '(' | ')' | '[' | ']') | None 299 | ) 300 | } 301 | 302 | fn expect_separator(&mut self) -> Result<()> { 303 | if Self::is_separator(self.peek()) { 304 | return Ok(()); 305 | } 306 | 307 | Err(LexError::ExpectedCharacters( 308 | vec![' ', '\n', ';', ','], 309 | self.peek().unwrap_or_default(), 310 | self.line, 311 | ) 312 | .into()) 313 | } 314 | 315 | fn eat_whitespace(&mut self) { 316 | while let Some(peek) = self.peek() { 317 | if peek == '\n' { 318 | self.line += 1; 319 | let _ = self.next(); 320 | } else if peek.is_whitespace() { 321 | let _ = self.next(); 322 | } else { 323 | break; 324 | } 325 | } 326 | } 327 | 328 | fn eat_line(&mut self) { 329 | while let Some(c) = self.peek() { 330 | let _ = self.next(); 331 | if c == '\n' { 332 | self.line += 1; 333 | break; 334 | } 335 | } 336 | } 337 | 338 | fn eat_is_or_isa(&mut self, first: char) -> Result { 339 | match self.eat_keyword_or_ident(first, Kind::Isa)? { 340 | Kind::Isa => Ok(Kind::Isa), 341 | Kind::Ident(maybe_is) if maybe_is.to_ascii_lowercase() == "is" => Ok(Kind::Is), 342 | ident => Ok(ident), 343 | } 344 | } 345 | 346 | fn eat_block_comment_or_ident(&mut self, first: char) -> Result> { 347 | match self.eat_keyword_or_ident(first, Kind::OiMate) { 348 | Err(_) => Ok(Some(self.eat_identifier(first)?)), 349 | Ok(_) => { 350 | self.eat_block_comment()?; 351 | Ok(None) 352 | } 353 | } 354 | } 355 | 356 | fn eat_block_comment(&mut self) -> Result<()> { 357 | loop { 358 | match self.next().map(|c| c.to_ascii_lowercase()) { 359 | None => break, 360 | Some('\n') => { 361 | self.line += 1; 362 | } 363 | Some('g') => { 364 | if self.eat_keyword(Kind::GotIt, true).is_ok() { 365 | break; 366 | } 367 | } 368 | _ => {} 369 | } 370 | } 371 | Ok(()) 372 | } 373 | 374 | fn eat_nah_or_yeah_or_ident(&mut self, first: char, kind: Kind) -> Result { 375 | let res: Result = match self.eat_keyword(kind, false) { 376 | Err(_) => self.eat_identifier(first), 377 | Ok(kind) => { 378 | self.peek_adv(','); 379 | Ok(kind) 380 | } 381 | }; 382 | res 383 | } 384 | 385 | fn eat_reckon_or_fully_reckon(&mut self, first: char) -> Result { 386 | if self.peek_n_is(2, 'f', false) { 387 | self.eat_keyword_or_ident(first, Kind::IFullyReckon) 388 | } else { 389 | self.eat_keyword_or_ident(first, Kind::IReckon) 390 | } 391 | } 392 | 393 | fn eat_keyword_or_ident(&mut self, first: char, kind: Kind) -> Result { 394 | let res: Result = match self.eat_keyword(kind, true) { 395 | Err(_) => self.eat_identifier(first), 396 | Ok(kind) => Ok(kind), 397 | }; 398 | res 399 | } 400 | 401 | fn eat_keyword(&mut self, kind: Kind, expect_separator: bool) -> Result { 402 | let after_first: String = kind.literal().chars().skip(1).collect(); 403 | 404 | let n = self.is_any_str(&after_first)?; 405 | 406 | if expect_separator { 407 | self.expect_separator()?; 408 | } 409 | 410 | // Eat peeked chars 411 | self.eat_n(n); 412 | 413 | Ok(kind) 414 | } 415 | 416 | fn is_any_str(&mut self, s: &str) -> Result { 417 | let len = s.len(); 418 | 419 | let mut expected: char; 420 | 421 | for i in 0..len { 422 | expected = s.chars().nth(i).unwrap(); 423 | match self.peek_multi() { 424 | None => { 425 | self.src.reset_peek(); 426 | return Err(LexError::ExpectedCharacter(expected, '\0', self.line).into()); 427 | } 428 | Some(c) => { 429 | if c.to_ascii_lowercase().ne(&expected) { 430 | self.src.reset_peek(); 431 | return Err(LexError::ExpectedCharacter(expected, c, self.line).into()); 432 | } 433 | } 434 | }; 435 | } 436 | 437 | Ok(len) 438 | } 439 | 440 | fn eat_n(&mut self, n: usize) { 441 | for _ in 0..n { 442 | let _ = self.next(); 443 | } 444 | } 445 | } 446 | 447 | // General utilities 448 | impl<'a, T: Source> Lexer { 449 | fn next(&mut self) -> Option { 450 | self.src.next() 451 | } 452 | 453 | fn peek(&mut self) -> Option { 454 | let res = self.src.peek().copied(); 455 | self.src.reset_peek(); 456 | res 457 | } 458 | 459 | fn peek_is(&mut self, c: char) -> bool { 460 | match self.peek() { 461 | Some(ch) => ch.to_ascii_lowercase().eq(&c), 462 | None => false, 463 | } 464 | } 465 | 466 | fn peek_n(&mut self, n: usize) -> Option { 467 | for _ in 0..(n - 1) { 468 | let _ = self.peek_multi(); 469 | } 470 | let c = self.peek_multi(); 471 | self.src.reset_peek(); 472 | c 473 | } 474 | 475 | fn peek_n_is(&mut self, n: usize, c: char, case_sensitive: bool) -> bool { 476 | if let Some(ch) = self.peek_n(n) { 477 | if case_sensitive { 478 | ch == c 479 | } else { 480 | ch.to_ascii_lowercase() == c 481 | } 482 | } else { 483 | false 484 | } 485 | } 486 | 487 | /// Can be called multiple times to peek more than one 488 | /// character ahead 489 | fn peek_multi(&mut self) -> Option { 490 | self.src.peek().copied() 491 | } 492 | 493 | fn peek_adv(&mut self, c: char) -> bool { 494 | match self.peek() { 495 | Some(ch) => { 496 | if ch == c { 497 | let _ = self.next(); 498 | true 499 | } else { 500 | false 501 | } 502 | } 503 | None => false, 504 | } 505 | } 506 | } 507 | 508 | #[derive(Error, Debug)] 509 | pub enum LexError { 510 | #[error("[line {0}] OI MATE! YA NEED AT LEAST 2 'NAH's or 'YEAH's TO MAKE A BOOL!!!")] 511 | TooLittleNahYeahs(usize), 512 | #[error("[line {2}] OI MATE! expected {0} but got {1}")] 513 | Expected(String, String, usize), 514 | #[error("[line {2}] OI MATE! expected {0} but got {1}")] 515 | ExpectedCharacter(char, char, usize), 516 | #[error("[line {2}] FUCK ME DEAD! EXPECTED ONE OF {0:?} BUT GOT {1}")] 517 | ExpectedCharacters(Vec, char, usize), 518 | #[error("[line {0}] STREWTH! unexpected EOF")] 519 | UnexpectedEOF(usize), 520 | #[error("[line {1}] BLOODY HELL! UNEXPECTED CHARACTER {0}")] 521 | UnexpectedCharacter(char, usize), 522 | #[error("[line {0}] UNTERMINATED STRING YA FUCKWIT!")] 523 | UnterminatedString(usize), 524 | #[error("[line {0}] OI BLUDGER! INVALID NUMBER")] 525 | InvalidNumber(usize), 526 | } 527 | -------------------------------------------------------------------------------- /src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; 2 | pub mod source; 3 | pub use lexer::*; 4 | -------------------------------------------------------------------------------- /src/lexer/source.rs: -------------------------------------------------------------------------------- 1 | use super::super::upside_down::rightside_up; 2 | use std::{iter::Rev, str::Chars}; 3 | 4 | use itertools::{multipeek, MultiPeek}; 5 | 6 | pub trait Source: Iterator { 7 | fn reset_peek(&mut self); 8 | fn peek(&mut self) -> Option<&char>; 9 | } 10 | 11 | pub struct Regular<'a> { 12 | src: MultiPeek>, 13 | } 14 | 15 | impl<'a> Regular<'a> { 16 | pub fn new(src: Chars<'a>) -> Self { 17 | Self { 18 | src: multipeek(src), 19 | } 20 | } 21 | } 22 | 23 | impl<'a> Source for Regular<'a> { 24 | fn reset_peek(&mut self) { 25 | self.src.reset_peek() 26 | } 27 | 28 | fn peek(&mut self) -> Option<&char> { 29 | self.src.peek() 30 | } 31 | } 32 | 33 | impl<'a> Iterator for Regular<'a> { 34 | type Item = char; 35 | 36 | fn next(&mut self) -> Option { 37 | self.src.next() 38 | } 39 | } 40 | 41 | pub struct UpsideDown<'a> { 42 | src: MultiPeek>>, 43 | dummy: Option, 44 | } 45 | 46 | impl<'a> UpsideDown<'a> { 47 | pub fn new(src: Chars<'a>) -> Self { 48 | Self { 49 | src: multipeek(src.rev()), 50 | dummy: None, 51 | } 52 | } 53 | } 54 | 55 | impl<'a> Source for UpsideDown<'a> { 56 | fn reset_peek(&mut self) { 57 | self.src.reset_peek() 58 | } 59 | 60 | fn peek(&mut self) -> Option<&char> { 61 | match self.src.peek() { 62 | None => None, 63 | Some(v) => { 64 | let _ = self.dummy.insert(rightside_up(*v)); 65 | self.dummy.as_ref().take() 66 | } 67 | } 68 | } 69 | } 70 | 71 | impl<'a> Iterator for UpsideDown<'a> { 72 | type Item = char; 73 | 74 | fn next(&mut self) -> Option { 75 | self.src.next().map(rightside_up) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use parser::parser::Parser; 3 | use resolver::Resolver; 4 | use runtime::Interpreter; 5 | 6 | pub mod ast; 7 | pub mod lexer; 8 | pub mod parser; 9 | pub mod resolver; 10 | pub mod runtime; 11 | pub mod token; 12 | pub mod upside_down; 13 | 14 | pub fn interpret(src: &str) -> Result<()> { 15 | let mut lex = lexer::Lexer::new(lexer::source::Regular::new(src.chars())); 16 | let (tokens, _) = lex.lex(); 17 | 18 | let mut parser = parser::parser::Parser::new(tokens); 19 | let mut stmts = parser.parse()?; 20 | 21 | if Resolver::new().resolve(&mut stmts) { 22 | return Ok(()); 23 | } 24 | 25 | let mut iptr = Interpreter::new(); 26 | iptr.interpret(stmts)?; 27 | 28 | Ok(()) 29 | } 30 | 31 | pub fn interpret_repl(src: &str, interpreter: &mut Interpreter, parser: &mut Parser) -> Result<()> { 32 | let mut lex = lexer::Lexer::new(lexer::source::Regular::new(src.chars())); 33 | let (tokens, _) = lex.lex(); 34 | 35 | parser.reset(tokens); 36 | let mut stmts = parser.parse()?; 37 | 38 | if Resolver::new().resolve(&mut stmts) { 39 | return Ok(()); 40 | } 41 | 42 | interpreter.interpret(stmts) 43 | } 44 | 45 | pub fn interpret_upside_down(src: &str) -> Result<()> { 46 | let mut lex = lexer::Lexer::new(lexer::source::UpsideDown::new(src.chars())); 47 | let (tokens, _) = lex.lex(); 48 | 49 | let mut parser = parser::parser::Parser::new(tokens); 50 | let mut stmts = parser.parse()?; 51 | 52 | if Resolver::new().resolve(&mut stmts) { 53 | return Ok(()); 54 | } 55 | 56 | let mut iptr = Interpreter::new(); 57 | iptr.interpret(stmts)?; 58 | 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "emscripten")] 2 | use std::{ffi::CString, mem, os::raw::c_char}; 3 | 4 | #[cfg(not(target_os = "emscripten"))] 5 | use std::{ 6 | fs, 7 | io::{self, BufRead}, 8 | path::PathBuf, 9 | }; 10 | #[cfg(not(target_os = "emscripten"))] 11 | use structopt::StructOpt; 12 | #[cfg(not(target_os = "emscripten"))] 13 | #[derive(StructOpt, Debug)] 14 | #[structopt(name = "aussie++")] 15 | struct Opt { 16 | /// Path to input file 17 | #[structopt(name = "File", parse(from_os_str))] 18 | filepath: Option, 19 | } 20 | #[cfg(not(target_os = "emscripten"))] 21 | fn main() { 22 | let opt = Opt::from_args(); 23 | let code: String; 24 | if let Some(filepath) = opt.filepath { 25 | code = fs::read_to_string(filepath).expect("failed to read file"); 26 | aussie_plus_plus::interpret(code.as_str()).unwrap(); 27 | println!("CHEERS C***!"); 28 | return; 29 | } 30 | 31 | let stdin = io::stdin(); 32 | let mut i = aussie_plus_plus::runtime::Interpreter::new(); 33 | let mut p = aussie_plus_plus::parser::parser::Parser::new(vec![]); 34 | for line in stdin.lock().lines() { 35 | let line = line.unwrap(); 36 | match aussie_plus_plus::interpret_repl(line.as_str(), &mut i, &mut p) { 37 | Ok(stmts) => stmts, 38 | Err(_) => { 39 | // eprintln!("Failed to run: {}", e); 40 | return; 41 | } 42 | }; 43 | } 44 | println!("CHEERS C***!"); 45 | } 46 | 47 | #[cfg(target_os = "emscripten")] 48 | fn main() {} 49 | 50 | #[cfg(target_os = "emscripten")] 51 | /// # Safety 52 | #[no_mangle] 53 | pub unsafe extern "C" fn interpret(src: *mut c_char, upside_down: bool) -> usize { 54 | let code = CString::from_raw(src).to_str().unwrap().to_string(); 55 | if !upside_down { 56 | let _ = aussie_plus_plus::interpret(&code); 57 | } else { 58 | let _ = aussie_plus_plus::interpret_upside_down(&code); 59 | } 60 | 0 61 | } 62 | 63 | #[cfg(target_os = "emscripten")] 64 | /// # Safety 65 | /// `src` must point to memory previously allocated. The function will consume 66 | /// the pointer and return a new pointer to the flipped text. 67 | #[no_mangle] 68 | pub unsafe extern "C" fn flip_text( 69 | src: *mut c_char, 70 | len_ptr: *mut usize, 71 | upside_down: bool, 72 | ) -> *const c_char { 73 | use std::ptr::null; 74 | 75 | let code = CString::from_raw(src).to_str().unwrap().to_string(); 76 | 77 | // Allocate new string because flipping orientation may change byte length 78 | let output: String = if upside_down { 79 | code.chars() 80 | .rev() 81 | .map(aussie_plus_plus::upside_down::upside_down) 82 | .collect() 83 | } else { 84 | code.chars() 85 | .rev() 86 | .map(aussie_plus_plus::upside_down::rightside_up) 87 | .collect() 88 | }; 89 | 90 | let output = match CString::new(output) { 91 | Ok(cstr) => cstr, 92 | Err(e) => { 93 | eprintln!("Failed to make cstring: {:?}", e); 94 | return null(); 95 | } 96 | }; 97 | 98 | *len_ptr = output.to_bytes().len(); 99 | 100 | let ptr = output.as_ptr(); 101 | mem::forget(output); 102 | 103 | ptr 104 | } 105 | 106 | #[cfg(target_os = "emscripten")] 107 | #[no_mangle] 108 | pub extern "C" fn alloc(len: usize) -> *mut u8 { 109 | let mut buf: Vec = Vec::with_capacity(len); 110 | 111 | let ptr = buf.as_mut_ptr(); 112 | 113 | mem::forget(buf); 114 | 115 | ptr 116 | } 117 | 118 | #[cfg(target_os = "emscripten")] 119 | /// # Safety 120 | #[no_mangle] 121 | pub unsafe extern "C" fn dealloc(ptr: *mut u8, len: usize) { 122 | let data = Vec::from_raw_parts(ptr, len, len); 123 | 124 | mem::drop(data) 125 | } 126 | -------------------------------------------------------------------------------- /src/parser/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | use crate::token::{Kind, Token}; 4 | 5 | #[derive(Error, Debug)] 6 | pub enum ParseError { 7 | #[error("[line {0}] {1}")] 8 | Any(usize, String), 9 | #[error("[line {2}] OI MATE! EXPECTED {0} BUT GOT '{1}'")] 10 | UnexpectedToken(Kind, Kind, usize), 11 | #[error("[line {2}] OI MATE! EXPECTED {0:?} BUT GOT '{1}'")] 12 | ExpectedTokens(Vec, Kind, usize), 13 | #[error("[line {}] MISSING EXPR ENDED WITH '{}'", .0.line(), .0.kind())] 14 | MissingExpr(Token), 15 | #[error("[line {}] HEY FUCKWIT! WHY YA FACKIN TRYNA ASSIGN TO A {}", .0.line(), .0.kind())] 16 | InvalidAssigment(Token), 17 | #[error("[line {0}] MATE, THAT'S TOO MANY ARGUMENTS (max 255)")] 18 | TooManyArguments(usize), 19 | #[error("[line {0}] TOO MANY DEFAULT BRANCHES IN MATCH STATEMENT, YA DAFT BUGGER")] 20 | TooManyMatchDefaultBranches(usize), 21 | #[error("[line {0}] CAN YA FUKING COUNT, MATE? INVALID RANGE {1} {2}")] 22 | InvalidRange(usize, String, String), 23 | #[error("[line {0}] EXPECTED NUMBER, STRING, BOOLEAN, NIL, OR IDENTIFIER BUT GOT '{1}'")] 24 | ExpectPrimary(usize, Kind), 25 | #[error("YA DAFT BUGGER! YA DIDN'T WRITE \"G'DAY MATE!\" TO START PROGRAM!!")] 26 | ExpectProgramStart, 27 | #[error("[line {0}] OI CUNT! INVALID WHATABOUT, NOT IN AN A RECKON YA BLUDGER!")] 28 | InvalidWhatabout(usize), 29 | } 30 | -------------------------------------------------------------------------------- /src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod parser; 3 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, mem, rc::Rc}; 2 | 3 | use crate::{ 4 | ast::{ 5 | Expr, ExprNode, FnDecl, ForLoop, Ident, If, Match, Pattern, Stmt, UnaryOp, Var as AstVar, 6 | VarDecl, 7 | }, 8 | token::Token, 9 | }; 10 | 11 | macro_rules! with_scope { 12 | ($self:ident, $code:tt) => { 13 | $self.begin_scope(); 14 | $code; 15 | $self.end_scope(); 16 | }; 17 | } 18 | 19 | enum FunctionKind { 20 | None, 21 | Function, 22 | } 23 | 24 | struct Var { 25 | // To prevent reading a variable in its initializer 26 | in_initializer: bool, 27 | immutable: bool, 28 | } 29 | 30 | pub struct Resolver { 31 | scopes: Vec, Var>>, 32 | had_error: bool, 33 | cur_fn: FunctionKind, 34 | } 35 | 36 | impl Resolver { 37 | pub fn new() -> Self { 38 | Self { 39 | scopes: vec![HashMap::new()], 40 | had_error: false, 41 | cur_fn: FunctionKind::None, 42 | } 43 | } 44 | 45 | pub fn resolve(&mut self, stmts: &mut [Stmt]) -> bool { 46 | for stmt in stmts { 47 | self.stmt(stmt); 48 | } 49 | 50 | self.had_error 51 | } 52 | 53 | fn stmt(&mut self, stmt: &mut Stmt) { 54 | match stmt { 55 | Stmt::Block(stmts) => self.block_stmt(stmts), 56 | Stmt::VarDecl(VarDecl { 57 | ident, 58 | initializer, 59 | immutable, 60 | }) => self.var_stmt(ident, initializer, *immutable), 61 | Stmt::FnDecl(decl) => self.func_stmt(decl, FunctionKind::Function), 62 | Stmt::If(If { cond, then, else_ }) => self.if_stmt(cond, then, else_), 63 | Stmt::Print(expr) => self.print_stmt(expr), 64 | Stmt::Return(tok, expr) => self.ret_stmt(tok, expr), 65 | Stmt::While(node) => self.while_stmt(&mut node.cond, &mut node.body), 66 | Stmt::For(for_loop) => self.for_stmt(for_loop), 67 | Stmt::Match(match_) => self.match_stmt(match_), 68 | Stmt::Import(ident) => self.import_stmt(ident), 69 | Stmt::Break(_) => {} 70 | Stmt::Exit(_) => {} 71 | Stmt::Expr(expr) => self.expr(expr.expr_mut()), 72 | } 73 | } 74 | 75 | fn block_stmt(&mut self, stmts: &mut [Stmt]) { 76 | self.begin_scope(); 77 | self.resolve(stmts); 78 | self.end_scope(); 79 | } 80 | 81 | fn var_stmt(&mut self, name: &Ident, init: &mut Option, constant: bool) { 82 | self.declare(name, constant); 83 | if let Some(init) = init { 84 | self.expr(init.expr_mut()); 85 | } 86 | self.define(name); 87 | } 88 | 89 | fn func_stmt(&mut self, decl: &mut FnDecl, kind: FunctionKind) { 90 | self.declare(&decl.ident, true); 91 | self.define(&decl.ident); 92 | 93 | self.resolve_fn(decl, kind) 94 | } 95 | 96 | fn if_stmt(&mut self, cond: &mut ExprNode, then: &mut Stmt, elze: &mut Option>) { 97 | self.expr(cond.expr_mut()); 98 | self.stmt(then); 99 | 100 | if let Some(e) = elze { 101 | self.stmt(e); 102 | } 103 | } 104 | 105 | fn print_stmt(&mut self, expr: &mut ExprNode) { 106 | self.expr(expr.expr_mut()); 107 | } 108 | 109 | fn ret_stmt(&mut self, tok: &mut Token, expr: &mut Option) { 110 | if let FunctionKind::None = self.cur_fn { 111 | self.print_error( 112 | tok.line(), 113 | &tok.kind.to_string(), 114 | "YA CAN ONLY RETURN IN FUNCTIONS DUMMY!", 115 | ); 116 | } 117 | if let Some(expr) = expr { 118 | self.expr(expr.expr_mut()); 119 | } 120 | } 121 | 122 | fn while_stmt(&mut self, cond: &mut ExprNode, body: &mut Vec) { 123 | self.expr(cond.expr_mut()); 124 | body.iter_mut().for_each(|stmt| self.stmt(stmt)); 125 | } 126 | 127 | fn for_stmt(&mut self, for_loop: &mut ForLoop) { 128 | self.expr(for_loop.range.0.expr_mut().expr_mut()); 129 | self.expr(for_loop.range.1.expr_mut().expr_mut()); 130 | 131 | with_scope!(self, { 132 | self.declare(&for_loop.var.ident, false); 133 | self.define(&for_loop.var.ident()); 134 | 135 | for_loop.body.iter_mut().for_each(|stmt| { 136 | self.stmt(stmt); 137 | }); 138 | }); 139 | } 140 | 141 | fn match_stmt(&mut self, match_: &mut Match) { 142 | self.expr(match_.val.expr_mut()); 143 | 144 | match_.branches.iter_mut().for_each(|branch| { 145 | self.block_stmt(&mut branch.body); 146 | }); 147 | 148 | if let Some(default) = &mut match_.default { 149 | if let Pattern::Var(var) = &mut default.pat { 150 | with_scope!(self, { 151 | self.declare(&var.ident, false); 152 | self.define(&var.ident()); 153 | default.body.iter_mut().for_each(|stmt| { 154 | self.stmt(stmt); 155 | }); 156 | }); 157 | } 158 | } 159 | } 160 | 161 | fn import_stmt(&mut self, name: &Ident) { 162 | self.declare(name, true); 163 | self.define(name); 164 | } 165 | 166 | fn declare(&mut self, ident: &Ident, immutable: bool) { 167 | let mut exists = false; 168 | let name = &ident.name; 169 | 170 | if let Some(scope) = self.scopes.last_mut() { 171 | if scope.contains_key(name) { 172 | exists = true; 173 | } 174 | scope.insert( 175 | name.clone(), 176 | Var { 177 | in_initializer: false, 178 | immutable, 179 | }, 180 | ); 181 | } 182 | 183 | if exists { 184 | self.print_error( 185 | ident.line(), 186 | name, 187 | "WAKE UP FUCK-WIT! A VARIABLE WITH THAT NAME ALREADY EXISTS IN THIS SCOPE.", 188 | ) 189 | } 190 | } 191 | 192 | fn define(&mut self, name: &Ident) { 193 | if let Some(scope) = self.scopes.last_mut() { 194 | if let Some(v) = scope.get_mut(&name.name) { 195 | v.in_initializer = true; 196 | } else { 197 | self.print_error(name.line(), &name.name, "CAN'T DEFINE AN UNDECLARED VAR") 198 | } 199 | } 200 | } 201 | 202 | fn begin_scope(&mut self) { 203 | self.scopes.push(HashMap::new()) 204 | } 205 | 206 | fn end_scope(&mut self) { 207 | self.scopes.pop(); 208 | } 209 | 210 | fn resolve_local(&mut self, var: &mut AstVar) -> Option<&mut Var> { 211 | for (i, scope) in self.scopes.iter_mut().rev().enumerate() { 212 | if let Some(v) = scope.get_mut(&*var.name()) { 213 | var.scope_distance = i; 214 | return Some(v); 215 | } 216 | } 217 | 218 | // Bug in borrow checker won't allow the code below to compile so just paste it in here for now 219 | self.had_error = true; 220 | eprintln!( 221 | "[line {}] {}: CAAARN! THAT VAR ISN'T DEFINED YA DAFT BUGGER!", 222 | var.line(), 223 | var.name() 224 | ); 225 | // self.print_error( 226 | // var.line(), 227 | // var.name(), 228 | // "CAAARN! THAT VAR ISN'T DEFINED YA DAFT BUGGER!", 229 | // ); 230 | 231 | None 232 | } 233 | 234 | fn resolve_fn(&mut self, decl: &mut FnDecl, kind: FunctionKind) { 235 | let enclosing_fn = mem::replace(&mut self.cur_fn, kind); 236 | 237 | self.begin_scope(); 238 | decl.params.iter().for_each(|param| { 239 | self.declare(param, false); 240 | self.define(param); 241 | }); 242 | self.block_stmt(&mut decl.body); 243 | self.end_scope(); 244 | 245 | self.cur_fn = enclosing_fn; 246 | } 247 | } 248 | 249 | impl Default for Resolver { 250 | fn default() -> Self { 251 | Self::new() 252 | } 253 | } 254 | 255 | impl Resolver { 256 | fn expr(&mut self, expr: &mut Expr) { 257 | match expr { 258 | Expr::Var(v) => self.expr_var(v), 259 | Expr::Assign(a, init) => self.expr_assign(a, init), 260 | Expr::Binary(left, _, right) => { 261 | self.expr(left.expr_mut()); 262 | self.expr(right.expr_mut()); 263 | } 264 | Expr::Call(callee, _, args) => { 265 | self.expr(callee.expr_mut()); 266 | args.iter_mut().for_each(|arg| self.expr(arg.expr_mut())); 267 | } 268 | Expr::Grouping(expr) => self.expr(expr.expr_mut()), 269 | Expr::Literal(_) => {} 270 | Expr::Logical(left, _, right) => { 271 | self.expr(left.expr_mut()); 272 | self.expr(right.expr_mut()); 273 | } 274 | Expr::Unary(UnaryOp::Incr | UnaryOp::Decr, expr) => self.expr_incr_decr(expr), 275 | Expr::Unary(_, expr) => self.expr(expr.expr_mut()), 276 | } 277 | } 278 | 279 | fn expr_incr_decr(&mut self, expr: &mut ExprNode) { 280 | if let Expr::Var(v) = expr.expr_mut() { 281 | if let Some(var) = self.resolve_local(v) { 282 | if var.immutable { 283 | self.print_error(v.line(), v.name(), "HEY DRONGO, YA CAN'T CHANGE THAT VAR!") 284 | } 285 | } 286 | } else { 287 | self.expr(expr.expr_mut()) 288 | } 289 | } 290 | 291 | fn expr_var(&mut self, var: &mut AstVar) { 292 | let name = var.name(); 293 | if let Some(scope) = self.scopes.last() { 294 | match scope.get(name) { 295 | Some(Var { 296 | in_initializer: initialized, 297 | .. 298 | }) if !initialized => { 299 | return self.print_error( 300 | var.line(), 301 | name, 302 | "FUCK ME DEAD MATE... YOU JUST TRIED TO READ A VARIABLE IN ITS INITIALIZER!", 303 | ); 304 | } 305 | _ => {} 306 | }; 307 | } 308 | 309 | self.resolve_local(var); 310 | } 311 | 312 | fn expr_assign(&mut self, var: &mut AstVar, init: &mut ExprNode) { 313 | self.expr(init.expr_mut()); 314 | if let Some(v) = self.resolve_local(var) { 315 | if v.immutable { 316 | self.print_error(var.line(), var.name(), "OI, YA CAN'T REDEFINE THIS!") 317 | } 318 | } 319 | } 320 | } 321 | 322 | impl Resolver { 323 | fn print_error(&mut self, line: usize, name: &str, msg: &str) { 324 | self.had_error = true; 325 | eprintln!("[line {}] {}: {}", line, name, msg) 326 | } 327 | } 328 | -------------------------------------------------------------------------------- /src/runtime/callable/builtin.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | use std::fmt::Display; 3 | use std::rc::Rc; 4 | use std::{thread, time::Duration}; 5 | 6 | #[cfg(not(target_os = "emscripten"))] 7 | use chrono::offset::TimeZone; 8 | #[cfg(not(target_os = "emscripten"))] 9 | use chrono::Utc; 10 | 11 | use rand::prelude::ThreadRng; 12 | use rand::Rng; 13 | 14 | use crate::runtime::error::RuntimeError; 15 | use crate::runtime::{Interpreter, Value}; 16 | 17 | use super::AussieCallable; 18 | 19 | #[cfg(target_os = "emscripten")] 20 | use std::os::raw::c_char; 21 | #[cfg(target_os = "emscripten")] 22 | extern "C" { 23 | pub fn aussie_time() -> *mut c_char; 24 | } 25 | #[derive(Clone, PartialEq, Debug)] 26 | pub enum BuiltIn { 27 | Sleep(Sleep), 28 | Time(Time), 29 | Rand(Rand), 30 | } 31 | 32 | impl BuiltIn { 33 | pub fn lookup(name: &str) -> Option { 34 | match name { 35 | "HitTheSack" => Some(BuiltIn::Sleep(Sleep::default())), 36 | "GimmeTime" => Some(BuiltIn::Time(Time::default())), 37 | "ChuckSomeDice" => Some(BuiltIn::Rand(Rand::default())), 38 | _ => None, 39 | } 40 | } 41 | } 42 | 43 | impl AussieCallable for BuiltIn { 44 | fn call(&self, interpreter: &mut Interpreter, args: &[Value]) -> anyhow::Result { 45 | match self { 46 | Self::Sleep(sleep) => sleep.call(interpreter, args), 47 | Self::Time(time) => time.call(interpreter, args), 48 | Self::Rand(rand) => rand.call(interpreter, args), 49 | } 50 | } 51 | 52 | fn arity(&self) -> u8 { 53 | match self { 54 | Self::Sleep(sleep) => sleep.arity(), 55 | Self::Time(time) => time.arity(), 56 | Self::Rand(rand) => rand.arity(), 57 | } 58 | } 59 | 60 | fn name(&self) -> &Rc { 61 | match self { 62 | Self::Sleep(sleep) => sleep.name(), 63 | Self::Time(time) => time.name(), 64 | Self::Rand(rand) => rand.name(), 65 | } 66 | } 67 | } 68 | 69 | impl Display for BuiltIn { 70 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 71 | match self { 72 | Self::Sleep(s) => write!(f, "{}(ms)", s.name()), 73 | Self::Time(t) => write!(f, "{}()", t.name()), 74 | Self::Rand(r) => write!(f, "{}(start, end)", r.name()), 75 | } 76 | } 77 | } 78 | 79 | #[derive(Clone, PartialEq, Debug)] 80 | pub struct Sleep { 81 | name: Rc, 82 | } 83 | 84 | impl Default for Sleep { 85 | fn default() -> Self { 86 | Self { 87 | name: Rc::from("HitTheSack"), 88 | } 89 | } 90 | } 91 | 92 | impl AussieCallable for Sleep { 93 | fn call(&self, _: &mut Interpreter, args: &[Value]) -> anyhow::Result { 94 | let duration = match &args[0] { 95 | Value::Number(n) => *n, 96 | _ => return Err(RuntimeError::General("expected a number".into()).into()), 97 | }; 98 | 99 | if duration < 0.0 { 100 | return Err(RuntimeError::General("expected a number > 0".into()).into()); 101 | } 102 | 103 | thread::sleep(Duration::from_millis(duration as u64)); 104 | 105 | Ok(Value::Nil) 106 | } 107 | 108 | fn arity(&self) -> u8 { 109 | 1 110 | } 111 | 112 | fn name(&self) -> &Rc { 113 | &self.name 114 | } 115 | } 116 | 117 | #[derive(Clone, PartialEq, Debug)] 118 | pub struct Time { 119 | name: Rc, 120 | } 121 | 122 | impl Default for Time { 123 | fn default() -> Self { 124 | Self { 125 | name: Rc::from("GimmeTime"), 126 | } 127 | } 128 | } 129 | 130 | impl AussieCallable for Time { 131 | #[cfg(not(target_os = "emscripten"))] 132 | fn call(&self, _: &mut Interpreter, _: &[Value]) -> anyhow::Result { 133 | let utc = Utc::now().naive_utc(); 134 | let tz = chrono_tz::Australia::Melbourne.from_utc_datetime(&utc); 135 | 136 | Ok(Value::String(tz.to_string())) 137 | } 138 | 139 | #[cfg(target_os = "emscripten")] 140 | fn call(&self, _: &mut Interpreter, _: &[Value]) -> anyhow::Result { 141 | use std::ffi::CString; 142 | 143 | let str = unsafe { 144 | CString::from_raw(aussie_time()) 145 | .to_str() 146 | .unwrap() 147 | .to_string() 148 | }; 149 | 150 | Ok(Value::String(str)) 151 | } 152 | 153 | fn arity(&self) -> u8 { 154 | 0 155 | } 156 | 157 | fn name(&self) -> &Rc { 158 | &self.name 159 | } 160 | } 161 | 162 | #[derive(Clone, Debug)] 163 | pub struct Rand { 164 | name: Rc, 165 | rng: RefCell, 166 | } 167 | 168 | impl PartialEq for Rand { 169 | fn eq(&self, other: &Self) -> bool { 170 | self.name == other.name 171 | } 172 | } 173 | 174 | impl Default for Rand { 175 | fn default() -> Self { 176 | Self { 177 | name: Rc::from("ChuckSomeDice"), 178 | rng: RefCell::new(rand::thread_rng()), 179 | } 180 | } 181 | } 182 | 183 | impl AussieCallable for Rand { 184 | fn call(&self, _: &mut Interpreter, args: &[Value]) -> anyhow::Result { 185 | let (start, end) = match (&args[0], &args[1]) { 186 | (Value::Number(a), Value::Number(b)) => (*a as i64, *b as i64), 187 | _ => { 188 | return Err(RuntimeError::General( 189 | "OI MATE, CAN YA FUCKIN' COUNT?? EXPECTED A NUMBER".into(), 190 | ) 191 | .into()) 192 | } 193 | }; 194 | 195 | if start > end { 196 | return Err(RuntimeError::General( 197 | "OI MATE, CAN YA FUCKIN' COUNT?? START MUST BE LESS THAN END!!".into(), 198 | ) 199 | .into()); 200 | } 201 | 202 | Ok(Value::Number( 203 | self.rng.borrow_mut().gen_range(start..end) as f64 204 | )) 205 | } 206 | 207 | fn arity(&self) -> u8 { 208 | 2 209 | } 210 | 211 | fn name(&self) -> &Rc { 212 | &self.name 213 | } 214 | } 215 | -------------------------------------------------------------------------------- /src/runtime/callable/callable.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, rc::Rc}; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::runtime::{Interpreter, Value}; 6 | 7 | use super::{BuiltIn, Function, UserDefined}; 8 | 9 | pub trait AussieCallable { 10 | fn call(&self, interpreter: &mut Interpreter, args: &[Value]) -> Result; 11 | fn arity(&self) -> u8; 12 | fn name(&self) -> &Rc; 13 | } 14 | 15 | #[derive(Clone, PartialEq, Debug)] 16 | pub enum Callable { 17 | Function(Function), 18 | } 19 | 20 | impl AussieCallable for Callable { 21 | fn call(&self, interpreter: &mut Interpreter, args: &[Value]) -> Result { 22 | match self { 23 | Callable::Function(func) => func.call(interpreter, args), 24 | } 25 | } 26 | 27 | fn arity(&self) -> u8 { 28 | match self { 29 | Callable::Function(func) => func.arity(), 30 | } 31 | } 32 | 33 | fn name(&self) -> &Rc { 34 | match self { 35 | Callable::Function(func) => func.name(), 36 | } 37 | } 38 | } 39 | 40 | impl Display for Callable { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | match self { 43 | Self::Function(func) => func.fmt(f), 44 | } 45 | } 46 | } 47 | 48 | impl From for Callable { 49 | fn from(builtin: BuiltIn) -> Self { 50 | Callable::Function(Function::BuiltIn(builtin)) 51 | } 52 | } 53 | 54 | impl From for Callable { 55 | fn from(user_defined: UserDefined) -> Self { 56 | Callable::Function(Function::UserDefined(Box::new(user_defined))) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/runtime/callable/function.rs: -------------------------------------------------------------------------------- 1 | use std::{cell::RefCell, fmt::Display, rc::Rc}; 2 | 3 | use anyhow::Result; 4 | 5 | use crate::{ 6 | ast::FnDecl, 7 | runtime::{exit::ExitKind, Environment, Interpreter, Value}, 8 | }; 9 | 10 | use super::{AussieCallable, BuiltIn}; 11 | 12 | #[derive(Clone, PartialEq, Debug)] 13 | pub enum Function { 14 | UserDefined(Box), 15 | BuiltIn(BuiltIn), 16 | } 17 | 18 | impl AussieCallable for Function { 19 | fn call(&self, interpreter: &mut Interpreter, args: &[Value]) -> Result { 20 | match self { 21 | Function::UserDefined(func) => func.call(interpreter, args), 22 | Function::BuiltIn(built_in) => built_in.call(interpreter, args), 23 | } 24 | } 25 | 26 | fn arity(&self) -> u8 { 27 | match self { 28 | Function::UserDefined(func) => func.arity(), 29 | Function::BuiltIn(built_in) => built_in.arity(), 30 | } 31 | } 32 | 33 | fn name(&self) -> &Rc { 34 | match self { 35 | Function::UserDefined(func) => func.name(), 36 | Function::BuiltIn(func) => func.name(), 37 | } 38 | } 39 | } 40 | 41 | impl Display for Function { 42 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 43 | match self { 44 | Self::UserDefined(func) => write!(f, "{}", func.to_string()), 45 | Self::BuiltIn(b) => write!(f, "{}", b.to_string()), 46 | } 47 | } 48 | } 49 | 50 | #[derive(Clone, PartialEq, Debug)] 51 | pub struct UserDefined { 52 | decl: FnDecl, 53 | // The closure this function was defined in 54 | env: Rc>, 55 | } 56 | 57 | impl UserDefined { 58 | pub fn new(decl: FnDecl, env: Rc>) -> Self { 59 | Self { decl, env } 60 | } 61 | } 62 | 63 | impl AussieCallable for UserDefined { 64 | fn call(&self, interpreter: &mut Interpreter, args: &[Value]) -> Result { 65 | let mut env = Environment::new_with_enclosing(self.env.clone()); 66 | 67 | for (parameter, value) in self.decl.params.iter().zip(args.iter()) { 68 | env.define(parameter.name.clone(), value.clone()); 69 | } 70 | 71 | if let Some(ExitKind::Return(val)) = interpreter.execute_block( 72 | &self.decl.body, 73 | Rc::new(RefCell::new(Environment::new_with_enclosing(Rc::new( 74 | RefCell::new(env), 75 | )))), 76 | )? { 77 | return Ok(val); 78 | } 79 | 80 | Ok(Value::Nil) 81 | } 82 | 83 | fn arity(&self) -> u8 { 84 | self.decl.params.len() as u8 85 | } 86 | 87 | fn name(&self) -> &Rc { 88 | self.decl.name() 89 | } 90 | } 91 | 92 | impl Display for UserDefined { 93 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 94 | write!(f, "{}", self.decl.to_string()) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/runtime/callable/mod.rs: -------------------------------------------------------------------------------- 1 | mod builtin; 2 | mod callable; 3 | mod function; 4 | pub use builtin::*; 5 | pub use callable::*; 6 | pub use function::*; 7 | -------------------------------------------------------------------------------- /src/runtime/environment.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::RefCell, 3 | collections::{hash_map::Entry, HashMap}, 4 | rc::Rc, 5 | }; 6 | 7 | use ahash::RandomState; 8 | 9 | use super::Value; 10 | 11 | type ValuesMap = HashMap, Value, RandomState>; 12 | // type ValuesMap = HashMap, Value>; 13 | 14 | #[derive(Clone, Debug, PartialEq)] 15 | pub struct Environment { 16 | pub inner: Inner, 17 | } 18 | 19 | impl Environment { 20 | pub fn ancestor( 21 | root: &Rc>, 22 | hops: usize, 23 | ) -> Option>> { 24 | if hops == 0 { 25 | Some(root.clone()) 26 | } else { 27 | match &root.borrow().inner.enclosing { 28 | None => None, 29 | Some(enclosing) => Environment::ancestor(enclosing, hops - 1), 30 | } 31 | } 32 | } 33 | } 34 | 35 | impl Default for Environment { 36 | fn default() -> Self { 37 | Self { 38 | inner: Inner::new(), 39 | } 40 | } 41 | } 42 | 43 | impl Environment { 44 | pub fn new_with_enclosing(inner: Rc>) -> Self { 45 | Self { 46 | inner: Inner::with_enclosing(inner), 47 | } 48 | } 49 | 50 | pub fn get(&self, key: &str) -> Option { 51 | self.inner.get(key) 52 | } 53 | 54 | pub fn assign(&mut self, key: Rc, val: Value) -> bool { 55 | self.inner.assign(key, val) 56 | } 57 | 58 | pub fn define(&mut self, key: Rc, value: Value) { 59 | self.inner.define(key, value); 60 | } 61 | 62 | pub fn clone_values(&self) -> ValuesMap { 63 | self.inner.values.clone() 64 | } 65 | } 66 | 67 | #[derive(Clone, PartialEq, Debug)] 68 | pub struct Inner { 69 | enclosing: Option>>, 70 | pub values: ValuesMap, 71 | } 72 | 73 | impl Inner { 74 | fn new() -> Self { 75 | Self { 76 | enclosing: None, 77 | values: HashMap::default(), 78 | } 79 | } 80 | 81 | fn define(&mut self, name: Rc, value: Value) { 82 | self.values.insert(name, value); 83 | } 84 | 85 | fn get(&self, name: &str) -> Option { 86 | match self.values.get(name) { 87 | None => match &self.enclosing { 88 | None => None, 89 | Some(enclosing) => enclosing.borrow().get(name), 90 | }, 91 | Some(val) => Some(val.clone()), 92 | } 93 | } 94 | 95 | fn assign(&mut self, name: Rc, value: Value) -> bool { 96 | match self.values.entry(name.clone()) { 97 | Entry::Vacant(_) => match &mut self.enclosing { 98 | None => false, 99 | Some(enclosing) => enclosing.borrow_mut().assign(name, value), 100 | }, 101 | Entry::Occupied(mut entry) => { 102 | entry.insert(value); 103 | true 104 | } 105 | } 106 | } 107 | 108 | fn with_enclosing(enclosing: Rc>) -> Self { 109 | Self { 110 | enclosing: Some(enclosing), 111 | values: HashMap::default(), 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/runtime/eq.rs: -------------------------------------------------------------------------------- 1 | pub trait RuntimePartialEq { 2 | fn runtime_eq(&self, other: &Rhs) -> bool; 3 | 4 | fn runtime_ne(&self, other: &Rhs) -> bool { 5 | !self.runtime_eq(other) 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/runtime/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | #[derive(Error, Debug)] 4 | pub enum RuntimeError { 5 | #[error("[{0}] {1}")] 6 | Syntax(usize, String), 7 | #[error("[{0}] INVALID BREAK, FIX IT FUCKWIT.")] 8 | InvalidBreak(usize), 9 | #[error("[{0}] SORRY MATE! YA CAN ONLY CALL FUNCTIONS, YA DAFT BUGGER!")] 10 | InvalidCallee(usize), 11 | #[error("[{0}] OI MATE, CAN YA FUCKIN' COUNT?? EXPECTED {1} ARGUMENTS BUT GOT {2}")] 12 | InvalidArity(usize, u8, usize), 13 | #[error("[{0}] CAN'T FIND THE IMPORT {1}")] 14 | UnknownImport(usize, String), 15 | #[error("{0}")] 16 | General(String), 17 | #[error("[{0}] SORRY C***! '{1}' ISN'T DEFINED, YA DAFT BUGGER!")] 18 | UndefinedVariable(usize, String), 19 | } 20 | 21 | impl RuntimeError { 22 | pub fn new_syntax>(msg: T, line: usize) -> RuntimeError { 23 | RuntimeError::Syntax(line, msg.into()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/runtime/exit.rs: -------------------------------------------------------------------------------- 1 | use super::Value; 2 | 3 | pub enum ExitKind { 4 | Break(usize), 5 | Return(Value), 6 | } 7 | 8 | pub type Exit = Option; 9 | -------------------------------------------------------------------------------- /src/runtime/interpreter.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use arrayvec::ArrayVec; 3 | use itertools::Itertools; 4 | use std::{ 5 | cell::RefCell, 6 | fmt::Arguments, 7 | io::{stdout, Write}, 8 | mem, 9 | ops::Add, 10 | process, 11 | rc::Rc, 12 | }; 13 | 14 | use crate::{ 15 | ast::{ 16 | BinaryOp, Expr, ExprNode, ForLoop, If, LogicalOp, Match, Pattern, Range, Stmt, UnaryOp, 17 | Var, VarDecl, WhileLoop, 18 | }, 19 | parser::error::ParseError, 20 | runtime::AussieCallable, 21 | token::Token, 22 | }; 23 | 24 | use super::{ 25 | environment::Environment, 26 | error::RuntimeError, 27 | exit::{Exit, ExitKind}, 28 | BuiltIn, Callable, RuntimePartialEq, UserDefined, Value, MAX_ARITY, 29 | }; 30 | 31 | pub struct Interpreter<'a> { 32 | writer: Option<&'a mut dyn Write>, 33 | env: Rc>, 34 | } 35 | 36 | impl<'a> Default for Interpreter<'a> { 37 | fn default() -> Self { 38 | Self::new() 39 | } 40 | } 41 | 42 | impl<'a> Interpreter<'a> { 43 | pub fn new() -> Self { 44 | Self { 45 | writer: None, 46 | env: Rc::new(RefCell::new(Environment::default())), 47 | } 48 | } 49 | 50 | pub fn new_with_writer(writer: &'a mut dyn Write) -> Interpreter<'a> { 51 | Interpreter { 52 | writer: Some(writer), 53 | env: Rc::new(RefCell::new(Environment::default())), 54 | } 55 | } 56 | 57 | pub fn env(&self) -> Rc> { 58 | self.env.clone() 59 | } 60 | 61 | fn print(&mut self, args: Arguments<'_>) { 62 | use std::borrow::BorrowMut; 63 | let w = self.writer.borrow_mut(); 64 | if let Some(w) = w { 65 | let _ = writeln!(w, "{}", args).unwrap(); 66 | } 67 | println!("{}", args); 68 | stdout().flush().unwrap(); 69 | } 70 | 71 | pub fn interpret(&mut self, stmts: Vec) -> Result<()> { 72 | for stmt in stmts { 73 | match self.execute_stmt(&stmt) { 74 | Err(e) => eprintln!("{}", e), 75 | Ok(None) => {} 76 | Ok(Some(ExitKind::Break(line))) => { 77 | return Err(RuntimeError::InvalidBreak(line).into()) 78 | } 79 | Ok(Some(_)) => return Ok(()), 80 | }; 81 | } 82 | Ok(()) 83 | } 84 | 85 | fn execute_stmt(&mut self, stmt: &Stmt) -> Result { 86 | match stmt { 87 | Stmt::Import(ident) => { 88 | match BuiltIn::lookup(&ident.name) { 89 | None => { 90 | return Err(RuntimeError::UnknownImport( 91 | ident.line(), 92 | ident.name.to_string(), 93 | ) 94 | .into()); 95 | } 96 | Some(builtin) => self.env.borrow_mut().define( 97 | builtin.name().clone(), 98 | Value::Callable(Rc::new(builtin.into())), 99 | ), 100 | }; 101 | Ok(None) 102 | } 103 | Stmt::Exit(fuckinpiker) => { 104 | if *fuckinpiker { 105 | process::exit(1) 106 | } else { 107 | Ok(Some(ExitKind::Return(Value::Nil))) 108 | } 109 | } 110 | Stmt::Return(_, expr) => match expr { 111 | None => Ok(Some(ExitKind::Return(Value::Nil))), 112 | Some(val) => Ok(Some(ExitKind::Return(self.evaluate(val)?))), 113 | }, 114 | Stmt::FnDecl(fn_decl) => { 115 | let function: Callable = UserDefined::new(fn_decl.clone(), self.env.clone()).into(); 116 | 117 | self.env 118 | .borrow_mut() 119 | .define(fn_decl.name().clone(), Value::Callable(Rc::new(function))); 120 | 121 | Ok(None) 122 | } 123 | Stmt::Break(tok) => Ok(Some(ExitKind::Break(tok.line()))), 124 | Stmt::While(while_loop) => self.execute_while_loop(while_loop), 125 | Stmt::For(for_loop) => self.execute_for_loop(for_loop), 126 | Stmt::Print(expr) => { 127 | let val = self.evaluate(expr)?; 128 | self.print(format_args!("{}", val)); 129 | Ok(None) 130 | } 131 | Stmt::Match(m) => self.execute_match(m), 132 | Stmt::Expr(expr_node) => { 133 | let _ = self.evaluate(expr_node)?; 134 | Ok(None) 135 | } 136 | Stmt::VarDecl(VarDecl { 137 | ident, initializer, .. 138 | }) => { 139 | let value = match initializer { 140 | None => Value::Nil, 141 | Some(expr_node) => self.evaluate(expr_node)?, 142 | }; 143 | 144 | self.env.borrow_mut().define(ident.name.clone(), value); 145 | 146 | Ok(None) 147 | } 148 | Stmt::If(If { cond, then, else_ }) => { 149 | let val = self.evaluate(cond)?; 150 | 151 | if Self::is_truthy(&val) { 152 | if let Some(exit) = self.execute_stmt(then)? { 153 | return Ok(Some(exit)); 154 | } 155 | } else if let Some(else_) = else_ { 156 | if let Some(exit) = self.execute_stmt(else_)? { 157 | return Ok(Some(exit)); 158 | } 159 | } 160 | 161 | Ok(None) 162 | } 163 | Stmt::Block(stmts) => self.execute_block( 164 | stmts, 165 | Rc::new(RefCell::new(Environment::new_with_enclosing(self.env()))), 166 | ), 167 | } 168 | } 169 | 170 | fn execute_match(&mut self, m: &Match) -> Result { 171 | let Match { 172 | val, 173 | branches, 174 | default, 175 | } = m; 176 | let val = self.evaluate(val)?; 177 | 178 | for branch in branches { 179 | if branch.pat.runtime_eq(&val) { 180 | return self.execute_block( 181 | &branch.body, 182 | Rc::new(RefCell::new(Environment::new_with_enclosing(self.env()))), 183 | ); 184 | } 185 | } 186 | 187 | match default { 188 | Some(branch) if !branch.pat.runtime_eq(&val) => { 189 | let mut env = Environment::new_with_enclosing(self.env()); 190 | let var = if let Pattern::Var(v) = &branch.pat { 191 | Some(v) 192 | } else { 193 | None 194 | }; 195 | 196 | env.define(var.unwrap().name().clone(), val); 197 | 198 | self.execute_block(&branch.body, Rc::new(RefCell::new(env))) 199 | } 200 | _ => Ok(None), 201 | } 202 | } 203 | 204 | fn execute_while_loop(&mut self, while_loop: &WhileLoop) -> Result { 205 | match while_loop.cond.literal() { 206 | Some(literal) => { 207 | if !Self::is_truthy(literal) { 208 | loop { 209 | for stmt in &while_loop.body { 210 | match self.execute_stmt(stmt)? { 211 | None => {} 212 | Some(ExitKind::Break(_)) => return Ok(None), 213 | ret => return Ok(ret), 214 | } 215 | } 216 | } 217 | } 218 | } 219 | None => { 220 | while !Self::is_truthy(&self.evaluate(&while_loop.cond)?) { 221 | for stmt in &while_loop.body { 222 | match self.execute_stmt(stmt)? { 223 | None => {} 224 | Some(ExitKind::Break(_)) => return Ok(None), 225 | ret => return Ok(ret), 226 | } 227 | } 228 | } 229 | } 230 | } 231 | 232 | Ok(None) 233 | } 234 | 235 | fn execute_for_loop(&mut self, for_loop: &ForLoop) -> Result { 236 | let mut env = Environment::new_with_enclosing(self.env()); 237 | let start = match self.evaluate(for_loop.range.0.expr())? { 238 | Value::Number(n) => n, 239 | other => { 240 | return Err(ParseError::InvalidRange( 241 | for_loop.var.line(), 242 | "start".into(), 243 | other.into(), 244 | ) 245 | .into()) 246 | } 247 | }; 248 | let end = match self.evaluate(for_loop.range.1.expr())? { 249 | Value::Number(n) => n, 250 | other => { 251 | let line = for_loop.var.line(); 252 | return Err(ParseError::InvalidRange(line, "start".into(), other.into()).into()); 253 | } 254 | }; 255 | 256 | let range = ( 257 | for_loop.range.0.to_evaluated(start), 258 | for_loop.range.1.to_evaluated(end), 259 | ); 260 | 261 | let (mut i, _) = range.values(); 262 | 263 | let var_name = for_loop.var.name(); 264 | env.define(var_name.clone(), Value::Number(i)); 265 | 266 | let env = Rc::new(RefCell::new(env)); 267 | 268 | while range.satisfied(i) { 269 | match self.execute_block(&for_loop.body, env.clone())? { 270 | None => {} 271 | Some(ExitKind::Break(_)) => break, 272 | Some(other) => return Ok(Some(other)), 273 | }; 274 | range.iterate(&mut i); 275 | env.borrow_mut().assign(var_name.clone(), Value::Number(i)); 276 | } 277 | 278 | Ok(None) 279 | } 280 | 281 | pub fn execute_block(&mut self, stmts: &[Stmt], env: Rc>) -> Result { 282 | let previous = mem::replace(&mut self.env, env); 283 | 284 | for stmt in stmts { 285 | match self.execute_stmt(stmt)? { 286 | None => {} 287 | Some(s) => { 288 | self.env = previous; 289 | return Ok(Some(s)); 290 | } 291 | }; 292 | } 293 | 294 | self.env = previous; 295 | 296 | Ok(None) 297 | } 298 | } 299 | 300 | impl<'a> Interpreter<'a> { 301 | pub fn evaluate(&mut self, node: &ExprNode) -> Result { 302 | match node.expr() { 303 | Expr::Call(expr_callee, token, params) => { 304 | self.evaluate_call(expr_callee, token, params) 305 | } 306 | Expr::Assign(ref var, ref expr) => { 307 | let value = self.evaluate(expr)?; 308 | self.env 309 | .borrow_mut() 310 | .assign(var.name().clone(), value.clone()); 311 | Ok(value) 312 | } 313 | Expr::Var(ref var) => self.lookup(var).map_or_else( 314 | || { 315 | Err( 316 | RuntimeError::UndefinedVariable(var.line(), (*var.name()).to_string()) 317 | .into(), 318 | ) 319 | }, 320 | Ok, 321 | ), 322 | Expr::Literal(ref val) => Ok(val.clone()), 323 | Expr::Grouping(ref expr) => self.evaluate(expr), 324 | Expr::Unary(op, ref expr) => { 325 | let right = self.evaluate(expr)?; 326 | 327 | let val = match (op, right) { 328 | (UnaryOp::Bang, right) => return Ok(Value::Bool(!Self::is_truthy(&right))), 329 | (UnaryOp::Minus, Value::Number(right)) => Value::Number(right * -1f64), 330 | (UnaryOp::Incr, Value::Number(right)) => Value::Number(right + 1f64), 331 | (UnaryOp::Decr, Value::Number(right)) => Value::Number(right - 1f64), 332 | _ => { 333 | return Err(RuntimeError::new_syntax( 334 | "invalid unary operation", 335 | expr.line(), 336 | ) 337 | .into()) 338 | } 339 | }; 340 | 341 | if let Expr::Var(v) = expr.expr() { 342 | self.env.borrow_mut().assign(v.name().clone(), val.clone()); 343 | } 344 | 345 | Ok(val) 346 | } 347 | Expr::Logical(left, op, right) => match op { 348 | LogicalOp::And => match Self::is_truthy(&self.evaluate(left)?) { 349 | true => { 350 | let right = self.evaluate(right)?; 351 | if Self::is_truthy(&right) { 352 | return Ok(right); 353 | } 354 | Ok(Value::Bool(false)) 355 | } 356 | false => Ok(Value::Bool(false)), 357 | }, 358 | LogicalOp::Or => { 359 | let left = self.evaluate(left)?; 360 | match Self::is_truthy(&left) { 361 | true => Ok(left), 362 | false => { 363 | let right = self.evaluate(right)?; 364 | if Self::is_truthy(&right) { 365 | return Ok(right); 366 | } 367 | Ok(Value::Bool(false)) 368 | } 369 | } 370 | } 371 | }, 372 | Expr::Binary(ref left_expr, op, ref right_expr) => { 373 | self.evaluate_binary(left_expr, op, right_expr) 374 | } 375 | } 376 | } 377 | 378 | fn evaluate_call( 379 | &mut self, 380 | expr_callee: &ExprNode, 381 | token: &Token, 382 | params: &[ExprNode], 383 | ) -> Result { 384 | let callee = self.evaluate(expr_callee)?; 385 | 386 | let callable = match callee { 387 | Value::Callable(callable) => callable, 388 | _ => return Err(RuntimeError::InvalidCallee(token.line()).into()), 389 | }; 390 | 391 | let arity = callable.arity().into(); 392 | if params.len() != arity { 393 | return Err( 394 | RuntimeError::InvalidArity(token.line(), callable.arity(), params.len()).into(), 395 | ); 396 | } 397 | 398 | let mut args = Vec::with_capacity(arity); 399 | // let mut args = ArrayVec::::new(); 400 | for arg in params { 401 | args.push(self.evaluate(arg)?); 402 | } 403 | 404 | callable.call(self, &args) 405 | } 406 | 407 | fn evaluate_binary( 408 | &mut self, 409 | left_expr: &ExprNode, 410 | op: &BinaryOp, 411 | right_expr: &ExprNode, 412 | ) -> Result { 413 | let line = left_expr.line(); 414 | let a = self.evaluate(left_expr)?; 415 | let b = self.evaluate(right_expr)?; 416 | 417 | match op { 418 | BinaryOp::Plus => match (a, b) { 419 | (Value::Number(a), Value::Number(b)) => Ok(Value::Number(a + b)), 420 | (Value::String(a), Value::String(b)) => Ok(Value::String(a + &b)), 421 | (Value::String(a), b) => Ok(Value::String(a.add(b.to_string().as_str()))), 422 | (a, Value::String(b)) => Ok(Value::String(b.add(a.to_string().as_str()))), 423 | _ => Err(RuntimeError::new_syntax( 424 | "Both operands cannot be converted to strings", 425 | line, 426 | ) 427 | .into()), 428 | }, 429 | BinaryOp::Modulo => { 430 | let (a, b) = self.unwrap_nums(a, b, line)?; 431 | Ok(Value::Number(a % b)) 432 | } 433 | BinaryOp::Minus => { 434 | let (a, b) = self.unwrap_nums(a, b, line)?; 435 | Ok(Value::Number(a - b)) 436 | } 437 | BinaryOp::Divide => { 438 | let (a, b) = self.unwrap_nums(a, b, line)?; 439 | Ok(Value::Number(a / b)) 440 | } 441 | BinaryOp::Multiply => { 442 | let (a, b) = self.unwrap_nums(a, b, line)?; 443 | Ok(Value::Number(a * b)) 444 | } 445 | BinaryOp::Greater => { 446 | let (a, b) = self.unwrap_nums(a, b, line)?; 447 | Ok(Value::Bool(a > b)) 448 | } 449 | BinaryOp::GreaterEqual => { 450 | let (a, b) = self.unwrap_nums(a, b, line)?; 451 | Ok(Value::Bool(a >= b)) 452 | } 453 | BinaryOp::Less => { 454 | let (a, b) = self.unwrap_nums(a, b, line)?; 455 | Ok(Value::Bool(a < b)) 456 | } 457 | BinaryOp::LessEqual => { 458 | let (a, b) = self.unwrap_nums(a, b, line)?; 459 | Ok(Value::Bool(a <= b)) 460 | } 461 | BinaryOp::NotEqual => Ok(Value::Bool(!self.is_equal(a, b))), 462 | BinaryOp::Equal => Ok(Value::Bool(self.is_equal(a, b))), 463 | } 464 | } 465 | } 466 | 467 | impl<'a> Interpreter<'a> { 468 | fn lookup(&self, var: &Var) -> Option { 469 | match Environment::ancestor(&self.env, var.scope_distance) { 470 | None => None, 471 | Some(ancestor) => { 472 | let val = ancestor.borrow().get(&var.ident().to_string()); 473 | val 474 | } 475 | } 476 | } 477 | 478 | fn is_truthy(val: &Value) -> bool { 479 | match val { 480 | Value::Bool(b) => *b, 481 | Value::Nil => false, 482 | _ => true, 483 | } 484 | } 485 | 486 | fn is_equal(&self, a: Value, b: Value) -> bool { 487 | if let Value::Nil = a { 488 | if let Value::Nil = b { 489 | return true; 490 | } 491 | return false; 492 | } 493 | 494 | match (a, b) { 495 | (Value::Number(a), Value::Number(b)) => (a - b).abs() < f64::EPSILON, 496 | (Value::String(a), Value::String(b)) => a == b, 497 | (Value::Bool(a), Value::Bool(b)) => a == b, 498 | _ => false, 499 | } 500 | } 501 | 502 | fn unwrap_nums(&self, a: Value, b: Value, line: usize) -> Result<(f64, f64)> { 503 | match (a, b) { 504 | (Value::Number(a), Value::Number(b)) => Ok((a, b)), 505 | _ => Err((RuntimeError::new_syntax("THOSE AREN'T FUCKIN NUMBERS MATE!", line)).into()), 506 | } 507 | } 508 | } 509 | -------------------------------------------------------------------------------- /src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | pub use value::*; 2 | mod value; 3 | pub use callable::*; 4 | pub use environment::*; 5 | pub use eq::*; 6 | pub use interpreter::*; 7 | mod callable; 8 | mod environment; 9 | mod eq; 10 | mod error; 11 | mod exit; 12 | mod interpreter; 13 | -------------------------------------------------------------------------------- /src/runtime/value.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, rc::Rc}; 2 | 3 | use crate::token::Kind; 4 | 5 | use super::Callable; 6 | 7 | pub const MAX_ARITY: usize = 32; 8 | 9 | #[derive(Clone, Debug, PartialEq)] 10 | pub enum Value { 11 | String(String), 12 | Number(f64), 13 | Bool(bool), 14 | Nil, 15 | Callable(Rc), 16 | } 17 | 18 | impl From for String { 19 | fn from(val: Value) -> Self { 20 | match val { 21 | Value::Bool(b) => { 22 | if b { 23 | "Nah, yeah!".into() 24 | } else { 25 | "Yeah, nah!".into() 26 | } 27 | } 28 | Value::Nil => format!("{}", Kind::BuggerAll), 29 | Value::Number(n) => format!("{}", n), 30 | Value::String(s) => s, 31 | Value::Callable(c) => format!("{}", c), 32 | } 33 | } 34 | } 35 | 36 | impl Display for Value { 37 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 38 | let s: String = self.clone().into(); 39 | write!(f, "{}", s) 40 | } 41 | } 42 | 43 | impl From<&str> for Value { 44 | fn from(s: &str) -> Self { 45 | let s: String = s.into(); 46 | s.into() 47 | } 48 | } 49 | 50 | impl From for Value { 51 | fn from(s: String) -> Self { 52 | Value::String(s) 53 | } 54 | } 55 | 56 | impl From for Value { 57 | fn from(b: bool) -> Self { 58 | Value::Bool(b) 59 | } 60 | } 61 | 62 | // impl From for Value { 63 | // fn from(b: f64) -> Self { 64 | // Value::Number(b) 65 | // } 66 | // } 67 | 68 | impl + Numeric> From for Value { 69 | fn from(num: T) -> Self { 70 | Value::Number(num.into()) 71 | } 72 | } 73 | 74 | pub trait Numeric {} 75 | impl Numeric for f64 {} 76 | impl Numeric for f32 {} 77 | impl Numeric for i64 {} 78 | impl Numeric for i32 {} 79 | impl Numeric for i16 {} 80 | impl Numeric for i8 {} 81 | impl Numeric for isize {} 82 | impl Numeric for u64 {} 83 | impl Numeric for u32 {} 84 | impl Numeric for u16 {} 85 | impl Numeric for u8 {} 86 | impl Numeric for usize {} 87 | -------------------------------------------------------------------------------- /src/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Clone, Debug, PartialEq)] 4 | pub struct Token { 5 | pub kind: Kind, 6 | line: usize, 7 | } 8 | 9 | impl Token { 10 | pub fn new(kind: Kind, line: usize) -> Self { 11 | Self { kind, line } 12 | } 13 | 14 | pub fn kind(&self) -> Kind { 15 | self.kind.clone() 16 | } 17 | 18 | pub fn line(&self) -> usize { 19 | self.line 20 | } 21 | } 22 | 23 | #[derive(Clone, PartialEq, Debug)] 24 | pub enum Kind { 25 | // Operators 26 | Modulo, // % 27 | Tilde, // ~ 28 | QuestionMark, // ? 29 | LeftBoomerang, // < 30 | RightBoomerang, // > 31 | LeftBracket, // [ 32 | RightBracket, // ] 33 | LeftParen, // ( 34 | RightParen, // ) 35 | Assign, // = 36 | Comma, // , 37 | Plus, // + 38 | Minus, // - 39 | Asterisk, // * 40 | Slash, // / 41 | Bang, // ! 42 | Semicolon, // ; 43 | LTE, // <= 44 | GTE, // >= 45 | Equals, // == 46 | BangEqual, // != 47 | And, // && 48 | Or, // || 49 | GoodOnYa, // GOOD ON YA 50 | PullYaHeadIn, // PullYaHeadIn 51 | 52 | // Keywords 53 | Import, // IMPOHT ME FUNC 54 | FuckinPiker, // FUCKINPIKER (early exit) 55 | MateFuckThis, // mate fuck this (break) 56 | Until, // until 57 | From, // from 58 | To, // to 59 | Gimme, // gimme 60 | Is, // (is) 61 | Isa, // (is a) 62 | BuggerAll, // Bugger all (nil/null) 63 | Cheers, // Cheers C***! (end of program) 64 | Whatabout, // Whatabout (else) 65 | IllHaveA, // I'll Have a 66 | Walkabout, // Walkabout (for loop) 67 | GdayMate, // G'DAY MATE! (program start) 68 | IReckon, // I reckon (var decl) 69 | IFullyReckon, // I fully reckon (constant var decl) 70 | YaReckon, // Ya reckon (analogous to if) 71 | HardYakkaFor, // Hard yakka for (function decl) 72 | Bail, // bail (return) 73 | True, // true 74 | False, // false 75 | OiMate, // OI MATE! (start of block comment) 76 | GotIt, // GOT IT? (end of block comment) 77 | 78 | // A sequence of Yeah/Nahs followed by a ! will be transformed 79 | // into one NahYeah or YeahNah. The parser will never see these tokens. 80 | Yeah, 81 | Nah, 82 | 83 | Ident(String), // Identifier 84 | Number(f64), // Number literal 85 | String(String), // String literal 86 | EOF, 87 | } 88 | 89 | impl Kind { 90 | pub fn literal(&self) -> String { 91 | match self { 92 | Kind::OiMate => "oi mate!", 93 | Kind::GotIt => "got it?", 94 | Kind::GoodOnYa => "good on ya", 95 | Kind::PullYaHeadIn => "pull ya head in", 96 | Kind::Import => "impoht me func", 97 | Kind::FuckinPiker => "fuckinpiker", 98 | Kind::Modulo => "%", 99 | Kind::MateFuckThis => "mate fuck this", 100 | Kind::LeftBracket => "[", 101 | Kind::RightBracket => "]", 102 | Kind::Until => "until", 103 | Kind::From => "from", 104 | Kind::To => "to", 105 | Kind::Is => "is", 106 | Kind::Isa => "is a", 107 | Kind::Tilde => "~", 108 | Kind::QuestionMark => "?", 109 | Kind::LeftBoomerang => "<", 110 | Kind::RightBoomerang => ">", 111 | Kind::LeftParen => "(", 112 | Kind::RightParen => ")", 113 | Kind::Assign => "=", 114 | Kind::Comma => ",", 115 | Kind::Plus => "+", 116 | Kind::Minus => "-", 117 | Kind::Asterisk => "*", 118 | Kind::Slash => "/", 119 | Kind::Bang => "!", 120 | Kind::Semicolon => ";", 121 | Kind::LTE => "<=", 122 | Kind::GTE => ">=", 123 | Kind::Equals => "==", 124 | Kind::BangEqual => "!=", 125 | Kind::And => "&&", 126 | Kind::Or => "||", 127 | Kind::Gimme => "gimme", 128 | Kind::IllHaveA => "i'll have a", 129 | Kind::BuggerAll => "bugger all", 130 | Kind::Cheers => "cheers c***!", // Chook bickey (end of program) 131 | Kind::Whatabout => "whatabout", // Whatabout (else) 132 | Kind::Walkabout => "walkabout", // Walkabout (for loop) 133 | Kind::GdayMate => "g'day mate!", // G'DAY MATE! (program start) 134 | Kind::IReckon => "i reckon", // I reckon (var decl) 135 | Kind::IFullyReckon => "i fully reckon", // I fully reckon (constant var decl) 136 | Kind::YaReckon => "ya reckon", // Ya reckon (analogous to if) 137 | Kind::HardYakkaFor => "the hard yakka for", // Hard yakka for (function decl) 138 | Kind::Bail => "bail", // bail (return) 139 | Kind::True => "nah, yeah!", // true 140 | Kind::False => "yeah, nah!", // false 141 | Kind::Nah => "nah", // true 142 | Kind::Yeah => "yeah", // false 143 | Kind::Ident(ref s) => s.as_str(), // Identifier 144 | Kind::Number(n) => return format!("{}", n), // Number literal 145 | Kind::String(ref s) => s.as_str(), // String literal 146 | Kind::EOF => "EOF", 147 | } 148 | .into() 149 | } 150 | } 151 | 152 | impl Display for Kind { 153 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 154 | write!(f, "{}", self.literal()) 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/upside_down.rs: -------------------------------------------------------------------------------- 1 | macro_rules! upside_down { 2 | ($($rightside:literal, $upside:literal),*) => { 3 | pub fn rightside_up(ch: char) -> char { 4 | match ch { 5 | $( 6 | $upside => $rightside, 7 | )* 8 | _ => ch 9 | } 10 | } 11 | pub fn upside_down(ch: char) -> char { 12 | match ch { 13 | $( 14 | $rightside => $upside, 15 | )* 16 | _ => ch 17 | } 18 | } 19 | }; 20 | } 21 | 22 | upside_down!( 23 | '?', '¿', '\'', ',', ',', '\'', '[', ']', ']', '[', '!', '¡', 'a', 'ɐ', 'b', 'q', 'c', 'ɔ', 24 | 'd', 'p', 'e', 'ǝ', 'f', 'ɟ', 'g', 'ƃ', 'h', 'ɥ', 'i', 'ᴉ', 'j', 'ɾ', 'k', 'ʞ', 'l', 'l', 'm', 25 | 'ɯ', 'n', 'u', 'p', 'd', 'q', 'b', 'r', 'ɹ', 's', 's', 't', 'ʇ', 'u', 'n', 'v', 'ʌ', 'w', 'ʍ', 26 | 'x', 'x', 'y', 'ʎ', 'z', 'z', 'A', '∀', 'B', 'q', 'C', 'Ɔ', 'D', 'D', 'E', 'Ǝ', 'F', 'Ⅎ', 'G', 27 | 'פ', 'I', 'I', 'F', 'ſ', 'K', 'ʞ', 'L', '˥', 'M', 'W', 'N', 'N', 'O', 'O', 'P', 'Ԁ', 'Q', 'Q', 28 | 'R', 'ɹ', 'S', 'S', 'T', '┴', 'U', '∩', 'V', 'Λ', 'W', 'M', 'X', 'X', 'Y', '⅄', 'Z', 'Z', '0', 29 | '0', '1', 'Ɩ', '2', 'ᄅ', '3', 'Ɛ', '4', 'ㄣ', '5', 'ϛ', '6', '9', '7', 'ㄥ', '8', '8', '9', 30 | '6', '(', ')', ')', '(', '<', '>', '>', '<', '"', '„' 31 | ); 32 | -------------------------------------------------------------------------------- /tests/interpreter_test.rs: -------------------------------------------------------------------------------- 1 | use aussie_plus_plus::{ 2 | lexer::{source, Lexer}, 3 | parser::parser::Parser, 4 | resolver::Resolver, 5 | runtime::Interpreter, 6 | }; 7 | 8 | enum FallibleKind { 9 | None, 10 | Resolver, 11 | Interpreter, 12 | } 13 | 14 | fn test(src: &str, expected: &str, fallible_kind: FallibleKind) { 15 | let expected = if expected.is_empty() { 16 | expected.to_owned() 17 | } else { 18 | expected.to_owned() + "\n" 19 | }; 20 | let mut s = "G'DAY MATE! ".to_string(); 21 | s.push_str(src); 22 | let mut lex = Lexer::new(source::Regular::new(s.chars())); 23 | let (tokens, failed) = lex.lex(); 24 | 25 | if failed { 26 | panic!("Lexing failed"); 27 | } 28 | 29 | println!("Tokens: {:#?}", tokens); 30 | let mut parser = Parser::new(tokens); 31 | let mut stmts = parser.parse().unwrap(); 32 | 33 | if Resolver::new().resolve(&mut stmts) { 34 | if !matches!(fallible_kind, FallibleKind::Resolver) { 35 | panic!("Resolver failed") 36 | } else { 37 | return; 38 | } 39 | } 40 | 41 | let mut buf: Vec = Vec::with_capacity(128); 42 | let mut iptr = Interpreter::new_with_writer(&mut buf); 43 | 44 | if let Err(e) = iptr.interpret(stmts) { 45 | if !matches!(fallible_kind, FallibleKind::Interpreter) { 46 | panic!("Failed to interpret: {}", e); 47 | } else { 48 | return; 49 | } 50 | } 51 | 52 | println!("Testing expression: {}", s); 53 | match std::str::from_utf8(&buf) { 54 | Err(e) => panic!("Failed to read buffer: {}", e), 55 | Ok(s) => { 56 | assert_eq!(s, expected); 57 | } 58 | } 59 | } 60 | 61 | fn test_code(src: &str, expected: &str) { 62 | test(src, expected, FallibleKind::None) 63 | } 64 | 65 | #[test] 66 | fn test_constants() { 67 | test( 68 | " 69 | I FULLY RECKON x = 420; 70 | GIMME x; 71 | x = 69;", 72 | "420", 73 | FallibleKind::Resolver, 74 | ); 75 | } 76 | 77 | #[test] 78 | fn test_imports() { 79 | test_code( 80 | " 81 | IMPOHT ME FUNC ChuckSomeDice; 82 | IMPOHT ME FUNC HitTheSack; 83 | IMPOHT ME FUNC GimmeTime; 84 | 85 | HitTheSack(100); 86 | ChuckSomeDice(0, 1); 87 | GimmeTime(); 88 | ", 89 | "", 90 | ); 91 | } 92 | 93 | #[test] 94 | fn test_scopes() { 95 | test_code( 96 | " 97 | i reckon x = 5; 98 | < 99 | THE HARD YAKKA FOR testFunc IS () < 100 | GIMME x; 101 | > 102 | 103 | testFunc(); 104 | i reckon x = 420; 105 | testFunc(); 106 | > 107 | ", 108 | "5\n5", 109 | ); 110 | } 111 | 112 | #[test] 113 | fn test_while_loop() { 114 | test_code( 115 | " 116 | i reckon x = 0; 117 | i reckon i'll have a walkabout until (x > 3) < 118 | gimme x; 119 | x = x + 1; 120 | >", 121 | "0\n1\n2\n3", 122 | ); 123 | 124 | test_code( 125 | " 126 | i reckon i'll have a walkabout until (Yeah, nah!) < 127 | gimme \"bloody oath!\"; 128 | mate fuck this; 129 | > 130 | gimme \"fair dinkum\"; 131 | ", 132 | "bloody oath!\nfair dinkum", 133 | ); 134 | } 135 | 136 | // #[test] 137 | // fn test_early_exit() { 138 | // test_code( 139 | // "i reckon x = 5; 140 | // ya reckon x == 5 ? FUCKINPIKER; 141 | // gimme \"this should not appear\";", 142 | // "", 143 | // ); 144 | // } 145 | 146 | #[test] 147 | fn test_functions() { 148 | test_code( 149 | " 150 | THE HARD YAKKA FOR fibonacci IS ( x ) < 151 | YA RECKON x <= 1 ? BAIL x; 152 | 153 | BAIL fibonacci(x - 1) + fibonacci(x - 2); 154 | > 155 | GIMME fibonacci(10); 156 | ", 157 | "55", 158 | ); 159 | } 160 | 161 | #[test] 162 | fn test_break() { 163 | test_code( 164 | " 165 | I RECKON x IS A walkabout FROM [1 to 5] < 166 | YA RECKON x == 2 ? MATE FUCK THIS; 167 | GIMME \"iteration number: \" + x; 168 | > 169 | ", 170 | "iteration number: 1", 171 | ); 172 | } 173 | 174 | #[test] 175 | fn test_for_loop_ranges() { 176 | test_code( 177 | " 178 | I reckon x is a walkabout from [0 to 2] < 179 | gimme x; 180 | > 181 | ", 182 | "0\n1\n2", 183 | ); 184 | 185 | test_code( 186 | " 187 | I reckon x is a walkabout from (0 to 2] < 188 | gimme x; 189 | > 190 | ", 191 | "1\n2", 192 | ); 193 | 194 | test_code( 195 | " 196 | I reckon x is a walkabout from [0 to 2] < 197 | gimme x; 198 | > 199 | ", 200 | "0\n1\n2", 201 | ); 202 | 203 | test_code( 204 | " 205 | I reckon x is a walkabout from [0 to 2) < 206 | gimme x; 207 | > 208 | ", 209 | "0\n1", 210 | ); 211 | 212 | test_code( 213 | " 214 | I reckon x is a walkabout from (0 to 0) < 215 | gimme \"val: \" + x; 216 | > 217 | ", 218 | "", 219 | ); 220 | 221 | test_code( 222 | " 223 | I reckon x is a walkabout from (0 to 0] < 224 | gimme \"val: \" + x; 225 | > 226 | ", 227 | "", 228 | ); 229 | 230 | test_code( 231 | " 232 | I reckon x is a walkabout from (-1 to 1] < 233 | gimme x; 234 | > 235 | ", 236 | "0\n1", 237 | ); 238 | 239 | // Variables as ranges 240 | test_code( 241 | " 242 | I reckon z = -1; 243 | I reckon y = 1; 244 | I reckon x is a walkabout from (z to y] < 245 | gimme x; 246 | > 247 | ", 248 | "0\n1", 249 | ); 250 | } 251 | 252 | #[test] 253 | fn test_vars() { 254 | test_code( 255 | " 256 | I RECKON x = 10; 257 | gimme x; 258 | x = 5; 259 | gimme x; 260 | ", 261 | "10\n5", 262 | ); 263 | 264 | test_code( 265 | "I RECKON x = 10; 266 | < 267 | I RECKON x = 5; 268 | gimme x; 269 | > 270 | gimme x; 271 | ", 272 | "5\n10", 273 | ); 274 | 275 | test_code( 276 | "I RECKON x = 10; 277 | < 278 | x = 5; 279 | gimme x; 280 | > 281 | gimme x; 282 | ", 283 | "5\n5", 284 | ); 285 | } 286 | 287 | #[test] 288 | fn test_match() { 289 | // Works with bools 290 | test_code( 291 | "i reckon x = 2; 292 | ya reckon x == 2 is a < 293 | Nah, yeah! ~ gimme \"FARK\"; 294 | Yeah, nah! ~ gimme 420; 295 | >", 296 | "FARK", 297 | ); 298 | 299 | // Works with numbers 300 | test_code( 301 | "i reckon x = 420; 302 | ya reckon x is a < 303 | 1 ~ gimme \"FARK\"; 304 | 2 ~ gimme \"CARN\"; 305 | 420 ~ gimme \"FAIR DINKUM\"; 306 | > 307 | gimme x;", 308 | "FAIR DINKUM\n420", 309 | ); 310 | 311 | // Works with strings 312 | test_code( 313 | "i reckon x = \"G'day mate\"; 314 | ya reckon x is a < 315 | \"Strewth!\" ~ gimme \"Strewth!\"; 316 | \"G'day mate\" ~ gimme \"G'day mate\"; 317 | > 318 | ", 319 | "G'day mate", 320 | ); 321 | 322 | // Works with nil 323 | test_code( 324 | "i reckon x = BUGGER ALL; 325 | ya reckon x is a < 326 | BuGGEr ALL ~ gimme bugger all; 327 | somethinElse ~ gimme somethinElse; 328 | > 329 | ", 330 | "bugger all", 331 | ); 332 | 333 | // Default case 334 | test_code( 335 | "i reckon x = 42069; 336 | ya reckon x is a < 337 | 1 ~ gimme \"fark!\"; 338 | 1 ~ gimme \"carn!\"; 339 | somethinElse ~ gimme somethinElse; 340 | > 341 | ", 342 | "42069", 343 | ); 344 | } 345 | 346 | #[test] 347 | fn test_if() { 348 | test_code( 349 | "i reckon x = 5; 350 | ya reckon x == 5 ? < 351 | gimme \"fair dinkum mate!\"; 352 | >", 353 | "fair dinkum mate!", 354 | ); 355 | 356 | test_code( 357 | "i reckon x = 5; 358 | ya reckon x == 5 ? gimme \"fair dinkum mate!\"; 359 | ", 360 | "fair dinkum mate!", 361 | ); 362 | 363 | test_code( 364 | "i reckon x = 5; 365 | ya reckon x == 42 ? gimme \"strewth!!\"; 366 | gimme \"lmao\";", 367 | "lmao", 368 | ); 369 | 370 | test_code( 371 | " 372 | YA RECKON 1 == 2 ? GIMME \"fark we broke maths!\"; 373 | WHATABOUT NAH, YEAH! == YEAH, NAH! ? GIMME \"strewth we broke boolean logic!\"; 374 | WHATABOUT ? GIMME \"the universe is okay\";", 375 | "the universe is okay", 376 | ); 377 | 378 | test_code( 379 | " 380 | YA RECKON 1 == 2 ? GIMME \"fark we broke maths!\"; 381 | WHATABOUT YEAH, NAH! == YEAH, NAH! ? GIMME \"lmao\"; 382 | WHATABOUT ? GIMME \"the universe is okay\";", 383 | "lmao", 384 | ); 385 | } 386 | 387 | #[test] 388 | fn test_ops() { 389 | test_code( 390 | "gimme !NAH YEAH YEAH YEAH YEAH YEAH YEAH NAH!; 391 | gimme !YEAH YEAH YEAH YEAH NAH YEAH!;", 392 | "Nah, yeah!\nYeah, nah!", 393 | ); 394 | test_code("gimme pull ya head in 70;", "69"); 395 | test_code("gimme good on ya 68;", "69"); 396 | test_code("gimme good on ya good on ya good on ya 417;", "420"); 397 | test_code( 398 | "gimme pull ya head in pull ya head in pull ya head in 423;", 399 | "420", 400 | ); 401 | test_code("I RECKON x = 5; GOOD ON YA x; GIMME x;", "6"); 402 | test_code("I RECKON x = 5; PULL YA HEAD IN x; GIMME x;", "4"); 403 | test( 404 | "I FULLY RECKON x = 5; GOOD ON YA x;", 405 | "", 406 | FallibleKind::Resolver, 407 | ); 408 | test( 409 | "I FULLY RECKON x = 5; PULL YA HEAD IN x;", 410 | "", 411 | FallibleKind::Resolver, 412 | ); 413 | 414 | test_code("gimme 5 + 2;", "7"); 415 | test_code("gimme 5 - 2;", "3"); 416 | test_code("gimme 5 * 2;", "10"); 417 | test_code("gimme 5 / 2;", "2.5"); 418 | test_code("gimme 5 > 2;", "Nah, yeah!"); 419 | test_code("gimme 5 >= 2;", "Nah, yeah!"); 420 | test_code("gimme 5 < 2;", "Yeah, nah!"); 421 | test_code("gimme 5 <= 2;", "Yeah, nah!"); 422 | test_code("gimme 5 == 2;", "Yeah, nah!"); 423 | test_code("gimme 5 != 2;", "Nah, yeah!"); 424 | test_code("gimme 4 % 2;", "0"); 425 | test_code("gimme 5 % 2;", "1"); 426 | 427 | test_code("gimme nah, yeah! && yeah, nah!;", "Yeah, nah!"); 428 | test_code("gimme nah, yeah! && nah, yeah!;", "Nah, yeah!"); 429 | test_code("gimme nah, yeah! || yeah, nah!;", "Nah, yeah!"); 430 | test_code("gimme yeah, nah! || yeah, nah!;", "Yeah, nah!"); 431 | 432 | test_code("gimme ((5 + 5) / 2) * 2;", "10"); 433 | 434 | test_code("gimme 5 + 5 * 2 / 2;", "10"); 435 | } 436 | -------------------------------------------------------------------------------- /tests/lexer_test.rs: -------------------------------------------------------------------------------- 1 | use aussie_plus_plus::{ 2 | lexer::{self, source::Source}, 3 | token::{Kind, Token}, 4 | }; 5 | 6 | fn test_lexing_with_src(mut expected_tokens: Vec, expected_error: bool, iter: T) { 7 | expected_tokens.insert(0, Token::new(Kind::GdayMate, 1)); 8 | let mut lexer = lexer::Lexer::new(iter); 9 | let (tokens, had_error) = lexer.lex(); 10 | 11 | assert_eq!(had_error, expected_error); 12 | { 13 | for (i, token) in tokens.iter().enumerate() { 14 | if &expected_tokens[i] != token { 15 | println!("expected: {:?} but got {:?}", expected_tokens[i], token); 16 | } 17 | assert_eq!(expected_tokens[i].clone(), token.clone()); 18 | } 19 | } 20 | assert_eq!(tokens, expected_tokens); 21 | } 22 | 23 | fn test_lexing_upside_down(src: &str, expected_tokens: Vec, expected_error: bool) { 24 | test_lexing_with_src( 25 | expected_tokens, 26 | expected_error, 27 | lexer::source::UpsideDown::new(src.chars()), 28 | ) 29 | } 30 | 31 | fn test_lexing(src: &str, expected_tokens: Vec, expected_error: bool) { 32 | let src = "G'day mate! ".to_owned() + src; 33 | test_lexing_with_src( 34 | expected_tokens, 35 | expected_error, 36 | lexer::source::Regular::new(src.chars()), 37 | ) 38 | } 39 | #[test] 40 | pub fn test_lex_fully_reckon() { 41 | test_lexing( 42 | "I FULLY RECKON x = 5;", 43 | vec![ 44 | Token::new(Kind::IFullyReckon, 1), 45 | Token::new(Kind::Ident("x".into()), 1), 46 | Token::new(Kind::Assign, 1), 47 | Token::new(Kind::Number(5f64), 1), 48 | Token::new(Kind::Semicolon, 1), 49 | Token::new(Kind::EOF, 1), 50 | ], 51 | false, 52 | ); 53 | } 54 | 55 | #[test] 56 | pub fn test_lex_block_comment() { 57 | test_lexing( 58 | "OI MATE! 59 | everything inside of this is a comment 60 | GOT IT?", 61 | vec![Token::new(Kind::EOF, 3)], 62 | false, 63 | ); 64 | 65 | test_lexing( 66 | "5 + OI MATE! inside an expression GOT IT? 12;", 67 | vec![ 68 | Token::new(Kind::Number(5f64), 1), 69 | Token::new(Kind::Plus, 1), 70 | Token::new(Kind::Number(12f64), 1), 71 | Token::new(Kind::Semicolon, 1), 72 | Token::new(Kind::EOF, 1), 73 | ], 74 | false, 75 | ); 76 | } 77 | 78 | #[test] 79 | pub fn test_lex_bool() { 80 | test_lexing( 81 | "NAH YEAH NAH YEAH!", 82 | vec![Token::new(Kind::True, 1), Token::new(Kind::EOF, 1)], 83 | false, 84 | ); 85 | 86 | test_lexing( 87 | "NAH 88 | YEAH NAH NAH!", 89 | vec![Token::new(Kind::False, 2), Token::new(Kind::EOF, 2)], 90 | false, 91 | ); 92 | 93 | test_lexing( 94 | "NAH, 95 | YEAH, NAH, NAH YEAH!", 96 | vec![Token::new(Kind::True, 2), Token::new(Kind::EOF, 2)], 97 | false, 98 | ); 99 | 100 | test_lexing( 101 | "NAH YEAH NAH 102 | NAH YEAH NAH NAH YEAH NAH NAH YEAH NAH 103 | YEAH YEAH YEAH YEAH YEAH YEAH NAH!", 104 | vec![Token::new(Kind::False, 3), Token::new(Kind::EOF, 3)], 105 | false, 106 | ); 107 | 108 | test_lexing( 109 | "!NAH YEAH NAH 110 | NAH YEAH NAH NAH YEAH NAH NAH YEAH NAH 111 | YEAH YEAH YEAH YEAH YEAH YEAH NAH!", 112 | vec![ 113 | Token::new(Kind::Bang, 1), 114 | Token::new(Kind::False, 3), 115 | Token::new(Kind::EOF, 3), 116 | ], 117 | false, 118 | ); 119 | } 120 | 121 | #[test] 122 | pub fn test_lex_is() { 123 | test_lexing( 124 | "is is a", 125 | vec![ 126 | Token::new(Kind::Is, 1), 127 | Token::new(Kind::Isa, 1), 128 | Token::new(Kind::EOF, 1), 129 | ], 130 | false, 131 | ); 132 | } 133 | 134 | #[test] 135 | pub fn test_upside_down() { 136 | test_lexing_upside_down( 137 | "¡***Ɔ SɹƎƎHƆ 138 | ;0Ɩ = ʎ NOʞƆƎɹ I 139 | ;ϛ = x NOʞƆƎɹ I 140 | ¡Ǝ┴∀W ⅄∀p,פ", 141 | vec![ 142 | Token::new(Kind::IReckon, 2), 143 | Token::new(Kind::Ident("x".into()), 2), 144 | Token::new(Kind::Assign, 2), 145 | Token::new(Kind::Number(5f64), 2), 146 | Token::new(Kind::Semicolon, 2), 147 | Token::new(Kind::IReckon, 3), 148 | Token::new(Kind::Ident("y".into()), 3), 149 | Token::new(Kind::Assign, 3), 150 | Token::new(Kind::Number(10f64), 3), 151 | Token::new(Kind::Semicolon, 3), 152 | Token::new(Kind::Cheers, 4), 153 | Token::new(Kind::EOF, 5), 154 | ], 155 | false, 156 | ); 157 | } 158 | 159 | #[test] 160 | pub fn test_separation() { 161 | test_lexing( 162 | "BUGGERALL", 163 | vec![ 164 | Token::new(Kind::Ident("BUGGERALL".into()), 1), 165 | Token::new(Kind::EOF, 1), 166 | ], 167 | false, 168 | ); 169 | } 170 | 171 | #[test] 172 | pub fn test_lex_keyword_casing() { 173 | let kinds = vec![ 174 | Kind::Gimme, 175 | Kind::Isa, 176 | Kind::Cheers, 177 | Kind::Walkabout, 178 | Kind::GdayMate, 179 | Kind::IReckon, 180 | Kind::YaReckon, 181 | Kind::HardYakkaFor, 182 | Kind::Bail, 183 | Kind::False, 184 | Kind::True, 185 | Kind::BuggerAll, 186 | ]; 187 | 188 | fn test_random_case(kind: &Kind, iterations: usize) { 189 | let mut random_case: String; 190 | for _ in 0..iterations { 191 | random_case = kind 192 | .literal() 193 | .chars() 194 | .into_iter() 195 | .map(|c| { 196 | if rand::random() { 197 | c.to_ascii_uppercase() 198 | } else { 199 | c.to_ascii_lowercase() 200 | } 201 | }) 202 | .collect(); 203 | 204 | let expected = { 205 | if kind.clone() == Kind::Cheers { 206 | vec![Token::new(Kind::Cheers, 1), Token::new(Kind::EOF, 2)] 207 | } else { 208 | vec![Token::new(kind.clone(), 1), Token::new(Kind::EOF, 1)] 209 | } 210 | }; 211 | 212 | test_lexing(random_case.as_str(), expected, false); 213 | } 214 | } 215 | 216 | for kind in kinds { 217 | test_random_case(&kind, 5); 218 | } 219 | } 220 | 221 | #[test] 222 | pub fn test_lex_hard_yakka() { 223 | test_lexing( 224 | "the Hard yakka for dummyFunction ( x ) < 225 | bail \"dinkum\" 226 | >", 227 | vec![ 228 | Token::new(Kind::HardYakkaFor, 1), 229 | Token::new(Kind::Ident("dummyFunction".into()), 1), 230 | Token::new(Kind::LeftParen, 1), 231 | Token::new(Kind::Ident("x".into()), 1), 232 | Token::new(Kind::RightParen, 1), 233 | Token::new(Kind::LeftBoomerang, 1), 234 | Token::new(Kind::Bail, 2), 235 | Token::new(Kind::String("dinkum".into()), 2), 236 | Token::new(Kind::RightBoomerang, 3), 237 | Token::new(Kind::EOF, 3), 238 | ], 239 | false, 240 | ); 241 | } 242 | 243 | #[test] 244 | pub fn test_lex_keywords() { 245 | test_lexing( 246 | "G'DAY MATE! 247 | I RECKON x = 5 248 | I RECKON y = 10 249 | CHEERS C***! 250 | I RECKON z = 12 251 | ", 252 | vec![ 253 | Token::new(Kind::GdayMate, 1), 254 | Token::new(Kind::IReckon, 2), 255 | Token::new(Kind::Ident("x".into()), 2), 256 | Token::new(Kind::Assign, 2), 257 | Token::new(Kind::Number(5f64), 2), 258 | Token::new(Kind::IReckon, 3), 259 | Token::new(Kind::Ident("y".into()), 3), 260 | Token::new(Kind::Assign, 3), 261 | Token::new(Kind::Number(10f64), 3), 262 | Token::new(Kind::Cheers, 4), 263 | Token::new(Kind::EOF, 5), 264 | ], 265 | false, 266 | ); 267 | } 268 | 269 | #[test] 270 | fn test_lex_ya_reckon() { 271 | test_lexing( 272 | "Ya reckon x == 5 < 273 | bail Nah, yeah! 274 | > 275 | bail Yeah, Nah!", 276 | vec![ 277 | Token::new(Kind::YaReckon, 1), 278 | Token::new(Kind::Ident("x".into()), 1), 279 | Token::new(Kind::Equals, 1), 280 | Token::new(Kind::Number(5f64), 1), 281 | Token::new(Kind::LeftBoomerang, 1), 282 | Token::new(Kind::Bail, 2), 283 | Token::new(Kind::True, 2), 284 | Token::new(Kind::RightBoomerang, 3), 285 | Token::new(Kind::Bail, 4), 286 | Token::new(Kind::False, 4), 287 | Token::new(Kind::EOF, 4), 288 | ], 289 | false, 290 | ) 291 | } 292 | 293 | #[test] 294 | fn test_lex_walkabout() { 295 | test_lexing( 296 | "WALKABOUT (I reckon x = 0; x < 5; x = x + 1) < 297 | x = x + 1 298 | >", 299 | vec![ 300 | Token::new(Kind::Walkabout, 1), 301 | Token::new(Kind::LeftParen, 1), 302 | Token::new(Kind::IReckon, 1), 303 | Token::new(Kind::Ident("x".into()), 1), 304 | Token::new(Kind::Assign, 1), 305 | Token::new(Kind::Number(0f64), 1), 306 | Token::new(Kind::Semicolon, 1), 307 | Token::new(Kind::Ident("x".into()), 1), 308 | Token::new(Kind::LeftBoomerang, 1), 309 | Token::new(Kind::Number(5f64), 1), 310 | Token::new(Kind::Semicolon, 1), 311 | Token::new(Kind::Ident("x".into()), 1), 312 | Token::new(Kind::Assign, 1), 313 | Token::new(Kind::Ident("x".into()), 1), 314 | Token::new(Kind::Plus, 1), 315 | Token::new(Kind::Number(1f64), 1), 316 | Token::new(Kind::RightParen, 1), 317 | Token::new(Kind::LeftBoomerang, 1), 318 | Token::new(Kind::Ident("x".into()), 2), 319 | Token::new(Kind::Assign, 2), 320 | Token::new(Kind::Ident("x".into()), 2), 321 | Token::new(Kind::Plus, 2), 322 | Token::new(Kind::Number(1f64), 2), 323 | Token::new(Kind::RightBoomerang, 3), 324 | Token::new(Kind::EOF, 3), 325 | ], 326 | false, 327 | ); 328 | } 329 | 330 | #[test] 331 | pub fn test_lex_incr_decr_ops() { 332 | test_lexing( 333 | "GOOD ON YA x; 334 | PULL YA HEAD IN x;", 335 | vec![ 336 | Token::new(Kind::GoodOnYa, 1), 337 | Token::new(Kind::Ident("x".into()), 1), 338 | Token::new(Kind::Semicolon, 1), 339 | Token::new(Kind::PullYaHeadIn, 2), 340 | Token::new(Kind::Ident("x".into()), 2), 341 | Token::new(Kind::Semicolon, 2), 342 | Token::new(Kind::EOF, 2), 343 | ], 344 | false, 345 | ); 346 | } 347 | 348 | #[test] 349 | pub fn test_operators() { 350 | test_lexing( 351 | "5 < 10", 352 | vec![ 353 | Token::new(Kind::Number(5f64), 1), 354 | Token::new(Kind::LeftBoomerang, 1), 355 | Token::new(Kind::Number(10f64), 1), 356 | Token::new(Kind::EOF, 1), 357 | ], 358 | false, 359 | ); 360 | 361 | test_lexing( 362 | "5 > 10", 363 | vec![ 364 | Token::new(Kind::Number(5f64), 1), 365 | Token::new(Kind::RightBoomerang, 1), 366 | Token::new(Kind::Number(10f64), 1), 367 | Token::new(Kind::EOF, 1), 368 | ], 369 | false, 370 | ); 371 | 372 | test_lexing( 373 | "5 <= 10", 374 | vec![ 375 | Token::new(Kind::Number(5f64), 1), 376 | Token::new(Kind::LTE, 1), 377 | Token::new(Kind::Number(10f64), 1), 378 | Token::new(Kind::EOF, 1), 379 | ], 380 | false, 381 | ); 382 | 383 | test_lexing( 384 | "5 >= 10", 385 | vec![ 386 | Token::new(Kind::Number(5f64), 1), 387 | Token::new(Kind::GTE, 1), 388 | Token::new(Kind::Number(10f64), 1), 389 | Token::new(Kind::EOF, 1), 390 | ], 391 | false, 392 | ); 393 | 394 | test_lexing( 395 | "5 + 10", 396 | vec![ 397 | Token::new(Kind::Number(5f64), 1), 398 | Token::new(Kind::Plus, 1), 399 | Token::new(Kind::Number(10f64), 1), 400 | Token::new(Kind::EOF, 1), 401 | ], 402 | false, 403 | ); 404 | 405 | test_lexing( 406 | "5 - 10", 407 | vec![ 408 | Token::new(Kind::Number(5f64), 1), 409 | Token::new(Kind::Minus, 1), 410 | Token::new(Kind::Number(10f64), 1), 411 | Token::new(Kind::EOF, 1), 412 | ], 413 | false, 414 | ); 415 | 416 | test_lexing( 417 | "5 * 10", 418 | vec![ 419 | Token::new(Kind::Number(5f64), 1), 420 | Token::new(Kind::Asterisk, 1), 421 | Token::new(Kind::Number(10f64), 1), 422 | Token::new(Kind::EOF, 1), 423 | ], 424 | false, 425 | ); 426 | 427 | test_lexing( 428 | "5 / 10", 429 | vec![ 430 | Token::new(Kind::Number(5f64), 1), 431 | Token::new(Kind::Slash, 1), 432 | Token::new(Kind::Number(10f64), 1), 433 | Token::new(Kind::EOF, 1), 434 | ], 435 | false, 436 | ); 437 | 438 | test_lexing( 439 | "5 == 10", 440 | vec![ 441 | Token::new(Kind::Number(5f64), 1), 442 | Token::new(Kind::Equals, 1), 443 | Token::new(Kind::Number(10f64), 1), 444 | Token::new(Kind::EOF, 1), 445 | ], 446 | false, 447 | ); 448 | 449 | test_lexing( 450 | "5 != 10", 451 | vec![ 452 | Token::new(Kind::Number(5f64), 1), 453 | Token::new(Kind::BangEqual, 1), 454 | Token::new(Kind::Number(10f64), 1), 455 | Token::new(Kind::EOF, 1), 456 | ], 457 | false, 458 | ); 459 | 460 | test_lexing( 461 | "5 && 10", 462 | vec![ 463 | Token::new(Kind::Number(5f64), 1), 464 | Token::new(Kind::And, 1), 465 | Token::new(Kind::Number(10f64), 1), 466 | Token::new(Kind::EOF, 1), 467 | ], 468 | false, 469 | ); 470 | 471 | test_lexing( 472 | "5 || 10", 473 | vec![ 474 | Token::new(Kind::Number(5f64), 1), 475 | Token::new(Kind::Or, 1), 476 | Token::new(Kind::Number(10f64), 1), 477 | Token::new(Kind::EOF, 1), 478 | ], 479 | false, 480 | ); 481 | 482 | test_lexing( 483 | "5 % 10", 484 | vec![ 485 | Token::new(Kind::Number(5f64), 1), 486 | Token::new(Kind::Modulo, 1), 487 | Token::new(Kind::Number(10f64), 1), 488 | Token::new(Kind::EOF, 1), 489 | ], 490 | false, 491 | ); 492 | 493 | test_lexing( 494 | "!Yeah, Nah!", 495 | vec![ 496 | Token::new(Kind::Bang, 1), 497 | Token::new(Kind::False, 1), 498 | Token::new(Kind::EOF, 1), 499 | ], 500 | false, 501 | ); 502 | } 503 | -------------------------------------------------------------------------------- /tests/parser_test.rs: -------------------------------------------------------------------------------- 1 | use aussie_plus_plus::{ 2 | ast::{ 3 | BinaryOp, Expr, ExprNode, ForLoop, Ident, If, Match, MatchBranch, Pattern, RangeBound, 4 | Stmt, UnaryOp, Var, VarDecl, 5 | }, 6 | lexer::{lexer, source}, 7 | parser::parser, 8 | runtime::Value, 9 | token::{Kind, Token}, 10 | }; 11 | 12 | fn test_parse(source: &str, check_fn: T) 13 | where 14 | T: FnOnce(Vec), 15 | { 16 | let source = "G'DAY MATE! ".to_owned() + source; 17 | let mut lex = lexer::Lexer::new(source::Regular::new(source.chars())); 18 | let (tokens, _) = lex.lex(); 19 | let mut parser = parser::Parser::new(tokens); 20 | let stmts = parser.parse().unwrap(); 21 | 22 | check_fn(stmts) 23 | } 24 | 25 | #[test] 26 | fn test_parse_for_loop() { 27 | test_parse( 28 | "i reckon x is a walkabout from (0 to 100) < 29 | gimme x; 30 | >", 31 | |stmts| { 32 | let inner = Stmt::Print(ExprNode::new(Expr::Var(("x", 2, usize::MAX).into()), 2)); 33 | let body = vec![Stmt::Block(vec![inner])]; 34 | let range = ( 35 | RangeBound::Exclusive(ExprNode::new(Expr::Literal(0.into()), 1)), 36 | RangeBound::Exclusive(ExprNode::new(Expr::Literal(100.into()), 1)), 37 | ); 38 | assert_eq!( 39 | stmts[0], 40 | Stmt::For(Box::new(ForLoop::new( 41 | Var::new(("x", 1).into(), usize::MAX), 42 | range, 43 | body 44 | ))) 45 | ) 46 | }, 47 | ); 48 | 49 | test_parse( 50 | "i reckon x is a walkabout from (0 to 100) < 51 | mate fuck this; 52 | >", 53 | |stmts| { 54 | let inner = Stmt::Break(Token::new(Kind::MateFuckThis, 2)); 55 | let body = vec![Stmt::Block(vec![inner])]; 56 | let range = ( 57 | RangeBound::Exclusive(ExprNode::new(Expr::Literal(0.into()), 1)), 58 | RangeBound::Exclusive(ExprNode::new(Expr::Literal(100.into()), 1)), 59 | ); 60 | assert_eq!( 61 | stmts[0], 62 | Stmt::For(Box::new(ForLoop::new( 63 | Var::new(("x", 1).into(), usize::MAX), 64 | range, 65 | body 66 | ))) 67 | ) 68 | }, 69 | ); 70 | } 71 | 72 | #[test] 73 | fn test_parse_assign() { 74 | test_parse( 75 | "i reckon x = 5; 76 | x = 10; 77 | ", 78 | |stmts| { 79 | assert_eq!( 80 | stmts[1], 81 | Stmt::Expr(ExprNode::new( 82 | Expr::Assign( 83 | Var::new(("x", 2).into(), usize::MAX), 84 | Box::new(ExprNode::new(Expr::Literal(10.into()), 2)) 85 | ), 86 | 2 87 | )) 88 | ) 89 | }, 90 | ); 91 | 92 | test_parse( 93 | " 94 | i reckon x = 5; 95 | i reckon y = x = 10; 96 | ", 97 | |stmts| { 98 | assert_eq!( 99 | stmts[1], 100 | Stmt::VarDecl(VarDecl { 101 | ident: ("y", 3).into(), 102 | initializer: Some(ExprNode::new( 103 | Expr::Assign( 104 | Var::new(("x", 3).into(), usize::MAX), 105 | Box::new(ExprNode::new(Expr::Literal(10.into()), 3)), 106 | ), 107 | 3, 108 | )), 109 | immutable: false 110 | }) 111 | ); 112 | }, 113 | ); 114 | } 115 | 116 | #[test] 117 | fn test_parse_match() { 118 | test_parse( 119 | "ya reckon x == 2 is a < 120 | Nah, yeah! ~ Bugger all; 121 | Yeah, nah! ~ 1; 122 | >", 123 | |stmts| { 124 | let cond = ExprNode::new( 125 | Expr::Binary( 126 | Box::new(ExprNode::new(Expr::Var(("x", 1, usize::MAX).into()), 1)), 127 | BinaryOp::Equal, 128 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 129 | ), 130 | 1, 131 | ); 132 | let branches = vec![ 133 | MatchBranch::new( 134 | { 135 | let pat: Option = Kind::True.into(); 136 | pat.unwrap() 137 | }, 138 | vec![Stmt::Expr(ExprNode::new(Expr::Literal(Value::Nil), 2))], 139 | 2, 140 | ), 141 | MatchBranch::new( 142 | { 143 | let pat: Option = Kind::False.into(); 144 | pat.unwrap() 145 | }, 146 | vec![Stmt::Expr(ExprNode::new(Expr::Literal(1.into()), 3))], 147 | 3, 148 | ), 149 | ]; 150 | assert_eq!(stmts[0], Stmt::Match(Match::new(cond, branches, None))); 151 | }, 152 | ); 153 | } 154 | 155 | #[test] 156 | fn test_parse_block() { 157 | test_parse( 158 | "ya reckon x == 2 ? < 159 | i reckon y; 160 | i reckon z; 161 | >", 162 | |stmts| { 163 | let cond = ExprNode::new( 164 | Expr::Binary( 165 | Box::new(ExprNode::new(Expr::Var(("x", 1, usize::MAX).into()), 1)), 166 | BinaryOp::Equal, 167 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 168 | ), 169 | 1, 170 | ); 171 | let then = Box::new(Stmt::Block(vec![ 172 | Stmt::VarDecl(VarDecl { 173 | ident: ("y", 2).into(), 174 | initializer: None, 175 | immutable: false, 176 | }), 177 | Stmt::VarDecl(VarDecl { 178 | ident: ("z", 3).into(), 179 | initializer: None, 180 | immutable: false, 181 | }), 182 | ])); 183 | assert_eq!( 184 | stmts[0], 185 | Stmt::If(If { 186 | cond, 187 | then, 188 | else_: None 189 | }), 190 | ); 191 | }, 192 | ); 193 | 194 | test_parse( 195 | "ya reckon x == 2 is a < 196 | Nah, yeah! ~ Bugger all; 197 | Yeah, nah! ~ 1; 198 | >", 199 | |stmts| { 200 | let cond = ExprNode::new( 201 | Expr::Binary( 202 | Box::new(ExprNode::new(Expr::Var(("x", 1, usize::MAX).into()), 1)), 203 | BinaryOp::Equal, 204 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 205 | ), 206 | 1, 207 | ); 208 | let branches = vec![ 209 | MatchBranch::new( 210 | { 211 | let pat: Option = Kind::True.into(); 212 | pat.unwrap() 213 | }, 214 | vec![Stmt::Expr(ExprNode::new(Expr::Literal(Value::Nil), 2))], 215 | 2, 216 | ), 217 | MatchBranch::new( 218 | { 219 | let pat: Option = Kind::False.into(); 220 | pat.unwrap() 221 | }, 222 | vec![Stmt::Expr(ExprNode::new(Expr::Literal(1.into()), 3))], 223 | 3, 224 | ), 225 | ]; 226 | assert_eq!(stmts[0], Stmt::Match(Match::new(cond, branches, None))); 227 | }, 228 | ); 229 | } 230 | 231 | #[test] 232 | fn test_parse_var() { 233 | test_parse( 234 | "i reckon x = 2; 235 | i reckon y;", 236 | |stmts| { 237 | assert_eq!( 238 | stmts[0], 239 | Stmt::VarDecl(VarDecl { 240 | ident: ("x", 1).into(), 241 | initializer: Some(ExprNode::new(Expr::Literal(2.into()), 1)), 242 | immutable: false, 243 | }) 244 | ); 245 | assert_eq!( 246 | stmts[1], 247 | Stmt::VarDecl(VarDecl { 248 | ident: Ident::new("y".into(), 2), 249 | initializer: None, 250 | immutable: false 251 | }) 252 | ) 253 | }, 254 | ); 255 | 256 | test_parse("i reckon x = 5 + 2;", |stmts| { 257 | assert_eq!( 258 | stmts[0], 259 | Stmt::VarDecl(VarDecl { 260 | ident: Ident::new("x".into(), 1), 261 | initializer: Some(ExprNode::new( 262 | Expr::Binary( 263 | Box::new(ExprNode::new(Expr::Literal(5.into()), 1)), 264 | BinaryOp::Plus, 265 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 266 | ), 267 | 1 268 | )), 269 | immutable: false, 270 | }) 271 | ); 272 | }); 273 | } 274 | 275 | #[test] 276 | fn test_parse_if() { 277 | test_parse("ya reckon 5 == 2 ? nah, yeah!;", |stmts| { 278 | assert_eq!( 279 | stmts[0], 280 | Stmt::If(If { 281 | cond: ExprNode::new( 282 | Expr::Binary( 283 | Box::new(ExprNode::new(Expr::Literal(5.into()), 1)), 284 | BinaryOp::Equal, 285 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 286 | ), 287 | 1 288 | ), 289 | then: Box::new(Stmt::Expr(ExprNode::new(Expr::Literal(true.into()), 1))), 290 | else_: None, 291 | }) 292 | ); 293 | }); 294 | } 295 | 296 | #[test] 297 | fn test_parse_unary_op() { 298 | test_parse("GOOD ON YA 1;", |stmts| { 299 | assert_eq!( 300 | stmts[0], 301 | Stmt::Expr(ExprNode::new( 302 | Expr::Unary( 303 | UnaryOp::Incr, 304 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 305 | ), 306 | 1 307 | )) 308 | ); 309 | }); 310 | 311 | test_parse("PULL YA HEAD IN 1;", |stmts| { 312 | assert_eq!( 313 | stmts[0], 314 | Stmt::Expr(ExprNode::new( 315 | Expr::Unary( 316 | UnaryOp::Decr, 317 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 318 | ), 319 | 1 320 | )) 321 | ); 322 | }); 323 | 324 | test_parse("!1;", |stmts| { 325 | assert_eq!( 326 | stmts[0], 327 | Stmt::Expr(ExprNode::new( 328 | Expr::Unary( 329 | UnaryOp::Bang, 330 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 331 | ), 332 | 1 333 | )) 334 | ); 335 | }); 336 | 337 | test_parse("!!1;", |stmts| { 338 | assert_eq!( 339 | stmts[0], 340 | Stmt::Expr(ExprNode::new( 341 | Expr::Unary( 342 | UnaryOp::Bang, 343 | Box::new(ExprNode::new( 344 | Expr::Unary( 345 | UnaryOp::Bang, 346 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 347 | ), 348 | 1, 349 | )) 350 | ), 351 | 1 352 | )) 353 | ); 354 | }); 355 | 356 | test_parse("!!!1;", |stmts| { 357 | assert_eq!( 358 | stmts[0], 359 | Stmt::Expr(ExprNode::new( 360 | Expr::Unary( 361 | UnaryOp::Bang, 362 | Box::new(ExprNode::new( 363 | Expr::Unary( 364 | UnaryOp::Bang, 365 | Box::new(ExprNode::new( 366 | Expr::Unary( 367 | UnaryOp::Bang, 368 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 369 | ), 370 | 1, 371 | )), 372 | ), 373 | 1, 374 | )) 375 | ), 376 | 1 377 | )) 378 | ); 379 | }); 380 | } 381 | 382 | #[test] 383 | fn test_parse_binary_op() { 384 | test_parse("1 + 2 + 3 + 4;", |stmts| { 385 | assert_eq!( 386 | stmts[0], 387 | Stmt::Expr(ExprNode::new( 388 | Expr::Binary( 389 | Box::new(ExprNode::new( 390 | Expr::Binary( 391 | Box::new(ExprNode::new( 392 | Expr::Binary( 393 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 394 | BinaryOp::Plus, 395 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 396 | ), 397 | 1, 398 | )), 399 | BinaryOp::Plus, 400 | Box::new(ExprNode::new(Expr::Literal(3.into()), 1)), 401 | ), 402 | 1, 403 | )), 404 | BinaryOp::Plus, 405 | Box::new(ExprNode::new(Expr::Literal(4.into()), 1)), 406 | ), 407 | 1 408 | )) 409 | ); 410 | }); 411 | 412 | test_parse("1 + (2 + 3);", |stmts| { 413 | assert_eq!( 414 | stmts[0], 415 | Stmt::Expr(ExprNode::new( 416 | Expr::Binary( 417 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 418 | BinaryOp::Plus, 419 | Box::new(ExprNode::new( 420 | Expr::Grouping(Box::new(ExprNode::new( 421 | Expr::Binary( 422 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 423 | BinaryOp::Plus, 424 | Box::new(ExprNode::new(Expr::Literal(3.into()), 1)), 425 | ), 426 | 1, 427 | ))), 428 | 1 429 | )) 430 | ), 431 | 1 432 | )) 433 | ); 434 | }); 435 | 436 | test_parse("1 + 2 + 3;", |stmts| { 437 | assert_eq!( 438 | stmts[0], 439 | Stmt::Expr(ExprNode::new( 440 | Expr::Binary( 441 | Box::new(ExprNode::new( 442 | Expr::Binary( 443 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 444 | BinaryOp::Plus, 445 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 446 | ), 447 | 1, 448 | )), 449 | BinaryOp::Plus, 450 | Box::new(ExprNode::new(Expr::Literal(3.into()), 1)) 451 | ), 452 | 1 453 | )) 454 | ); 455 | }); 456 | 457 | test_parse("1 + 2;", |stmts| { 458 | assert_eq!( 459 | stmts[0], 460 | Stmt::Expr(ExprNode::new( 461 | Expr::Binary( 462 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 463 | BinaryOp::Plus, 464 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 465 | ), 466 | 1 467 | )) 468 | ); 469 | }); 470 | 471 | test_parse("1 - 2;", |stmts| { 472 | assert_eq!( 473 | stmts[0], 474 | Stmt::Expr(ExprNode::new( 475 | Expr::Binary( 476 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 477 | BinaryOp::Minus, 478 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 479 | ), 480 | 1 481 | )) 482 | ); 483 | }); 484 | 485 | test_parse("1 / 2;", |stmts| { 486 | assert_eq!( 487 | stmts[0], 488 | Stmt::Expr(ExprNode::new( 489 | Expr::Binary( 490 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 491 | BinaryOp::Divide, 492 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 493 | ), 494 | 1 495 | )) 496 | ); 497 | }); 498 | 499 | test_parse("1 * 2;", |stmts| { 500 | assert_eq!( 501 | stmts[0], 502 | Stmt::Expr(ExprNode::new( 503 | Expr::Binary( 504 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 505 | BinaryOp::Multiply, 506 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 507 | ), 508 | 1 509 | )) 510 | ); 511 | }); 512 | 513 | test_parse("1 == 2;", |stmts| { 514 | assert_eq!( 515 | stmts[0], 516 | Stmt::Expr(ExprNode::new( 517 | Expr::Binary( 518 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 519 | BinaryOp::Equal, 520 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 521 | ), 522 | 1 523 | )) 524 | ); 525 | }); 526 | 527 | test_parse("1 != 2;", |stmts| { 528 | assert_eq!( 529 | stmts[0], 530 | Stmt::Expr(ExprNode::new( 531 | Expr::Binary( 532 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 533 | BinaryOp::NotEqual, 534 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 535 | ), 536 | 1 537 | )) 538 | ); 539 | }); 540 | 541 | test_parse("1 > 2;", |stmts| { 542 | assert_eq!( 543 | stmts[0], 544 | Stmt::Expr(ExprNode::new( 545 | Expr::Binary( 546 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 547 | BinaryOp::Greater, 548 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 549 | ), 550 | 1 551 | )) 552 | ); 553 | }); 554 | 555 | test_parse("1 >= 2;", |stmts| { 556 | assert_eq!( 557 | stmts[0], 558 | Stmt::Expr(ExprNode::new( 559 | Expr::Binary( 560 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 561 | BinaryOp::GreaterEqual, 562 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 563 | ), 564 | 1 565 | )) 566 | ); 567 | }); 568 | 569 | test_parse("1 < 2;", |stmts| { 570 | assert_eq!( 571 | stmts[0], 572 | Stmt::Expr(ExprNode::new( 573 | Expr::Binary( 574 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 575 | BinaryOp::Less, 576 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 577 | ), 578 | 1 579 | )) 580 | ); 581 | }); 582 | 583 | test_parse("1 <= 2;", |stmts| { 584 | assert_eq!( 585 | stmts[0], 586 | Stmt::Expr(ExprNode::new( 587 | Expr::Binary( 588 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 589 | BinaryOp::LessEqual, 590 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 591 | ), 592 | 1 593 | )) 594 | ); 595 | }); 596 | 597 | test_parse("1 % 2;", |stmts| { 598 | assert_eq!( 599 | stmts[0], 600 | Stmt::Expr(ExprNode::new( 601 | Expr::Binary( 602 | Box::new(ExprNode::new(Expr::Literal(1.into()), 1)), 603 | BinaryOp::Modulo, 604 | Box::new(ExprNode::new(Expr::Literal(2.into()), 1)), 605 | ), 606 | 1 607 | )) 608 | ); 609 | }); 610 | } 611 | --------------------------------------------------------------------------------