├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── README.md ├── fib.ty ├── functions.ty ├── lists.ty └── variables.ty └── src ├── cli ├── Cargo.toml └── src │ ├── file.rs │ ├── main.rs │ └── repl.rs └── tay ├── Cargo.toml ├── benches ├── bench_main.rs └── benchmarks │ ├── evaluator.rs │ ├── lexer.rs │ ├── mod.rs │ └── parser.rs └── src ├── ast └── mod.rs ├── evaluator ├── builtins.rs ├── env.rs ├── error.rs ├── mod.rs ├── object.rs └── test.rs ├── lexer ├── mod.rs ├── test.rs └── token.rs ├── lib.rs └── parser ├── mod.rs └── test.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .idea/ 3 | docs/ 4 | 5 | -------------------------------------------------------------------------------- /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 = "anes" 7 | version = "0.1.6" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 10 | 11 | [[package]] 12 | name = "atty" 13 | version = "0.2.14" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 16 | dependencies = [ 17 | "hermit-abi", 18 | "libc", 19 | "winapi", 20 | ] 21 | 22 | [[package]] 23 | name = "autocfg" 24 | version = "1.0.1" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 27 | 28 | [[package]] 29 | name = "bitflags" 30 | version = "1.3.2" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 33 | 34 | [[package]] 35 | name = "bumpalo" 36 | version = "3.4.0" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" 39 | 40 | [[package]] 41 | name = "cast" 42 | version = "0.3.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 45 | 46 | [[package]] 47 | name = "cc" 48 | version = "1.0.62" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "f1770ced377336a88a67c473594ccc14eca6f4559217c34f64aac8f83d641b40" 51 | 52 | [[package]] 53 | name = "cfg-if" 54 | version = "0.1.10" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 57 | 58 | [[package]] 59 | name = "cfg-if" 60 | version = "1.0.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 63 | 64 | [[package]] 65 | name = "ciborium" 66 | version = "0.2.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" 69 | dependencies = [ 70 | "ciborium-io", 71 | "ciborium-ll", 72 | "serde", 73 | ] 74 | 75 | [[package]] 76 | name = "ciborium-io" 77 | version = "0.2.0" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" 80 | 81 | [[package]] 82 | name = "ciborium-ll" 83 | version = "0.2.0" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" 86 | dependencies = [ 87 | "ciborium-io", 88 | "half", 89 | ] 90 | 91 | [[package]] 92 | name = "clap" 93 | version = "3.2.22" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750" 96 | dependencies = [ 97 | "bitflags", 98 | "clap_lex", 99 | "indexmap", 100 | "textwrap", 101 | ] 102 | 103 | [[package]] 104 | name = "clap_lex" 105 | version = "0.2.4" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 108 | dependencies = [ 109 | "os_str_bytes", 110 | ] 111 | 112 | [[package]] 113 | name = "clipboard-win" 114 | version = "4.4.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "c4ab1b92798304eedc095b53942963240037c0516452cb11aeba709d420b2219" 117 | dependencies = [ 118 | "error-code", 119 | "str-buf", 120 | "winapi", 121 | ] 122 | 123 | [[package]] 124 | name = "const_fn" 125 | version = "0.4.3" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "c478836e029dcef17fb47c89023448c64f781a046e0300e257ad8225ae59afab" 128 | 129 | [[package]] 130 | name = "criterion" 131 | version = "0.4.0" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 134 | dependencies = [ 135 | "anes", 136 | "atty", 137 | "cast", 138 | "ciborium", 139 | "clap", 140 | "criterion-plot", 141 | "itertools", 142 | "lazy_static", 143 | "num-traits", 144 | "oorandom", 145 | "plotters", 146 | "rayon", 147 | "regex", 148 | "serde", 149 | "serde_derive", 150 | "serde_json", 151 | "tinytemplate", 152 | "walkdir", 153 | ] 154 | 155 | [[package]] 156 | name = "criterion-plot" 157 | version = "0.5.0" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 160 | dependencies = [ 161 | "cast", 162 | "itertools", 163 | ] 164 | 165 | [[package]] 166 | name = "crossbeam-channel" 167 | version = "0.5.0" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" 170 | dependencies = [ 171 | "cfg-if 1.0.0", 172 | "crossbeam-utils", 173 | ] 174 | 175 | [[package]] 176 | name = "crossbeam-deque" 177 | version = "0.8.2" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" 180 | dependencies = [ 181 | "cfg-if 1.0.0", 182 | "crossbeam-epoch", 183 | "crossbeam-utils", 184 | ] 185 | 186 | [[package]] 187 | name = "crossbeam-epoch" 188 | version = "0.9.0" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "ec0f606a85340376eef0d6d8fec399e6d4a544d648386c6645eb6d0653b27d9f" 191 | dependencies = [ 192 | "cfg-if 1.0.0", 193 | "const_fn", 194 | "crossbeam-utils", 195 | "lazy_static", 196 | "memoffset", 197 | "scopeguard", 198 | ] 199 | 200 | [[package]] 201 | name = "crossbeam-utils" 202 | version = "0.8.11" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc" 205 | dependencies = [ 206 | "cfg-if 1.0.0", 207 | "once_cell", 208 | ] 209 | 210 | [[package]] 211 | name = "ctor" 212 | version = "0.1.16" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "7fbaabec2c953050352311293be5c6aba8e141ba19d6811862b232d6fd020484" 215 | dependencies = [ 216 | "quote", 217 | "syn", 218 | ] 219 | 220 | [[package]] 221 | name = "diff" 222 | version = "0.1.13" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" 225 | 226 | [[package]] 227 | name = "dirs-next" 228 | version = "2.0.0" 229 | source = "registry+https://github.com/rust-lang/crates.io-index" 230 | checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" 231 | dependencies = [ 232 | "cfg-if 1.0.0", 233 | "dirs-sys-next", 234 | ] 235 | 236 | [[package]] 237 | name = "dirs-sys-next" 238 | version = "0.1.1" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "99de365f605554ae33f115102a02057d4fc18b01f3284d6870be0938743cfe7d" 241 | dependencies = [ 242 | "libc", 243 | "redox_users", 244 | "winapi", 245 | ] 246 | 247 | [[package]] 248 | name = "either" 249 | version = "1.6.1" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 252 | 253 | [[package]] 254 | name = "endian-type" 255 | version = "0.1.2" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" 258 | 259 | [[package]] 260 | name = "errno" 261 | version = "0.2.8" 262 | source = "registry+https://github.com/rust-lang/crates.io-index" 263 | checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" 264 | dependencies = [ 265 | "errno-dragonfly", 266 | "libc", 267 | "winapi", 268 | ] 269 | 270 | [[package]] 271 | name = "errno-dragonfly" 272 | version = "0.1.2" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" 275 | dependencies = [ 276 | "cc", 277 | "libc", 278 | ] 279 | 280 | [[package]] 281 | name = "error-code" 282 | version = "2.3.1" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21" 285 | dependencies = [ 286 | "libc", 287 | "str-buf", 288 | ] 289 | 290 | [[package]] 291 | name = "fd-lock" 292 | version = "3.0.6" 293 | source = "registry+https://github.com/rust-lang/crates.io-index" 294 | checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517" 295 | dependencies = [ 296 | "cfg-if 1.0.0", 297 | "rustix", 298 | "windows-sys", 299 | ] 300 | 301 | [[package]] 302 | name = "getrandom" 303 | version = "0.1.15" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" 306 | dependencies = [ 307 | "cfg-if 0.1.10", 308 | "libc", 309 | "wasi", 310 | ] 311 | 312 | [[package]] 313 | name = "half" 314 | version = "1.6.0" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" 317 | 318 | [[package]] 319 | name = "hashbrown" 320 | version = "0.12.3" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 323 | 324 | [[package]] 325 | name = "hermit-abi" 326 | version = "0.1.17" 327 | source = "registry+https://github.com/rust-lang/crates.io-index" 328 | checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" 329 | dependencies = [ 330 | "libc", 331 | ] 332 | 333 | [[package]] 334 | name = "indexmap" 335 | version = "1.9.1" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" 338 | dependencies = [ 339 | "autocfg", 340 | "hashbrown", 341 | ] 342 | 343 | [[package]] 344 | name = "io-lifetimes" 345 | version = "0.7.3" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "1ea37f355c05dde75b84bba2d767906ad522e97cd9e2eef2be7a4ab7fb442c06" 348 | 349 | [[package]] 350 | name = "itertools" 351 | version = "0.10.5" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 354 | dependencies = [ 355 | "either", 356 | ] 357 | 358 | [[package]] 359 | name = "itoa" 360 | version = "0.4.6" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" 363 | 364 | [[package]] 365 | name = "js-sys" 366 | version = "0.3.60" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 369 | dependencies = [ 370 | "wasm-bindgen", 371 | ] 372 | 373 | [[package]] 374 | name = "lazy_static" 375 | version = "1.4.0" 376 | source = "registry+https://github.com/rust-lang/crates.io-index" 377 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 378 | 379 | [[package]] 380 | name = "libc" 381 | version = "0.2.133" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "c0f80d65747a3e43d1596c7c5492d95d5edddaabd45a7fcdb02b95f644164966" 384 | 385 | [[package]] 386 | name = "linux-raw-sys" 387 | version = "0.0.46" 388 | source = "registry+https://github.com/rust-lang/crates.io-index" 389 | checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" 390 | 391 | [[package]] 392 | name = "log" 393 | version = "0.4.11" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" 396 | dependencies = [ 397 | "cfg-if 0.1.10", 398 | ] 399 | 400 | [[package]] 401 | name = "memchr" 402 | version = "2.3.4" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 405 | 406 | [[package]] 407 | name = "memoffset" 408 | version = "0.5.6" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" 411 | dependencies = [ 412 | "autocfg", 413 | ] 414 | 415 | [[package]] 416 | name = "nibble_vec" 417 | version = "0.1.0" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" 420 | dependencies = [ 421 | "smallvec", 422 | ] 423 | 424 | [[package]] 425 | name = "nix" 426 | version = "0.24.2" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "195cdbc1741b8134346d515b3a56a1c94b0912758009cfd53f99ea0f57b065fc" 429 | dependencies = [ 430 | "bitflags", 431 | "cfg-if 1.0.0", 432 | "libc", 433 | ] 434 | 435 | [[package]] 436 | name = "num-traits" 437 | version = "0.2.14" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 440 | dependencies = [ 441 | "autocfg", 442 | ] 443 | 444 | [[package]] 445 | name = "num_cpus" 446 | version = "1.13.0" 447 | source = "registry+https://github.com/rust-lang/crates.io-index" 448 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 449 | dependencies = [ 450 | "hermit-abi", 451 | "libc", 452 | ] 453 | 454 | [[package]] 455 | name = "once_cell" 456 | version = "1.15.0" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 459 | 460 | [[package]] 461 | name = "oorandom" 462 | version = "11.1.2" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "a170cebd8021a008ea92e4db85a72f80b35df514ec664b296fdcbb654eac0b2c" 465 | 466 | [[package]] 467 | name = "os_str_bytes" 468 | version = "6.3.0" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 471 | 472 | [[package]] 473 | name = "output_vt100" 474 | version = "0.1.2" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" 477 | dependencies = [ 478 | "winapi", 479 | ] 480 | 481 | [[package]] 482 | name = "plotters" 483 | version = "0.3.4" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" 486 | dependencies = [ 487 | "num-traits", 488 | "plotters-backend", 489 | "plotters-svg", 490 | "wasm-bindgen", 491 | "web-sys", 492 | ] 493 | 494 | [[package]] 495 | name = "plotters-backend" 496 | version = "0.3.4" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" 499 | 500 | [[package]] 501 | name = "plotters-svg" 502 | version = "0.3.3" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" 505 | dependencies = [ 506 | "plotters-backend", 507 | ] 508 | 509 | [[package]] 510 | name = "pretty_assertions" 511 | version = "1.3.0" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" 514 | dependencies = [ 515 | "ctor", 516 | "diff", 517 | "output_vt100", 518 | "yansi", 519 | ] 520 | 521 | [[package]] 522 | name = "proc-macro2" 523 | version = "1.0.44" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "7bd7356a8122b6c4a24a82b278680c73357984ca2fc79a0f9fa6dea7dced7c58" 526 | dependencies = [ 527 | "unicode-ident", 528 | ] 529 | 530 | [[package]] 531 | name = "quote" 532 | version = "1.0.7" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 535 | dependencies = [ 536 | "proc-macro2", 537 | ] 538 | 539 | [[package]] 540 | name = "radix_trie" 541 | version = "0.2.1" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" 544 | dependencies = [ 545 | "endian-type", 546 | "nibble_vec", 547 | ] 548 | 549 | [[package]] 550 | name = "rayon" 551 | version = "1.5.0" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" 554 | dependencies = [ 555 | "autocfg", 556 | "crossbeam-deque", 557 | "either", 558 | "rayon-core", 559 | ] 560 | 561 | [[package]] 562 | name = "rayon-core" 563 | version = "1.9.0" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" 566 | dependencies = [ 567 | "crossbeam-channel", 568 | "crossbeam-deque", 569 | "crossbeam-utils", 570 | "lazy_static", 571 | "num_cpus", 572 | ] 573 | 574 | [[package]] 575 | name = "redox_syscall" 576 | version = "0.1.57" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" 579 | 580 | [[package]] 581 | name = "redox_users" 582 | version = "0.3.5" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "de0737333e7a9502c789a36d7c7fa6092a49895d4faa31ca5df163857ded2e9d" 585 | dependencies = [ 586 | "getrandom", 587 | "redox_syscall", 588 | ] 589 | 590 | [[package]] 591 | name = "regex" 592 | version = "1.6.0" 593 | source = "registry+https://github.com/rust-lang/crates.io-index" 594 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 595 | dependencies = [ 596 | "regex-syntax", 597 | ] 598 | 599 | [[package]] 600 | name = "regex-syntax" 601 | version = "0.6.27" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 604 | 605 | [[package]] 606 | name = "rustix" 607 | version = "0.35.10" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "af895b90e5c071badc3136fc10ff0bcfc98747eadbaf43ed8f214e07ba8f8477" 610 | dependencies = [ 611 | "bitflags", 612 | "errno", 613 | "io-lifetimes", 614 | "libc", 615 | "linux-raw-sys", 616 | "windows-sys", 617 | ] 618 | 619 | [[package]] 620 | name = "rustyline" 621 | version = "10.0.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "1d1cd5ae51d3f7bf65d7969d579d502168ef578f289452bd8ccc91de28fda20e" 624 | dependencies = [ 625 | "bitflags", 626 | "cfg-if 1.0.0", 627 | "clipboard-win", 628 | "dirs-next", 629 | "fd-lock", 630 | "libc", 631 | "log", 632 | "memchr", 633 | "nix", 634 | "radix_trie", 635 | "scopeguard", 636 | "unicode-segmentation", 637 | "unicode-width", 638 | "utf8parse", 639 | "winapi", 640 | ] 641 | 642 | [[package]] 643 | name = "ryu" 644 | version = "1.0.5" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 647 | 648 | [[package]] 649 | name = "same-file" 650 | version = "1.0.6" 651 | source = "registry+https://github.com/rust-lang/crates.io-index" 652 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 653 | dependencies = [ 654 | "winapi-util", 655 | ] 656 | 657 | [[package]] 658 | name = "scopeguard" 659 | version = "1.1.0" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 662 | 663 | [[package]] 664 | name = "serde" 665 | version = "1.0.117" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "b88fa983de7720629c9387e9f517353ed404164b1e482c970a90c1a4aaf7dc1a" 668 | dependencies = [ 669 | "serde_derive", 670 | ] 671 | 672 | [[package]] 673 | name = "serde_derive" 674 | version = "1.0.117" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "cbd1ae72adb44aab48f325a02444a5fc079349a8d804c1fc922aed3f7454c74e" 677 | dependencies = [ 678 | "proc-macro2", 679 | "quote", 680 | "syn", 681 | ] 682 | 683 | [[package]] 684 | name = "serde_json" 685 | version = "1.0.59" 686 | source = "registry+https://github.com/rust-lang/crates.io-index" 687 | checksum = "dcac07dbffa1c65e7f816ab9eba78eb142c6d44410f4eeba1e26e4f5dfa56b95" 688 | dependencies = [ 689 | "itoa", 690 | "ryu", 691 | "serde", 692 | ] 693 | 694 | [[package]] 695 | name = "smallvec" 696 | version = "1.9.0" 697 | source = "registry+https://github.com/rust-lang/crates.io-index" 698 | checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1" 699 | 700 | [[package]] 701 | name = "str-buf" 702 | version = "1.0.6" 703 | source = "registry+https://github.com/rust-lang/crates.io-index" 704 | checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0" 705 | 706 | [[package]] 707 | name = "syn" 708 | version = "1.0.101" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" 711 | dependencies = [ 712 | "proc-macro2", 713 | "quote", 714 | "unicode-ident", 715 | ] 716 | 717 | [[package]] 718 | name = "tay" 719 | version = "0.2.0" 720 | dependencies = [ 721 | "criterion", 722 | "crossbeam-deque", 723 | "crossbeam-utils", 724 | "pretty_assertions", 725 | ] 726 | 727 | [[package]] 728 | name = "tay-cli" 729 | version = "0.2.0" 730 | dependencies = [ 731 | "rustyline", 732 | "tay", 733 | ] 734 | 735 | [[package]] 736 | name = "textwrap" 737 | version = "0.15.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16" 740 | 741 | [[package]] 742 | name = "tinytemplate" 743 | version = "1.1.0" 744 | source = "registry+https://github.com/rust-lang/crates.io-index" 745 | checksum = "6d3dc76004a03cec1c5932bca4cdc2e39aaa798e3f82363dd94f9adf6098c12f" 746 | dependencies = [ 747 | "serde", 748 | "serde_json", 749 | ] 750 | 751 | [[package]] 752 | name = "unicode-ident" 753 | version = "1.0.4" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" 756 | 757 | [[package]] 758 | name = "unicode-segmentation" 759 | version = "1.6.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 762 | 763 | [[package]] 764 | name = "unicode-width" 765 | version = "0.1.8" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" 768 | 769 | [[package]] 770 | name = "utf8parse" 771 | version = "0.2.0" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 774 | 775 | [[package]] 776 | name = "walkdir" 777 | version = "2.3.1" 778 | source = "registry+https://github.com/rust-lang/crates.io-index" 779 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 780 | dependencies = [ 781 | "same-file", 782 | "winapi", 783 | "winapi-util", 784 | ] 785 | 786 | [[package]] 787 | name = "wasi" 788 | version = "0.9.0+wasi-snapshot-preview1" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 791 | 792 | [[package]] 793 | name = "wasm-bindgen" 794 | version = "0.2.83" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 797 | dependencies = [ 798 | "cfg-if 1.0.0", 799 | "wasm-bindgen-macro", 800 | ] 801 | 802 | [[package]] 803 | name = "wasm-bindgen-backend" 804 | version = "0.2.83" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 807 | dependencies = [ 808 | "bumpalo", 809 | "log", 810 | "once_cell", 811 | "proc-macro2", 812 | "quote", 813 | "syn", 814 | "wasm-bindgen-shared", 815 | ] 816 | 817 | [[package]] 818 | name = "wasm-bindgen-macro" 819 | version = "0.2.83" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 822 | dependencies = [ 823 | "quote", 824 | "wasm-bindgen-macro-support", 825 | ] 826 | 827 | [[package]] 828 | name = "wasm-bindgen-macro-support" 829 | version = "0.2.83" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 832 | dependencies = [ 833 | "proc-macro2", 834 | "quote", 835 | "syn", 836 | "wasm-bindgen-backend", 837 | "wasm-bindgen-shared", 838 | ] 839 | 840 | [[package]] 841 | name = "wasm-bindgen-shared" 842 | version = "0.2.83" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 845 | 846 | [[package]] 847 | name = "web-sys" 848 | version = "0.3.60" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 851 | dependencies = [ 852 | "js-sys", 853 | "wasm-bindgen", 854 | ] 855 | 856 | [[package]] 857 | name = "winapi" 858 | version = "0.3.9" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 861 | dependencies = [ 862 | "winapi-i686-pc-windows-gnu", 863 | "winapi-x86_64-pc-windows-gnu", 864 | ] 865 | 866 | [[package]] 867 | name = "winapi-i686-pc-windows-gnu" 868 | version = "0.4.0" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 871 | 872 | [[package]] 873 | name = "winapi-util" 874 | version = "0.1.5" 875 | source = "registry+https://github.com/rust-lang/crates.io-index" 876 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 877 | dependencies = [ 878 | "winapi", 879 | ] 880 | 881 | [[package]] 882 | name = "winapi-x86_64-pc-windows-gnu" 883 | version = "0.4.0" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 886 | 887 | [[package]] 888 | name = "windows-sys" 889 | version = "0.36.1" 890 | source = "registry+https://github.com/rust-lang/crates.io-index" 891 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 892 | dependencies = [ 893 | "windows_aarch64_msvc", 894 | "windows_i686_gnu", 895 | "windows_i686_msvc", 896 | "windows_x86_64_gnu", 897 | "windows_x86_64_msvc", 898 | ] 899 | 900 | [[package]] 901 | name = "windows_aarch64_msvc" 902 | version = "0.36.1" 903 | source = "registry+https://github.com/rust-lang/crates.io-index" 904 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 905 | 906 | [[package]] 907 | name = "windows_i686_gnu" 908 | version = "0.36.1" 909 | source = "registry+https://github.com/rust-lang/crates.io-index" 910 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 911 | 912 | [[package]] 913 | name = "windows_i686_msvc" 914 | version = "0.36.1" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 917 | 918 | [[package]] 919 | name = "windows_x86_64_gnu" 920 | version = "0.36.1" 921 | source = "registry+https://github.com/rust-lang/crates.io-index" 922 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 923 | 924 | [[package]] 925 | name = "windows_x86_64_msvc" 926 | version = "0.36.1" 927 | source = "registry+https://github.com/rust-lang/crates.io-index" 928 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 929 | 930 | [[package]] 931 | name = "yansi" 932 | version = "0.5.1" 933 | source = "registry+https://github.com/rust-lang/crates.io-index" 934 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 935 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["src/*"] 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## tay 2 | 3 | Programming language writing experiment for learning interpreters and VMs based on https://interpreterbook.com/. 4 | 5 | ![](https://mesuutt.com/static/tay/tay-lang.svg) 6 | 7 | You can look at [examples](https://github.com/mesuutt/tay/tree/master/examples) directory for more usage examples. 8 | 9 | --- 10 | 11 | #### Development 12 | 13 | ```sh 14 | # Start REPL 15 | cargo run 16 | 17 | # Run file 18 | cargo run examples/fib.ty 19 | 20 | # Build 21 | cargo build 22 | 23 | # Test 24 | cargo test 25 | ``` 26 | 27 | ##### TODO 28 | 29 | - [x] Interpreter 30 | - [ ] VM 31 | - [ ] Error reporting 32 | - [ ] Loops 33 | - [ ] Modules 34 | - [ ] Python like `*args` and `**kwargs` 35 | - [ ] Js like arrow functions 36 | - [ ] Standard libraries 37 | - [ ] Playground 38 | - [ ] And more ... 39 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | 2 | You can run files shown as below: 3 | 4 | ```bash 5 | tay examples/fib.ty 6 | ``` -------------------------------------------------------------------------------- /examples/fib.ty: -------------------------------------------------------------------------------- 1 | // vi: ft=rust 2 | 3 | fn fibonacci(x) { 4 | if (x == 0) { 5 | return 0; 6 | } 7 | 8 | if (x == 1) { 9 | return 1; 10 | } 11 | 12 | return fibonacci(x - 1) + fibonacci(x - 2); 13 | } 14 | 15 | fibonacci(25); 16 | -------------------------------------------------------------------------------- /examples/functions.ty: -------------------------------------------------------------------------------- 1 | // vi: ft=rust 2 | a = 1 3 | foo = fn(x){x+a} 4 | 5 | assert_eq(foo(1), 2) 6 | 7 | fn add(a,b) {a+b} 8 | fn sub(a,b) {a-b} 9 | fn applyFunc(a,b,func) {func(a,b)} 10 | 11 | assert_eq(applyFunc(2,2, add), 4) 12 | -------------------------------------------------------------------------------- /examples/lists.ty: -------------------------------------------------------------------------------- 1 | // vi: ft=rust 2 | assert_eq([1 + 2 ,3 * 4], [3, 12]) 3 | my_list = [1 + 2 ,3 * 4, fn(x){x * 2}] 4 | 5 | assert_eq(my_list[1], 12) 6 | assert_eq(my_list[2](2), 4) 7 | -------------------------------------------------------------------------------- /examples/variables.ty: -------------------------------------------------------------------------------- 1 | // vi: ft=rust 2 | // We can create variables with or without let keyword. 3 | 4 | // integers 5 | num1 = 2 6 | num2 = 4 7 | assert_eq(num1 ^ num2, 16) 8 | 9 | // floats 10 | float1 = 0.1 11 | float2 = 0.2 12 | assert_eq(float1 + float2, 0.30000000000000004) 13 | 14 | // strings 15 | str1 = "hello" 16 | str2 = "world" 17 | 18 | assert_eq(str1 + " " + str2, "hello world") 19 | 20 | /* 21 | multi 22 | line 23 | comment 24 | */ 25 | -------------------------------------------------------------------------------- /src/cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tay-cli" 3 | version = "0.2.0" 4 | authors = ["Mesut Tasci "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | tay = {path = "../tay"} 9 | rustyline = "10.0.0" 10 | 11 | 12 | [[bin]] 13 | name = "tay" 14 | path = "src/main.rs" -------------------------------------------------------------------------------- /src/cli/src/file.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Read; 3 | 4 | use tay::evaluator::{Env, eval, Object}; 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | use tay::lexer::Lexer; 8 | use tay::parser::Parser; 9 | 10 | pub(crate) fn eval_file(filename: &str) -> Result { 11 | let mut file = File::open(filename).map_err(|_e| format!("file not found: '{}'", filename))?; 12 | let mut source = String::new(); 13 | 14 | file.read_to_string(&mut source).map_err(|_e| "error reading file")?; 15 | 16 | let env = Env::new(); 17 | let program = Parser::new(Lexer::new(source)).parse().map_err(|e| format!("Parse error: {}", e))?; 18 | let obj = eval(program, Rc::new(RefCell::new(env))).map_err(|e| format!("Eval Error: {}", e))?; 19 | Ok(obj) 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/cli/src/main.rs: -------------------------------------------------------------------------------- 1 | mod repl; 2 | mod file; 3 | 4 | use std::process; 5 | use std::env; 6 | 7 | fn main() { 8 | if let Some(filename) = env::args().nth(1) { 9 | if let Err(e) = file::eval_file(&filename) { 10 | println!("{}", e); 11 | process::exit(1); 12 | } 13 | } else { 14 | repl::start(); 15 | } 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/cli/src/repl.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | 3 | use tay::evaluator::{Env, eval, Object}; 4 | use std::rc::Rc; 5 | use std::cell::RefCell; 6 | use tay::lexer::Lexer; 7 | use tay::parser::Parser; 8 | 9 | use rustyline::Editor; 10 | use rustyline::error::ReadlineError; 11 | 12 | const PROMPT: &str = ">> "; 13 | 14 | pub(crate) fn start() { 15 | let history_file_name = ".tay_history"; 16 | println!( 17 | r#" 18 | ./|,,/| 19 | < o o) {name} 20 | <\ ( | ======= 21 | <\\ |\ | Version: {version} 22 | <\\\ |(__) History: ~/{history} 23 | <\\\\ | 24 | "#, 25 | name = env!("CARGO_PKG_NAME"), 26 | version = env!("CARGO_PKG_VERSION"), 27 | history = history_file_name, 28 | ); 29 | 30 | let mut editor = Editor::<()>::new().unwrap(); 31 | let history_path = format!("{}/{}", env::var("HOME").unwrap(), history_file_name); 32 | if editor.load_history(&history_path).is_err() {} 33 | let env= Rc::new(RefCell::new(Env::new())); 34 | 35 | loop { 36 | let readline = editor.readline(PROMPT); 37 | match readline { 38 | Ok(line) => { 39 | editor.add_history_entry(line.as_str()); 40 | let program = Parser::new(Lexer::new(line)).parse(); 41 | match program { 42 | Ok(p) => { 43 | match eval(p, env.clone()) { 44 | Ok(obj) => { 45 | match obj { 46 | Object::Null => {}, 47 | _ => println!("{}", obj), 48 | } 49 | } 50 | Err(e) => println!("EvalError: {}", e) 51 | } 52 | }, 53 | Err(e) => { 54 | println!("ParseError: {}", e); 55 | continue 56 | } 57 | } 58 | } 59 | Err(ReadlineError::Interrupted) => { 60 | println!("CTRL-C"); 61 | break; 62 | } 63 | Err(ReadlineError::Eof) => { 64 | println!("CTRL-D"); 65 | break; 66 | } 67 | Err(err) => { 68 | println!("Error: {:?}", err); 69 | break; 70 | } 71 | } 72 | } 73 | 74 | editor.save_history(&history_path).unwrap(); 75 | } 76 | -------------------------------------------------------------------------------- /src/tay/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tay" 3 | version = "0.2.0" 4 | authors = ["Mesut Tasci "] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dev-dependencies] 10 | pretty_assertions = "1.3.0" 11 | criterion = "0.4.0" 12 | 13 | crossbeam-utils = "0.8.7" 14 | crossbeam-deque = "0.8.2" 15 | 16 | 17 | [[bench]] 18 | name = "bench_main" 19 | harness = false 20 | -------------------------------------------------------------------------------- /src/tay/benches/bench_main.rs: -------------------------------------------------------------------------------- 1 | use criterion::criterion_main; 2 | 3 | mod benchmarks; 4 | 5 | criterion_main! { 6 | benchmarks::lexer::benches, 7 | benchmarks::parser::benches, 8 | benchmarks::evaluator::benches, 9 | } -------------------------------------------------------------------------------- /src/tay/benches/benchmarks/evaluator.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, Criterion}; 2 | use tay::parser::Parser; 3 | use tay::lexer::Lexer; 4 | use tay::evaluator::{Env, eval}; 5 | use std::rc::Rc; 6 | use std::cell::RefCell; 7 | 8 | pub fn bench_evaluator(c: &mut Criterion) { 9 | let input = r#"fn fibonacci(x) { if (x == 0) {0} else {if (x == 1) {1} else {fibonacci(x - 1) + fibonacci(x - 2)}}}; fibonacci(2)"#.to_string(); 10 | let program = Parser::new(Lexer::new(input.clone())).parse().unwrap(); 11 | c.bench_function("eval fib", |b| b.iter(|| { 12 | let _ = eval(program.clone(), Rc::new(RefCell::new(Env::new()))); 13 | })); 14 | } 15 | 16 | criterion_group!(benches, bench_evaluator); 17 | -------------------------------------------------------------------------------- /src/tay/benches/benchmarks/lexer.rs: -------------------------------------------------------------------------------- 1 | use criterion::{Criterion, criterion_group}; 2 | 3 | use tay::lexer::{Lexer, Token}; 4 | 5 | pub fn bench_lexer(c: &mut Criterion) { 6 | let input = r#"fn fibonacci(x) { if (x == 0) {0} else {if (x == 1) {1} else {fibonacci(x - 1) + fibonacci(x - 2)}}}"#.to_string(); 7 | 8 | c.bench_function("lexer fib", |b| b.iter(|| { 9 | let mut lex = Lexer::new(input.clone()); 10 | loop { 11 | if let Token::EndOfFile = lex.next_token() { 12 | break; 13 | } 14 | } 15 | })); 16 | } 17 | 18 | criterion_group!(benches, bench_lexer); -------------------------------------------------------------------------------- /src/tay/benches/benchmarks/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; 2 | pub mod parser; 3 | pub mod evaluator; -------------------------------------------------------------------------------- /src/tay/benches/benchmarks/parser.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, Criterion}; 2 | use tay::parser::Parser; 3 | use tay::lexer::Lexer; 4 | 5 | pub fn bench_parser(c: &mut Criterion) { 6 | let input = r#"fn fibonacci(x) { if (x == 0) {0} else {if (x == 1) {1} else {fibonacci(x - 1) + fibonacci(x - 2)}}}"#.to_string(); 7 | c.bench_function("parse fib", |b| b.iter(|| 8 | Parser::new(Lexer::new(input.clone())).parse().unwrap()) 9 | ); 10 | } 11 | 12 | criterion_group!(benches, bench_parser); 13 | -------------------------------------------------------------------------------- /src/tay/src/ast/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | pub type IntegerSize = i64; 4 | pub type FloatSize = f64; 5 | 6 | #[derive(PartialEq, Debug, Clone)] 7 | pub enum Prefix { 8 | Minus, 9 | Bang, 10 | } 11 | 12 | impl fmt::Display for Prefix { 13 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 14 | match self { 15 | Prefix::Minus => write!(f, "-"), 16 | Prefix::Bang => write!(f, "!"), 17 | } 18 | } 19 | } 20 | 21 | 22 | #[derive(PartialEq, Debug, Clone)] 23 | pub enum Infix { 24 | Plus, 25 | Minus, 26 | Mul, 27 | Div, 28 | Exponent, 29 | Percent, 30 | 31 | Gt, 32 | Lt, 33 | Gte, 34 | Lte, 35 | Eq, 36 | NotEq, 37 | } 38 | 39 | impl fmt::Display for Infix { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | match self { 42 | Infix::Plus => write!(f, "+"), 43 | Infix::Minus => write!(f, "-"), 44 | Infix::Mul => write!(f, "*"), 45 | Infix::Div => write!(f, "/"), 46 | Infix::Percent => write!(f, "%"), 47 | Infix::Exponent => write!(f, "^"), 48 | 49 | Infix::Gt => write!(f, ">"), 50 | Infix::Lt => write!(f, "<"), 51 | Infix::Lte => write!(f, "<="), 52 | Infix::Gte => write!(f, ">="), 53 | Infix::Eq => write!(f, "=="), 54 | Infix::NotEq => write!(f, "!="), 55 | } 56 | } 57 | } 58 | 59 | 60 | #[derive(Debug, PartialEq, Clone)] 61 | pub enum Expression { 62 | Ident(String), 63 | Literal(Literal), 64 | Prefix(Prefix, Box), 65 | Infix(Infix, Box, Box), 66 | List(Vec), 67 | Hash(Vec<(Expression, Expression)>), 68 | // left can be an ident, a list, a func call etc. 69 | Index(/*left*/Box, /*index*/Box), 70 | 71 | // myFunc(x, y) 72 | Call { 73 | // func should be Ident or Expression::Func 74 | func: Box, 75 | args: Vec, 76 | }, 77 | 78 | // fn (x, y) {} 79 | Func { 80 | // name of the function 81 | identifier: Option, 82 | params: Vec, 83 | body: BlockStatement, 84 | }, 85 | 86 | If { 87 | condition: Box, 88 | consequence: BlockStatement, 89 | alternative: Option, 90 | }, 91 | 92 | } 93 | 94 | impl fmt::Display for Expression { 95 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 96 | match self { 97 | Expression::Ident(ident) => { 98 | write!(f, "{}", ident) 99 | } 100 | Expression::Literal(literal) => { 101 | write!(f, "{}", literal) 102 | } 103 | Expression::Prefix(prefix, expr) => { 104 | write!(f, "({}{})", prefix, expr) 105 | } 106 | Expression::Infix(infix, left, right) => { 107 | write!(f, "({} {} {})", left, infix, right) 108 | } 109 | Expression::List(elems) => { 110 | let elem_list = elems.iter().map(|s| format!("{}", s)).collect::>(); 111 | write!(f, "[{}]", elem_list.join(", ")) 112 | } 113 | Expression::Index(left, index) => { 114 | write!(f, "({}[{}])", left, index) 115 | } 116 | Expression::Call { func, args } => { 117 | let arg_list = args.iter().map(|expr| format!("{}", expr)).collect::>(); 118 | write!(f, "{}({})", func, arg_list.join(", ")) 119 | } 120 | Expression::Func { identifier: name, params, body } => { 121 | let param_list = params.iter().map(|s| s.to_string()).collect::>(); 122 | let statement_list = body.iter().map(|s| format!("{}", s)).collect::>(); 123 | write!(f, "fn {}({}) {{\n{}\n}}", name.clone().unwrap_or_else(|| "".to_string()), param_list.join(", "), statement_list.join("")) 124 | } 125 | 126 | Expression::If { condition, consequence, alternative } => { 127 | if !alternative.is_some() { 128 | write!(f, "if {} {} else {}", 129 | condition, 130 | consequence.iter().map(|x| format!("{}", x)).collect::>().join(""), 131 | alternative.as_ref().unwrap().iter().map(|x| format!("{}", x)).collect::>().join(""), 132 | ) 133 | } else { 134 | write!(f, "if {} {}", 135 | condition, 136 | consequence.iter().map(|x| format!("{}", x)).collect::>().join("") 137 | ) 138 | } 139 | } 140 | Expression::Hash(pairs) => { 141 | let pair_list = pairs.iter().map(|(k, v)| format!("{}: {}", k, v)).collect::>(); 142 | write!(f, "{{{}}}", pair_list.join(", ")) 143 | } 144 | } 145 | } 146 | } 147 | 148 | #[derive(Debug, PartialEq, Clone)] 149 | pub enum Statement { 150 | Let(String, Expression), 151 | Return(Expression), 152 | 153 | // x + 10; 154 | Expression(Expression), 155 | } 156 | 157 | impl fmt::Display for Statement { 158 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 159 | match self { 160 | Statement::Let(ident, expr) => { 161 | write!(f, "let {} = {};", ident, expr) 162 | } 163 | Statement::Expression(expr) => write!(f, "{}", expr), 164 | Statement::Return(expr) => write!(f, "{}", expr), 165 | } 166 | } 167 | } 168 | 169 | #[derive(Debug, PartialEq, Clone)] 170 | pub enum Literal { 171 | Int(IntegerSize), 172 | Float(FloatSize), 173 | String(String), 174 | Bool(bool), 175 | } 176 | 177 | 178 | impl fmt::Display for Literal { 179 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 180 | match self { 181 | Literal::Int(x) => write!(f, "{}", x), 182 | Literal::Float(x) => write!(f, "{}", x), 183 | Literal::String(x) => write!(f, "\"{}\"", x), 184 | Literal::Bool(x) => write!(f, "{}", x), 185 | } 186 | } 187 | } 188 | 189 | pub type BlockStatement = Vec; 190 | 191 | #[derive(Clone)] // Clone added for benchmarking evaluator 192 | pub struct Program { 193 | pub statements: BlockStatement, 194 | // pub errors: Vec, 195 | } 196 | 197 | impl fmt::Display for Program { 198 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 199 | for stmt in &self.statements { 200 | write!(f, "{}", stmt)?; 201 | } 202 | Ok(()) 203 | } 204 | } 205 | 206 | #[derive(PartialOrd, PartialEq)] 207 | pub enum Precedence { 208 | Lowest, 209 | // == or != 210 | Equal, 211 | // < or > 212 | LessGreater, 213 | // + or - 214 | Sum, 215 | // *, /, % 216 | Product, 217 | // ^ 218 | Exponent, 219 | // -X, !X 220 | Prefix, 221 | // myFunc(x), LParen 222 | Call, 223 | Index, 224 | } -------------------------------------------------------------------------------- /src/tay/src/evaluator/builtins.rs: -------------------------------------------------------------------------------- 1 | use crate::evaluator::object::{Object, EvalResult, assert_argument_count, assert_argument_count_range}; 2 | use crate::evaluator::error::EvalErrorKind; 3 | use crate::evaluator::is_truthy; 4 | 5 | pub struct Builtin { 6 | name: &'static str, 7 | builtin: Object 8 | } 9 | 10 | pub const BUILTINS: &[Builtin] = &[ 11 | Builtin{ 12 | name: "len", 13 | builtin: Object::Builtin(len_fn) 14 | }, 15 | Builtin{ 16 | name: "print", 17 | builtin: Object::Builtin(print_fn) 18 | }, 19 | Builtin{ 20 | name: "println", 21 | builtin: Object::Builtin(println_fn) 22 | }, 23 | Builtin{ 24 | name: "assert", 25 | builtin: Object::Builtin(assert_fn) 26 | }, 27 | Builtin{ 28 | name: "assert_eq", 29 | builtin: Object::Builtin(assert_eq_fn) 30 | } 31 | ]; 32 | 33 | pub fn lookup_builtin(name: &str) -> Option { 34 | for b in BUILTINS { 35 | if b.name == name { 36 | return Some(b.builtin.clone()) 37 | } 38 | } 39 | None 40 | } 41 | 42 | fn len_fn(args: Vec) -> EvalResult { 43 | assert_argument_count(1, &args)?; 44 | match &args[0] { 45 | Object::String(x) => Ok(Object::Int(x.len() as i64)), 46 | Object::List(elems) => Ok(Object::Int(elems.len() as i64)), 47 | _ => Err(EvalErrorKind::UnsupportedArguments("len", args)) 48 | } 49 | } 50 | 51 | fn print_fn(args: Vec) -> EvalResult { 52 | assert_argument_count(1, &args)?; 53 | for i in args { 54 | print!("{}", i) 55 | } 56 | Ok(Object::Null) 57 | } 58 | 59 | fn println_fn(args: Vec) -> EvalResult { 60 | assert_argument_count(1, &args)?; 61 | println!("{}", args[0]); 62 | Ok(Object::Null) 63 | } 64 | 65 | fn assert_fn(args: Vec) -> EvalResult { 66 | assert_argument_count_range(1..2, &args)?; 67 | if !is_truthy(&args[0].clone()) { 68 | if args.len() == 2 { 69 | Err(EvalErrorKind::AssertionError(args[1].clone())) 70 | } else { 71 | Err(EvalErrorKind::AssertionError(Object::String("".to_owned()))) 72 | } 73 | } else { 74 | Ok(Object::Null) 75 | } 76 | } 77 | 78 | fn assert_eq_fn(args: Vec) -> EvalResult { 79 | assert_argument_count_range(2..3, &args)?; 80 | if args[0] != args[1] { 81 | if args.len() == 3 { 82 | Err(EvalErrorKind::AssertionError(Object::String(format!("{}", args.last().unwrap().to_owned())))) 83 | } else { 84 | Err(EvalErrorKind::AssertionError(Object::String("".to_owned()))) 85 | } 86 | } else { 87 | Ok(Object::Null) 88 | } 89 | 90 | } -------------------------------------------------------------------------------- /src/tay/src/evaluator/env.rs: -------------------------------------------------------------------------------- 1 | use super::object::Object; 2 | use std::collections::HashMap; 3 | use std::rc::Rc; 4 | use std::cell::RefCell; 5 | 6 | #[derive(PartialEq, Debug, Clone)] 7 | pub struct Env { 8 | store: HashMap, 9 | outer: Option>>, // Without RefCell we can't borrow Env as mutable 10 | } 11 | 12 | impl Env { 13 | pub fn new() -> Self { 14 | Env { 15 | store: HashMap::new(), 16 | outer: None, 17 | } 18 | } 19 | 20 | pub fn get(&self, key: &str) -> Option { 21 | match self.store.get(key) { 22 | Some(obj) => Some(obj.clone()), 23 | None => match self.outer { 24 | Some(ref outer) => outer.borrow_mut().get(key), 25 | None => None, 26 | }, 27 | } 28 | } 29 | 30 | pub fn set(&mut self, key: String, val: &Object) { 31 | self.store.insert(key, val.clone()); 32 | } 33 | 34 | pub fn extend(outer: &Rc>) -> Self { 35 | Env { 36 | store: HashMap::new(), 37 | outer: Some(outer.clone()), 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/tay/src/evaluator/error.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Formatter; 2 | use std::fmt; 3 | use crate::ast::{Infix, Prefix}; 4 | use crate::evaluator::object::Object; 5 | 6 | #[derive(Debug, Clone, PartialEq)] 7 | pub enum EvalErrorKind { 8 | DivideByZero, 9 | ExponentTooLarge, 10 | 11 | UndefinedIdent(String), 12 | UnknownPrefixOp(Prefix, Object), 13 | UnknownInfixOp(Infix, Object, Object), 14 | UnsupportedInfixOp(Infix, Object, Object), 15 | TypeMismatch(Infix, Object, Object), 16 | NotCallable(Object), 17 | WrongArgumentCount(/*expected*/usize, /*given*/usize), 18 | UnsupportedArguments(/*func_name*/ &'static str, Vec), 19 | UnknownIndexOperator(Object, Object), 20 | KeyError(Object), 21 | AssertionError(/*message*/Object), 22 | UnsupportedHashKey(Object), 23 | } 24 | 25 | impl fmt::Display for EvalErrorKind { 26 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 27 | match self { 28 | EvalErrorKind::DivideByZero => write!(f, "division by zero"), 29 | EvalErrorKind::ExponentTooLarge => write!(f, "exponent too large"), 30 | EvalErrorKind::UndefinedIdent(x) => write!(f, "undefined identifier: '{}'", x), 31 | 32 | EvalErrorKind::UnknownPrefixOp(prefix, o) => write!(f, "unknown prefix: {} {}", prefix, o), 33 | EvalErrorKind::UnknownInfixOp(infix, l, r) => { 34 | write!(f, "unknown infix: {} {} {}", l.type_name(), infix, r.type_name()) 35 | } 36 | EvalErrorKind::UnsupportedInfixOp(infix, l, r) => { 37 | write!(f, "unsupported operand types for {}: '{}' and '{}'", infix, l.type_name(), r.type_name()) 38 | } 39 | EvalErrorKind::TypeMismatch(infix, l, r) => { 40 | write!(f, "type mismatch: {} {} {}", l.type_name(), infix, r.type_name()) 41 | } 42 | EvalErrorKind::NotCallable(obj) => { 43 | write!(f, "not callable: {}", obj) 44 | } 45 | EvalErrorKind::WrongArgumentCount(expected, got) => { 46 | write!(f, "wrong number of arguments, want={}, got={}", expected, got) 47 | } 48 | EvalErrorKind::UnsupportedArguments(/*func_name*/ fn_name, objects) => { 49 | write!(f, "unsupported argument to {}: {}", fn_name, objects[0].type_name()) 50 | } 51 | EvalErrorKind::UnknownIndexOperator(_left, index) => { 52 | write!(f, "unknown index operator: {}", index.type_name()) 53 | } 54 | EvalErrorKind::KeyError(index) => { 55 | write!(f, "key error: {}", index) 56 | } 57 | EvalErrorKind::AssertionError(message) => { 58 | write!(f, "assertion error: {}", message) 59 | } 60 | EvalErrorKind::UnsupportedHashKey(obj) => { 61 | write!(f, "unsupported hash key {}", obj) 62 | } 63 | } 64 | } 65 | } 66 | 67 | -------------------------------------------------------------------------------- /src/tay/src/evaluator/mod.rs: -------------------------------------------------------------------------------- 1 | mod env; 2 | mod object; 3 | mod error; 4 | mod builtins; 5 | 6 | #[cfg(test)] 7 | mod test; 8 | 9 | pub use env::Env; 10 | pub use object::Object; 11 | use crate::ast; 12 | use crate::evaluator::error::EvalErrorKind; 13 | use crate::ast::{FloatSize, Expression, BlockStatement, Program, Statement, Infix, Prefix, IntegerSize}; 14 | use std::rc::Rc; 15 | use std::cell::RefCell; 16 | use crate::evaluator::object::EvalResult; 17 | use crate::evaluator::builtins::lookup_builtin; 18 | use std::collections::HashMap; 19 | use object::HashKey; 20 | 21 | 22 | pub fn eval(program: Program, env: Rc>) -> EvalResult { 23 | let mut result = Object::Null; 24 | for s in program.statements { 25 | result = eval_statement(&s, Rc::clone(&env))?; 26 | if let Object::Return(value) = result { 27 | return Ok(*value); // Stop evaluation 28 | } 29 | } 30 | 31 | Ok(result) 32 | } 33 | 34 | fn eval_statement(statement: &Statement, env: Rc>) -> EvalResult { 35 | match statement { 36 | Statement::Expression(expr) => eval_expr(expr, env), 37 | Statement::Let(ident, expr) => eval_let_statement(ident, expr, env), 38 | Statement::Return(expr) => eval_return_statement(expr, env), 39 | } 40 | } 41 | 42 | fn eval_block_statement(statement: &BlockStatement, env: Rc>) -> EvalResult { 43 | let mut result = Object::Null; 44 | for s in statement { 45 | result = eval_statement(&s, Rc::clone(&env))?; 46 | if let Object::Return(value) = result { 47 | return Ok(Object::Return(value)); // Stop evaluation 48 | } 49 | } 50 | 51 | Ok(result) 52 | } 53 | 54 | 55 | fn eval_expr(expr: &Expression, env: Rc>) -> EvalResult { 56 | match expr { 57 | Expression::Literal(lit) => Ok(eval_literal(lit)), 58 | Expression::Ident(ident) => eval_ident(ident, env), 59 | Expression::Prefix(operator, expr) => { 60 | eval_prefix_expr(operator, expr, env) 61 | } 62 | Expression::Infix(operator, left_expr, right_expr) => { 63 | eval_infix_expr(operator, left_expr, right_expr, env) 64 | } 65 | Expression::Func { identifier, params, body } => { 66 | let func = Object::Func(params.to_vec(), body.clone(), env.clone()); 67 | if identifier.is_some() { // if function has a name, add to env 68 | env.borrow_mut().set(identifier.clone().unwrap(), &func); 69 | } 70 | 71 | Ok(func) 72 | } 73 | Expression::Call { func, args } => { 74 | eval_call_expr(func, args, env) 75 | } 76 | Expression::If { condition, consequence, alternative } => { 77 | eval_if_expression(condition, consequence, alternative, env) 78 | } 79 | Expression::List(elements) => { 80 | Ok(Object::List(eval_exprs(elements, env)?)) 81 | } 82 | Expression::Index(left, index) => { 83 | eval_index_expr(left, index, env) 84 | } 85 | Expression::Hash(pairs) => { 86 | eval_hash_literal(pairs, env) 87 | } 88 | } 89 | } 90 | 91 | fn eval_exprs(exprs: &[Expression], env: Rc>) -> Result, EvalErrorKind> { 92 | let mut result = Vec::::new(); 93 | for e in exprs { 94 | result.push(eval_expr(&e, env.clone())?) 95 | } 96 | Ok(result) 97 | } 98 | 99 | fn eval_prefix_expr(operator: &Prefix, right_expr: &Expression, env: Rc>) -> EvalResult { 100 | let right = eval_expr(right_expr, env)?; 101 | match operator { 102 | Prefix::Minus => { 103 | match right { 104 | Object::Int(x) => Ok(Object::Int(-x)), 105 | _ => Err(EvalErrorKind::UnknownPrefixOp(Prefix::Minus, right)) 106 | } 107 | } 108 | Prefix::Bang => { 109 | Ok(match right { 110 | Object::Bool(true) => Object::Bool(false), 111 | Object::Bool(false) => Object::Bool(true), 112 | Object::Null => Object::Bool(true), 113 | _ => Object::Bool(false), 114 | }) 115 | } 116 | } 117 | } 118 | 119 | fn eval_infix_expr(operator: &Infix, left_expr: &Expression, right_expr: &Expression, env: Rc>) -> EvalResult { 120 | let left_obj = eval_expr(left_expr, Rc::clone(&env))?; 121 | let right_obj = eval_expr(right_expr, env)?; 122 | match (left_obj, right_obj) { 123 | (Object::Int(x), Object::Int(y)) => { 124 | eval_integer_infix_expr(operator, x, y) 125 | } 126 | (Object::Float(x), Object::Float(y)) => { 127 | eval_float_infix_expr(operator, x, y) 128 | } 129 | (Object::Float(x), Object::Int(y)) => { 130 | eval_float_with_int_infix_expr(operator, x, y) 131 | } 132 | (Object::Int(x), Object::Float(y)) => { 133 | eval_int_with_float_infix_expr(operator, x, y) 134 | } 135 | (Object::String(x), Object::String(y)) => { 136 | eval_string_infix_expr(operator, &x, &y) 137 | } 138 | (l, r) => Err(EvalErrorKind::TypeMismatch(operator.clone(), l, r)) 139 | } 140 | } 141 | 142 | 143 | fn eval_if_expression( 144 | cond: &Expression, 145 | consequence: &BlockStatement, 146 | alternative: &Option, 147 | env: Rc>, 148 | ) -> EvalResult { 149 | let cond = eval_expr(cond, env.clone())?; 150 | if is_truthy(&cond) { 151 | eval_block_statement(consequence, env) 152 | } else if let Some(alt) = alternative { 153 | eval_block_statement(alt, env) 154 | } else { 155 | Ok(Object::Null) 156 | } 157 | } 158 | 159 | 160 | fn eval_literal(literal: &ast::Literal) -> Object { 161 | match literal { 162 | ast::Literal::Int(x) => Object::Int(*x), 163 | ast::Literal::Float(x) => Object::Float(*x), 164 | ast::Literal::Bool(x) => Object::Bool(*x), 165 | ast::Literal::String(x) => Object::String(x.clone()), 166 | } 167 | } 168 | 169 | fn eval_ident(ident: &str, env: Rc>) -> EvalResult { 170 | // If we use borrow() instead borrow_mut() value removing after return. 171 | match env.borrow_mut().get(&ident) { 172 | Some(val) => Ok(val), 173 | None => { 174 | if let Some(builtin) = lookup_builtin(ident) { 175 | Ok(builtin) 176 | } else { 177 | Err(EvalErrorKind::UndefinedIdent(ident.to_string())) 178 | } 179 | } 180 | } 181 | } 182 | 183 | fn eval_integer_infix_expr(operator: &ast::Infix, left_val: i64, right_val: i64) -> EvalResult { 184 | let result = match operator { 185 | ast::Infix::Minus => Object::Int(left_val - right_val), 186 | ast::Infix::Plus => Object::Int(left_val + right_val), 187 | ast::Infix::Mul => Object::Int(left_val * right_val), 188 | ast::Infix::Div => { 189 | if right_val == 0 { 190 | return Err(EvalErrorKind::DivideByZero); 191 | } 192 | 193 | let result = left_val as FloatSize / right_val as FloatSize; 194 | if result.fract() == 0.0 { 195 | Object::Int(result as IntegerSize) 196 | } else { 197 | Object::Float(result) 198 | } 199 | } 200 | ast::Infix::Percent => Object::Int(left_val % right_val), 201 | ast::Infix::Exponent => { 202 | let (num, is_overflow) = (left_val as i32).overflowing_pow(right_val as u32); 203 | if is_overflow { 204 | return Err(EvalErrorKind::ExponentTooLarge); 205 | } else { 206 | Object::Int(num as i64) 207 | } 208 | } 209 | ast::Infix::Lt => { 210 | if left_val < right_val { 211 | Object::Bool(true) 212 | } else { 213 | Object::Bool(false) 214 | } 215 | } 216 | ast::Infix::Gt => { 217 | if left_val > right_val { 218 | Object::Bool(true) 219 | } else { 220 | Object::Bool(false) 221 | } 222 | } 223 | ast::Infix::Lte => { 224 | if left_val <= right_val { 225 | Object::Bool(true) 226 | } else { 227 | Object::Bool(false) 228 | } 229 | } 230 | ast::Infix::Gte => { 231 | if left_val >= right_val { 232 | Object::Bool(true) 233 | } else { 234 | Object::Bool(false) 235 | } 236 | } 237 | ast::Infix::Eq => { 238 | if left_val == right_val { 239 | Object::Bool(true) 240 | } else { 241 | Object::Bool(false) 242 | } 243 | } 244 | ast::Infix::NotEq => { 245 | if left_val != right_val { 246 | Object::Bool(true) 247 | } else { 248 | Object::Bool(false) 249 | } 250 | } 251 | }; 252 | 253 | Ok(result) 254 | } 255 | 256 | fn eval_float_infix_expr(operator: &ast::Infix, left_val: ast::FloatSize, right_val: ast::FloatSize) -> EvalResult { 257 | let result = match operator { 258 | ast::Infix::Minus => Object::Float(left_val - right_val), 259 | ast::Infix::Plus => Object::Float(left_val + right_val), 260 | ast::Infix::Mul => Object::Float(left_val * right_val), 261 | ast::Infix::Div => { 262 | if right_val == 0.0 { 263 | return Err(EvalErrorKind::DivideByZero); 264 | } 265 | Object::Float(left_val / right_val) 266 | } 267 | ast::Infix::Percent => Object::Float(left_val % right_val), 268 | ast::Infix::Exponent => { 269 | let num = left_val.powf(right_val); 270 | Object::Float(num) 271 | } 272 | ast::Infix::Lt => { 273 | if (left_val as FloatSize) < right_val { 274 | Object::Bool(true) 275 | } else { 276 | Object::Bool(false) 277 | } 278 | } 279 | ast::Infix::Gt => { 280 | if (left_val as FloatSize) > right_val { 281 | Object::Bool(true) 282 | } else { 283 | Object::Bool(false) 284 | } 285 | } 286 | ast::Infix::Lte => { 287 | if (left_val as FloatSize) <= right_val { 288 | Object::Bool(true) 289 | } else { 290 | Object::Bool(false) 291 | } 292 | } 293 | ast::Infix::Gte => { 294 | if (left_val as FloatSize) >= right_val { 295 | Object::Bool(true) 296 | } else { 297 | Object::Bool(false) 298 | } 299 | } 300 | ast::Infix::Eq => { 301 | if ((left_val as FloatSize) - right_val).abs() == 0.0 { 302 | Object::Bool(true) 303 | } else { 304 | Object::Bool(false) 305 | } 306 | } 307 | ast::Infix::NotEq => { 308 | if ((left_val as FloatSize) - right_val).abs() != 0.0 { 309 | Object::Bool(true) 310 | } else { 311 | Object::Bool(false) 312 | } 313 | } 314 | }; 315 | 316 | Ok(result) 317 | } 318 | 319 | fn eval_float_with_int_infix_expr(operator: &ast::Infix, left_val: ast::FloatSize, right_val: ast::IntegerSize) -> EvalResult { 320 | let result = match operator { 321 | ast::Infix::Minus => Object::Float(left_val - right_val as ast::FloatSize), 322 | ast::Infix::Plus => Object::Float(left_val + right_val as ast::FloatSize), 323 | ast::Infix::Mul => Object::Float(left_val * right_val as ast::FloatSize), 324 | ast::Infix::Div => Object::Float(left_val / right_val as ast::FloatSize), 325 | ast::Infix::Percent => Object::Float(left_val % right_val as ast::FloatSize), 326 | ast::Infix::Exponent => { 327 | // let num = left_val.powi(right_val as i32); 328 | // Ok(Object::Float(num)) 329 | return Err(EvalErrorKind::UnsupportedInfixOp( 330 | operator.clone(), Object::Float(left_val), Object::Int(right_val)) 331 | ); 332 | } 333 | ast::Infix::Lt => { 334 | if left_val < (right_val as FloatSize) { 335 | Object::Bool(true) 336 | } else { 337 | Object::Bool(false) 338 | } 339 | } 340 | ast::Infix::Gt => { 341 | if left_val > (right_val as FloatSize) { 342 | Object::Bool(true) 343 | } else { 344 | Object::Bool(false) 345 | } 346 | } 347 | ast::Infix::Lte => { 348 | if left_val <= (right_val as FloatSize) { 349 | Object::Bool(true) 350 | } else { 351 | Object::Bool(false) 352 | } 353 | } 354 | ast::Infix::Gte => { 355 | if left_val >= (right_val as FloatSize) { 356 | Object::Bool(true) 357 | } else { 358 | Object::Bool(false) 359 | } 360 | } 361 | ast::Infix::Eq => { 362 | if (left_val - (right_val as FloatSize)).abs() == 0.0 { 363 | Object::Bool(true) 364 | } else { 365 | Object::Bool(false) 366 | } 367 | } 368 | ast::Infix::NotEq => { 369 | if (left_val - (right_val as FloatSize)).abs() != 0.0 { 370 | Object::Bool(true) 371 | } else { 372 | Object::Bool(false) 373 | } 374 | } 375 | }; 376 | 377 | Ok(result) 378 | } 379 | 380 | fn eval_int_with_float_infix_expr(operator: &ast::Infix, left_val: ast::IntegerSize, right_val: ast::FloatSize) -> EvalResult { 381 | let result = match operator { 382 | ast::Infix::Minus => Object::Float(left_val as ast::FloatSize - right_val), 383 | ast::Infix::Plus => Object::Float(left_val as ast::FloatSize + right_val), 384 | ast::Infix::Mul => Object::Float(left_val as ast::FloatSize * right_val), 385 | ast::Infix::Div => Object::Float(left_val as ast::FloatSize / right_val), 386 | ast::Infix::Percent => Object::Float(left_val as ast::FloatSize % right_val), 387 | ast::Infix::Exponent => { 388 | // let num = left_val.pow(right_val as u32); 389 | // Ok(Object::Float(num as ast::FloatSize)) 390 | return Err(EvalErrorKind::UnsupportedInfixOp( 391 | operator.clone(), Object::Int(left_val), Object::Float(right_val)) 392 | ); 393 | } 394 | ast::Infix::Lt => { 395 | if (left_val as FloatSize) < right_val { 396 | Object::Bool(true) 397 | } else { 398 | Object::Bool(false) 399 | } 400 | } 401 | ast::Infix::Gt => { 402 | if (left_val as FloatSize) > right_val { 403 | Object::Bool(true) 404 | } else { 405 | Object::Bool(false) 406 | } 407 | } 408 | ast::Infix::Lte => { 409 | if (left_val as FloatSize) <= right_val { 410 | Object::Bool(true) 411 | } else { 412 | Object::Bool(false) 413 | } 414 | } 415 | ast::Infix::Gte => { 416 | if (left_val as FloatSize) >= right_val { 417 | Object::Bool(true) 418 | } else { 419 | Object::Bool(false) 420 | } 421 | } 422 | ast::Infix::Eq => { 423 | if ((left_val as FloatSize) - right_val).abs() == 0.0 { 424 | Object::Bool(true) 425 | } else { 426 | Object::Bool(false) 427 | } 428 | } 429 | ast::Infix::NotEq => { 430 | if (right_val - (left_val as FloatSize)).abs() != 0.0 { 431 | Object::Bool(true) 432 | } else { 433 | Object::Bool(false) 434 | } 435 | } 436 | }; 437 | 438 | Ok(result) 439 | } 440 | 441 | fn eval_string_infix_expr(operator: &ast::Infix, left_val: &str, right_val: &str) -> EvalResult { 442 | let result = match operator { 443 | ast::Infix::Plus => { 444 | let mut s = left_val.to_owned(); 445 | s.push_str(&right_val.to_owned()); 446 | Object::String(s) 447 | } 448 | ast::Infix::Eq => { 449 | Object::Bool(left_val == right_val) 450 | } 451 | ast::Infix::NotEq => { 452 | Object::Bool(left_val != right_val) 453 | } 454 | _op => { 455 | return Err(EvalErrorKind::UnknownInfixOp( 456 | operator.clone(), 457 | Object::String(left_val.to_owned()), 458 | Object::String(right_val.to_owned()), 459 | )); 460 | } 461 | }; 462 | 463 | Ok(result) 464 | } 465 | 466 | fn eval_let_statement(ident: &str, expr: &ast::Expression, env: Rc>) -> EvalResult { 467 | let value = eval_expr(expr, env.clone())?; 468 | env.borrow_mut().set(ident.to_string(), &value); 469 | Ok(Object::Null) 470 | } 471 | 472 | fn eval_return_statement(expr: &ast::Expression, env: Rc>) -> EvalResult { 473 | let value = eval_expr(&expr, env)?; 474 | Ok(Object::Return(Box::new(value))) 475 | } 476 | 477 | fn eval_call_expr(func_expr: &Expression, arguments: &[Expression], env: Rc>) -> EvalResult { 478 | let args = eval_exprs(arguments, env.clone())?; 479 | let func = eval_expr(&*func_expr, env.clone())?; 480 | apply_func(&func, &args, env) 481 | } 482 | 483 | fn apply_func(func: &Object, args: &[Object], env: Rc>) -> EvalResult { 484 | match func { 485 | Object::Func(params, body, _) => { 486 | let extended_env = extend_func_env(params.clone(), args, env); 487 | let evaluated = eval_block_statement(body, extended_env)?; 488 | match evaluated { 489 | Object::Return(val) => Ok(*val), 490 | _ => Ok(evaluated) 491 | } 492 | } 493 | Object::Builtin(func) => { 494 | // TODO: Make builtings runnable with refs instead values. 495 | // for example: len(['a', 'big', 'list']) 496 | func(args.to_vec()) 497 | } 498 | _ => Err(EvalErrorKind::NotCallable(func.clone())) 499 | } 500 | } 501 | 502 | fn extend_func_env(params: Vec, args: &[Object], parent_env: Rc>) -> Rc> { 503 | let new_env = Rc::new(RefCell::new(Env::extend(&parent_env))); 504 | for (i, param) in params.iter().enumerate() { 505 | new_env.borrow_mut().set(param.clone(), args.get(i).unwrap()) 506 | } 507 | new_env 508 | } 509 | 510 | 511 | fn eval_index_expr(left: &Expression, index: &Expression, env: Rc>) -> EvalResult { 512 | let left_evalulated = eval_expr(left, env.clone())?; 513 | let index_evaluated = eval_expr(index, env)?; 514 | 515 | match (left_evalulated, index_evaluated) { 516 | (Object::List(elems), Object::Int(index)) => { 517 | match elems.get(index as usize) { 518 | Some(e) => Ok(e.clone()), 519 | None => Err(EvalErrorKind::KeyError(Object::Int(index))) 520 | } 521 | } 522 | (l, i) => Err(EvalErrorKind::UnknownIndexOperator(l, i)) 523 | } 524 | } 525 | 526 | fn eval_hash_literal(pairs: &[(Expression, Expression)], env: Rc>) -> EvalResult { 527 | let mut map = HashMap::new(); 528 | for (k, v) in pairs { 529 | let key = eval_expr(k, env.clone())?; 530 | let val = eval_expr(v, env.clone())?; 531 | let hash_key = HashKey::from_object(&key)?; 532 | map.insert(hash_key, val); 533 | 534 | } 535 | Ok(Object::Hash(map)) 536 | } 537 | 538 | fn is_truthy(obj: &Object) -> bool { 539 | match obj { 540 | Object::Null => false, 541 | Object::Bool(false) => false, 542 | Object::Bool(true) => true, 543 | _ => true // other expressions. Maybe we dont allow this in the future: if (1){...} 544 | } 545 | } -------------------------------------------------------------------------------- /src/tay/src/evaluator/object.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::{BlockStatement, FloatSize, IntegerSize}; 2 | use crate::evaluator::error::EvalErrorKind; 3 | use crate::evaluator::Env; 4 | use std::cell::RefCell; 5 | use std::collections::HashMap; 6 | use std::fmt; 7 | use std::rc::Rc; 8 | use std::ops::Range; 9 | 10 | pub type EvalResult = Result; 11 | pub type BuiltinFunc = fn(Vec) -> EvalResult; 12 | 13 | #[derive(PartialEq, Clone, Debug)] 14 | pub enum Object { 15 | Int(IntegerSize), 16 | Float(FloatSize), 17 | String(String), 18 | Bool(bool), 19 | Return(Box), 20 | Func( 21 | /*params*/ Vec, 22 | BlockStatement, 23 | Rc>, 24 | ), 25 | Builtin(BuiltinFunc), 26 | List(Vec), 27 | Hash(HashMap), 28 | // falsy if expr conditions and assigning variables returns NULL 29 | // if (1>2) {10} => NULL 30 | // a = 12 => NULL 31 | Null, 32 | } 33 | 34 | impl fmt::Display for Object { 35 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 36 | match self { 37 | Object::Int(x) => write!(f, "{}", x), 38 | Object::Float(x) => write!(f, "{}", x), 39 | Object::String(x) => write!(f, "{}", x), 40 | Object::Bool(x) => write!(f, "{}", x), 41 | Object::Func(params, _body, _) => { 42 | let param_list = params 43 | .iter() 44 | .map(|s| s.to_string()) 45 | .collect::>(); 46 | // let statement_list = body.iter().map(|s| format!("{}", s)).collect::>(); 47 | // write!(f, "fn ({}) {{\n{}\n}}", param_list.join(", "), statement_list.join("")) 48 | write!(f, "", param_list.join(", ")) 49 | } 50 | Object::List(elements) => { 51 | let element_list = elements 52 | .iter() 53 | .map(|s| format!("{}", s)) 54 | .collect::>(); 55 | write!(f, "[{}]", element_list.join(", ")) 56 | } 57 | Object::Hash(pairs) => { 58 | let mut element_list = pairs 59 | .iter() 60 | .map(|(k, v)| format!("{}: {}", k, v)) 61 | .collect::>(); 62 | 63 | // sorting for testing. 64 | element_list.sort(); 65 | write!(f, "{{{}}}", element_list.join(", ")) 66 | } 67 | Object::Builtin(_func) => write!(f, ""), 68 | Object::Return(x) => write!(f, "{}", x), 69 | Object::Null => write!(f, ""), 70 | } 71 | } 72 | } 73 | 74 | impl Object { 75 | pub fn type_name(&self) -> &str { 76 | match self { 77 | Object::String(_) => "STRING", 78 | Object::Int(_) => "INTEGER", 79 | Object::Float(_) => "FLOAT", 80 | Object::Bool(_) => "BOOL", 81 | Object::Return(_) => "RETURN", 82 | Object::Null => "NULL", 83 | Object::Func(_, _, _) => "FUNC", 84 | Object::Builtin(_) => "BUILTIN", 85 | Object::List(_) => "LIST", 86 | Object::Hash(_) => "HASH", 87 | } 88 | } 89 | } 90 | 91 | #[derive(Hash, Eq, PartialEq, Debug, Clone)] 92 | pub enum HashKey { 93 | String(String), 94 | Int(IntegerSize), 95 | Bool(bool), 96 | } 97 | 98 | impl HashKey { 99 | pub fn from_object(obj: &Object) -> Result { 100 | match obj { 101 | Object::Int(v) => Ok(HashKey::Int(*v)), 102 | Object::Bool(v) => Ok(HashKey::Bool(*v)), 103 | Object::String(v) => Ok(HashKey::String(v.clone())), 104 | _ => Err(EvalErrorKind::UnsupportedHashKey(obj.clone())), 105 | } 106 | } 107 | } 108 | 109 | impl fmt::Display for HashKey { 110 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 111 | match self { 112 | HashKey::Int(v) => write!(f, "{}", v), 113 | HashKey::String(v) => write!(f, "\"{}\"", v), 114 | HashKey::Bool(v) => write!(f, "{}", v), 115 | } 116 | } 117 | } 118 | 119 | pub fn assert_argument_count(expected: usize, args: &[Object]) -> Result<(), EvalErrorKind> { 120 | if args.len() != expected { 121 | Err(EvalErrorKind::WrongArgumentCount(expected, args.len())) 122 | } else { 123 | Ok(()) 124 | } 125 | } 126 | 127 | pub fn assert_argument_count_range(expected: Range, args: &[Object]) -> Result<(), EvalErrorKind> { 128 | let arg_len = args.len(); 129 | if arg_len < expected.start || arg_len > expected.end { 130 | Err(EvalErrorKind::WrongArgumentCount(expected.start, args.len())) 131 | } else { 132 | Ok(()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/tay/src/evaluator/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use crate::lexer::Lexer; 4 | use crate::parser::Parser; 5 | use crate::evaluator::{Env, eval}; 6 | use super::super::Object; 7 | use crate::evaluator::error::EvalErrorKind; 8 | use std::rc::Rc; 9 | use std::cell::RefCell; 10 | use pretty_assertions::assert_eq; 11 | use crate::evaluator::object::EvalResult; 12 | 13 | fn test_eval(input: &str) -> EvalResult { 14 | let prog = Parser::new(Lexer::new(input.to_owned())).parse().unwrap(); 15 | let env = Rc::new(RefCell::new(Env::new())); 16 | eval(prog, env) 17 | } 18 | 19 | #[test] 20 | fn integer_expr() { 21 | let expected = vec![ 22 | ("5", 5), 23 | ("10", 10), 24 | ("-10", -10), 25 | ("5 + 5 - 5", 5), 26 | ("(5 + 5) * 5", 50), 27 | ]; 28 | 29 | for (input, expected) in expected { 30 | let evaluated = test_eval(input).unwrap(); 31 | assert_eq!(evaluated, Object::Int(expected)); 32 | } 33 | } 34 | 35 | #[test] 36 | fn float_expr() { 37 | let expected = vec![ 38 | ("5.1", 5.1), 39 | ("5.2 * 5.1", 26.52), 40 | ("2 * 4.2", 8.4), 41 | ("4.2 * 2", 8.4), 42 | ("5 / 2", 2.5), 43 | ]; 44 | 45 | for (input, expected) in expected { 46 | let evaluated = test_eval(input); 47 | assert_eq!(evaluated, Ok(Object::Float(expected))); 48 | } 49 | 50 | let inputs = vec![ 51 | "4.2 ^ 2", 52 | "2 ^ 4.3", 53 | ]; 54 | 55 | for input in inputs { 56 | let evaluated = test_eval(input); 57 | match evaluated { 58 | Err(EvalErrorKind::UnsupportedInfixOp(_, _, _)) => {} 59 | _ => assert!(false), 60 | } 61 | } 62 | } 63 | 64 | #[test] 65 | fn let_statement() { 66 | let expected = vec![ 67 | ("let x = 5; x;", 5), 68 | ("let x = 5 * 5; x;", 25), 69 | ("let a = 5; let b = a; b;", 5), 70 | ("let a = 5; let b = a; let c = a + b + 5; c;", 15), 71 | ]; 72 | 73 | for (input, expected) in expected { 74 | let evaluated = test_eval(input); 75 | assert_eq!(evaluated, Ok(Object::Int(expected))); 76 | } 77 | } 78 | 79 | #[test] 80 | fn return_statement() { 81 | let expected = vec![ 82 | ("5+5; return 2; 2+2", Object::Int(2)), 83 | ("return 10;", Object::Int(10)), 84 | ("let a = 6; let b = a; return a+b; b;", Object::Int(12)), 85 | ]; 86 | 87 | for (input, expected) in expected { 88 | let evaluated = test_eval(input); 89 | assert_eq!(evaluated, Ok(expected)); 90 | } 91 | } 92 | 93 | #[test] 94 | fn function() { 95 | let evaluated = test_eval("fn(x) {x+2}"); 96 | 97 | if let Ok(Object::Func(params, statements, _)) = evaluated { 98 | assert_eq!(params.len(), 1); 99 | assert_eq!(params.iter().map(|i| format!("{}", i)).collect::>().join(", "), "x"); 100 | assert_eq!(statements.iter().map(|s| format!("{}", s)).collect::>().join(""), "(x + 2)"); 101 | } else { 102 | assert!(false); 103 | } 104 | } 105 | 106 | #[test] 107 | fn apply_func() { 108 | let test_data = vec![ 109 | ("let identity = fn(x) { x; }; identity(5);", 5_i64), 110 | ("let identity = fn(x) { return x; }; identity(5);", 5), 111 | ("let double = fn(x) { x * 2; }; double(5);", 10), 112 | ("let add = fn(x, y) { x + y; }; add(5, 5);", 10), 113 | ("let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20), 114 | ("fn(x) { x; }(5)", 5), 115 | ]; 116 | 117 | for (input, expected) in test_data { 118 | let evaluated = test_eval(input); 119 | assert_eq!(evaluated, Ok(Object::Int(expected))) 120 | } 121 | } 122 | 123 | #[test] 124 | fn if_else() { 125 | let test_data = vec![ 126 | ("if (true) { 10 }", Object::Int(10)), 127 | ("if (false) { 10 }", Object::Null), 128 | ("if (1) { 10 }", Object::Int(10)), 129 | ("if (1 < 2) { 10 }", Object::Int(10)), 130 | ("if (1 > 2) { 10 }", Object::Null), 131 | ("if (1 > 2) { 10 } else { 20 }", Object::Int(20)), 132 | ("if (1 < 2) { 10 } else { 20 }", Object::Int(10)), 133 | ]; 134 | 135 | for (input, expected) in test_data { 136 | let evaluated = test_eval(input); 137 | assert_eq!(evaluated, Ok(expected)) 138 | } 139 | } 140 | 141 | #[test] 142 | fn string_literal() { 143 | let test_data = vec![ 144 | (r#""hello world""#, Object::String("hello world".to_owned())), 145 | (r#""hello" + " " + "world" "#, Object::String("hello world".to_owned())), 146 | ]; 147 | 148 | for (input, expected) in test_data { 149 | let evaluated = test_eval(input); 150 | assert_eq!(evaluated, Ok(expected)) 151 | } 152 | } 153 | 154 | #[test] 155 | fn builtins() { 156 | expect_values(vec![ 157 | (r#"len("")"#, "0"), 158 | (r#"len("four")"#, "4"), 159 | ("assert(true)", ""), 160 | ("assert(true, true)", ""), 161 | ("assert_eq(1, 1)", ""), 162 | (r##"assert_eq("str1", "str1")"##, ""), 163 | ]); 164 | expect_error(vec![ 165 | ("len(1)", "unsupported argument to len: INTEGER"), 166 | (r#"len("one", "two")"#, "wrong number of arguments, want=1, got=2"), 167 | (r#"assert(false, "msg")"#, r#"assertion error: msg"#), 168 | ("assert(false)", "assertion error: "), 169 | ("assert_eq(1, 2)", "assertion error: "), 170 | (r#"assert_eq(1, 2, "msg")"#, "assertion error: msg"), 171 | ("assert_eq(1, 2, 3, 4)", "wrong number of arguments, want=2, got=4"), 172 | ]); 173 | } 174 | 175 | #[test] 176 | fn lists() { 177 | expect_values(vec![ 178 | ("[1 + 2 ,3 * 4]", "[3, 12]"), 179 | ("[1 + 2 ,3 * 4][1]", "12"), 180 | ("let i = 0; [1][0]", "1"), 181 | ("let ml = [1,2,3]; ml[0] + ml[1] + ml[2]", "6"), 182 | ("len([1,2,3])", "3"), 183 | ]); 184 | 185 | expect_error(vec![ 186 | ("[1][-1]", "key error: -1"), 187 | ]) 188 | } 189 | 190 | #[test] 191 | fn hash_literal() { 192 | expect_values(vec![ 193 | ( 194 | r#"{"one": 10 - 9, "two" : 1+1, "th"+"ree": 6/2, 4: 4, true: 5, false: 6}"#, 195 | r#"{"one": 1, "three": 3, "two": 2, 4: 4, false: 6, true: 5}"# 196 | ) 197 | ]) 198 | } 199 | 200 | 201 | fn expect_values(tests: Vec<(&str, &str)>) { 202 | for (input, expected) in tests { 203 | match test_eval(input) { 204 | Ok(obj) => { 205 | assert_eq!(obj.to_string(), expected) 206 | } 207 | Err(e) => { 208 | panic!("expected `{}`, but got error: `{}`", expected, e.to_string()) 209 | } 210 | } 211 | } 212 | } 213 | 214 | fn expect_error(tests: Vec<(&str, &str)>) { 215 | for (input, expected) in tests { 216 | match test_eval(input) { 217 | Ok(obj) => { 218 | panic!("expected error `{}`, but got object: `{}`", expected, obj.to_string()) 219 | } 220 | Err(e) => { 221 | assert_eq!(e.to_string(), expected) 222 | } 223 | } 224 | } 225 | } 226 | 227 | } -------------------------------------------------------------------------------- /src/tay/src/lexer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod token; 2 | #[cfg(test)] 3 | mod test; 4 | 5 | pub use token::Token; 6 | use token::lookup_ident; 7 | 8 | pub struct Lexer { 9 | input: String, 10 | position: usize, 11 | read_position: usize, 12 | ch: char, 13 | line: usize, 14 | col: usize, 15 | } 16 | 17 | impl Lexer { 18 | pub fn new(input: String) -> Self { 19 | let mut lexer = Lexer { 20 | input, 21 | position: 0, 22 | read_position: 0, 23 | ch: '0', 24 | line: 0, 25 | col: 0, 26 | }; 27 | lexer.read_char(); // initialize l.ch, l.position and l.read_position 28 | lexer 29 | } 30 | 31 | fn read_char(&mut self) { 32 | if self.read_position == self.input.len() { 33 | self.ch = '\0'; 34 | } else if let Some(ch) = self.input.chars().skip(self.read_position).nth(0) { 35 | self.ch = ch; 36 | } 37 | self.position = self.read_position; 38 | self.read_position += 1; 39 | self.col += 1; 40 | } 41 | 42 | pub fn next_token(&mut self) -> Token { 43 | // if this fn called after already EOF, do not continue anymore. 44 | if self.position == self.input.len() { 45 | return Token::EndOfFile; 46 | } 47 | 48 | let token: Token; 49 | 50 | self.skip_whitespace(); 51 | 52 | match self.ch { 53 | '\0' => token = Token::EndOfFile, 54 | '-' => token = Token::Minus, 55 | '+' => token = Token::Plus, 56 | '*' => token = Token::Asterisk, 57 | '/' => if self.next_ch_is('/') { 58 | self.skip_line(); 59 | return self.next_token(); 60 | } else if self.next_ch_is('*') { 61 | self.skip_multi_line_comment(); 62 | return self.next_token(); 63 | } else { 64 | token = Token::Slash 65 | } 66 | '%' => token = Token::Percent, 67 | '^' => token = Token::Caret, 68 | ',' => token = Token::Comma, 69 | ';' => token = Token::Semicolon, 70 | '(' => token = Token::LParen, 71 | ')' => token = Token::RParen, 72 | '{' => token = Token::LBrace, 73 | '}' => token = Token::RBrace, 74 | '[' => token = Token::LBracket, 75 | ']' => token = Token::RBracket, 76 | ':' => token = Token::Colon, 77 | '=' => { 78 | if self.next_ch_is('=') { 79 | self.read_char(); 80 | token = Token::Eq; 81 | } else { 82 | token = Token::Assign 83 | } 84 | } 85 | '!' => { 86 | if self.next_ch_is('=') { 87 | self.read_char(); 88 | token = Token::NotEq; 89 | } else { 90 | token = Token::Bang 91 | } 92 | } 93 | '>' => { 94 | if self.next_ch_is('=') { 95 | self.read_char(); 96 | token = Token::Gte; 97 | } else { 98 | token = Token::Gt 99 | } 100 | } 101 | '<' => { 102 | if self.next_ch_is('=') { 103 | self.read_char(); 104 | token = Token::Lte; 105 | } else { 106 | token = Token::Lt 107 | } 108 | } 109 | '"' => { 110 | token = Token::String(self.read_string()) 111 | } 112 | _ => { 113 | // identifiers must start with alphabet chars 114 | if self.ch.is_ascii_alphabetic() { 115 | let literal = self.read_identifier(); 116 | return lookup_ident(literal); 117 | } else if self.ch.is_ascii_digit() || self.ch == '.' { 118 | let literal = self.read_number(); 119 | if literal.contains('.') { 120 | return Token::Float(literal); 121 | } else { 122 | return Token::Int(literal); 123 | } 124 | } else { 125 | token = Token::Illegal(self.ch) 126 | } 127 | } 128 | } 129 | self.read_char(); 130 | token 131 | } 132 | 133 | fn read_string(&mut self) -> String { 134 | self.read_char(); // Skip " 135 | let pos = self.position; 136 | loop { 137 | if self.ch == '"' || self.ch == '\0' { 138 | break; 139 | } 140 | self.read_char(); 141 | } 142 | // self.input.chars().skip(pos).take(self.position - pos).collect::(); 143 | self.input[pos..self.position].to_string() 144 | } 145 | 146 | fn read_identifier(&mut self) -> String { 147 | let pos = self.position; 148 | 149 | while self.ch.is_ascii_alphanumeric() || self.ch == '_' { 150 | self.read_char(); 151 | } 152 | 153 | // self.input.chars().skip(pos).take(self.position - pos).collect::() 154 | self.input[pos..self.position].to_string() 155 | } 156 | 157 | fn read_number(&mut self) -> String { 158 | let pos = self.position; 159 | while self.ch.is_ascii_digit() || self.ch == '.' { 160 | self.read_char(); 161 | } 162 | 163 | // self.input.chars().skip(pos).take(self.position - pos).collect::() 164 | self.input[pos..self.position].to_string() 165 | } 166 | 167 | fn skip_whitespace(&mut self) { 168 | while self.ch.is_whitespace() { 169 | if self.ch == '\n' || self.ch == '\r' { 170 | self.line += 1; 171 | self.col = 0; 172 | } 173 | self.read_char() 174 | } 175 | } 176 | 177 | fn skip_line(&mut self) { 178 | while self.ch != '\n' { 179 | self.read_char(); 180 | } 181 | 182 | self.line += 1; 183 | } 184 | 185 | fn skip_multi_line_comment(&mut self) { 186 | loop { 187 | if self.ch == '\n' || self.ch == '\r' { 188 | self.line += 1; 189 | self.col = 0; 190 | } 191 | self.read_char(); 192 | 193 | if self.ch == '*' && self.next_ch_is('/') { 194 | self.read_char(); 195 | self.read_char(); 196 | break; 197 | } 198 | } 199 | } 200 | 201 | fn peek_char(&self) -> char { 202 | if let Some(ch) = self.input.chars().skip(self.read_position).nth(0) { 203 | return ch; 204 | } 205 | 206 | '\0' 207 | } 208 | 209 | fn next_ch_is(&self, ch: char) -> bool { 210 | self.peek_char() == ch 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/tay/src/lexer/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test { 3 | use crate::lexer::{Token, Lexer}; 4 | use pretty_assertions::assert_eq; 5 | 6 | #[test] 7 | fn read_char() { 8 | let mut l = Lexer::new("()".to_owned()); 9 | assert_eq!(l.position, 0); 10 | l.read_char(); 11 | assert_eq!(l.position, 1); 12 | assert_eq!(l.read_position, 2); 13 | assert_eq!(l.ch, ')'); 14 | } 15 | 16 | #[test] 17 | fn tokens() { 18 | let input = r#"let five = 5; 19 | let ten = 10 ^ 2 % 4; 20 | 21 | let add = fn(x, y) { 22 | x + y; 23 | }; 24 | 25 | let result = add(five, ten); 26 | my_float = 1.2; 27 | 28 | < <= == != > >= 29 | "foo" 30 | "foo bar" 31 | let a = "hello"; 32 | ident_with_num_1 33 | [1, 2] 34 | // comment 1 35 | /* comment 2 */ 36 | {"foo": "bar"} 37 | "#; 38 | let expected = vec![ 39 | Token::Let, 40 | Token::Ident(String::from("five")), 41 | Token::Assign, 42 | Token::Int(String::from("5")), 43 | Token::Semicolon, 44 | Token::Let, 45 | Token::Ident(String::from("ten")), 46 | Token::Assign, 47 | Token::Int(String::from("10")), 48 | Token::Caret, 49 | Token::Int(String::from("2")), 50 | Token::Percent, 51 | Token::Int(String::from("4")), 52 | Token::Semicolon, 53 | Token::Let, 54 | Token::Ident(String::from("add")), 55 | Token::Assign, 56 | Token::Function, 57 | Token::LParen, 58 | Token::Ident(String::from("x")), 59 | Token::Comma, 60 | Token::Ident(String::from("y")), 61 | Token::RParen, 62 | Token::LBrace, 63 | Token::Ident(String::from("x")), 64 | Token::Plus, 65 | Token::Ident(String::from("y")), 66 | Token::Semicolon, 67 | Token::RBrace, 68 | Token::Semicolon, 69 | Token::Let, 70 | Token::Ident(String::from("result")), 71 | Token::Assign, 72 | Token::Ident(String::from("add")), 73 | Token::LParen, 74 | Token::Ident(String::from("five")), 75 | Token::Comma, 76 | Token::Ident(String::from("ten")), 77 | Token::RParen, 78 | Token::Semicolon, 79 | Token::Ident(String::from("my_float")), 80 | Token::Assign, 81 | Token::Float(String::from("1.2")), 82 | Token::Semicolon, 83 | Token::Lt, 84 | Token::Lte, 85 | Token::Eq, 86 | Token::NotEq, 87 | Token::Gt, 88 | Token::Gte, 89 | Token::String(String::from("foo")), 90 | Token::String(String::from("foo bar")), 91 | Token::Let, 92 | Token::Ident(String::from("a")), 93 | Token::Assign, 94 | Token::String(String::from("hello")), 95 | Token::Semicolon, 96 | Token::Ident(String::from("ident_with_num_1")), 97 | Token::LBracket, 98 | Token::Int("1".to_string()), 99 | Token::Comma, 100 | Token::Int("2".to_string()), 101 | Token::RBracket, 102 | Token::LBrace, 103 | Token::String("foo".to_string()), 104 | Token::Colon, 105 | Token::String("bar".to_string()), 106 | Token::RBrace, 107 | Token::EndOfFile, 108 | ]; 109 | 110 | let mut lex = Lexer::new(input.to_owned()); 111 | for i in expected { 112 | let t = lex.next_token(); 113 | assert_eq!(t, i); 114 | } 115 | } 116 | 117 | #[test] 118 | fn end_of_file() { 119 | let input = "a+b"; 120 | let expected = vec![ 121 | Token::Ident(String::from("a")), 122 | Token::Plus, 123 | Token::Ident(String::from("b")), 124 | Token::EndOfFile 125 | ]; 126 | 127 | let mut lex = Lexer::new(input.to_owned()); 128 | for i in expected { 129 | let t = lex.next_token(); 130 | assert_eq!(t, i); 131 | } 132 | } 133 | 134 | #[test] 135 | fn row_col() { 136 | let input = r#" 137 | let a = foo; 138 | let b = bar; 139 | // my comment 140 | "#; 141 | let expected = vec![ 142 | ('\n', 0, 1), 143 | (' ', 1, 4), 144 | (' ', 1, 6), 145 | (' ', 1, 8), 146 | (';', 1, 12), 147 | ('\n', 1, 13), 148 | (' ', 2, 4), 149 | (' ', 2, 6), 150 | (' ', 2, 8), 151 | (';', 2, 12), 152 | ('\n', 2, 13), 153 | ('\0', 5, 2), 154 | ]; 155 | 156 | let mut lex = Lexer::new(input.to_owned()); 157 | for i in expected { 158 | assert_eq!((lex.ch, lex.line, lex.col), i); 159 | // println!("{:?}", (lex.ch, lex.row, lex.col)); 160 | lex.next_token(); 161 | } 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /src/tay/src/lexer/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fmt::{Formatter}; 3 | 4 | #[derive(Debug, PartialEq, Clone)] 5 | pub enum Token { 6 | Illegal(char), 7 | EndOfFile, 8 | Ident(String), 9 | Int(String), 10 | Float(String), 11 | Bool(bool), 12 | String(String), 13 | 14 | Assign, 15 | Plus, 16 | Minus, 17 | Bang, 18 | Asterisk, 19 | Slash, 20 | Percent, 21 | Caret, 22 | Colon, 23 | 24 | Gt, 25 | Lt, 26 | Gte, 27 | Lte, 28 | Eq, 29 | NotEq, 30 | 31 | Comma, 32 | Semicolon, 33 | LParen, 34 | RParen, 35 | LBrace, 36 | RBrace, 37 | LBracket, 38 | RBracket, 39 | 40 | Let, 41 | If, 42 | Else, 43 | Return, 44 | Function, 45 | } 46 | 47 | impl fmt::Display for Token { 48 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 49 | match self { 50 | Token::Illegal(c) => write!(f, "{}", c), 51 | Token::EndOfFile => write!(f, "EOF",), 52 | 53 | Token::Ident(s) => write!(f, "{}", s), 54 | Token::Int(i) => i.fmt(f), 55 | Token::Float(i) => i.fmt(f), 56 | Token::Bool(b) => write!(f, "{}", b), 57 | Token::String(s) => write!(f, "{}", s), 58 | 59 | Token::Assign => write!(f, "="), 60 | Token::Plus => write!(f, "+"), 61 | Token::Minus => write!(f, "-"), 62 | Token::Bang => write!(f, "!"), 63 | Token::Asterisk => write!(f, "*"), 64 | Token::Slash => write!(f, "/"), 65 | Token::Caret => write!(f, "^"), 66 | Token::Percent => write!(f, "%"), 67 | Token::Colon => write!(f, ":"), 68 | 69 | Token::Lt => write!(f, "<"), 70 | Token::Gt => write!(f, ">"), 71 | Token::Lte => write!(f, "<="), 72 | Token::Gte => write!(f, "=<"), 73 | Token::Eq => write!(f, "=="), 74 | Token::NotEq => write!(f, "!="), 75 | 76 | Token::Comma => write!(f, ","), 77 | Token::Semicolon => write!(f, ";"), 78 | Token::LParen => write!(f, "("), 79 | Token::RParen => write!(f, ")"), 80 | Token::LBrace => write!(f, "{{"), 81 | Token::RBrace => write!(f, "}}"), 82 | Token::LBracket => write!(f, "["), 83 | Token::RBracket => write!(f, "]"), 84 | 85 | Token::Function => write!(f, "fn"), 86 | Token::Let => write!(f, "let"), 87 | Token::Return => write!(f, "return"), 88 | Token::If => write!(f, "if"), 89 | Token::Else => write!(f, "else"), 90 | } 91 | } 92 | } 93 | 94 | pub fn lookup_ident(literal: String) -> Token { 95 | match literal.as_str() { 96 | "fn" => Token::Function, 97 | "let" => Token::Let, 98 | "true" => Token::Bool(true), 99 | "false" => Token::Bool(false), 100 | "if" => Token::If, 101 | "else" => Token::Else, 102 | "return" => Token::Return, 103 | _ => Token::Ident(literal), 104 | } 105 | } -------------------------------------------------------------------------------- /src/tay/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod lexer; 2 | pub mod ast; 3 | pub mod parser; 4 | pub mod evaluator; 5 | -------------------------------------------------------------------------------- /src/tay/src/parser/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod test; 3 | 4 | use crate::ast::{ 5 | BlockStatement, Expression, FloatSize, Infix, IntegerSize, Literal, Precedence, Prefix, 6 | Program, Statement, 7 | }; 8 | use crate::lexer::{Lexer, Token}; 9 | use std::{fmt, mem}; 10 | 11 | #[derive(PartialEq, Debug)] 12 | pub enum ParseError { 13 | InvalidToken(Token), 14 | InvalidSyntax(String), 15 | NoPrefixParseFn(Token), 16 | ExpectedRBracket, 17 | ExpectedRBrace, 18 | } 19 | 20 | impl fmt::Display for ParseError { 21 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 22 | match self { 23 | ParseError::InvalidSyntax(error) => write!(f, "Invalid syntax: `{}`", error), 24 | ParseError::InvalidToken(token) => write!(f, "Invalid token: `{}`", token), 25 | ParseError::NoPrefixParseFn(token) => { 26 | write!(f, "no prefix parse function for {} found", token) 27 | } 28 | ParseError::ExpectedRBracket => write!(f, "] expected"), 29 | ParseError::ExpectedRBrace => write!(f, "}} expected"), 30 | } 31 | } 32 | } 33 | 34 | pub(self) type ParseResult = Result; 35 | type PrefixParseFn = fn(&mut Parser) -> ParseResult; 36 | type InfixParseFn = fn(&mut Parser, Expression) -> ParseResult; 37 | 38 | pub struct Parser { 39 | lexer: Lexer, 40 | current_token: Token, 41 | peek_token: Token, 42 | } 43 | 44 | impl Parser { 45 | pub fn new(lexer: Lexer) -> Self { 46 | let mut p = Parser { 47 | lexer, 48 | current_token: Token::EndOfFile, 49 | peek_token: Token::EndOfFile, 50 | }; 51 | 52 | p.next_token(); 53 | p.next_token(); 54 | p 55 | } 56 | 57 | fn next_token(&mut self) { 58 | self.current_token = mem::replace(&mut self.peek_token, self.lexer.next_token()); 59 | } 60 | 61 | pub fn parse(&mut self) -> ParseResult { 62 | let mut statements = vec![]; 63 | while !self.current_token_is(Token::EndOfFile) { 64 | match self.parse_statement() { 65 | Ok(stmt) => statements.push(stmt), 66 | Err(e) => return Err(e), 67 | } 68 | self.next_token(); 69 | } 70 | Ok(Program { statements }) 71 | } 72 | 73 | fn current_token_is(&self, token: Token) -> bool { 74 | self.current_token == token 75 | } 76 | 77 | fn peek_token_is(&self, token: Token) -> bool { 78 | // TODO: should be get Token as ref 79 | self.peek_token == token 80 | } 81 | 82 | fn expect_peek(&mut self, token: Token, error: ParseError) -> Result { 83 | // TODO: should be get Token as ref 84 | if self.peek_token == token { 85 | self.next_token(); 86 | Ok(true) 87 | } else { 88 | Err(error) 89 | } 90 | } 91 | 92 | fn token_to_precedence(&self, token: &Token) -> Precedence { 93 | match token { 94 | Token::Plus | Token::Minus => Precedence::Sum, 95 | Token::Asterisk | Token::Slash | Token::Percent => Precedence::Product, 96 | Token::Eq | Token::NotEq => Precedence::Equal, 97 | Token::Lt | Token::Gt | Token::Lte | Token::Gte => Precedence::LessGreater, 98 | Token::Caret => Precedence::Exponent, 99 | Token::LParen => Precedence::Call, 100 | Token::LBracket => Precedence::Index, 101 | _ => Precedence::Lowest, 102 | } 103 | } 104 | 105 | fn current_precedence(&self) -> Precedence { 106 | self.token_to_precedence(&self.current_token) 107 | } 108 | 109 | fn peek_precedence(&self) -> Precedence { 110 | self.token_to_precedence(&self.peek_token) 111 | } 112 | 113 | fn parse_statement(&mut self) -> ParseResult { 114 | match self.current_token { 115 | Token::Let => self.parse_let_statement(), 116 | Token::Return => self.parse_return_statement(), 117 | Token::Ident(_) if self.peek_token_is(Token::Assign) => self.parse_let_statement(), 118 | 119 | _ => self.parse_expression_statement(), 120 | } 121 | } 122 | 123 | fn parse_let_statement(&mut self) -> ParseResult { 124 | if let Token::Ident(_) = self.peek_token { 125 | // allowing variable assignment without let keyword 126 | self.next_token(); 127 | } 128 | 129 | let ident = match self.parse_ident() { 130 | Ok(ident) => ident, 131 | Err(e) => return Err(e), 132 | }; 133 | 134 | self.expect_peek( 135 | Token::Assign, 136 | ParseError::InvalidToken(self.peek_token.clone()), 137 | )?; 138 | 139 | self.next_token(); 140 | 141 | let expr = match self.parse_expression(Precedence::Lowest) { 142 | Ok(expr) => expr, 143 | Err(e) => return Err(e), 144 | }; 145 | 146 | if self.peek_token_is(Token::Semicolon) { 147 | self.next_token(); 148 | } 149 | 150 | Ok(Statement::Let(ident, expr)) 151 | } 152 | 153 | fn parse_return_statement(&mut self) -> ParseResult { 154 | self.next_token(); 155 | 156 | let expr = match self.parse_expression(Precedence::Lowest) { 157 | Ok(expr) => expr, 158 | Err(err) => return Err(err), 159 | }; 160 | 161 | if self.peek_token_is(Token::Semicolon) { 162 | self.next_token(); 163 | } 164 | 165 | Ok(Statement::Return(expr)) 166 | } 167 | 168 | fn parse_expression_statement(&mut self) -> ParseResult { 169 | match self.parse_expression(Precedence::Lowest) { 170 | Ok(expr) => { 171 | if self.peek_token_is(Token::Semicolon) { 172 | self.next_token(); 173 | } 174 | Ok(Statement::Expression(expr)) 175 | } 176 | Err(e) => Err(e), 177 | } 178 | } 179 | 180 | fn parse_expression(&mut self, precedence: Precedence) -> ParseResult { 181 | let prefix = self 182 | .prefix_parse_fn() 183 | .ok_or_else(|| ParseError::NoPrefixParseFn(self.current_token.clone()))?; 184 | let mut left_expr = prefix(self)?; 185 | 186 | while !self.peek_token_is(Token::Semicolon) && precedence < self.peek_precedence() { 187 | if let Some(infix) = self.infix_parse_fn() { 188 | self.next_token(); 189 | left_expr = infix(self, left_expr)?; 190 | } 191 | } 192 | 193 | Ok(left_expr) 194 | } 195 | 196 | fn prefix_parse_fn(&self) -> Option { 197 | let left = match &self.current_token { 198 | Token::Ident(_) => Parser::parse_ident_expr, 199 | Token::String(_) => Parser::parse_string_literal, 200 | Token::Int(_) => Parser::parse_integer_literal, 201 | Token::Float(_) => Parser::parse_float_literal, 202 | Token::Bool(_) => Parser::parse_boolean, 203 | Token::Minus | Token::Bang => Parser::parse_prefix_expr, 204 | Token::LParen => Parser::parse_grouped_expr, 205 | Token::If => Parser::parse_if_expr, 206 | Token::Function => Parser::parse_func_literal, 207 | Token::LBracket => Parser::parse_list_literal, 208 | Token::LBrace => Parser::parse_hash_literal, 209 | _ => return None, 210 | }; 211 | 212 | Some(left) 213 | } 214 | 215 | fn infix_parse_fn(&self) -> Option { 216 | Some(match &self.peek_token { 217 | Token::Plus 218 | | Token::Minus 219 | | Token::Asterisk 220 | | Token::Slash 221 | | Token::Percent 222 | | Token::Caret 223 | | Token::Eq 224 | | Token::NotEq 225 | | Token::Lt 226 | | Token::Gt 227 | | Token::Lte 228 | | Token::Gte => Parser::parse_infix_expr, 229 | Token::LParen => Parser::parse_call_expr, 230 | Token::LBracket => Parser::parse_index_expr, 231 | _ => return None, 232 | }) 233 | } 234 | 235 | fn parse_ident(&mut self) -> ParseResult { 236 | match self.current_token { 237 | Token::Ident(ref mut ident_str) => Ok(ident_str.clone()), 238 | _ => Err(ParseError::InvalidToken(self.current_token.clone())), 239 | } 240 | } 241 | 242 | fn parse_string_literal(&mut self) -> ParseResult { 243 | match &self.current_token { 244 | Token::String(literal) => Ok(Expression::Literal(Literal::String(literal.clone()))), 245 | _ => Err(ParseError::InvalidToken(self.current_token.clone())), // FIXME: unreachable 246 | } 247 | } 248 | 249 | fn parse_integer_literal(&mut self) -> ParseResult { 250 | match &self.current_token { 251 | Token::Int(literal) => match literal.parse::() { 252 | Ok(x) => Ok(Expression::Literal(Literal::Int(x))), 253 | Err(_) => Err(ParseError::InvalidSyntax(literal.clone())), 254 | }, 255 | _ => Err(ParseError::InvalidToken(self.current_token.clone())), // FIXME: unreachable 256 | } 257 | } 258 | 259 | fn parse_float_literal(&mut self) -> ParseResult { 260 | match &self.current_token { 261 | Token::Float(literal) => match &literal.parse::() { 262 | Ok(x) => Ok(Expression::Literal(Literal::Float(*x))), 263 | Err(_) => Err(ParseError::InvalidSyntax(literal.clone())), 264 | }, 265 | _ => Err(ParseError::InvalidToken(self.current_token.clone())), // FIXME: unreachable 266 | } 267 | } 268 | 269 | fn parse_boolean(&mut self) -> ParseResult { 270 | match self.current_token { 271 | Token::Bool(x) => Ok(Expression::Literal(Literal::Bool(x))), 272 | _ => Err(ParseError::InvalidToken(self.current_token.clone())), // FIXME: unreachable 273 | } 274 | } 275 | 276 | fn parse_ident_expr(&mut self) -> ParseResult { 277 | match self.parse_ident() { 278 | Ok(ident) => Ok(Expression::Ident(ident)), 279 | Err(err) => Err(err), 280 | } 281 | } 282 | 283 | fn parse_prefix_expr(&mut self) -> ParseResult { 284 | let prefix = match self.current_token { 285 | Token::Bang => Prefix::Bang, 286 | Token::Minus => Prefix::Minus, 287 | _ => return Err(ParseError::InvalidToken(self.current_token.clone())), 288 | }; 289 | 290 | self.next_token(); 291 | 292 | match self.parse_expression(Precedence::Prefix) { 293 | Ok(expr) => Ok(Expression::Prefix(prefix, Box::new(expr))), 294 | Err(e) => Err(e), 295 | } 296 | } 297 | 298 | fn parse_infix_expr(&mut self, left: Expression) -> ParseResult { 299 | let infix = match self.current_token { 300 | Token::Plus => Infix::Plus, 301 | Token::Minus => Infix::Minus, 302 | Token::Asterisk => Infix::Mul, 303 | Token::Slash => Infix::Div, 304 | Token::Caret => Infix::Exponent, 305 | Token::Percent => Infix::Percent, 306 | Token::Lt => Infix::Lt, 307 | Token::Gt => Infix::Gt, 308 | Token::Lte => Infix::Lte, 309 | Token::Gte => Infix::Gte, 310 | Token::Eq => Infix::Eq, 311 | Token::NotEq => Infix::NotEq, 312 | _ => return Err(ParseError::InvalidToken(self.current_token.clone())), 313 | }; 314 | 315 | let precedence = self.current_precedence(); 316 | self.next_token(); 317 | 318 | match self.parse_expression(precedence) { 319 | Ok(expr) => Ok(Expression::Infix(infix, Box::new(left), Box::new(expr))), 320 | Err(e) => Err(e), 321 | } 322 | } 323 | 324 | fn parse_expression_list(&mut self, end: Token) -> Result, ParseError> { 325 | let mut args: Vec = vec![]; 326 | if self.peek_token_is(end.clone()) { 327 | self.next_token(); 328 | return Ok(args); 329 | } 330 | 331 | self.next_token(); 332 | 333 | match self.parse_expression(Precedence::Lowest) { 334 | Ok(expr) => args.push(expr), 335 | Err(e) => return Err(e), 336 | } 337 | 338 | while self.peek_token_is(Token::Comma) { 339 | // Used expect_peek instead peek_token_is 340 | self.next_token(); 341 | self.next_token(); // skip comma 342 | match self.parse_expression(Precedence::Lowest) { 343 | Ok(expr) => args.push(expr), 344 | Err(e) => return Err(e), 345 | } 346 | } 347 | 348 | self.expect_peek(end, ParseError::InvalidToken(self.peek_token.clone()))?; 349 | 350 | Ok(args) 351 | } 352 | 353 | fn parse_call_expr(&mut self, func: Expression) -> ParseResult { 354 | let args = match self.parse_expression_list(Token::RParen) { 355 | Ok(args) => args, 356 | Err(e) => return Err(e), 357 | }; 358 | 359 | Ok(Expression::Call { 360 | func: Box::new(func), 361 | args, 362 | }) 363 | } 364 | 365 | fn parse_func_literal(&mut self) -> ParseResult { 366 | let mut identifier = None; 367 | if self.peek_token_is(Token::LParen) { 368 | self.next_token(); 369 | } else if let Token::Ident(_ident) = self.peek_token.clone() { 370 | self.next_token(); // skip fn 371 | identifier = match self.parse_ident() { 372 | Ok(i) => { 373 | self.next_token(); // skip ident 374 | Some(i) 375 | } 376 | Err(e) => return Err(e), 377 | }; 378 | } else { 379 | return Err(ParseError::InvalidToken(self.peek_token.clone())); 380 | } 381 | 382 | let params = match self.parse_func_params() { 383 | Ok(prs) => prs, 384 | Err(e) => return Err(e), 385 | }; 386 | 387 | self.expect_peek( 388 | Token::LBrace, 389 | ParseError::InvalidToken(self.peek_token.clone()), 390 | )?; 391 | 392 | let body = match self.parse_block_statement() { 393 | Ok(stmt) => stmt, 394 | Err(e) => return Err(e), 395 | }; 396 | 397 | Ok(Expression::Func { 398 | identifier, 399 | params, 400 | body, 401 | }) 402 | } 403 | 404 | fn parse_func_params(&mut self) -> ParseResult> { 405 | let mut idents = vec![]; 406 | 407 | if self.peek_token_is(Token::RParen) { 408 | self.next_token(); 409 | return Ok(idents); 410 | } 411 | 412 | self.next_token(); 413 | 414 | match self.parse_ident() { 415 | Ok(id) => idents.push(id), 416 | Err(e) => return Err(e), 417 | } 418 | 419 | while self.peek_token_is(Token::Comma) { 420 | self.next_token(); 421 | self.next_token(); 422 | match self.parse_ident() { 423 | Ok(id) => idents.push(id), 424 | Err(e) => return Err(e), 425 | } 426 | } 427 | 428 | self.expect_peek( 429 | Token::RParen, 430 | ParseError::InvalidToken(self.peek_token.clone()), 431 | )?; 432 | 433 | Ok(idents) 434 | } 435 | 436 | fn parse_grouped_expr(&mut self) -> ParseResult { 437 | self.next_token(); 438 | 439 | let expr = self.parse_expression(Precedence::Lowest); 440 | self.expect_peek( 441 | Token::RParen, 442 | ParseError::InvalidToken(self.peek_token.clone()), 443 | )?; 444 | expr 445 | } 446 | 447 | fn parse_if_expr(&mut self) -> ParseResult { 448 | self.expect_peek( 449 | Token::LParen, 450 | ParseError::InvalidSyntax(self.peek_token.to_string()), 451 | )?; 452 | self.next_token(); 453 | 454 | let condition = match self.parse_expression(Precedence::Lowest) { 455 | Ok(expr) => expr, 456 | Err(err) => return Err(err), 457 | }; 458 | 459 | self.expect_peek( 460 | Token::RParen, 461 | ParseError::InvalidSyntax(self.peek_token.to_string()), 462 | )?; 463 | self.expect_peek( 464 | Token::LBrace, 465 | ParseError::InvalidSyntax(self.peek_token.to_string()), 466 | )?; 467 | 468 | let consequence = match self.parse_block_statement() { 469 | Ok(stmt) => stmt, 470 | Err(err) => return Err(err), 471 | }; 472 | 473 | let mut alternative = None; 474 | 475 | if self.peek_token_is(Token::Else) { 476 | self.next_token(); 477 | self.expect_peek( 478 | Token::LBrace, 479 | ParseError::InvalidToken(self.peek_token.clone()), 480 | )?; 481 | match self.parse_block_statement() { 482 | Ok(stmt) => alternative = Some(stmt), 483 | Err(err) => return Err(err), 484 | } 485 | } 486 | 487 | Ok(Expression::If { 488 | condition: Box::new(condition), 489 | consequence, 490 | alternative, 491 | }) 492 | } 493 | 494 | fn parse_block_statement(&mut self) -> ParseResult { 495 | self.next_token(); 496 | let mut statements: BlockStatement = vec![]; 497 | 498 | while !self.current_token_is(Token::RBrace) && !self.current_token_is(Token::EndOfFile) { 499 | match self.parse_statement() { 500 | Ok(stmt) => statements.push(stmt), 501 | Err(err) => return Err(err), 502 | } 503 | self.next_token(); 504 | } 505 | 506 | Ok(statements) 507 | } 508 | 509 | fn parse_list_literal(&mut self) -> ParseResult { 510 | let elems = self.parse_expression_list(Token::RBracket)?; 511 | Ok(Expression::List(elems)) 512 | } 513 | 514 | fn parse_index_expr(&mut self, left: Expression) -> ParseResult { 515 | self.next_token(); 516 | let index = self.parse_expression(Precedence::Lowest)?; 517 | 518 | self.expect_peek(Token::RBracket, ParseError::ExpectedRBracket)?; 519 | 520 | Ok(Expression::Index(Box::new(left), Box::new(index))) 521 | } 522 | 523 | fn parse_hash_literal(&mut self) -> ParseResult { 524 | let mut pairs = vec![]; 525 | 526 | while !self.peek_token_is(Token::RBrace) { 527 | self.next_token(); 528 | let key = self.parse_expression(Precedence::Lowest)?; 529 | 530 | self.expect_peek( 531 | Token::Colon, 532 | ParseError::InvalidToken(self.peek_token.clone()), 533 | )?; 534 | self.next_token(); 535 | 536 | let value = self.parse_expression(Precedence::Lowest)?; 537 | pairs.push((key, value)); 538 | 539 | if !self.peek_token_is(Token::RBrace) { 540 | self.expect_peek(Token::Comma, ParseError::ExpectedRBrace)?; 541 | } 542 | } 543 | self.expect_peek( 544 | Token::RBrace, 545 | ParseError::InvalidToken(self.peek_token.clone()), 546 | )?; 547 | 548 | Ok(Expression::Hash(pairs)) 549 | } 550 | } 551 | -------------------------------------------------------------------------------- /src/tay/src/parser/test.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod test { 3 | use crate::ast::{Expression, Infix, Literal, Prefix, Program, Statement}; 4 | use crate::lexer::Lexer; 5 | use crate::parser::{ParseResult, Parser}; 6 | use pretty_assertions::assert_eq; 7 | 8 | fn parse(input: &str) -> ParseResult { 9 | Parser::new(Lexer::new(input.to_owned())).parse() 10 | } 11 | 12 | #[test] 13 | fn let_statement() { 14 | let input = r#" 15 | let x = 5; 16 | let y = 10; 17 | let z = 1.2; 18 | let foo = "hello"; 19 | "#; 20 | 21 | let program = parse(input).unwrap(); 22 | assert_eq!(program.statements.len(), 4); 23 | assert_eq!( 24 | program.statements, 25 | vec![ 26 | Statement::Let(String::from("x"), Expression::Literal(Literal::Int(5))), 27 | Statement::Let(String::from("y"), Expression::Literal(Literal::Int(10))), 28 | Statement::Let(String::from("z"), Expression::Literal(Literal::Float(1.2))), 29 | Statement::Let( 30 | String::from("foo"), 31 | Expression::Literal(Literal::String("hello".to_owned())) 32 | ), 33 | ] 34 | ); 35 | } 36 | 37 | #[test] 38 | fn return_statement() { 39 | let input = r#" 40 | return 1; 41 | return foo; 42 | "#; 43 | 44 | let program = parse(input).unwrap(); 45 | assert_eq!(program.statements.len(), 2); 46 | assert_eq!( 47 | program.statements, 48 | vec![ 49 | Statement::Return(Expression::Literal(Literal::Int(1))), 50 | Statement::Return(Expression::Ident("foo".to_string())), 51 | ] 52 | ); 53 | } 54 | 55 | #[test] 56 | fn if_expression() { 57 | let input = "if (x < y) {x} else {y}"; 58 | let program = parse(input).unwrap(); 59 | assert_eq!(program.statements.len(), 1); 60 | assert_eq!( 61 | program.statements, 62 | vec![Statement::Expression(Expression::If { 63 | condition: Box::new(Expression::Infix( 64 | Infix::Lt, 65 | Box::new(Expression::Ident("x".to_string())), 66 | Box::new(Expression::Ident("y".to_string())), 67 | )), 68 | consequence: vec![Statement::Expression(Expression::Ident("x".to_string()))], 69 | alternative: Some(vec![Statement::Expression(Expression::Ident( 70 | "y".to_string() 71 | ))]), 72 | }),] 73 | ); 74 | } 75 | 76 | #[test] 77 | fn function() { 78 | let expected = vec![ 79 | ("fn () {}", None, Vec::<&str>::new(), Vec::<&str>::new()), 80 | ("fn (x, y) {x * y}", None, vec!["x", "y"], vec!["(x * y)"]), 81 | ( 82 | "fn (x, y, z) {x * y * z}", 83 | None, 84 | vec!["x", "y", "z"], 85 | vec!["((x * y) * z)"], 86 | ), 87 | ( 88 | "fn foo() {}", 89 | Some("foo".to_owned()), 90 | Vec::<&str>::new(), 91 | Vec::<&str>::new(), 92 | ), 93 | ]; 94 | 95 | for (input, expect_name, expect_param, expect_body) in expected { 96 | let program = parse(input).unwrap(); 97 | assert_eq!(program.statements.len(), 1); 98 | if let Statement::Expression(Expression::Func { 99 | identifier, 100 | params, 101 | body, 102 | }) = program.statements.first().unwrap() 103 | { 104 | assert_eq!(*identifier, expect_name); 105 | assert_eq!( 106 | params 107 | .iter() 108 | .map(|s| format!("{}", s)) 109 | .collect::>(), 110 | expect_param 111 | ); 112 | assert_eq!( 113 | body.iter() 114 | .map(|s| format!("{}", s)) 115 | .collect::>(), 116 | expect_body 117 | ) 118 | } else { 119 | assert!(false) 120 | } 121 | } 122 | } 123 | 124 | #[test] 125 | fn call_expression() { 126 | let program = parse("add(1, 2)").unwrap(); 127 | assert_eq!(program.statements.len(), 1); 128 | if let Statement::Expression(Expression::Call { func, args }) = 129 | program.statements.first().unwrap() 130 | { 131 | assert_eq!(format!("{}", *func), "add"); 132 | assert_eq!( 133 | args.iter() 134 | .map(|s| format!("{}", s)) 135 | .collect::>(), 136 | vec!["1", "2"] 137 | ); 138 | } else { 139 | assert!(false) 140 | } 141 | } 142 | 143 | #[test] 144 | fn parser_errors() { 145 | if parse("let x 5;").is_ok() { 146 | assert!(false, "parser not failed") 147 | } 148 | } 149 | 150 | #[test] 151 | fn integer_float_literal_expr() { 152 | let program = parse("5;6.1;").unwrap(); 153 | let expected = vec![ 154 | Statement::Expression(Expression::Literal(Literal::Int(5))), 155 | Statement::Expression(Expression::Literal(Literal::Float(6.1))), 156 | ]; 157 | assert_eq!(program.statements, expected); 158 | } 159 | 160 | #[test] 161 | fn prefix_expr() { 162 | let program = parse("!5;-15; !false").unwrap(); 163 | let expected = vec![ 164 | Statement::Expression(Expression::Prefix( 165 | Prefix::Bang, 166 | Box::new(Expression::Literal(Literal::Int(5))), 167 | )), 168 | Statement::Expression(Expression::Prefix( 169 | Prefix::Minus, 170 | Box::new(Expression::Literal(Literal::Int(15))), 171 | )), 172 | Statement::Expression(Expression::Prefix( 173 | Prefix::Bang, 174 | Box::new(Expression::Literal(Literal::Bool(false))), 175 | )), 176 | ]; 177 | assert_eq!(program.statements, expected); 178 | } 179 | 180 | #[test] 181 | fn infix_expr() { 182 | let program = parse("5 + 15 * 18;").unwrap(); 183 | let expected = vec![Statement::Expression(Expression::Infix( 184 | Infix::Plus, 185 | Box::new(Expression::Literal(Literal::Int(5))), 186 | Box::new(Expression::Infix( 187 | Infix::Mul, 188 | Box::new(Expression::Literal(Literal::Int(15))), 189 | Box::new(Expression::Literal(Literal::Int(18))), 190 | )), 191 | ))]; 192 | 193 | assert_eq!(program.statements, expected); 194 | } 195 | 196 | #[test] 197 | fn string_literal() { 198 | let program = parse(r#""hello world""#).unwrap(); 199 | let expected = vec![Statement::Expression(Expression::Literal(Literal::String( 200 | String::from("hello world"), 201 | )))]; 202 | assert_eq!(program.statements, expected); 203 | } 204 | 205 | #[test] 206 | fn operator_precedence() { 207 | let data = vec![ 208 | ("-a * b", "((-a) * b)"), 209 | ("a ^ b % c * d", "(((a ^ b) % c) * d)"), 210 | ("!-a", "(!(-a))"), 211 | ("a + b + c", "((a + b) + c)"), 212 | ("a + b - c", "((a + b) - c)"), 213 | ("a * b * c", "((a * b) * c)"), 214 | ("a * b / c", "((a * b) / c)"), 215 | ("a + b / c", "(a + (b / c))"), 216 | ("a + b * c + d / e - f", "(((a + (b * c)) + (d / e)) - f)"), 217 | ("1 + (2 + 3) + 4", "((1 + (2 + 3)) + 4)"), 218 | ("(5 + 5) * 2", "((5 + 5) * 2)"), 219 | ("5 < 4 != 3 > 4", "((5 < 4) != (3 > 4))"), 220 | ( 221 | "3 + 4 * 5 == 3 * 1 + 4 * 5", 222 | "((3 + (4 * 5)) == ((3 * 1) + (4 * 5)))", 223 | ), 224 | ("a + add(b * c) + d", "((a + add((b * c))) + d)"), 225 | ( 226 | "add(a, b, 1, 2 * 3, 4 + 5, add(6, 7 * 8))", 227 | "add(a, b, 1, (2 * 3), (4 + 5), add(6, (7 * 8)))", 228 | ), 229 | ( 230 | "a * [1, 2, 3, 4][b * c] * d", 231 | "((a * ([1, 2, 3, 4][(b * c)])) * d)", 232 | ), 233 | ( 234 | "add(a * b[2], b[1], 2 * [1, 2][1])", 235 | "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))", 236 | ), 237 | ]; 238 | 239 | for &(input, expected) in data.iter() { 240 | let program = parse(input).unwrap(); 241 | assert_eq!(format!("{}", program.statements.first().unwrap()), expected); 242 | } 243 | } 244 | 245 | #[test] 246 | fn list_literal() { 247 | let tests = vec![ 248 | ("[1, 2 * 2, 3 + 3]", "[1, (2 * 2), (3 + 3)]"), 249 | ("[]", "[]"), 250 | ("my_list[1]", "(my_list[1])"), 251 | ]; 252 | 253 | for &(input, expected) in tests.iter() { 254 | let program = parse(input).unwrap(); 255 | assert_eq!(program.to_string(), expected) 256 | } 257 | } 258 | 259 | #[test] 260 | fn hash_literal() { 261 | let tests = vec![ 262 | ("{}", "{}"), 263 | ("{1: true, false: 2}", "{1: true, false: 2}"), 264 | (r#"{"one": 1, "two": 2}"#, r#"{"one": 1, "two": 2}"#) 265 | ]; 266 | 267 | for &(input, expected) in tests.iter() { 268 | let program = parse(input).unwrap(); 269 | assert_eq!(program.to_string(), expected) 270 | } 271 | } 272 | } 273 | --------------------------------------------------------------------------------