Compare commits
332 commits
Author | SHA1 | Date | |
---|---|---|---|
wackbyte | 37fd7bc9e1 | ||
wackbyte | c69216082e | ||
Erin | 57bbc44b82 | ||
Erin | 407c8674a7 | ||
Erin | c1698d9b25 | ||
Erin | fd0e7012ac | ||
Erin | 165e7c6b5f | ||
Erin | f335302b2a | ||
Erin | 152c5c0f88 | ||
Erin | b346995bfd | ||
Erin | f7cba4b2b6 | ||
Erin | e3d49c9c1f | ||
Erin | ea931d1f4b | ||
Erin | 5a18642cd6 | ||
Erin | f1da9b2117 | ||
Erin | 6720ecc534 | ||
Erin | afac0cdac7 | ||
Erin | 7a35885282 | ||
Erin | ec1b8200e0 | ||
Alex Bethel | 7dfdb35685 | ||
Erin | 6dec02c60c | ||
Erin | 3bb8af5b3f | ||
Erin | ac42fdc5ca | ||
Erin | 306e6cfc8e | ||
Erin | f7b18ec6ce | ||
Erin | d34d7dd488 | ||
Erin | 176467dfc8 | ||
Erin | ce2de21d9b | ||
Erin | a6ecf782db | ||
Erin | c2e9daf1fc | ||
Erin | f18742a9b3 | ||
Erin | 8d24ad5528 | ||
Erin | 04b44fb01e | ||
Erin | 77c201476b | ||
Erin | 8ad10e132b | ||
Erin | 4d8ee1a0d7 | ||
Erin | e17b67e438 | ||
Erin | dbbe46fa5e | ||
Erin | f2f509ded0 | ||
Erin | d7ec6b3658 | ||
Erin | 81f433e633 | ||
Erin | a3c0e65f6b | ||
Erin | 55a455d110 | ||
Erin | a3a0663433 | ||
able | d57b692e71 | ||
Erin | 392a357c8e | ||
Erin | 5f135be37c | ||
Erin | 97981c8772 | ||
Erin | 9fa6b57fe5 | ||
Erin | 6fc1f59e23 | ||
Erin | 5f48d20f2a | ||
Erin | 417b3b53b4 | ||
Erin | f63861f59f | ||
Erin | 1d08231a34 | ||
Erin | 7e2cba314b | ||
Erin | a636c5926d | ||
Erin | bf0e64c26a | ||
Erin | 7f859081ba | ||
Erin | b84b86eef7 | ||
Erin | 56c9ccdf38 | ||
Erin | 0ac507e3b3 | ||
Erin | 5d5c66fb3c | ||
Erin | aa94ae57d8 | ||
Erin | 49d4699928 | ||
Erin | 1d1919976f | ||
Erin | 159a462b0a | ||
Erin | 6f5b96b26b | ||
Erin | a3a9b777ad | ||
Erin | 3f29fb09cd | ||
Erin | 30ab0cf7da | ||
Alex Bethel | a7bba352d5 | ||
Alex Bethel | 78afee1472 | ||
Erin | b9634080b9 | ||
Alex Bethel | cdc50a847f | ||
Alex Bethel | 0324c08ce1 | ||
Erin | cb10b9f1d9 | ||
Alex Bethel | f1e964988c | ||
Alex Bethel | ec4a7e2447 | ||
Erin | 007917a217 | ||
Erin | 183f26f415 | ||
Erin | 6149dfddb6 | ||
Erin | 0da3d58718 | ||
Alex Bethel | b93333262c | ||
Alex Bethel | f890d6248b | ||
Erin | cd902e0036 | ||
Erin | 690c78c9c0 | ||
Erin | 6f193577ab | ||
Erin | 7aeb9d525d | ||
Erin | d570dfcb6f | ||
Erin | d7725b0db7 | ||
Erin | dc8f72bcf7 | ||
Erin | 656926387b | ||
Alex Bethel | 7225bd3004 | ||
Erin | 920ff99a7f | ||
Alex Bethel | 8993e78ee6 | ||
Erin | 66ceb8f8c6 | ||
Alex Bethel | 7812058fbf | ||
Alex Bethel | 8824633aae | ||
Erin | 07d021f610 | ||
Erin | 1a78eaf8c3 | ||
Erin | 6b5b8998c9 | ||
Erin | 3ed60f7306 | ||
Erin | 6e70b3d41a | ||
Alex Bethel | b3bbf03e2d | ||
Alex Bethel | da1fcfd861 | ||
Alex Bethel | 716e4997c5 | ||
Alex Bethel | 38a3414e88 | ||
Alex Bethel | 8a04d9d1e0 | ||
Alex Bethel | 99f84dfd5b | ||
Erin | 9712d58962 | ||
Alex Bethel | 2871e95e75 | ||
Alex Bethel | 7e0daeab29 | ||
Erin | 48d9d1e2e1 | ||
Alex Bethel | 77d75eea62 | ||
Erin | a82a9d7b79 | ||
Erin | f82ad34e34 | ||
Erin | 06464dfea9 | ||
0c755c927f | |||
Erin | 3cff0da70a | ||
Alex Bethel | 990e2806f1 | ||
Erin | cce392aaaa | ||
Erin | ea79e805f2 | ||
Alex Bethel | 863d03575b | ||
Erin | 1a5d2edee1 | ||
Erin | d229c3d3c3 | ||
Alex Bethel | 7673b64a71 | ||
Erin | 1b9f8a6179 | ||
Erin | fb16cb2ebb | ||
Erin | 23f03e7f67 | ||
Erin | a33a3ddd7f | ||
Erin | 70288ae409 | ||
Alex Bethel | a3b6ae326e | ||
Erin | af2fe717fb | ||
Erin | bd7209c004 | ||
Erin | 539989e2d0 | ||
Erin | e2c7a33a59 | ||
Erin | e84c4b2a39 | ||
Erin | d6e99acdc9 | ||
Erin | 90d0ed7a93 | ||
Erin | 9b81ccf57c | ||
Erin | 0cf8576a77 | ||
Erin | c764cda7c7 | ||
Erin | f08778ca3e | ||
Erin | 24b6683b21 | ||
Erin | e95e0744c6 | ||
Erin | 1b243d142c | ||
Erin | 2010b13168 | ||
Erin | 160ebd649d | ||
Erin | 1d24082ee8 | ||
Alex Bethel | 5368dd5209 | ||
06358adafa | |||
Erin | 507189c5f7 | ||
b5bb66f504 | |||
Erin | 17f5388de8 | ||
Erin | 60b63a7699 | ||
Alex Bethel | 0bec0a5a17 | ||
Alex Bethel | 22afba44b1 | ||
Erin | 6de8c17c19 | ||
Erin | 1f891353df | ||
Erin | 3262702465 | ||
Erin | f4beb85de2 | ||
Erin | 82767864ab | ||
Erin | fa63fda7f8 | ||
Erin | 35ddf85a92 | ||
Erin | 281fc9c635 | ||
Erin | d1146824f8 | ||
Alex Bethel | d1e0393681 | ||
Alex Bethel | 8570f495f5 | ||
Erin | 02b8e02f27 | ||
Erin | 754ad496af | ||
Erin | 50be7ca556 | ||
Erin | 68d0b8d334 | ||
Erin | 1f8d6a8ec2 | ||
Erin | fa87efa7e8 | ||
Erin | 4c735d0928 | ||
Erin | 1714629492 | ||
Alex Bethel | 003d800d6e | ||
Alex Bethel | 594968f781 | ||
Alex Bethel | 4fd61b71dd | ||
Alex Bethel | afc1d9b18c | ||
Alex Bethel | 5b87ed220a | ||
Alex Bethel | 9beb45adf0 | ||
Alex Bethel | 56623de96a | ||
ffcbdc258b | |||
Alex Bethel | f6e6f8cea1 | ||
Alex Bethel | a4d815cf7c | ||
Alex Bethel | e00df9d5ac | ||
Alex Bethel | 6b4a2469b6 | ||
Alex Bethel | 6d49d142f7 | ||
Alex Bethel | 53e624fdff | ||
Alex Bethel | 09d551075a | ||
Alex Bethel | f7ac2a1a43 | ||
Erin | 2ec416db97 | ||
Alex Bethel | 4b6c3528da | ||
Alex Bethel | b26c0ab639 | ||
Alex Bethel | e45df2efe7 | ||
Alex Bethel | 151564b718 | ||
Alex Bethel | ca61561ade | ||
Alex Bethel | 90c3e31824 | ||
Alex Bethel | d0f7052290 | ||
Alex Bethel | c35f3d80ff | ||
Alex Bethel | f02b33d564 | ||
Alex Bethel | 0e6bf2b27e | ||
Alex Bethel | 5ac9117651 | ||
Alex Bethel | f06fba1741 | ||
Alex Bethel | da53ba4040 | ||
Alex Bethel | 817ca7cf9a | ||
Alex Bethel | e709f398f7 | ||
a1aa4edc30 | |||
aa33fe88db | |||
6a01893f2e | |||
552c793e2f | |||
087768fcbc | |||
acff3e2139 | |||
7ceab0d3fd | |||
cbe07d87db | |||
Erin | 9912ea4b9b | ||
6e13e9163f | |||
7ca5917d59 | |||
aa5000c6c8 | |||
1bba812018 | |||
141feac31c | |||
f9f891cd20 | |||
3ffdd15b41 | |||
Alex Bethel | ede6eccc71 | ||
Alex Bethel | 083eb01282 | ||
Alex Bethel | 1a8f17339e | ||
Erin | ae3a7b7c8a | ||
Erin | 76e5fb9043 | ||
Erin | 8dbf93caa5 | ||
Erin | f3c459f26e | ||
Erin | eecde7635c | ||
Alex Bethel | 2a428e8415 | ||
Erin | 588f69b710 | ||
0046149c0d | |||
6a6658fd75 | |||
6d7fbb3514 | |||
Alex Bethel | 0fe7819e28 | ||
Alex Bethel | 39c5709db7 | ||
Alex Bethel | bdb32c4599 | ||
Alex Bethel | dfededfe26 | ||
Alex Bethel | b6a4ecba29 | ||
Alex Bethel | aedeeb2512 | ||
Alex Bethel | 3bca3f88a6 | ||
Erin | 717d592710 | ||
Erin | 84f58dab3d | ||
Erin | 99ebd71dac | ||
Erin | 7b1546387e | ||
Erin | beffef80c6 | ||
Erin | 17a7f33c0b | ||
Erin | 42df59705b | ||
Erin | f0cd6cd0ad | ||
Erin | 927ad5e955 | ||
Erin | afee5fb82d | ||
Erin | d66874624b | ||
Erin | b9d10fae03 | ||
Erin | e5c6feacb9 | ||
Erin | bccf5bc74b | ||
Erin | 935ba3b791 | ||
Alex Bethel | 08b4fff20d | ||
Alex Bethel | 2cb915dd24 | ||
723719b9df | |||
Alex Bethel | 98b2fae6f3 | ||
Alex Bethel | d80f47716d | ||
Alex Bethel | 2c35691ec4 | ||
Alex Bethel | b3866eea9e | ||
Alex Bethel | d13c22a473 | ||
Alex Bethel | 7bfefd3ba1 | ||
3e94e9e78c | |||
Alex Bethel | fcfa83a8a6 | ||
Alex Bethel | 00e80e9740 | ||
cedc52024c | |||
bbfc2351f9 | |||
55a0d9923e | |||
7379f9650c | |||
Alex Bethel | 51db37f1fe | ||
7537b4ac98 | |||
e7560f9364 | |||
1c2032ab87 | |||
Able | b37c189da9 | ||
0f1a106381 | |||
e79fc9a005 | |||
464337bd53 | |||
e31b8fb00d | |||
2febc944e4 | |||
1306c797c8 | |||
99758a177b | |||
7f5576c119 | |||
598917bf58 | |||
able | fafe6cf672 | ||
able | ac88a2e3c6 | ||
able | e2b7239992 | ||
able | f98c4d83d9 | ||
785ddda4cf | |||
Erin | 1618262948 | ||
Erin | 5bdbc2bbb0 | ||
Erin | 7057c3cfef | ||
Erin | c0ecf5f2a3 | ||
045ca17b52 | |||
Erin | 4b2d306ccb | ||
a0b3f7c34a | |||
Erin | d80b5514ce | ||
Erin | f8db60bc7c | ||
Erin | ecd972e55d | ||
Erin | 4a9b656093 | ||
Erin | 91bd015e9b | ||
Erin | d7cf1b013d | ||
Erin | c26c4a14bb | ||
Erin | 5c00446c59 | ||
Erin | 27a8de70fa | ||
Erin | 3e019621b2 | ||
345bed5f66 | |||
Erin | 00a464321a | ||
Erin | fc8c3a3499 | ||
Erin | 2a0b2952d4 | ||
Erin | 138b9efadc | ||
Erin | 83c549607c | ||
Erin | 47400ee2ce | ||
31c9fb3203 | |||
Erin | f7d1287fd5 | ||
Erin | 3e2dc5fba9 | ||
Erin | 7026711b64 | ||
able | 08af75fbda | ||
35586aa700 | |||
Erin | 4ca017671c | ||
Erin | 406c6b1297 | ||
able | ceaed053b4 | ||
e9aea03b29 | |||
Erin | 2194e2726f | ||
able | 00cca9fc74 | ||
b2e1db1d92 | |||
b062b9a216 |
570
Cargo.lock
generated
570
Cargo.lock
generated
|
@ -3,19 +3,20 @@
|
|||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "able-script"
|
||||
version = "0.1.0"
|
||||
name = "ablescript"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"logos",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
name = "ablescript_cli"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"ablescript",
|
||||
"clap",
|
||||
"rustyline",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -30,67 +31,502 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "beef"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bed554bd50246729a1ec158d08aa3235d1b69d94ad120ebe187e28894787e736"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
version = "3.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
checksum = "71c47df61d9e16dc010b55dba1952a57d8c215dbb533fd13cdd13369aac73b1c"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"os_str_bytes",
|
||||
"strsim",
|
||||
"termcolor",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.18"
|
||||
name = "clipboard-win"
|
||||
version = "4.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c"
|
||||
checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
|
||||
dependencies = [
|
||||
"error-code",
|
||||
"str-buf",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-next"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"dirs-sys-next",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys-next"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"redox_users",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "endian-type"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-code"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64f18991e7bf11e7ffee451b5318b5c1a73c52d0d0ada6e5a3017c8c1ced6a21"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"str-buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fd-lock"
|
||||
version = "3.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46e245f4c8ec30c6415c56cb132c07e69e74f1942f6b4a4061da748b49f486ca"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.93"
|
||||
name = "indexmap"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
name = "io-lifetimes"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "9448015e586b611e5d322f6703812bbca2f1e709d5773ecd38ddb4e3bb649504"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
name = "libc"
|
||||
version = "0.2.122"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.0.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "427e2abca5be13136da9afdbf874e6b34ad9001dd70f2b103b083a85daa7b345"
|
||||
dependencies = [
|
||||
"logos-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "logos-derive"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56a7d287fd2ac3f75b11f19a1c8a874a7d55744bd91f7a1b3e7cf87d4343c36d"
|
||||
dependencies = [
|
||||
"beef",
|
||||
"fnv",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex-syntax",
|
||||
"syn",
|
||||
"utf8-ranges",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nibble_vec"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
|
||||
dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_str_bytes"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radix_trie"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
|
||||
dependencies = [
|
||||
"endian-type",
|
||||
"nibble_vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.34.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96619609a54d638872db136f56941d34e2a00bb0acf3fa783a90d6b96a093ba2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustyline"
|
||||
version = "9.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db7826789c0e25614b03e5a54a0717a86f9ff6e6e5247f92b369472869320039"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"clipboard-win",
|
||||
"dirs-next",
|
||||
"fd-lock",
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"radix_trie",
|
||||
"scopeguard",
|
||||
"smallvec",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"utf8parse",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83"
|
||||
|
||||
[[package]]
|
||||
name = "str-buf"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d44a3643b4ff9caf57abcee9c2c621d6c03d9135e0d8b589bd9afb5992cb176a"
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.91"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "utf8-ranges"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcfc827f90e53a02eaef5e535ee14266c1d569214c6aa70133a624d8a3164ba"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
|
@ -108,8 +544,60 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "030b7ff91626e57a05ca64a07c481973cbb2db774e4852c9c7ca342408c6a99a"
|
||||
dependencies = [
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29277a4435d642f775f63c7d1faeb927adba532886ce0287bd985bffb16b6bca"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1145e1989da93956c68d1864f32fb97c8f561a8f89a5125f6a2b7ea75524e4b8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4a09e3a0d4753b73019db171c1339cd4362c8c44baf1bcea336235e955954a6"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ca64fcb0220d58db4c119e050e7af03c69e6f4f415ef69ec1773d9aab422d5a"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.30.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08cabc9f0066848fef4bc6a1c1668e6efce38b661d2aeec75d18d8617eebb5f1"
|
||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,10 +1,6 @@
|
|||
[package]
|
||||
name = "able-script"
|
||||
version = "0.1.0"
|
||||
authors = ["able <abl3theabove@gmail.com>"]
|
||||
edition = "2018"
|
||||
[workspace]
|
||||
members = ["ablescript", "ablescript_cli"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap="*"
|
||||
[profile.release]
|
||||
lto = true
|
||||
strip = true
|
||||
|
|
26
LICENSE
26
LICENSE
|
@ -1,7 +1,21 @@
|
|||
You can do whatever you want with this software assuming you meet the following conditions:
|
||||
1) Answer me why?
|
||||
2) Never attribute me
|
||||
MIT License
|
||||
|
||||
You can do whatever you want with this license assuming you meet the following conditions:
|
||||
1) attribute me
|
||||
2) I (AbleTheAbove#3819 on discord) own you
|
||||
Copyright (c) 2021 AbleCorp
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
19
README.md
19
README.md
|
@ -1,4 +1,19 @@
|
|||
# Ablescript
|
||||
# AbleScript
|
||||
![Build Status](https://img.shields.io/github/workflow/status/AbleCorp/able-script/Rust)
|
||||
![Lines of code](https://img.shields.io/tokei/lines/github/abletheabove/able-script)
|
||||
![Discord](https://img.shields.io/discord/831368967385120810)
|
||||
|
||||
Bravely going where some languages have gone before.
|
||||
[Able Script Book](https://ablecorp.us/able-script-the-book)
|
||||
|
||||
Still better than Javascript ://
|
||||
|
||||
## About
|
||||
Ablescript is designed to be bad.
|
||||
|
||||
## Key Features
|
||||
- Brain Fuck functions
|
||||
- No floats
|
||||
- U is equal to -210
|
||||
- bannable variables
|
||||
- fuck you
|
||||
- constants
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
functio hello(words){
|
||||
words print;
|
||||
}
|
||||
hello("wonk");
|
|
@ -1,2 +0,0 @@
|
|||
var hello = "world";
|
||||
hello print;
|
16
ablescript/Cargo.toml
Normal file
16
ablescript/Cargo.toml
Normal file
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "ablescript"
|
||||
version = "0.3.0"
|
||||
authors = ["able <abl3theabove@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
description = "The best programming language"
|
||||
license = "MIT"
|
||||
documentation = "https://ablecorp.us/able-script-the-book/"
|
||||
repository = "https://github.com/AbleCorp/able-script"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
logos = "0.12"
|
||||
rand = "0.8"
|
223
ablescript/src/ast.rs
Normal file
223
ablescript/src/ast.rs
Normal file
|
@ -0,0 +1,223 @@
|
|||
//! AbleScript's Abstract Syntax tree
|
||||
//!
|
||||
//! Statements are the type which is AST made of, as they
|
||||
//! express an effect.
|
||||
//!
|
||||
//! Expressions are just operations and they cannot be
|
||||
//! used as statements. Functions in AbleScript are in fact
|
||||
//! just plain subroutines and they do not return any value,
|
||||
//! so their calls are statements.
|
||||
|
||||
use crate::{base_55::char2num, variables::Value};
|
||||
use std::{fmt::Debug, hash::Hash};
|
||||
|
||||
type Span = std::ops::Range<usize>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Spanned<T> {
|
||||
pub item: T,
|
||||
pub span: Span,
|
||||
}
|
||||
|
||||
impl<T> Spanned<T> {
|
||||
pub fn new(item: T, span: Span) -> Self {
|
||||
Self { item, span }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for Spanned<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if f.alternate() {
|
||||
write!(f, "{:#?} @ {:?}", self.item, self.span)
|
||||
} else {
|
||||
write!(f, "{:?} @ {:?}", self.item, self.span)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq> PartialEq for Spanned<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.item == other.item
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Hash> Hash for Spanned<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.item.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub struct Assignable {
|
||||
pub ident: Spanned<String>,
|
||||
pub kind: AssignableKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum AssignableKind {
|
||||
Variable,
|
||||
Index { indices: Vec<Spanned<Expr>> },
|
||||
}
|
||||
|
||||
pub struct InvalidAssignable;
|
||||
|
||||
impl Assignable {
|
||||
pub fn from_expr(expr: Spanned<Expr>) -> Result<Assignable, InvalidAssignable> {
|
||||
match expr.item {
|
||||
Expr::Variable(ident) => Ok(Assignable {
|
||||
ident: Spanned::new(ident, expr.span),
|
||||
kind: AssignableKind::Variable,
|
||||
}),
|
||||
Expr::Index { expr, index } => Self::from_index(*expr, *index),
|
||||
_ => Err(InvalidAssignable),
|
||||
}
|
||||
}
|
||||
|
||||
fn from_index(
|
||||
mut buf: Spanned<Expr>,
|
||||
index: Spanned<Expr>,
|
||||
) -> Result<Assignable, InvalidAssignable> {
|
||||
let mut indices = vec![index];
|
||||
let ident = loop {
|
||||
match buf.item {
|
||||
Expr::Variable(ident) => break ident,
|
||||
Expr::Index { expr, index } => {
|
||||
indices.push(*index);
|
||||
buf = *expr;
|
||||
}
|
||||
_ => return Err(InvalidAssignable),
|
||||
}
|
||||
};
|
||||
|
||||
indices.reverse();
|
||||
Ok(Assignable {
|
||||
ident: Spanned::new(ident, buf.span),
|
||||
kind: AssignableKind::Index { indices },
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub type Block = Vec<Spanned<Stmt>>;
|
||||
|
||||
/// A syntactic unit expressing an effect.
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum Stmt {
|
||||
// Control flow
|
||||
Unless {
|
||||
cond: Spanned<Expr>,
|
||||
body: Block,
|
||||
},
|
||||
Loop {
|
||||
body: Block,
|
||||
},
|
||||
Break,
|
||||
HopBack,
|
||||
|
||||
Dim {
|
||||
ident: Spanned<String>,
|
||||
init: Option<Spanned<Expr>>,
|
||||
},
|
||||
Assign {
|
||||
assignable: Assignable,
|
||||
value: Spanned<Expr>,
|
||||
},
|
||||
|
||||
Functio {
|
||||
ident: Spanned<String>,
|
||||
body: Spanned<FunctioBody>,
|
||||
},
|
||||
BfFunctio {
|
||||
ident: Spanned<String>,
|
||||
body: Spanned<BfFunctioBody>,
|
||||
},
|
||||
Call {
|
||||
expr: Spanned<Expr>,
|
||||
args: Vec<Spanned<Expr>>,
|
||||
},
|
||||
Print(Spanned<Expr>),
|
||||
Read(Assignable),
|
||||
Melo(Spanned<String>),
|
||||
Rlyeh,
|
||||
Rickroll,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub struct FunctioBody {
|
||||
pub params: Vec<Spanned<String>>,
|
||||
pub body: Block,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub struct BfFunctioBody {
|
||||
pub tape_len: Option<Spanned<Expr>>,
|
||||
pub code: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Expression is parse unit which do not cause any effect,
|
||||
/// like math and logical operations or values.
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum Expr {
|
||||
BinOp {
|
||||
lhs: Box<Spanned<Expr>>,
|
||||
rhs: Box<Spanned<Expr>>,
|
||||
kind: BinOpKind,
|
||||
},
|
||||
Aint(Box<Spanned<Expr>>),
|
||||
Literal(Literal),
|
||||
Cart(Vec<(Spanned<Expr>, Spanned<Expr>)>),
|
||||
Lambda(Spanned<FunctioBody>),
|
||||
BfLambda(Box<Spanned<BfFunctioBody>>),
|
||||
Index {
|
||||
expr: Box<Spanned<Expr>>,
|
||||
index: Box<Spanned<Expr>>,
|
||||
},
|
||||
Len(Box<Spanned<Expr>>),
|
||||
Variable(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum Literal {
|
||||
Char(char),
|
||||
Int(isize),
|
||||
Str(String),
|
||||
}
|
||||
|
||||
impl From<Literal> for Value {
|
||||
fn from(lit: Literal) -> Self {
|
||||
match lit {
|
||||
Literal::Char(c) => Self::Int(char2num(c)),
|
||||
Literal::Int(i) => Self::Int(i),
|
||||
Literal::Str(s) => Self::Str(s),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum BinOpKind {
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide,
|
||||
Greater,
|
||||
Less,
|
||||
Equal,
|
||||
NotEqual,
|
||||
}
|
||||
|
||||
impl BinOpKind {
|
||||
pub fn from_token(t: crate::lexer::Token) -> Result<Self, crate::error::ErrorKind> {
|
||||
use crate::lexer::Token;
|
||||
|
||||
match t {
|
||||
Token::Plus => Ok(Self::Add),
|
||||
Token::Minus => Ok(Self::Subtract),
|
||||
Token::Star => Ok(Self::Multiply),
|
||||
Token::FwdSlash => Ok(Self::Divide),
|
||||
Token::GreaterThan => Ok(Self::Greater),
|
||||
Token::LessThan => Ok(Self::Less),
|
||||
Token::Equals => Ok(Self::Equal),
|
||||
Token::Aint => Ok(Self::NotEqual),
|
||||
t => Err(crate::error::ErrorKind::UnexpectedToken(t)),
|
||||
}
|
||||
}
|
||||
}
|
23
ablescript/src/base_55.rs
Normal file
23
ablescript/src/base_55.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
pub const fn char2num(c: char) -> isize {
|
||||
match c {
|
||||
' ' => 0,
|
||||
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
|
||||
'/' => 53,
|
||||
'\\' => 54,
|
||||
'.' => 55,
|
||||
'U' => -210,
|
||||
'A'..='Z' => -(c as isize) + 64,
|
||||
'a'..='z' => (c as isize) - 96,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn str_to_base55() {
|
||||
let chrs: Vec<isize> = "AbleScript".chars().map(char2num).collect();
|
||||
assert_eq!(chrs, &[-1, 2, 12, 5, -19, 3, 18, 9, 16, 20]);
|
||||
}
|
||||
}
|
454
ablescript/src/brian.rs
Normal file
454
ablescript/src/brian.rs
Normal file
|
@ -0,0 +1,454 @@
|
|||
//! A brainfuck interpreter capable of executing arbitrary code, with arbitrary inputs and outputs.
|
||||
//!
|
||||
//! If you just want to execute some simple brainfuck, check the [`interpret_with_io`] function.
|
||||
//!
|
||||
//! To construct the interpreter, use the [`from_ascii`] or [`from_ascii_with_input_buffer`] methods
|
||||
//! (or their variants that take a maximum tape size). The latter grants access to
|
||||
//! the method [`add_input`], which allows for the addition of input while the interpreter is running.
|
||||
//!
|
||||
//! [`from_ascii`]: Interpreter::from_ascii
|
||||
//! [`from_ascii_with_input_buffer`]: Interpreter::from_ascii_with_input_buffer
|
||||
//! [`add_input`]: Interpreter::add_input
|
||||
//!
|
||||
//! Finally, to run the interpreter, you can use the [`advance`], [`advance_until_io`], or [`interpret_with_output`] methods.
|
||||
//!
|
||||
//! [`advance`]: Interpreter::advance
|
||||
//! [`advance_until_io`]: Interpreter::advance_until_io
|
||||
//! [`interpret_with_output`]: Interpreter::interpret_with_output
|
||||
|
||||
#![deny(missing_docs)]
|
||||
// Putting this here because we still don't use the entire capabilities of this module. ~~Alex
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
error::Error,
|
||||
fmt::Display,
|
||||
io::{Read, Write},
|
||||
};
|
||||
|
||||
// NOTE(Able): This is the brain fuck interface
|
||||
|
||||
/// The default limit for the tape size. This is the value used by methods that don't take it as a parameter
|
||||
pub const DEFAULT_TAPE_SIZE_LIMIT: usize = 30_000;
|
||||
|
||||
/// Mappings from integers to BF instructions
|
||||
pub const INSTRUCTION_MAPPINGS: &[u8] = b"[]+-,.<>";
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
/// A brainfuck interpreter. Read the [module level documentation](self) for more
|
||||
pub struct Interpreter<'a, I> {
|
||||
code: &'a [u8],
|
||||
instr_ptr: usize,
|
||||
tape: Vec<i8>,
|
||||
data_ptr: usize,
|
||||
tape_size_limit: usize,
|
||||
input: I,
|
||||
}
|
||||
|
||||
impl<'a> Interpreter<'a, InputBuffer> {
|
||||
/// Construct an `Interpreter` from an ASCII string of code with an empty input buffer
|
||||
/// This methods sets the tape size limit to [its default value](DEFAULT_TAPE_SIZE_LIMIT)
|
||||
pub fn from_ascii_with_input_buffer(code: &'a [u8]) -> Self {
|
||||
Self::from_ascii_with_input_buffer_and_tape_limit(code, DEFAULT_TAPE_SIZE_LIMIT)
|
||||
}
|
||||
|
||||
/// Construct an `Interpreter` from an ASCII string of code with an empty input buffer,
|
||||
/// setting the tape size limit to the specified value
|
||||
pub fn from_ascii_with_input_buffer_and_tape_limit(
|
||||
code: &'a [u8],
|
||||
tape_size_limit: usize,
|
||||
) -> Self {
|
||||
Self {
|
||||
code,
|
||||
instr_ptr: 0,
|
||||
tape: Vec::new(),
|
||||
data_ptr: 0,
|
||||
tape_size_limit,
|
||||
input: InputBuffer(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a byte to the input buffer of this interpreter
|
||||
pub fn add_input(&mut self, input: i8) {
|
||||
self.input.0.push_back(input);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, I: BootlegRead> Interpreter<'a, I> {
|
||||
/// Construct an interpreter from an ASCII string of code, a source of input bytes, and a tape size limit
|
||||
pub fn from_ascii_with_tape_limit(code: &'a [u8], input: I, tape_size_limit: usize) -> Self {
|
||||
Self {
|
||||
code,
|
||||
instr_ptr: 0,
|
||||
tape: Vec::new(),
|
||||
data_ptr: 0,
|
||||
tape_size_limit,
|
||||
input,
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs an interpreter from an ASCII string of code, a source of input bytes, and [the default tape size limit](DEFAULT_TAPE_SIZE_LIMIT)
|
||||
pub fn from_ascii(code: &'a [u8], input: I) -> Self {
|
||||
Self::from_ascii_with_tape_limit(code, input, DEFAULT_TAPE_SIZE_LIMIT)
|
||||
}
|
||||
|
||||
/// Advance the interpreter by one instruction.
|
||||
/// A return value of Ok(None) indicates succesful termination of the interpreter
|
||||
pub fn advance(&mut self) -> Result<Option<Status>, ProgramError> {
|
||||
let &opcode = match self.code.get(self.instr_ptr) {
|
||||
Some(opcode) => opcode,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
match opcode {
|
||||
b'>' => self.data_ptr += 1,
|
||||
|
||||
b'<' => {
|
||||
self.data_ptr = self
|
||||
.data_ptr
|
||||
.checked_sub(1)
|
||||
.ok_or(ProgramError::DataPointerUnderflow)?;
|
||||
}
|
||||
|
||||
b'+' => {
|
||||
let val = self
|
||||
.get_or_resize_tape_mut()
|
||||
.ok_or(ProgramError::TapeSizeExceededLimit)?;
|
||||
*val = val.wrapping_add(1)
|
||||
}
|
||||
|
||||
b'-' => {
|
||||
let val = self
|
||||
.get_or_resize_tape_mut()
|
||||
.ok_or(ProgramError::TapeSizeExceededLimit)?;
|
||||
*val = val.wrapping_sub(1)
|
||||
}
|
||||
|
||||
b'.' => {
|
||||
self.instr_ptr += 1;
|
||||
return Ok(Some(Status::Output(self.get_at_data_ptr())));
|
||||
}
|
||||
|
||||
b',' => match self.input.bootleg_read() {
|
||||
Ok(Some(num)) => {
|
||||
let cell = self
|
||||
.get_or_resize_tape_mut()
|
||||
.ok_or(ProgramError::TapeSizeExceededLimit)?;
|
||||
*cell = num;
|
||||
}
|
||||
Ok(None) => return Ok(Some(Status::NeedsInput)),
|
||||
Err(_) => return Err(ProgramError::InputReadError),
|
||||
},
|
||||
|
||||
b'[' => {
|
||||
if self.get_at_data_ptr() == 0 {
|
||||
self.instr_ptr = self
|
||||
.get_matching_closing_bracket(self.instr_ptr)
|
||||
.ok_or(ProgramError::UnmatchedOpeningBracket)?
|
||||
//Instruction pointer will be incremented by 1 after the match
|
||||
}
|
||||
}
|
||||
|
||||
b']' => {
|
||||
if self.get_at_data_ptr() != 0 {
|
||||
self.instr_ptr = self
|
||||
.get_matching_opening_bracket(self.instr_ptr)
|
||||
.ok_or(ProgramError::UnmatchedClosingBracket)?
|
||||
//Instruction pointer will be incremented by 1 after the match
|
||||
}
|
||||
}
|
||||
|
||||
_ => {} //brainfuck treats all characters it doesn't understand as comments
|
||||
}
|
||||
|
||||
self.instr_ptr += 1;
|
||||
|
||||
Ok(Some(Status::Continue))
|
||||
}
|
||||
|
||||
/// Advances the interpreter until the next IO operation. See [`advance`](Interpreter::advance)
|
||||
pub fn advance_until_io(&mut self) -> Result<Option<IoStatus>, ProgramError> {
|
||||
while let Some(status) = self.advance()? {
|
||||
match status {
|
||||
Status::NeedsInput => return Ok(Some(IoStatus::NeedsInput)),
|
||||
Status::Output(out) => return Ok(Some(IoStatus::Output(out))),
|
||||
Status::Continue => continue,
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Executes the interpreter until it halts, writing all return values to the provided `Write` type.
|
||||
/// For more granular control, use [`advance`](Interpreter::advance)
|
||||
pub fn interpret_with_output<O: Write>(&mut self, mut output: O) -> Result<(), InterpretError> {
|
||||
while let Some(status) = self.advance_until_io()? {
|
||||
match status {
|
||||
IoStatus::NeedsInput => return Err(InterpretError::EndOfInput),
|
||||
IoStatus::Output(out) => match output.write(&[out as u8]) {
|
||||
Ok(0) => return Err(InterpretError::OutputBufferFull),
|
||||
Ok(_) => continue,
|
||||
Err(_) => return Err(InterpretError::OutputWriteError),
|
||||
},
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_or_resize_tape_mut(&mut self) -> Option<&mut i8> {
|
||||
if self.data_ptr > self.tape_size_limit {
|
||||
return None;
|
||||
}
|
||||
if self.data_ptr >= self.tape.len() {
|
||||
self.tape.resize(self.data_ptr + 1, 0);
|
||||
}
|
||||
Some(&mut self.tape[self.data_ptr])
|
||||
}
|
||||
|
||||
fn get_at_data_ptr(&self) -> i8 {
|
||||
//No need to resize the tape to read: if the tape doesn't extend that far already, it holds a value of 0
|
||||
self.tape.get(self.data_ptr).copied().unwrap_or(0)
|
||||
}
|
||||
|
||||
fn get_matching_closing_bracket(&mut self, opening: usize) -> Option<usize> {
|
||||
self.code[opening..]
|
||||
.iter()
|
||||
.zip(opening..)
|
||||
.scan(0, |counter, (char, index)| {
|
||||
match char {
|
||||
b'[' => *counter += 1,
|
||||
b']' => *counter -= 1,
|
||||
_ => {}
|
||||
};
|
||||
Some((*counter, index))
|
||||
})
|
||||
.find_map(
|
||||
|(counter, index)| {
|
||||
if counter == 0 {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_matching_opening_bracket(&mut self, closing: usize) -> Option<usize> {
|
||||
self.code[..closing + 1]
|
||||
.iter()
|
||||
.zip(0..closing + 1)
|
||||
.rev()
|
||||
.scan(0, |counter, (char, index)| {
|
||||
match char {
|
||||
b']' => *counter += 1,
|
||||
b'[' => *counter -= 1,
|
||||
_ => {}
|
||||
};
|
||||
Some((*counter, index))
|
||||
})
|
||||
.find_map(
|
||||
|(counter, index)| {
|
||||
if counter == 0 {
|
||||
Some(index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience function for interpreting brainfuck code with a given input and output source.
|
||||
/// For more information, consult [the module level documentation](self)
|
||||
pub fn interpret_with_io<I: BootlegRead, O: Write>(
|
||||
code: &[u8],
|
||||
input: I,
|
||||
output: O,
|
||||
) -> Result<(), InterpretError> {
|
||||
Interpreter::from_ascii(code, input).interpret_with_output(output)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
///The result of advancing the interpreter by one step, assuming it didn't terminate
|
||||
pub enum Status {
|
||||
NeedsInput,
|
||||
Output(i8),
|
||||
Continue,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// The result of advancing the interpreter until the next IO operation, assuming it didn't terminate
|
||||
pub enum IoStatus {
|
||||
NeedsInput,
|
||||
Output(i8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// An error that occurred while the interpreter was advancing
|
||||
pub enum ProgramError {
|
||||
DataPointerUnderflow,
|
||||
InputReadError,
|
||||
UnmatchedOpeningBracket,
|
||||
UnmatchedClosingBracket,
|
||||
TapeSizeExceededLimit,
|
||||
}
|
||||
|
||||
impl Display for ProgramError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
ProgramError::DataPointerUnderflow => "data pointer underflow",
|
||||
ProgramError::InputReadError => "input read error",
|
||||
ProgramError::UnmatchedOpeningBracket => "unmatched `[`",
|
||||
ProgramError::UnmatchedClosingBracket => "unmatched `]`",
|
||||
ProgramError::TapeSizeExceededLimit => "tape size exceeded",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
impl Error for ProgramError {}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
/// An error that occurred while the interpreter was being run start-to-end all in one go
|
||||
pub enum InterpretError {
|
||||
ProgramError(ProgramError),
|
||||
EndOfInput,
|
||||
OutputBufferFull,
|
||||
OutputWriteError,
|
||||
}
|
||||
|
||||
impl Display for InterpretError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
InterpretError::ProgramError(e) => write!(f, "program error: {}", e),
|
||||
InterpretError::EndOfInput => write!(f, "unexpected end of input"),
|
||||
InterpretError::OutputBufferFull => write!(f, "output buffer full"),
|
||||
InterpretError::OutputWriteError => write!(f, "output write error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Error for InterpretError {}
|
||||
|
||||
impl From<ProgramError> for InterpretError {
|
||||
fn from(e: ProgramError) -> Self {
|
||||
InterpretError::ProgramError(e)
|
||||
}
|
||||
}
|
||||
|
||||
/// A bootlegged version of the standard library's read trait, so as to allow the interpreter to be generic over any `Read`
|
||||
/// type, as well as over an input buffer.
|
||||
pub trait BootlegRead {
|
||||
type Error;
|
||||
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error>;
|
||||
}
|
||||
|
||||
impl<T: Read> BootlegRead for T {
|
||||
type Error = std::io::Error;
|
||||
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error> {
|
||||
let mut buffer = [0];
|
||||
match self.read(&mut buffer) {
|
||||
Ok(0) => Ok(None),
|
||||
Ok(_) => Ok(Some(buffer[0] as i8)),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper around a `VecDeque`, to be able to implement `BootlegRead` for it
|
||||
struct InputBuffer(VecDeque<i8>);
|
||||
impl BootlegRead for InputBuffer {
|
||||
type Error = std::convert::Infallible;
|
||||
fn bootleg_read(&mut self) -> Result<Option<i8>, Self::Error> {
|
||||
Ok(self.0.pop_front())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn adder() {
|
||||
let mut interpreter = Interpreter {
|
||||
code: b"[->+<]", //Source: https://en.wikipedia.org/wiki/Brainfuck
|
||||
instr_ptr: 0,
|
||||
tape: vec![10, 5],
|
||||
data_ptr: 0,
|
||||
tape_size_limit: DEFAULT_TAPE_SIZE_LIMIT,
|
||||
input: std::io::empty(),
|
||||
};
|
||||
|
||||
while let Some(status) = interpreter.advance_until_io().expect("Unexpected error") {
|
||||
match status {
|
||||
IoStatus::NeedsInput => panic!("Requested input in an IO-less program"),
|
||||
IoStatus::Output(_) => panic!("Produced output in an IO-less program"),
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(interpreter.tape, vec![0, 15]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hello_world() {
|
||||
let mut interpreter = Interpreter::from_ascii(
|
||||
b"++++++++[>++++[>++>+++>+++>+<<<<-]>+>+>->>+[<]<-]>>.>---.+++++++..+++.>>.<-.<.+++.------.--------.>>+.>++.",
|
||||
std::io::empty(),
|
||||
);
|
||||
|
||||
let mut string = Vec::new();
|
||||
interpreter
|
||||
.interpret_with_output(&mut string)
|
||||
.expect("Failed to write to output buffer");
|
||||
assert_eq!(string, b"Hello World!\n");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn with_input_buffer() {
|
||||
let mut interpreter = Interpreter::from_ascii_with_input_buffer(b"+++++.>,[-<->].");
|
||||
let output = match interpreter
|
||||
.advance_until_io()
|
||||
.expect("Unexpected error")
|
||||
.expect("Unexpected termination")
|
||||
{
|
||||
IoStatus::NeedsInput => panic!("Unexpected input request"),
|
||||
IoStatus::Output(out) => out,
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
interpreter.advance_until_io(),
|
||||
Ok(Some(IoStatus::NeedsInput))
|
||||
);
|
||||
|
||||
interpreter.add_input(output);
|
||||
|
||||
assert_eq!(
|
||||
interpreter.advance_until_io(),
|
||||
Ok(Some(IoStatus::Output(0)))
|
||||
);
|
||||
assert_eq!(interpreter.advance_until_io(), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hit_tape_size_limit() {
|
||||
let mut interpreter =
|
||||
Interpreter::from_ascii_with_tape_limit(b"+>+>+>+>+>", std::io::empty(), 1);
|
||||
let result = interpreter.interpret_with_output(std::io::sink());
|
||||
assert_eq!(
|
||||
result,
|
||||
Err(InterpretError::ProgramError(
|
||||
ProgramError::TapeSizeExceededLimit
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn positive_integer_overflow() {
|
||||
interpret_with_io(b"+[+]", std::io::empty(), std::io::sink()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negative_integer_overflow() {
|
||||
interpret_with_io(b"-", std::io::empty(), std::io::sink()).unwrap();
|
||||
}
|
||||
}
|
42
ablescript/src/consts.rs
Normal file
42
ablescript/src/consts.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
//! Number constants.
|
||||
|
||||
use crate::variables::{Value, Variable};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub const ANSWER: isize = 42;
|
||||
|
||||
/// Initialize a HashMap between the constant names and values
|
||||
/// accessible from within AbleScript.
|
||||
pub fn ablescript_consts() -> HashMap<String, Variable> {
|
||||
use Value::*;
|
||||
|
||||
[
|
||||
("TAU", Int(6)), // Circumference / radius
|
||||
("PI", Int(3)), // Deprecated, do not use
|
||||
("EULER", Int(3)), // Mathematical constant e
|
||||
("MASS", Int(70)), // @Kev#6900's weight in kilograms
|
||||
("PHI", Int(2)), // Golden ratio
|
||||
("WUA", Int(1)), // 1
|
||||
("EULERS_CONSTANT", Int(0)), // ???
|
||||
("GRAVITY", Int(10)), // Earth surface gravity, m/s
|
||||
("RNG", Int(12)), // Kixiron#5289 Randomly rolled dice
|
||||
("STD_RNG", Int(4)), // The standard random number is 4 (https://xkcd.com/221/)
|
||||
("INF", Int(isize::max_value())), // The biggest number
|
||||
("INTERESSANT", Int(114514)), // HTGAzureX1212.#5959 intéressant number
|
||||
("FUNNY", Int(69)), // HTGAzureX1212.#5959 funny number
|
||||
(
|
||||
// Never gonna let you down
|
||||
"NEVERGONNAGIVEYOUUP",
|
||||
Str("1452251871514141792252515212116".to_owned()),
|
||||
),
|
||||
("OCTOTHORPE", Str("#".to_owned())), // It's an octothorpe
|
||||
("ANSWER", Int(ANSWER)),
|
||||
("nul", Nul),
|
||||
("always", Abool(crate::variables::Abool::Always)),
|
||||
("sometimes", Abool(crate::variables::Abool::Sometimes)),
|
||||
("never", Abool(crate::variables::Abool::Never)),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(name, value)| (name.to_owned(), Variable::from_value(value)))
|
||||
.collect()
|
||||
}
|
71
ablescript/src/error.rs
Normal file
71
ablescript/src/error.rs
Normal file
|
@ -0,0 +1,71 @@
|
|||
use crate::{brian::InterpretError, lexer::Token};
|
||||
use std::{fmt::Display, io, ops::Range};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error {
|
||||
pub kind: ErrorKind,
|
||||
pub span: Range<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ErrorKind {
|
||||
UnexpectedEof,
|
||||
UnexpectedToken(Token),
|
||||
UnknownVariable(String),
|
||||
MeloVariable(String),
|
||||
TopLevelBreak,
|
||||
BfInterpretError(InterpretError),
|
||||
MismatchedArgumentError,
|
||||
MissingLhs,
|
||||
IOError(io::Error),
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub fn new(kind: ErrorKind, span: Range<usize>) -> Self {
|
||||
Self { kind, span }
|
||||
}
|
||||
|
||||
/// Create an UnexpectedEof error, where the EOF occurs at the
|
||||
/// given index in the file.
|
||||
pub fn unexpected_eof(index: usize) -> Self {
|
||||
Self::new(ErrorKind::UnexpectedEof, index..index)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Error at range {}-{}: {}",
|
||||
self.span.start, self.span.end, self.kind
|
||||
)
|
||||
}
|
||||
}
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
impl Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
ErrorKind::UnexpectedEof => write!(f, "unexpected end of file"),
|
||||
ErrorKind::UnexpectedToken(Token::Melo) => write!(f, "unexpected marten"),
|
||||
ErrorKind::UnexpectedToken(token) => write!(f, "unexpected token {:?}", token),
|
||||
ErrorKind::UnknownVariable(name) => write!(f, "unknown identifier \"{}\"", name),
|
||||
ErrorKind::MeloVariable(name) => write!(f, "banned variable \"{}\"", name),
|
||||
ErrorKind::TopLevelBreak => write!(f, "can only `break` out of a loop"),
|
||||
ErrorKind::BfInterpretError(err) => write!(f, "brainfuck error: {}", err),
|
||||
// TODO: give concrete numbers here.
|
||||
ErrorKind::MismatchedArgumentError => write!(f, "wrong number of function arguments"),
|
||||
ErrorKind::MissingLhs => write!(f, "missing expression before binary operation"),
|
||||
ErrorKind::IOError(err) => write!(f, "I/O error: {}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(e: io::Error) -> Self {
|
||||
Self {
|
||||
kind: ErrorKind::IOError(e),
|
||||
span: 0..0,
|
||||
}
|
||||
}
|
||||
}
|
795
ablescript/src/interpret.rs
Normal file
795
ablescript/src/interpret.rs
Normal file
|
@ -0,0 +1,795 @@
|
|||
//! Expression evaluator and statement interpreter.
|
||||
//!
|
||||
//! To interpret a piece of AbleScript code, you first need to
|
||||
//! construct an [ExecEnv], which is responsible for storing the stack
|
||||
//! of local variable and function definitions accessible from an
|
||||
//! AbleScript snippet. You can then call [ExecEnv::eval_stmts] to
|
||||
//! evaluate or execute any number of expressions or statements.
|
||||
|
||||
#![deny(missing_docs)]
|
||||
|
||||
use crate::{
|
||||
ast::{Assignable, AssignableKind, BfFunctioBody, Expr, FunctioBody, Spanned, Stmt},
|
||||
consts::ablescript_consts,
|
||||
error::{Error, ErrorKind},
|
||||
variables::{Functio, Value, ValueRef, Variable},
|
||||
};
|
||||
use rand::random;
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
collections::{HashMap, VecDeque},
|
||||
io::{stdin, stdout, Read, Write},
|
||||
mem::take,
|
||||
ops::Range,
|
||||
process::exit,
|
||||
};
|
||||
|
||||
/// An environment for executing AbleScript code.
|
||||
pub struct ExecEnv {
|
||||
/// The stack, ordered such that `stack[stack.len() - 1]` is the
|
||||
/// top-most (newest) stack frame, and `stack[0]` is the
|
||||
/// bottom-most (oldest) stack frame.
|
||||
stack: Vec<Scope>,
|
||||
|
||||
/// The `read` statement maintains a buffer of up to 7 bits,
|
||||
/// because input comes from the operating system 8 bits at a time
|
||||
/// (via stdin) but gets delivered to AbleScript 3 bits at a time
|
||||
/// (via the `read` statement). We store each of those bits as
|
||||
/// booleans to facilitate easy manipulation.
|
||||
read_buf: VecDeque<bool>,
|
||||
}
|
||||
|
||||
/// A set of visible variable and function definitions in a single
|
||||
/// stack frame.
|
||||
struct Scope {
|
||||
/// The mapping from variable names to values.
|
||||
variables: HashMap<String, Variable>,
|
||||
}
|
||||
|
||||
impl Default for ExecEnv {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
stack: vec![Default::default()],
|
||||
read_buf: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Scope {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
variables: ablescript_consts(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The reason a successful series of statements halted.
|
||||
enum HaltStatus {
|
||||
/// We ran out of statements to execute.
|
||||
Finished,
|
||||
|
||||
/// A `break` statement occurred at the given span, and was not
|
||||
/// caught by a `loop` statement up to this point.
|
||||
Break(Range<usize>),
|
||||
|
||||
/// A `hopback` statement occurred at the given span, and was not
|
||||
/// caught by a `loop` statement up to this point.
|
||||
Hopback(Range<usize>),
|
||||
}
|
||||
|
||||
/// The number of bits the `read` statement reads at once from
|
||||
/// standard input.
|
||||
pub const READ_BITS: u8 = 3;
|
||||
|
||||
impl ExecEnv {
|
||||
/// Create a new Scope with no predefined variable definitions or
|
||||
/// other information.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Create a new Scope with predefined variables
|
||||
pub fn new_with_vars<I>(vars: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = (String, Variable)>,
|
||||
{
|
||||
let scope = Scope {
|
||||
variables: ablescript_consts().into_iter().chain(vars).collect(),
|
||||
};
|
||||
|
||||
Self {
|
||||
stack: vec![scope],
|
||||
read_buf: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a set of Statements in the root stack frame. Return an
|
||||
/// error if one or more of the Stmts failed to evaluate, or if a
|
||||
/// `break` or `hopback` statement occurred at the top level.
|
||||
pub fn eval_stmts(&mut self, stmts: &[Spanned<Stmt>]) -> Result<(), Error> {
|
||||
match self.eval_stmts_hs(stmts, false)? {
|
||||
HaltStatus::Finished => Ok(()),
|
||||
HaltStatus::Break(span) | HaltStatus::Hopback(span) => Err(Error {
|
||||
// It's an error to issue a `break` outside of a
|
||||
// `loop` statement.
|
||||
kind: ErrorKind::TopLevelBreak,
|
||||
span,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// The same as `eval_stmts`, but report "break" and "hopback"
|
||||
/// exit codes as normal conditions in a HaltStatus enum, and
|
||||
/// create a new stack frame if `stackframe` is true.
|
||||
///
|
||||
/// `interpret`-internal code should typically prefer this
|
||||
/// function over `eval_stmts`.
|
||||
fn eval_stmts_hs(
|
||||
&mut self,
|
||||
stmts: &[Spanned<Stmt>],
|
||||
stackframe: bool,
|
||||
) -> Result<HaltStatus, Error> {
|
||||
let init_depth = self.stack.len();
|
||||
|
||||
if stackframe {
|
||||
self.stack.push(Default::default());
|
||||
}
|
||||
|
||||
let mut final_result = Ok(HaltStatus::Finished);
|
||||
for stmt in stmts {
|
||||
final_result = self.eval_stmt(stmt);
|
||||
if !matches!(final_result, Ok(HaltStatus::Finished)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if stackframe {
|
||||
self.stack.pop();
|
||||
}
|
||||
|
||||
// Invariant: stack size must have net 0 change.
|
||||
debug_assert_eq!(self.stack.len(), init_depth);
|
||||
final_result
|
||||
}
|
||||
|
||||
/// Evaluate a functio body, returning its functio.
|
||||
fn eval_functio_body(&self, body: &Spanned<FunctioBody>) -> Functio {
|
||||
Functio::Able {
|
||||
params: body
|
||||
.item
|
||||
.params
|
||||
.iter()
|
||||
.map(|ident| ident.item.to_owned())
|
||||
.collect(),
|
||||
body: body.item.body.to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate a BF functio body, returning its functio or an error.
|
||||
fn eval_bff_body(&self, body: &Spanned<BfFunctioBody>) -> Result<Functio, Error> {
|
||||
Ok(Functio::Bf {
|
||||
instructions: body.item.code.to_owned(),
|
||||
tape_len: body
|
||||
.item
|
||||
.tape_len
|
||||
.as_ref()
|
||||
.map(|tape_len| self.eval_expr(tape_len).map(|v| v.into_isize() as usize))
|
||||
.unwrap_or(Ok(crate::brian::DEFAULT_TAPE_SIZE_LIMIT))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluate an Expr, returning its value or an error.
|
||||
fn eval_expr(&self, expr: &Spanned<Expr>) -> Result<Value, Error> {
|
||||
use crate::ast::BinOpKind::*;
|
||||
use crate::ast::Expr::*;
|
||||
|
||||
Ok(match &expr.item {
|
||||
BinOp { lhs, rhs, kind } => {
|
||||
let lhs = self.eval_expr(lhs)?;
|
||||
let rhs = self.eval_expr(rhs)?;
|
||||
match kind {
|
||||
Add => lhs + rhs,
|
||||
Subtract => lhs - rhs,
|
||||
Multiply => lhs * rhs,
|
||||
Divide => lhs / rhs,
|
||||
Greater => Value::Abool((lhs > rhs).into()),
|
||||
Less => Value::Abool((lhs < rhs).into()),
|
||||
Equal => Value::Abool((lhs == rhs).into()),
|
||||
NotEqual => Value::Abool((lhs != rhs).into()),
|
||||
}
|
||||
}
|
||||
Aint(expr) => !self.eval_expr(expr)?,
|
||||
Literal(lit) => lit.clone().into(),
|
||||
Lambda(body) => Value::Functio(self.eval_functio_body(body)),
|
||||
BfLambda(body) => Value::Functio(self.eval_bff_body(body)?),
|
||||
Expr::Cart(members) => Value::Cart(
|
||||
members
|
||||
.iter()
|
||||
.map(|(value, key)| {
|
||||
self.eval_expr(value).and_then(|value| {
|
||||
self.eval_expr(key).map(|key| (key, ValueRef::new(value)))
|
||||
})
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>, _>>()?,
|
||||
),
|
||||
Index { expr, index } => {
|
||||
let value = self.eval_expr(expr)?;
|
||||
let index = self.eval_expr(index)?;
|
||||
|
||||
value
|
||||
.into_cart()
|
||||
.get(&index)
|
||||
.map(|x| x.borrow().clone())
|
||||
.unwrap_or(Value::Nul)
|
||||
}
|
||||
Len(expr) => Value::Int(self.eval_expr(expr)?.length()),
|
||||
|
||||
// TODO: not too happy with constructing an artificial
|
||||
// Ident here.
|
||||
Variable(name) => self.get_var(&Spanned::new(name.to_owned(), expr.span.clone()))?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Perform the action indicated by a statement.
|
||||
fn eval_stmt(&mut self, stmt: &Spanned<Stmt>) -> Result<HaltStatus, Error> {
|
||||
match &stmt.item {
|
||||
Stmt::Print(expr) => {
|
||||
println!("{}", self.eval_expr(expr)?);
|
||||
}
|
||||
Stmt::Dim { ident, init } => {
|
||||
let init = match init {
|
||||
Some(e) => self.eval_expr(e)?,
|
||||
None => Value::Nul,
|
||||
};
|
||||
|
||||
self.decl_var(&ident.item, init);
|
||||
}
|
||||
Stmt::Functio { ident, body } => {
|
||||
self.decl_var(&ident.item, Value::Functio(self.eval_functio_body(body)));
|
||||
}
|
||||
Stmt::BfFunctio { ident, body } => {
|
||||
self.decl_var(&ident.item, Value::Functio(self.eval_bff_body(body)?));
|
||||
}
|
||||
Stmt::Unless { cond, body } => {
|
||||
if !self.eval_expr(cond)?.into_abool().to_bool() {
|
||||
return self.eval_stmts_hs(body, true);
|
||||
}
|
||||
}
|
||||
Stmt::Call { expr, args } => {
|
||||
let func = self.eval_expr(expr)?.into_functio();
|
||||
|
||||
self.fn_call(func, args, &stmt.span)?;
|
||||
}
|
||||
Stmt::Loop { body } => loop {
|
||||
let res = self.eval_stmts_hs(body, true)?;
|
||||
match res {
|
||||
HaltStatus::Finished => {}
|
||||
HaltStatus::Break(_) => break,
|
||||
HaltStatus::Hopback(_) => continue,
|
||||
}
|
||||
},
|
||||
Stmt::Assign { assignable, value } => {
|
||||
self.assign(assignable, self.eval_expr(value)?)?;
|
||||
}
|
||||
Stmt::Break => {
|
||||
return Ok(HaltStatus::Break(stmt.span.clone()));
|
||||
}
|
||||
Stmt::HopBack => {
|
||||
return Ok(HaltStatus::Hopback(stmt.span.clone()));
|
||||
}
|
||||
Stmt::Melo(ident) => {
|
||||
self.get_var_mut(ident)?.melo = true;
|
||||
}
|
||||
Stmt::Rlyeh => {
|
||||
// Maybe print a creepy error message or something
|
||||
// here at some point. ~~Alex
|
||||
exit(random());
|
||||
}
|
||||
Stmt::Rickroll => {
|
||||
stdout()
|
||||
.write_all(include_str!("rickroll").as_bytes())
|
||||
.expect("Failed to write to stdout");
|
||||
}
|
||||
Stmt::Read(assignable) => {
|
||||
let mut value = 0;
|
||||
for _ in 0..READ_BITS {
|
||||
value <<= 1;
|
||||
value += self.get_bit()? as isize;
|
||||
}
|
||||
|
||||
self.assign(assignable, Value::Int(value))?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(HaltStatus::Finished)
|
||||
}
|
||||
|
||||
/// Assign a value to an Assignable.
|
||||
fn assign(&mut self, dest: &Assignable, value: Value) -> Result<(), Error> {
|
||||
match dest.kind {
|
||||
AssignableKind::Variable => {
|
||||
self.get_var_mut(&dest.ident)?.value.replace(value);
|
||||
}
|
||||
AssignableKind::Index { ref indices } => {
|
||||
let mut cell = self.get_var_rc(&dest.ident)?;
|
||||
for index in indices {
|
||||
let index = self.eval_expr(index)?;
|
||||
|
||||
let next_cell = match &mut *cell.borrow_mut() {
|
||||
Value::Cart(c) => {
|
||||
// cell is a cart, so we can do simple
|
||||
// indexing.
|
||||
if let Some(x) = c.get(&index) {
|
||||
// cell[index] exists, get a shared
|
||||
// reference to it.
|
||||
ValueRef::clone(x)
|
||||
} else {
|
||||
// cell[index] does not exist, so we
|
||||
// insert an empty cart by default
|
||||
// instead.
|
||||
let next_cell = ValueRef::new(Value::Cart(Default::default()));
|
||||
c.insert(index, ValueRef::clone(&next_cell));
|
||||
next_cell
|
||||
}
|
||||
}
|
||||
x => {
|
||||
// cell is not a cart; `take` it, convert
|
||||
// it into a cart, and write the result
|
||||
// back into it.
|
||||
let mut cart = take(x).into_cart();
|
||||
let next_cell = ValueRef::new(Value::Cart(Default::default()));
|
||||
cart.insert(index, ValueRef::clone(&next_cell));
|
||||
*x = Value::Cart(cart);
|
||||
next_cell
|
||||
}
|
||||
};
|
||||
cell = next_cell;
|
||||
}
|
||||
cell.replace(value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Call a function with the given arguments (i.e., actual
|
||||
/// parameters). If the function invocation fails for some reason,
|
||||
/// report the error at `span`.
|
||||
fn fn_call(
|
||||
&mut self,
|
||||
func: Functio,
|
||||
args: &[Spanned<Expr>],
|
||||
span: &Range<usize>,
|
||||
) -> Result<(), Error> {
|
||||
// Arguments that are ExprKind::Variable are pass by
|
||||
// reference; all other expressions are pass by value.
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|arg| {
|
||||
if let Expr::Variable(name) = &arg.item {
|
||||
self.get_var_rc(&Spanned::new(name.to_owned(), arg.span.clone()))
|
||||
} else {
|
||||
self.eval_expr(arg).map(ValueRef::new)
|
||||
}
|
||||
})
|
||||
.collect::<Result<Vec<_>, Error>>()?;
|
||||
|
||||
self.fn_call_with_values(func, &args, span)
|
||||
}
|
||||
|
||||
fn fn_call_with_values(
|
||||
&mut self,
|
||||
func: Functio,
|
||||
args: &[ValueRef],
|
||||
span: &Range<usize>,
|
||||
) -> Result<(), Error> {
|
||||
match func {
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
let mut input: Vec<u8> = vec![];
|
||||
for arg in args {
|
||||
arg.borrow().bf_write(&mut input);
|
||||
}
|
||||
|
||||
let mut output = vec![];
|
||||
|
||||
crate::brian::Interpreter::from_ascii_with_tape_limit(
|
||||
&instructions,
|
||||
&input as &[_],
|
||||
tape_len,
|
||||
)
|
||||
.interpret_with_output(&mut output)
|
||||
.map_err(|e| Error {
|
||||
kind: ErrorKind::BfInterpretError(e),
|
||||
span: span.to_owned(),
|
||||
})?;
|
||||
|
||||
stdout()
|
||||
.write_all(&output)
|
||||
.expect("Failed to write to stdout");
|
||||
}
|
||||
Functio::Able { params, body } => {
|
||||
if params.len() != args.len() {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::MismatchedArgumentError,
|
||||
span: span.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
self.stack.push(Default::default());
|
||||
|
||||
for (param, arg) in params.iter().zip(args.iter()) {
|
||||
self.decl_var_shared(param, arg.to_owned());
|
||||
}
|
||||
|
||||
let res = self.eval_stmts_hs(&body, false);
|
||||
|
||||
self.stack.pop();
|
||||
res?;
|
||||
}
|
||||
Functio::Builtin(b) => b.call(args).map_err(|e| Error::new(e, span.clone()))?,
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (left_functio, right_functio) = *functios;
|
||||
match kind {
|
||||
crate::variables::FunctioChainKind::Equal => {
|
||||
let (l, r) = args.split_at(args.len() / 2);
|
||||
|
||||
self.fn_call_with_values(left_functio, l, span)?;
|
||||
self.fn_call_with_values(right_functio, r, span)?;
|
||||
}
|
||||
crate::variables::FunctioChainKind::ByArity => {
|
||||
let (l, r) =
|
||||
Self::deinterlace(args, (left_functio.arity(), right_functio.arity()));
|
||||
|
||||
self.fn_call_with_values(left_functio, &l, span)?;
|
||||
self.fn_call_with_values(right_functio, &r, span)?;
|
||||
}
|
||||
};
|
||||
}
|
||||
Functio::Eval(code) => {
|
||||
if !args.is_empty() {
|
||||
return Err(Error {
|
||||
kind: ErrorKind::MismatchedArgumentError,
|
||||
span: span.to_owned(),
|
||||
});
|
||||
}
|
||||
|
||||
let stmts = crate::parser::parse(&code)?;
|
||||
self.eval_stmts(&stmts)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn deinterlace(args: &[ValueRef], arities: (usize, usize)) -> (Vec<ValueRef>, Vec<ValueRef>) {
|
||||
let n_alternations = usize::min(arities.0, arities.1);
|
||||
let (extra_l, extra_r) = match Ord::cmp(&arities.0, &arities.1) {
|
||||
Ordering::Less => (0, arities.1 - arities.0),
|
||||
Ordering::Equal => (0, 0),
|
||||
Ordering::Greater => (arities.0 - arities.1, 0),
|
||||
};
|
||||
|
||||
(
|
||||
args.chunks(2)
|
||||
.take(n_alternations)
|
||||
.map(|chunk| ValueRef::clone(&chunk[0]))
|
||||
.chain(
|
||||
args[2 * n_alternations..]
|
||||
.iter()
|
||||
.map(ValueRef::clone)
|
||||
.take(extra_l),
|
||||
)
|
||||
.collect(),
|
||||
args.chunks(2)
|
||||
.take(n_alternations)
|
||||
.map(|chunk| ValueRef::clone(&chunk[1]))
|
||||
.chain(
|
||||
args[2 * n_alternations..]
|
||||
.iter()
|
||||
.map(ValueRef::clone)
|
||||
.take(extra_r),
|
||||
)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a single bit from the bit buffer, or refill it from
|
||||
/// standard input if it is empty.
|
||||
fn get_bit(&mut self) -> Result<bool, Error> {
|
||||
const BITS_PER_BYTE: u8 = 8;
|
||||
|
||||
if self.read_buf.is_empty() {
|
||||
let mut data = [0];
|
||||
stdin().read_exact(&mut data)?;
|
||||
|
||||
for n in (0..BITS_PER_BYTE).rev() {
|
||||
self.read_buf.push_back(((data[0] >> n) & 1) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(self
|
||||
.read_buf
|
||||
.pop_front()
|
||||
.expect("We just pushed to the buffer if it was empty"))
|
||||
}
|
||||
|
||||
/// Get the value of a variable. Throw an error if the variable is
|
||||
/// inaccessible or banned.
|
||||
fn get_var(&self, name: &Spanned<String>) -> Result<Value, Error> {
|
||||
// Search for the name in the stack from top to bottom.
|
||||
match self
|
||||
.stack
|
||||
.iter()
|
||||
.rev()
|
||||
.find_map(|scope| scope.variables.get(&name.item))
|
||||
{
|
||||
Some(var) => {
|
||||
if !var.melo {
|
||||
Ok(var.value.borrow().clone())
|
||||
} else {
|
||||
Err(Error {
|
||||
kind: ErrorKind::MeloVariable(name.item.to_owned()),
|
||||
span: name.span.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error {
|
||||
kind: ErrorKind::UnknownVariable(name.item.to_owned()),
|
||||
span: name.span.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a mutable reference to a variable. Throw an error if the
|
||||
/// variable is inaccessible or banned.
|
||||
fn get_var_mut(&mut self, name: &Spanned<String>) -> Result<&mut Variable, Error> {
|
||||
// This function has a lot of duplicated code with `get_var`,
|
||||
// which I feel like is a bad sign...
|
||||
match self
|
||||
.stack
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find_map(|scope| scope.variables.get_mut(&name.item))
|
||||
{
|
||||
Some(var) => {
|
||||
if !var.melo {
|
||||
Ok(var)
|
||||
} else {
|
||||
Err(Error {
|
||||
kind: ErrorKind::MeloVariable(name.item.to_owned()),
|
||||
span: name.span.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
None => Err(Error {
|
||||
kind: ErrorKind::UnknownVariable(name.item.to_owned()),
|
||||
span: name.span.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an Rc'd pointer to the value of a variable. Throw an error
|
||||
/// if the variable is inaccessible or banned.
|
||||
fn get_var_rc(&mut self, name: &Spanned<String>) -> Result<ValueRef, Error> {
|
||||
Ok(self.get_var_mut(name)?.value.clone())
|
||||
}
|
||||
|
||||
/// Declare a new variable, with the given initial value.
|
||||
fn decl_var(&mut self, name: &str, value: Value) {
|
||||
self.decl_var_shared(name, ValueRef::new(value));
|
||||
}
|
||||
|
||||
/// Declare a new variable, with the given shared initial value.
|
||||
fn decl_var_shared(&mut self, name: &str, value: ValueRef) {
|
||||
self.stack
|
||||
.iter_mut()
|
||||
.last()
|
||||
.expect("Declaring variable on empty stack")
|
||||
.variables
|
||||
.insert(name.to_owned(), Variable { melo: false, value });
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ast::{Expr, Literal};
|
||||
|
||||
#[test]
|
||||
fn basic_expression_test() {
|
||||
// Check that 2 + 2 = 4.
|
||||
let env = ExecEnv::new();
|
||||
assert_eq!(
|
||||
env.eval_expr(&Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(2)),
|
||||
span: 1..1,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(2)),
|
||||
span: 1..1,
|
||||
}),
|
||||
kind: crate::ast::BinOpKind::Add,
|
||||
},
|
||||
span: 1..1
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(4)
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_coercions() {
|
||||
// The sum of an integer and an aboolean causes an aboolean
|
||||
// coercion.
|
||||
|
||||
let env = ExecEnv::new();
|
||||
assert_eq!(
|
||||
env.eval_expr(&Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(2)),
|
||||
span: 1..1,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Variable("always".to_owned()),
|
||||
span: 1..1,
|
||||
}),
|
||||
kind: crate::ast::BinOpKind::Add,
|
||||
},
|
||||
span: 1..1
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn overflow_should_not_panic() {
|
||||
// Integer overflow should throw a recoverable error instead
|
||||
// of panicking.
|
||||
let env = ExecEnv::new();
|
||||
assert_eq!(
|
||||
env.eval_expr(&Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(isize::MAX)),
|
||||
span: 1..1,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(1)),
|
||||
span: 1..1,
|
||||
}),
|
||||
kind: crate::ast::BinOpKind::Add,
|
||||
},
|
||||
span: 1..1
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(-9223372036854775808)
|
||||
);
|
||||
|
||||
// And the same for divide by zero.
|
||||
assert_eq!(
|
||||
env.eval_expr(&Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(84)),
|
||||
span: 1..1,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(0)),
|
||||
span: 1..1,
|
||||
}),
|
||||
kind: crate::ast::BinOpKind::Divide,
|
||||
},
|
||||
span: 1..1
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(2)
|
||||
);
|
||||
}
|
||||
|
||||
// From here on out, I'll use this function to parse and run
|
||||
// expressions, because writing out abstract syntax trees by hand
|
||||
// takes forever and is error-prone.
|
||||
fn eval(env: &mut ExecEnv, src: &str) -> Result<Value, Error> {
|
||||
// We can assume there won't be any syntax errors in the
|
||||
// interpreter tests.
|
||||
let ast = crate::parser::parse(src).unwrap();
|
||||
env.eval_stmts(&ast).map(|()| Value::Nul)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_decl_and_assignment() {
|
||||
// Functions have no return values, so use some
|
||||
// pass-by-reference hacks to detect the correct
|
||||
// functionality.
|
||||
let mut env = ExecEnv::new();
|
||||
|
||||
// Declaring and reading from a variable.
|
||||
eval(&mut env, "dim foo 32; dim bar foo + 1;").unwrap();
|
||||
assert_eq!(
|
||||
env.get_var(&Spanned {
|
||||
item: "bar".to_owned(),
|
||||
span: 1..1,
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(33)
|
||||
);
|
||||
|
||||
// Assigning an existing variable.
|
||||
eval(&mut env, "/*hi*/ =: foo;").unwrap();
|
||||
assert_eq!(
|
||||
env.get_var(&Spanned {
|
||||
item: "foo".to_owned(),
|
||||
span: 1..1,
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Str("hi".to_owned())
|
||||
);
|
||||
|
||||
// But variable assignment should be illegal when the variable
|
||||
// hasn't been declared in advance.
|
||||
eval(&mut env, "bar + 1 =: invalid;").unwrap_err();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scope_visibility_rules() {
|
||||
// Declaration and assignment of variables declared in an `if`
|
||||
// statement should have no effect on those declared outside
|
||||
// of it.
|
||||
let mut env = ExecEnv::new();
|
||||
eval(
|
||||
&mut env,
|
||||
"dim foo 1; 2 =: foo; unless (never) { dim foo 3; 4 =: foo; }",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
env.get_var(&Spanned {
|
||||
item: "foo".to_owned(),
|
||||
span: 1..1,
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambdas() {
|
||||
let mut env = ExecEnv::new();
|
||||
|
||||
// Lambda call from a variable.
|
||||
eval(
|
||||
&mut env,
|
||||
r#"
|
||||
dim foo 1;
|
||||
dim to2 λ (var) { 2 =: var; };
|
||||
to2(foo);
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
env.get_var(&Spanned {
|
||||
item: "foo".to_owned(),
|
||||
span: 1..1,
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(2),
|
||||
);
|
||||
|
||||
// Inline lambda call.
|
||||
eval(&mut env, "(λ (var) { 3 =: var; })(foo);").unwrap();
|
||||
assert_eq!(
|
||||
env.get_var(&Spanned {
|
||||
item: "foo".to_owned(),
|
||||
span: 1..1,
|
||||
})
|
||||
.unwrap(),
|
||||
Value::Int(3),
|
||||
);
|
||||
}
|
||||
}
|
194
ablescript/src/lexer.rs
Normal file
194
ablescript/src/lexer.rs
Normal file
|
@ -0,0 +1,194 @@
|
|||
use logos::{Lexer, Logos};
|
||||
|
||||
#[derive(Logos, Debug, PartialEq, Clone)]
|
||||
pub enum Token {
|
||||
// Symbols
|
||||
#[token("(")]
|
||||
LeftParen,
|
||||
|
||||
#[token(")")]
|
||||
RightParen,
|
||||
|
||||
#[token("[")]
|
||||
LeftBracket,
|
||||
|
||||
#[token("]")]
|
||||
RightBracket,
|
||||
|
||||
#[token("{")]
|
||||
LeftCurly,
|
||||
|
||||
#[token("}")]
|
||||
RightCurly,
|
||||
|
||||
#[token(";")]
|
||||
Semicolon,
|
||||
|
||||
#[token(",")]
|
||||
Comma,
|
||||
|
||||
// Operators
|
||||
#[token("+")]
|
||||
Plus,
|
||||
|
||||
#[token("-")]
|
||||
Minus,
|
||||
|
||||
#[token("*")]
|
||||
Star,
|
||||
|
||||
#[token("/")]
|
||||
FwdSlash,
|
||||
|
||||
#[token("=:")]
|
||||
Assign,
|
||||
|
||||
#[token("<=")]
|
||||
Arrow,
|
||||
|
||||
// Logical operators
|
||||
#[token("<")]
|
||||
LessThan,
|
||||
|
||||
#[token(">")]
|
||||
GreaterThan,
|
||||
|
||||
#[token("=")]
|
||||
Equals,
|
||||
|
||||
#[token("ain't")]
|
||||
Aint,
|
||||
|
||||
// Keywords
|
||||
#[token("functio")]
|
||||
Functio,
|
||||
|
||||
/// Brain fuck FFI
|
||||
#[token("bff")]
|
||||
Bff,
|
||||
|
||||
/// Lambda
|
||||
#[token("λ")]
|
||||
Lambda,
|
||||
|
||||
/// BFF but lambda
|
||||
#[token("bfλ")]
|
||||
BfLambda,
|
||||
|
||||
/// Variable bro
|
||||
#[token("dim")]
|
||||
Dim,
|
||||
|
||||
/// Prints the preceding things
|
||||
#[token("print")]
|
||||
Print,
|
||||
|
||||
/// Read input into preceding variable
|
||||
#[token("read")]
|
||||
Read,
|
||||
|
||||
/// Ban the following variable from ever being used again
|
||||
#[token("melo")]
|
||||
Melo,
|
||||
|
||||
#[token("T-Dark")]
|
||||
TDark,
|
||||
|
||||
// Control flow keywords
|
||||
#[token("unless")]
|
||||
Unless,
|
||||
|
||||
#[token("loop")]
|
||||
Loop,
|
||||
|
||||
#[token("break")]
|
||||
Break,
|
||||
|
||||
/// HopBack hops on the back of loop - like `continue`
|
||||
#[token("hopback")]
|
||||
HopBack,
|
||||
|
||||
/// Crash with random error (see discussion #17)
|
||||
#[token("rlyeh")]
|
||||
Rlyeh,
|
||||
|
||||
#[token("rickroll")]
|
||||
Rickroll,
|
||||
|
||||
// Literals
|
||||
/// String
|
||||
#[token("/*", get_string)]
|
||||
String(String),
|
||||
|
||||
/// Integer
|
||||
#[regex(r"-?[0-9]+", get_value)]
|
||||
Integer(isize),
|
||||
|
||||
// A character (to be base-55 converted)
|
||||
#[regex(r"\p{XID_Start}", get_value)]
|
||||
Char(char),
|
||||
|
||||
/// An identifier
|
||||
#[regex(r"\p{XID_Start}[\p{XID_Continue}]+", get_ident)]
|
||||
Identifier(String),
|
||||
|
||||
#[regex(r"owo .*")]
|
||||
Comment,
|
||||
|
||||
#[regex(r"[ \t\n\f]+", logos::skip)]
|
||||
#[error]
|
||||
Error,
|
||||
}
|
||||
|
||||
fn get_value<T: std::str::FromStr>(lexer: &mut Lexer<Token>) -> Option<T> {
|
||||
lexer.slice().parse().ok()
|
||||
}
|
||||
|
||||
fn get_string(lexer: &mut Lexer<Token>) -> Option<String> {
|
||||
lexer.bump(lexer.remainder().find("*/")?);
|
||||
let string = lexer.slice()[2..].to_owned();
|
||||
lexer.bump(2);
|
||||
|
||||
Some(string)
|
||||
}
|
||||
|
||||
fn get_ident(lexer: &mut Lexer<Token>) -> String {
|
||||
lexer.slice().to_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Token;
|
||||
use super::Token::*;
|
||||
use logos::Logos;
|
||||
|
||||
#[test]
|
||||
fn simple_fn() {
|
||||
let code = "functio test() { dim var 3; unless (var ain't 3) { var print } }";
|
||||
let expected = &[
|
||||
Functio,
|
||||
Identifier("test".to_owned()),
|
||||
LeftParen,
|
||||
RightParen,
|
||||
LeftCurly,
|
||||
Dim,
|
||||
Identifier("var".to_owned()),
|
||||
Integer(3),
|
||||
Semicolon,
|
||||
Unless,
|
||||
LeftParen,
|
||||
Identifier("var".to_owned()),
|
||||
Aint,
|
||||
Integer(3),
|
||||
RightParen,
|
||||
LeftCurly,
|
||||
Identifier("var".to_owned()),
|
||||
Print,
|
||||
RightCurly,
|
||||
RightCurly,
|
||||
];
|
||||
let lexer = Token::lexer(code);
|
||||
let result: Vec<Token> = lexer.collect();
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
13
ablescript/src/lib.rs
Normal file
13
ablescript/src/lib.rs
Normal file
|
@ -0,0 +1,13 @@
|
|||
#![doc = include_str!("../../README.md")]
|
||||
#![forbid(unsafe_code, clippy::unwrap_used)]
|
||||
|
||||
pub mod ast;
|
||||
pub mod error;
|
||||
pub mod interpret;
|
||||
pub mod parser;
|
||||
pub mod variables;
|
||||
|
||||
mod base_55;
|
||||
mod brian;
|
||||
mod consts;
|
||||
mod lexer;
|
897
ablescript/src/parser.rs
Normal file
897
ablescript/src/parser.rs
Normal file
|
@ -0,0 +1,897 @@
|
|||
//! AbleScript Parser
|
||||
//!
|
||||
//! Type of this parser is recursive descent
|
||||
|
||||
use crate::ast::*;
|
||||
use crate::error::{Error, ErrorKind};
|
||||
use crate::lexer::Token;
|
||||
use logos::{Lexer, Logos};
|
||||
|
||||
/// Parser structure which holds lexer and metadata
|
||||
///
|
||||
/// Make one using [`Parser::new`] function
|
||||
struct Parser<'source> {
|
||||
lexer: Lexer<'source, Token>,
|
||||
tdark: bool,
|
||||
}
|
||||
|
||||
impl<'source> Parser<'source> {
|
||||
/// Create a new parser from source code
|
||||
fn new(source: &'source str) -> Self {
|
||||
Self {
|
||||
lexer: Token::lexer(source),
|
||||
tdark: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Start parsing tokens
|
||||
///
|
||||
/// Loops trough lexer, parses statements, returns AST
|
||||
fn parse(&mut self) -> Result<Block, Error> {
|
||||
let mut ast = vec![];
|
||||
while let Some(token) = self.lexer.next() {
|
||||
match token {
|
||||
// Ignore comments
|
||||
Token::Comment => continue,
|
||||
|
||||
// T-Dark block (replace `lang` with `script`)
|
||||
Token::TDark => ast.extend(self.tdark_flow()?),
|
||||
token => ast.push(self.parse_stmt(token)?),
|
||||
}
|
||||
}
|
||||
Ok(ast)
|
||||
}
|
||||
|
||||
/// Get next item
|
||||
///
|
||||
/// If EOF, return Error instead of None
|
||||
fn checked_next(&mut self) -> Result<Token, Error> {
|
||||
loop {
|
||||
match self
|
||||
.lexer
|
||||
.next()
|
||||
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?
|
||||
{
|
||||
Token::Comment => (),
|
||||
token => break Ok(token),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a token
|
||||
///
|
||||
/// This function will route to corresponding flow functions
|
||||
/// which may advance the lexer iterator
|
||||
fn parse_stmt(&mut self, token: Token) -> Result<Spanned<Stmt>, Error> {
|
||||
let start = self.lexer.span().start;
|
||||
|
||||
match token {
|
||||
Token::Unless => Ok(Spanned::new(
|
||||
self.unless_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Functio => Ok(Spanned::new(
|
||||
self.functio_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Bff => Ok(Spanned::new(self.bff_flow()?, start..self.lexer.span().end)),
|
||||
Token::Dim => Ok(Spanned::new(self.dim_flow()?, start..self.lexer.span().end)),
|
||||
Token::Melo => Ok(Spanned::new(
|
||||
self.melo_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Loop => Ok(Spanned::new(
|
||||
self.loop_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Break => Ok(Spanned::new(
|
||||
self.semicolon_terminated(Stmt::Break)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::HopBack => Ok(Spanned::new(
|
||||
self.semicolon_terminated(Stmt::HopBack)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Rlyeh => Ok(Spanned::new(
|
||||
self.semicolon_terminated(Stmt::Rlyeh)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Rickroll => Ok(Spanned::new(
|
||||
self.semicolon_terminated(Stmt::Rickroll)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
Token::Identifier(_)
|
||||
| Token::String(_)
|
||||
| Token::Integer(_)
|
||||
| Token::Char(_)
|
||||
| Token::Aint
|
||||
| Token::LeftBracket
|
||||
| Token::LeftParen => Ok(Spanned::new(
|
||||
self.value_flow(token)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
t => Err(Error {
|
||||
kind: ErrorKind::UnexpectedToken(t),
|
||||
span: start..self.lexer.span().end,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Require statement to be semicolon terminated
|
||||
///
|
||||
/// Utility function for short statements
|
||||
fn semicolon_terminated(&mut self, stmt_kind: Stmt) -> Result<Stmt, Error> {
|
||||
self.require(Token::Semicolon)?;
|
||||
Ok(stmt_kind)
|
||||
}
|
||||
|
||||
/// Require next item to be equal with expected one
|
||||
fn require(&mut self, required: Token) -> Result<(), Error> {
|
||||
match self.checked_next()? {
|
||||
t if t == required => Ok(()),
|
||||
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get an Identifier
|
||||
fn get_ident(&mut self) -> Result<Spanned<String>, Error> {
|
||||
match self.checked_next()? {
|
||||
Token::Identifier(ident) => Ok(Spanned::new(
|
||||
if self.tdark {
|
||||
ident.replace("lang", "script")
|
||||
} else {
|
||||
ident
|
||||
},
|
||||
self.lexer.span(),
|
||||
)),
|
||||
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse an expression
|
||||
///
|
||||
/// AbleScript strongly separates expressions from statements.
|
||||
/// Expressions do not have any side effects and the are
|
||||
/// only mathematial and logical operations or values.
|
||||
fn parse_expr(
|
||||
&mut self,
|
||||
token: Token,
|
||||
buf: &mut Option<Spanned<Expr>>,
|
||||
) -> Result<Spanned<Expr>, Error> {
|
||||
let start = match buf {
|
||||
Some(e) => e.span.start,
|
||||
None => self.lexer.span().start,
|
||||
};
|
||||
|
||||
match token {
|
||||
// Values
|
||||
Token::Identifier(i) => Ok(Spanned::new(
|
||||
Expr::Variable(if self.tdark {
|
||||
i.replace("lang", "script")
|
||||
} else {
|
||||
i
|
||||
}),
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Integer(i) => Ok(Spanned::new(
|
||||
Expr::Literal(Literal::Int(i)),
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::String(s) => Ok(Spanned::new(
|
||||
Expr::Literal(Literal::Str(if self.tdark {
|
||||
s.replace("lang", "script")
|
||||
} else {
|
||||
s
|
||||
})),
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::Char(c) => Ok(Spanned::new(
|
||||
Expr::Literal(Literal::Char(c)),
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
Token::LeftBracket => match buf.take() {
|
||||
Some(buf) => Ok(Spanned::new(
|
||||
self.index_flow(buf)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
None => Ok(Spanned::new(
|
||||
self.cart_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
},
|
||||
|
||||
// Lambdas
|
||||
Token::Lambda => Ok(Spanned::new(
|
||||
self.lambda_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
Token::BfLambda => Ok(Spanned::new(
|
||||
self.bf_lambda_flow()?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
// Operations
|
||||
Token::Aint if buf.is_none() => Ok(Spanned::new(
|
||||
{
|
||||
let next = self.checked_next()?;
|
||||
Expr::Aint(Box::new(self.parse_expr(next, buf)?))
|
||||
},
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
Token::Plus
|
||||
| Token::Minus
|
||||
| Token::Star
|
||||
| Token::FwdSlash
|
||||
| Token::Equals
|
||||
| Token::LessThan
|
||||
| Token::GreaterThan
|
||||
| Token::Aint => Ok(Spanned::new(
|
||||
self.binop_flow(
|
||||
BinOpKind::from_token(token).map_err(|e| Error::new(e, self.lexer.span()))?,
|
||||
buf,
|
||||
)?,
|
||||
start..self.lexer.span().end,
|
||||
)),
|
||||
|
||||
Token::LeftParen => self.expr_flow(Token::RightParen),
|
||||
t => Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Flow for creating carts
|
||||
fn cart_flow(&mut self) -> Result<Expr, Error> {
|
||||
let mut cart = vec![];
|
||||
let mut buf = None;
|
||||
|
||||
match self.checked_next()? {
|
||||
Token::RightBracket => (),
|
||||
t => {
|
||||
buf = Some(self.parse_expr(t, &mut buf)?);
|
||||
'cart: loop {
|
||||
let value = loop {
|
||||
match self.checked_next()? {
|
||||
Token::Arrow => break buf.take(),
|
||||
t => buf = Some(self.parse_expr(t, &mut buf)?),
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| {
|
||||
Error::new(ErrorKind::UnexpectedToken(Token::Arrow), self.lexer.span())
|
||||
})?;
|
||||
|
||||
let key = loop {
|
||||
match self.checked_next()? {
|
||||
Token::RightBracket => {
|
||||
cart.push((
|
||||
value,
|
||||
buf.take().ok_or_else(|| {
|
||||
Error::unexpected_eof(self.lexer.span().start)
|
||||
})?,
|
||||
));
|
||||
|
||||
break 'cart;
|
||||
}
|
||||
Token::Comma => break buf.take(),
|
||||
t => buf = Some(self.parse_expr(t, &mut buf)?),
|
||||
}
|
||||
}
|
||||
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?;
|
||||
|
||||
cart.push((value, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Expr::Cart(cart))
|
||||
}
|
||||
|
||||
/// Flow for indexing operations
|
||||
///
|
||||
/// Indexing with empty index resolves to length of expression, else it indexes
|
||||
fn index_flow(&mut self, expr: Spanned<Expr>) -> Result<Expr, Error> {
|
||||
let mut buf = None;
|
||||
Ok(loop {
|
||||
match self.checked_next()? {
|
||||
Token::RightBracket => match buf {
|
||||
Some(index) => {
|
||||
break Expr::Index {
|
||||
expr: Box::new(expr),
|
||||
index: Box::new(index),
|
||||
}
|
||||
}
|
||||
None => break Expr::Len(Box::new(expr)),
|
||||
},
|
||||
token => buf = Some(self.parse_expr(token, &mut buf)?),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Flow for operators
|
||||
///
|
||||
/// Generates operation from LHS buffer and next expression as RHS
|
||||
///
|
||||
/// This is unaware of precedence, as AbleScript do not have it
|
||||
fn binop_flow(
|
||||
&mut self,
|
||||
kind: BinOpKind,
|
||||
lhs: &mut Option<Spanned<Expr>>,
|
||||
) -> Result<Expr, Error> {
|
||||
Ok(Expr::BinOp {
|
||||
lhs: Box::new(
|
||||
lhs.take()
|
||||
.ok_or_else(|| Error::new(ErrorKind::MissingLhs, self.lexer.span()))?,
|
||||
),
|
||||
rhs: {
|
||||
let next = self
|
||||
.lexer
|
||||
.next()
|
||||
.ok_or_else(|| Error::unexpected_eof(self.lexer.span().start))?;
|
||||
Box::new(self.parse_expr(next, &mut None)?)
|
||||
},
|
||||
kind,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse expressions until terminate token
|
||||
fn expr_flow(&mut self, terminate: Token) -> Result<Spanned<Expr>, Error> {
|
||||
let mut buf = None;
|
||||
Ok(loop {
|
||||
match self.checked_next()? {
|
||||
t if t == terminate => {
|
||||
break buf.take().ok_or_else(|| {
|
||||
Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())
|
||||
})?
|
||||
}
|
||||
t => buf = Some(self.parse_expr(t, &mut buf)?),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a list of statements between curly braces
|
||||
fn get_block(&mut self) -> Result<Block, Error> {
|
||||
self.require(Token::LeftCurly)?;
|
||||
let mut block = vec![];
|
||||
|
||||
loop {
|
||||
match self.checked_next()? {
|
||||
Token::RightCurly => break,
|
||||
Token::TDark => block.extend(self.tdark_flow()?),
|
||||
t => block.push(self.parse_stmt(t)?),
|
||||
}
|
||||
}
|
||||
Ok(block)
|
||||
}
|
||||
|
||||
/// Parse T-Dark block
|
||||
fn tdark_flow(&mut self) -> Result<Block, Error> {
|
||||
self.tdark = true;
|
||||
let block = self.get_block();
|
||||
self.tdark = false;
|
||||
block
|
||||
}
|
||||
|
||||
/// If Statement parser gets any kind of value (Identifier or Literal)
|
||||
/// It cannot parse it as it do not parse expressions. Instead of it it
|
||||
/// will parse it to function call or print statement.
|
||||
fn value_flow(&mut self, init: Token) -> Result<Stmt, Error> {
|
||||
let mut buf = Some(self.parse_expr(init, &mut None)?);
|
||||
let r = loop {
|
||||
match self.checked_next()? {
|
||||
// Print to stdout
|
||||
Token::Print => {
|
||||
let stmt = Stmt::Print(buf.take().ok_or_else(|| {
|
||||
Error::new(ErrorKind::UnexpectedToken(Token::Print), self.lexer.span())
|
||||
})?);
|
||||
break self.semicolon_terminated(stmt)?;
|
||||
}
|
||||
|
||||
// Functio call
|
||||
Token::LeftParen => {
|
||||
break self.functio_call_flow(buf.take().ok_or_else(|| {
|
||||
Error::new(
|
||||
ErrorKind::UnexpectedToken(Token::LeftParen),
|
||||
self.lexer.span(),
|
||||
)
|
||||
})?)?;
|
||||
}
|
||||
|
||||
// Variable Assignment
|
||||
Token::Assign => {
|
||||
return match buf.take() {
|
||||
Some(expr) => self.assignment_flow(expr),
|
||||
None => Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(Token::Assign),
|
||||
self.lexer.span(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
// Read input
|
||||
Token::Read => {
|
||||
if let Some(Ok(assignable)) = buf.take().map(Assignable::from_expr) {
|
||||
self.require(Token::Semicolon)?;
|
||||
break Stmt::Read(assignable);
|
||||
} else {
|
||||
return Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(Token::Read),
|
||||
self.lexer.span(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
t => buf = Some(self.parse_expr(t, &mut buf)?),
|
||||
}
|
||||
};
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
/// Parse Unless flow
|
||||
///
|
||||
/// Consists of condition and block, there is no else
|
||||
fn unless_flow(&mut self) -> Result<Stmt, Error> {
|
||||
self.require(Token::LeftParen)?;
|
||||
Ok(Stmt::Unless {
|
||||
cond: self.expr_flow(Token::RightParen)?,
|
||||
body: self.get_block()?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse functio body
|
||||
///
|
||||
/// `(a, b, c) { ... }`
|
||||
fn get_functio_body(&mut self) -> Result<Spanned<FunctioBody>, Error> {
|
||||
self.require(Token::LeftParen)?;
|
||||
let start = self.lexer.span().start;
|
||||
|
||||
let mut params = vec![];
|
||||
loop {
|
||||
match self.checked_next()? {
|
||||
Token::RightParen => break,
|
||||
Token::Identifier(i) => {
|
||||
params.push(Spanned::new(i, self.lexer.span()));
|
||||
|
||||
// Require comma (next) or right paren (end) after identifier
|
||||
match self.checked_next()? {
|
||||
Token::Comma => continue,
|
||||
Token::RightParen => break,
|
||||
t => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(t),
|
||||
self.lexer.span(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
t => return Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
|
||||
}
|
||||
}
|
||||
|
||||
let body = self.get_block()?;
|
||||
|
||||
Ok(Spanned::new(
|
||||
FunctioBody { params, body },
|
||||
start..self.lexer.span().end,
|
||||
))
|
||||
}
|
||||
|
||||
/// Parse functio flow
|
||||
///
|
||||
/// functio $ident (a, b, c) { ... }
|
||||
fn functio_flow(&mut self) -> Result<Stmt, Error> {
|
||||
let ident = self.get_ident()?;
|
||||
let body = self.get_functio_body()?;
|
||||
|
||||
Ok(Stmt::Functio { ident, body })
|
||||
}
|
||||
|
||||
/// Parse lambda flow
|
||||
///
|
||||
/// `λ (a, b, c) { ... }`
|
||||
fn lambda_flow(&mut self) -> Result<Expr, Error> {
|
||||
Ok(Expr::Lambda(self.get_functio_body()?))
|
||||
}
|
||||
|
||||
/// Parse BF function body
|
||||
///
|
||||
/// `([tapelen]) { ... }`
|
||||
fn get_bff_body(&mut self) -> Result<Spanned<BfFunctioBody>, Error> {
|
||||
let (tape_len, start) = match self.checked_next()? {
|
||||
Token::LeftParen => {
|
||||
let len = Some(self.expr_flow(Token::RightParen)?);
|
||||
let start = self.lexer.span().start;
|
||||
self.require(Token::LeftCurly)?;
|
||||
(len, start)
|
||||
}
|
||||
Token::LeftCurly => (None, self.lexer.span().start),
|
||||
token => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(token),
|
||||
self.lexer.span(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let mut code: Vec<u8> = vec![];
|
||||
loop {
|
||||
match self.checked_next()? {
|
||||
Token::Plus
|
||||
| Token::Minus
|
||||
| Token::Comma
|
||||
| Token::LeftBracket
|
||||
| Token::RightBracket
|
||||
| Token::LessThan
|
||||
| Token::GreaterThan => code.push(self.lexer.slice().as_bytes()[0]),
|
||||
Token::RightCurly => break,
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Spanned::new(
|
||||
BfFunctioBody { tape_len, code },
|
||||
start..self.lexer.span().end,
|
||||
))
|
||||
}
|
||||
|
||||
/// Parse BF function declaration
|
||||
///
|
||||
/// `bff $ident ([tapelen]) { ... }`
|
||||
fn bff_flow(&mut self) -> Result<Stmt, Error> {
|
||||
let ident = self.get_ident()?;
|
||||
let body = self.get_bff_body()?;
|
||||
|
||||
Ok(Stmt::BfFunctio { ident, body })
|
||||
}
|
||||
|
||||
/// Parse a BF lambda
|
||||
///
|
||||
/// `bfλ ([tapelen]) { ... }`
|
||||
fn bf_lambda_flow(&mut self) -> Result<Expr, Error> {
|
||||
Ok(Expr::BfLambda(Box::new(self.get_bff_body()?)))
|
||||
}
|
||||
|
||||
/// Parse functio call flow
|
||||
fn functio_call_flow(&mut self, expr: Spanned<Expr>) -> Result<Stmt, Error> {
|
||||
let mut args = vec![];
|
||||
let mut buf = None;
|
||||
loop {
|
||||
match self.checked_next()? {
|
||||
// End of argument list
|
||||
Token::RightParen => {
|
||||
if let Some(expr) = buf.take() {
|
||||
args.push(expr)
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Next argument
|
||||
Token::Comma => match buf.take() {
|
||||
Some(expr) => args.push(expr),
|
||||
// Comma alone
|
||||
None => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(Token::Comma),
|
||||
self.lexer.span(),
|
||||
))
|
||||
}
|
||||
},
|
||||
t => buf = Some(self.parse_expr(t, &mut buf)?),
|
||||
}
|
||||
}
|
||||
|
||||
self.require(Token::Semicolon)?;
|
||||
Ok(Stmt::Call { expr, args })
|
||||
}
|
||||
|
||||
/// Parse variable declaration
|
||||
fn dim_flow(&mut self) -> Result<Stmt, Error> {
|
||||
let ident = self.get_ident()?;
|
||||
let mut init = None;
|
||||
loop {
|
||||
match self.checked_next()? {
|
||||
Token::Semicolon => break,
|
||||
t => init = Some(self.parse_expr(t, &mut init)?),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Stmt::Dim { ident, init })
|
||||
}
|
||||
|
||||
/// Parse assignment to assignable
|
||||
fn assignment_flow(&mut self, value: Spanned<Expr>) -> Result<Stmt, Error> {
|
||||
let ident = self.get_ident()?;
|
||||
let kind = match self.checked_next()? {
|
||||
Token::Semicolon => AssignableKind::Variable,
|
||||
Token::LeftBracket => {
|
||||
let mut indices = vec![];
|
||||
loop {
|
||||
indices.push(self.expr_flow(Token::RightBracket)?);
|
||||
match self.checked_next()? {
|
||||
Token::Semicolon => break AssignableKind::Index { indices },
|
||||
Token::LeftBracket => (),
|
||||
t => {
|
||||
return Err(Error::new(
|
||||
ErrorKind::UnexpectedToken(t),
|
||||
self.lexer.span(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
t => return Err(Error::new(ErrorKind::UnexpectedToken(t), self.lexer.span())),
|
||||
};
|
||||
|
||||
Ok(Stmt::Assign {
|
||||
assignable: Assignable { ident, kind },
|
||||
value,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse Melo flow
|
||||
fn melo_flow(&mut self) -> Result<Stmt, Error> {
|
||||
let ident = self.get_ident()?;
|
||||
self.semicolon_terminated(Stmt::Melo(ident))
|
||||
}
|
||||
|
||||
/// Parse loop flow
|
||||
///
|
||||
/// `loop` is an infinite loop, no condition, only body
|
||||
fn loop_flow(&mut self) -> Result<Stmt, Error> {
|
||||
Ok(Stmt::Loop {
|
||||
body: self.get_block()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse AbleScript code into AST
|
||||
pub fn parse(source: &str) -> Result<Block, Error> {
|
||||
Parser::new(source).parse()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn simple_math() {
|
||||
let code = "1 * (num + 3) / 666 print;";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Print(Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(1)),
|
||||
span: 0..1,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Variable("num".to_owned()),
|
||||
span: 5..6,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(3)),
|
||||
span: 9..10,
|
||||
}),
|
||||
kind: BinOpKind::Add,
|
||||
},
|
||||
span: 5..10,
|
||||
}),
|
||||
kind: BinOpKind::Multiply,
|
||||
},
|
||||
span: 0..11,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(666)),
|
||||
span: 14..17,
|
||||
}),
|
||||
kind: BinOpKind::Divide,
|
||||
},
|
||||
span: 0..17,
|
||||
}),
|
||||
span: 0..24,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn variable_declaration() {
|
||||
let code = "dim var 42;";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Dim {
|
||||
ident: Spanned {
|
||||
item: "var".to_owned(),
|
||||
span: 4..5,
|
||||
},
|
||||
init: Some(Spanned {
|
||||
item: Expr::Literal(Literal::Int(42)),
|
||||
span: 8..10,
|
||||
}),
|
||||
},
|
||||
span: 0..11,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unless_flow() {
|
||||
let code = "unless (never + never) { /*Buy Able products!*/ print; }";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Unless {
|
||||
cond: Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Variable("never".to_owned()),
|
||||
span: 8..13,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Variable("never".to_owned()),
|
||||
span: 16..21,
|
||||
}),
|
||||
kind: BinOpKind::Add,
|
||||
},
|
||||
span: 8..21,
|
||||
},
|
||||
body: vec![Spanned {
|
||||
item: Stmt::Print(Spanned {
|
||||
item: Expr::Literal(Literal::Str("Buy Able products!".to_owned())),
|
||||
span: 25..47,
|
||||
}),
|
||||
span: 25..54,
|
||||
}],
|
||||
},
|
||||
span: 0..56,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lambda() {
|
||||
let code = "(λ () { /*ablelambda*/ print; })();";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Call {
|
||||
expr: Spanned {
|
||||
item: Expr::Lambda(Spanned {
|
||||
item: FunctioBody {
|
||||
params: Vec::new(),
|
||||
body: vec![Spanned {
|
||||
item: Stmt::Print(Spanned {
|
||||
item: Expr::Literal(Literal::Str("ablelambda".to_owned())),
|
||||
span: 9..23,
|
||||
}),
|
||||
span: 9..30,
|
||||
}],
|
||||
},
|
||||
span: 4..32,
|
||||
}),
|
||||
span: 1..32,
|
||||
},
|
||||
args: Vec::new(),
|
||||
},
|
||||
span: 0..36,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tdark() {
|
||||
let code = "T-Dark { dim lang /*lang*/ + lang; }";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Dim {
|
||||
ident: Spanned {
|
||||
item: "script".to_owned(),
|
||||
span: 13..17,
|
||||
},
|
||||
init: Some(Spanned {
|
||||
item: Expr::BinOp {
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Str("script".to_owned())),
|
||||
span: 20..26,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Variable("script".to_owned()),
|
||||
span: 29..33,
|
||||
}),
|
||||
kind: BinOpKind::Add,
|
||||
},
|
||||
span: 20..33,
|
||||
}),
|
||||
},
|
||||
span: 9..34,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cart_construction() {
|
||||
let code = "[/*able*/ <= 1, /*script*/ <= 3 - 1] print;";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Print(Spanned {
|
||||
item: Expr::Cart(vec![
|
||||
(
|
||||
Spanned {
|
||||
item: Expr::Literal(Literal::Str("able".to_owned())),
|
||||
span: 1..7,
|
||||
},
|
||||
Spanned {
|
||||
item: Expr::Literal(Literal::Int(1)),
|
||||
span: 11..12,
|
||||
},
|
||||
),
|
||||
(
|
||||
Spanned {
|
||||
item: Expr::Literal(Literal::Str("script".to_owned())),
|
||||
span: 14..22,
|
||||
},
|
||||
Spanned {
|
||||
item: Expr::BinOp {
|
||||
kind: BinOpKind::Subtract,
|
||||
lhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(3)),
|
||||
span: 26..27,
|
||||
}),
|
||||
rhs: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Int(1)),
|
||||
span: 30..31,
|
||||
}),
|
||||
},
|
||||
span: 26..31,
|
||||
},
|
||||
),
|
||||
]),
|
||||
span: 0..32,
|
||||
}),
|
||||
span: 0..39,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cart_index() {
|
||||
let code = "[/*able*/ <= /*ablecorp*/][/*ablecorp*/] print;";
|
||||
let expected = &[Spanned {
|
||||
item: Stmt::Print(Spanned {
|
||||
item: Expr::Index {
|
||||
expr: Box::new(Spanned {
|
||||
item: Expr::Cart(vec![(
|
||||
Spanned {
|
||||
item: Expr::Literal(Literal::Str("able".to_owned())),
|
||||
span: 1..7,
|
||||
},
|
||||
Spanned {
|
||||
item: Expr::Literal(Literal::Str("ablecorp".to_owned())),
|
||||
span: 11..21,
|
||||
},
|
||||
)]),
|
||||
span: 0..22,
|
||||
}),
|
||||
index: Box::new(Spanned {
|
||||
item: Expr::Literal(Literal::Str("ablecorp".to_owned())),
|
||||
span: 23..33,
|
||||
}),
|
||||
},
|
||||
span: 0..34,
|
||||
}),
|
||||
span: 0..41,
|
||||
}];
|
||||
|
||||
let ast = Parser::new(code).parse().unwrap();
|
||||
assert_eq!(ast, expected);
|
||||
}
|
||||
}
|
67
ablescript/src/rickroll
Normal file
67
ablescript/src/rickroll
Normal file
|
@ -0,0 +1,67 @@
|
|||
We're no strangers to love
|
||||
You know the rules and so do I
|
||||
A full commitments what I'm thinking of
|
||||
You wouldn't get this from another guy
|
||||
|
||||
I just wanna tell you how I'm feeling
|
||||
Gotta make you understand
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
||||
Never gonna tell a lie and hurt you
|
||||
|
||||
We've known each other for so long
|
||||
Your heart's been aching but you're too shy to say it
|
||||
Inside we both know what's been going on
|
||||
We know the game and we're gonna play it
|
||||
|
||||
And if you ask me how I'm feeling
|
||||
Don't tell me you're too blind to see
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
||||
Never gonna tell a lie and hurt you
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
||||
Never gonna tell a lie and hurt you
|
||||
|
||||
Never gonna give, never gonna give
|
||||
(Give you up)
|
||||
|
||||
We've known each other for so long
|
||||
Your heart's been aching but you're too shy to say it
|
||||
Inside we both know what's been going on
|
||||
We know the game and we're gonna play it
|
||||
|
||||
I just wanna tell you how I'm feeling
|
||||
Gotta make you understand
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
||||
Never gonna tell a lie and hurt you
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
||||
Never gonna tell a lie and hurt you
|
||||
|
||||
Never gonna give you up
|
||||
Never gonna let you down
|
||||
Never gonna run around and desert you
|
||||
Never gonna make you cry
|
||||
Never gonna say goodbye
|
989
ablescript/src/variables.rs
Normal file
989
ablescript/src/variables.rs
Normal file
|
@ -0,0 +1,989 @@
|
|||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
hash::Hash,
|
||||
io::Write,
|
||||
mem::discriminant,
|
||||
ops,
|
||||
rc::Rc,
|
||||
vec,
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
|
||||
use crate::{ast::Block, brian::INSTRUCTION_MAPPINGS, consts};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
|
||||
pub enum Abool {
|
||||
Never = -1,
|
||||
Sometimes = 0,
|
||||
Always = 1,
|
||||
}
|
||||
|
||||
impl Abool {
|
||||
pub fn to_bool(&self) -> bool {
|
||||
match self {
|
||||
Self::Always => true,
|
||||
Self::Sometimes if rand::random() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for Abool {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Abool::Never => write!(f, "never"),
|
||||
Abool::Sometimes => write!(f, "sometimes"),
|
||||
Abool::Always => write!(f, "always"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Abool {
|
||||
fn from(b: bool) -> Self {
|
||||
if b {
|
||||
Abool::Always
|
||||
} else {
|
||||
Abool::Never
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Hash)]
|
||||
pub enum Functio {
|
||||
Bf {
|
||||
instructions: Vec<u8>,
|
||||
tape_len: usize,
|
||||
},
|
||||
Able {
|
||||
params: Vec<String>,
|
||||
body: Block,
|
||||
},
|
||||
Builtin(BuiltinFunctio),
|
||||
Chain {
|
||||
functios: Box<(Functio, Functio)>,
|
||||
kind: FunctioChainKind,
|
||||
},
|
||||
Eval(String),
|
||||
}
|
||||
|
||||
impl Functio {
|
||||
pub fn arity(&self) -> usize {
|
||||
match self {
|
||||
Functio::Bf {
|
||||
instructions: _,
|
||||
tape_len: _,
|
||||
} => 0,
|
||||
Functio::Able { params, body: _ } => params.len(),
|
||||
Functio::Builtin(b) => b.arity,
|
||||
Functio::Chain { functios, kind: _ } => functios.0.arity() + functios.1.arity(),
|
||||
Functio::Eval(_) => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BuiltinFunctio {
|
||||
function: Rc<dyn Fn(&[ValueRef]) -> Result<(), crate::error::ErrorKind>>,
|
||||
arity: usize,
|
||||
}
|
||||
|
||||
impl BuiltinFunctio {
|
||||
pub fn new<F>(f: F, arity: usize) -> Self
|
||||
where
|
||||
F: Fn(&[ValueRef]) -> Result<(), crate::error::ErrorKind> + 'static,
|
||||
{
|
||||
Self {
|
||||
function: Rc::new(f),
|
||||
arity,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, args: &[ValueRef]) -> Result<(), crate::error::ErrorKind> {
|
||||
(self.function)(args)
|
||||
}
|
||||
|
||||
pub fn fn_addr(&self) -> usize {
|
||||
Rc::as_ptr(&self.function) as *const () as _
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for BuiltinFunctio {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("BuiltinFunctio")
|
||||
.field("function", &"built-in")
|
||||
.field("arity", &self.arity)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BuiltinFunctio {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.fn_addr() == other.fn_addr() && self.arity == other.arity
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for BuiltinFunctio {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.fn_addr().hash(state);
|
||||
self.arity.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Copy, Clone, Hash)]
|
||||
pub enum FunctioChainKind {
|
||||
Equal,
|
||||
ByArity,
|
||||
}
|
||||
|
||||
pub type Cart = HashMap<Value, ValueRef>;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
Nul,
|
||||
Str(String),
|
||||
Int(isize),
|
||||
Abool(Abool),
|
||||
Functio(Functio),
|
||||
Cart(Cart),
|
||||
}
|
||||
|
||||
impl Default for Value {
|
||||
fn default() -> Self {
|
||||
Self::Nul
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Value {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
discriminant(self).hash(state);
|
||||
match self {
|
||||
Value::Nul => (),
|
||||
Value::Str(v) => v.hash(state),
|
||||
Value::Int(v) => v.hash(state),
|
||||
Value::Abool(v) => v.to_string().hash(state),
|
||||
Value::Functio(statements) => statements.hash(state),
|
||||
Value::Cart(_) => self.to_string().hash(state),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Write an AbleScript value to a Brainfuck input stream by
|
||||
/// coercing the value to an integer, then truncating that integer
|
||||
/// to a single byte, then writing that byte. This should only be
|
||||
/// called on `Write`rs that cannot fail, e.g., `Vec<u8>`, because
|
||||
/// any IO errors will cause a panic.
|
||||
pub fn bf_write(&self, stream: &mut impl Write) {
|
||||
stream
|
||||
.write_all(&[self.clone().into_isize() as u8])
|
||||
.expect("Failed to write to Brainfuck input");
|
||||
}
|
||||
|
||||
/// Coerce a value to an integer.
|
||||
pub fn into_isize(self) -> isize {
|
||||
match self {
|
||||
Value::Abool(a) => a as _,
|
||||
Value::Functio(f) => match f {
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
instructions.into_iter().map(|x| x as isize).sum::<isize>() * tape_len as isize
|
||||
}
|
||||
Functio::Able { params, body } => {
|
||||
params
|
||||
.into_iter()
|
||||
.map(|x| x.bytes().map(|x| x as isize).sum::<isize>())
|
||||
.sum::<isize>()
|
||||
+ body.len() as isize
|
||||
}
|
||||
Functio::Builtin(b) => (b.fn_addr() + b.arity) as _,
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (lf, rf) = *functios;
|
||||
Value::Functio(lf).into_isize()
|
||||
+ Value::Functio(rf).into_isize()
|
||||
* match kind {
|
||||
FunctioChainKind::Equal => -1,
|
||||
FunctioChainKind::ByArity => 1,
|
||||
}
|
||||
}
|
||||
Functio::Eval(code) => code.bytes().map(|x| x as isize).sum(),
|
||||
},
|
||||
Value::Int(i) => i,
|
||||
Value::Nul => consts::ANSWER,
|
||||
Value::Str(text) => text.parse().unwrap_or(consts::ANSWER),
|
||||
Value::Cart(c) => c
|
||||
.into_iter()
|
||||
.map(|(i, v)| i.into_isize() * v.borrow().clone().into_isize())
|
||||
.sum(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Coerce a value to an aboolean.
|
||||
pub fn into_abool(self) -> Abool {
|
||||
match self {
|
||||
Value::Nul => Abool::Never,
|
||||
Value::Str(s) => match s.to_lowercase().as_str() {
|
||||
"never" | "no" | "🇳🇴" => Abool::Never,
|
||||
"sometimes" => Abool::Sometimes,
|
||||
"always" | "yes" => Abool::Always,
|
||||
s => (!s.is_empty()).into(),
|
||||
},
|
||||
Value::Int(x) => match x.cmp(&0) {
|
||||
std::cmp::Ordering::Less => Abool::Never,
|
||||
std::cmp::Ordering::Equal => Abool::Sometimes,
|
||||
std::cmp::Ordering::Greater => Abool::Always,
|
||||
},
|
||||
Value::Abool(a) => a,
|
||||
Value::Functio(f) => match f {
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => Value::Int(
|
||||
(instructions.iter().map(|x| *x as usize).sum::<usize>() * tape_len) as _,
|
||||
)
|
||||
.into_abool(),
|
||||
Functio::Able { params, body } => {
|
||||
let str_to_isize =
|
||||
|x: String| -> isize { x.as_bytes().iter().map(|x| *x as isize).sum() };
|
||||
|
||||
let params: isize = params.into_iter().map(str_to_isize).sum();
|
||||
let body: isize = body
|
||||
.into_iter()
|
||||
.map(|x| format!("{:?}", x))
|
||||
.map(str_to_isize)
|
||||
.sum();
|
||||
|
||||
Value::Int((params + body) % 3 - 1).into_abool()
|
||||
}
|
||||
Functio::Builtin(b) => (b.fn_addr() % b.arity == 0).into(),
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (lhs, rhs) = *functios;
|
||||
match kind {
|
||||
FunctioChainKind::Equal => {
|
||||
Value::Abool(Value::Functio(lhs).into_abool())
|
||||
+ Value::Abool(Value::Functio(rhs).into_abool())
|
||||
}
|
||||
FunctioChainKind::ByArity => {
|
||||
Value::Abool(Value::Functio(lhs).into_abool())
|
||||
* Value::Abool(Value::Functio(rhs).into_abool())
|
||||
}
|
||||
}
|
||||
.into_abool()
|
||||
}
|
||||
Functio::Eval(code) => Value::Str(code).into_abool(),
|
||||
},
|
||||
Value::Cart(c) => {
|
||||
if c.is_empty() {
|
||||
Abool::Never
|
||||
} else {
|
||||
Abool::Always
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coerce a value to a functio.
|
||||
pub fn into_functio(self) -> Functio {
|
||||
match self {
|
||||
Value::Nul => Functio::Able {
|
||||
body: vec![],
|
||||
params: vec![],
|
||||
},
|
||||
Value::Str(s) => Functio::Eval(s),
|
||||
Value::Int(i) => Functio::Bf {
|
||||
instructions: {
|
||||
std::iter::successors(Some(i as usize), |i| {
|
||||
Some(i / INSTRUCTION_MAPPINGS.len())
|
||||
})
|
||||
.take_while(|&i| i != 0)
|
||||
.map(|i| INSTRUCTION_MAPPINGS[i % INSTRUCTION_MAPPINGS.len()])
|
||||
.collect()
|
||||
},
|
||||
tape_len: crate::brian::DEFAULT_TAPE_SIZE_LIMIT,
|
||||
},
|
||||
Value::Abool(a) => Functio::Eval(match a {
|
||||
Abool::Never => "".to_owned(),
|
||||
Abool::Sometimes => {
|
||||
use rand::seq::SliceRandom;
|
||||
let mut str_chars: Vec<_> = "Buy Able Products!".chars().collect();
|
||||
str_chars.shuffle(&mut rand::thread_rng());
|
||||
|
||||
format!(r#""{}"print;"#, str_chars.iter().collect::<String>())
|
||||
}
|
||||
Abool::Always => r#"loop{"Buy Able products!"print;}"#.to_owned(),
|
||||
}),
|
||||
Value::Functio(f) => f,
|
||||
Value::Cart(c) => {
|
||||
let kind = if let Some(114514) = c
|
||||
.get(&Value::Str("1452251871514141792252515212116".to_owned()))
|
||||
.map(|x| x.borrow().to_owned().into_isize())
|
||||
{
|
||||
FunctioChainKind::Equal
|
||||
} else {
|
||||
FunctioChainKind::ByArity
|
||||
};
|
||||
|
||||
let mut cart_vec = c.iter().collect::<Vec<_>>();
|
||||
cart_vec.sort_by(|x, y| x.0.partial_cmp(y.0).unwrap_or(std::cmp::Ordering::Less));
|
||||
|
||||
cart_vec
|
||||
.into_iter()
|
||||
.map(|(_, x)| x.borrow().to_owned().into_functio())
|
||||
.reduce(|acc, x| Functio::Chain {
|
||||
functios: Box::new((acc, x)),
|
||||
kind,
|
||||
})
|
||||
.unwrap_or_else(|| Functio::Eval(r#""Buy Able Products!"print;"#.to_owned()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Coerce a value into a cart.
|
||||
pub fn into_cart(self) -> Cart {
|
||||
match self {
|
||||
Value::Nul => HashMap::new(),
|
||||
Value::Str(s) => s
|
||||
.chars()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
(
|
||||
Value::Int(i as isize + 1),
|
||||
ValueRef::new(Value::Str(x.to_string())),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
Value::Int(i) => Value::Str(i.to_string()).into_cart(),
|
||||
Value::Abool(a) => Value::Str(a.to_string()).into_cart(),
|
||||
Value::Functio(f) => match f {
|
||||
Functio::Able { params, body } => {
|
||||
let params: Cart = params
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| (Value::Int(i as isize + 1), ValueRef::new(Value::Str(x))))
|
||||
.collect();
|
||||
|
||||
let body: Cart = body
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
(
|
||||
Value::Int(i as isize + 1),
|
||||
ValueRef::new(Value::Str(format!("{:?}", x))),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut cart = HashMap::new();
|
||||
cart.insert(
|
||||
Value::Str("params".to_owned()),
|
||||
ValueRef::new(Value::Cart(params)),
|
||||
);
|
||||
|
||||
cart.insert(
|
||||
Value::Str("body".to_owned()),
|
||||
ValueRef::new(Value::Cart(body)),
|
||||
);
|
||||
|
||||
cart
|
||||
}
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
let mut cart: Cart = instructions
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
(
|
||||
Value::Int(i as isize + 1),
|
||||
ValueRef::new(
|
||||
char::from_u32(x as u32)
|
||||
.map(|x| Value::Str(x.to_string()))
|
||||
.unwrap_or(Value::Nul),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
cart.insert(
|
||||
Value::Str("tapelen".to_owned()),
|
||||
ValueRef::new(Value::Int(tape_len as _)),
|
||||
);
|
||||
cart
|
||||
}
|
||||
Functio::Builtin(b) => {
|
||||
let mut cart = HashMap::new();
|
||||
cart.insert(
|
||||
Value::Str("addr".to_owned()),
|
||||
ValueRef::new(Value::Cart(Value::Int(b.fn_addr() as _).into_cart())),
|
||||
);
|
||||
|
||||
cart.insert(
|
||||
Value::Str("arity".to_owned()),
|
||||
ValueRef::new(Value::Int(b.arity as _)),
|
||||
);
|
||||
|
||||
cart
|
||||
}
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (lhs, rhs) = *functios;
|
||||
match kind {
|
||||
FunctioChainKind::Equal => {
|
||||
Value::Cart(Value::Functio(lhs).into_cart())
|
||||
+ Value::Cart(Value::Functio(rhs).into_cart())
|
||||
}
|
||||
FunctioChainKind::ByArity => {
|
||||
Value::Cart(Value::Functio(lhs).into_cart())
|
||||
* Value::Cart(Value::Functio(rhs).into_cart())
|
||||
}
|
||||
}
|
||||
.into_cart()
|
||||
}
|
||||
Functio::Eval(s) => Value::Str(s).into_cart(),
|
||||
},
|
||||
Value::Cart(c) => c,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a length of a value
|
||||
pub fn length(&self) -> isize {
|
||||
match self {
|
||||
Value::Nul => 0,
|
||||
Value::Str(s) => s.len() as _,
|
||||
Value::Int(i) => i.count_zeros() as _,
|
||||
Value::Abool(a) => match a {
|
||||
Abool::Never => -3,
|
||||
Abool::Sometimes => {
|
||||
if rand::thread_rng().gen() {
|
||||
3
|
||||
} else {
|
||||
-3
|
||||
}
|
||||
}
|
||||
Abool::Always => 3,
|
||||
},
|
||||
Value::Functio(f) => match f {
|
||||
// Compares lengths of functions:
|
||||
// BfFunctio - Sum of lengths of instructions and length of tape
|
||||
// AbleFunctio - Sum of argument count and body length
|
||||
// Eval - Length of input code
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => (instructions.len() + tape_len) as _,
|
||||
Functio::Able { params, body } => (params.len() + format!("{:?}", body).len()) as _,
|
||||
Functio::Builtin(b) => (std::mem::size_of_val(b.function.as_ref()) + b.arity) as _,
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (lhs, rhs) = *functios.clone();
|
||||
match kind {
|
||||
FunctioChainKind::Equal => {
|
||||
Value::Int(Value::Functio(lhs).into_isize())
|
||||
+ Value::Int(Value::Functio(rhs).into_isize())
|
||||
}
|
||||
FunctioChainKind::ByArity => {
|
||||
Value::Int(Value::Functio(lhs).into_isize())
|
||||
* Value::Int(Value::Functio(rhs).into_isize())
|
||||
}
|
||||
}
|
||||
.into_isize()
|
||||
}
|
||||
Functio::Eval(s) => s.len() as _,
|
||||
},
|
||||
Value::Cart(c) => c.len() as _,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
match self {
|
||||
Value::Nul => match rhs {
|
||||
Value::Nul => Value::Nul,
|
||||
Value::Str(_) => Value::Str(self.to_string()) + rhs,
|
||||
Value::Int(_) => Value::Int(self.into_isize()) + rhs,
|
||||
Value::Abool(_) => Value::Abool(self.into_abool()) + rhs,
|
||||
Value::Functio(_) => Value::Functio(self.into_functio()) + rhs,
|
||||
Value::Cart(_) => Value::Cart(self.into_cart()) + rhs,
|
||||
},
|
||||
Value::Str(s) => Value::Str(format!("{s}{rhs}")),
|
||||
Value::Int(i) => Value::Int(i.wrapping_add(rhs.into_isize())),
|
||||
Value::Abool(_) => {
|
||||
Value::Abool(Value::Int(self.into_isize().max(rhs.into_isize())).into_abool())
|
||||
}
|
||||
Value::Functio(f) => Value::Functio(Functio::Chain {
|
||||
functios: Box::new((f, rhs.into_functio())),
|
||||
kind: FunctioChainKind::Equal,
|
||||
}),
|
||||
Value::Cart(c) => {
|
||||
Value::Cart(c.into_iter().chain(rhs.into_cart().into_iter()).collect())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match self {
|
||||
Value::Nul => match rhs {
|
||||
Value::Nul => Value::Nul,
|
||||
Value::Str(_) => Value::Str(self.to_string()) - rhs,
|
||||
Value::Int(_) => Value::Int(self.into_isize()) - rhs,
|
||||
Value::Abool(_) => Value::Abool(self.into_abool()) - rhs,
|
||||
Value::Functio(_) => Value::Functio(self.into_functio()) - rhs,
|
||||
Value::Cart(_) => Value::Cart(self.into_cart()) - rhs,
|
||||
},
|
||||
Value::Str(s) => Value::Str(s.replace(&rhs.to_string(), "")),
|
||||
Value::Int(i) => Value::Int(i.wrapping_sub(rhs.into_isize())),
|
||||
Value::Abool(_) => (self.clone() + rhs.clone()) * !(self * rhs),
|
||||
Value::Functio(f) => Value::Functio(match f {
|
||||
Functio::Bf {
|
||||
instructions: lhs_ins,
|
||||
tape_len: lhs_tl,
|
||||
} => match rhs.into_functio() {
|
||||
Functio::Bf {
|
||||
instructions: rhs_ins,
|
||||
tape_len: rhs_tl,
|
||||
} => Functio::Bf {
|
||||
instructions: lhs_ins
|
||||
.into_iter()
|
||||
.zip(rhs_ins.into_iter())
|
||||
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
|
||||
.collect(),
|
||||
tape_len: lhs_tl - rhs_tl,
|
||||
},
|
||||
rhs => Functio::Bf {
|
||||
instructions: lhs_ins
|
||||
.into_iter()
|
||||
.zip(Value::Functio(rhs).to_string().bytes())
|
||||
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
|
||||
.collect(),
|
||||
tape_len: lhs_tl,
|
||||
},
|
||||
},
|
||||
Functio::Able {
|
||||
params: lhs_params,
|
||||
body: lhs_body,
|
||||
} => match rhs.into_functio() {
|
||||
Functio::Able {
|
||||
params: rhs_params,
|
||||
body: rhs_body,
|
||||
} => Functio::Able {
|
||||
params: lhs_params
|
||||
.into_iter()
|
||||
.zip(rhs_params.into_iter())
|
||||
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
|
||||
.collect(),
|
||||
body: lhs_body
|
||||
.into_iter()
|
||||
.zip(rhs_body.into_iter())
|
||||
.filter_map(|(l, r)| if l != r { Some(l) } else { None })
|
||||
.collect(),
|
||||
},
|
||||
rhs => Value::Int(
|
||||
Value::Functio(Functio::Able {
|
||||
params: lhs_params,
|
||||
body: lhs_body,
|
||||
})
|
||||
.into_isize()
|
||||
- Value::Functio(rhs).into_isize(),
|
||||
)
|
||||
.into_functio(),
|
||||
},
|
||||
Functio::Builtin(b) => {
|
||||
let arity = b.arity;
|
||||
let resulting_arity = arity.saturating_sub(rhs.into_isize() as usize);
|
||||
|
||||
Functio::Builtin(BuiltinFunctio::new(
|
||||
move |args| {
|
||||
b.call(
|
||||
&args
|
||||
.iter()
|
||||
.take(resulting_arity)
|
||||
.cloned()
|
||||
.chain(std::iter::repeat_with(|| ValueRef::new(Value::Nul)))
|
||||
.take(arity)
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
},
|
||||
resulting_arity,
|
||||
))
|
||||
}
|
||||
Functio::Chain { functios, .. } => {
|
||||
let rhs = rhs.into_functio();
|
||||
let (a, b) = *functios;
|
||||
|
||||
match (a == rhs, b == rhs) {
|
||||
(_, true) => a,
|
||||
(true, _) => b,
|
||||
(_, _) => (Value::Functio(a) - Value::Functio(rhs)).into_functio(),
|
||||
}
|
||||
}
|
||||
Functio::Eval(lhs_code) => Functio::Eval(lhs_code.replace(
|
||||
&match rhs.into_functio() {
|
||||
Functio::Eval(code) => code,
|
||||
rhs => Value::Functio(rhs).to_string(),
|
||||
},
|
||||
"",
|
||||
)),
|
||||
}),
|
||||
Value::Cart(c) => Value::Cart({
|
||||
let rhs_cart = rhs.into_cart();
|
||||
c.into_iter()
|
||||
.filter(|(k, v)| rhs_cart.get(k) != Some(v))
|
||||
.collect()
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
match self {
|
||||
Value::Nul => match rhs {
|
||||
Value::Nul => Value::Nul,
|
||||
Value::Str(_) => Value::Str(self.to_string()) * rhs,
|
||||
Value::Int(_) => Value::Int(self.into_isize()) * rhs,
|
||||
Value::Abool(_) => Value::Abool(self.into_abool()) * rhs,
|
||||
Value::Functio(_) => Value::Functio(self.into_functio()) * rhs,
|
||||
Value::Cart(_) => Value::Cart(self.into_cart()) * rhs,
|
||||
},
|
||||
Value::Str(s) => Value::Str(s.repeat(rhs.into_isize() as usize)),
|
||||
Value::Int(i) => Value::Int(i.wrapping_mul(rhs.into_isize())),
|
||||
Value::Abool(_) => {
|
||||
Value::Abool(Value::Int(self.into_isize().min(rhs.into_isize())).into_abool())
|
||||
}
|
||||
Value::Functio(f) => Value::Functio(Functio::Chain {
|
||||
functios: Box::new((f, rhs.into_functio())),
|
||||
kind: FunctioChainKind::ByArity,
|
||||
}),
|
||||
Value::Cart(c) => {
|
||||
let rhsc = rhs.into_cart();
|
||||
|
||||
Value::Cart(
|
||||
c.into_iter()
|
||||
.map(|(k, v)| {
|
||||
if let Some(k) = rhsc.get(&k) {
|
||||
(k.borrow().clone(), v)
|
||||
} else {
|
||||
(k, v)
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Div for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
match self {
|
||||
Value::Nul => match rhs {
|
||||
Value::Nul => Value::Nul,
|
||||
Value::Str(_) => Value::Str(self.to_string()) / rhs,
|
||||
Value::Int(_) => Value::Int(self.into_isize()) / rhs,
|
||||
Value::Abool(_) => Value::Abool(self.into_abool()) / rhs,
|
||||
Value::Functio(_) => Value::Functio(self.into_functio()) / rhs,
|
||||
Value::Cart(_) => Value::Cart(self.into_cart()) / rhs,
|
||||
},
|
||||
Value::Str(s) => Value::Cart(
|
||||
s.split(&rhs.to_string())
|
||||
.enumerate()
|
||||
.map(|(i, x)| {
|
||||
(
|
||||
Value::Int(i as isize + 1),
|
||||
ValueRef::new(Value::Str(x.to_owned())),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
),
|
||||
Value::Int(i) => Value::Int(i.wrapping_div(match rhs.into_isize() {
|
||||
0 => consts::ANSWER,
|
||||
x => x,
|
||||
})),
|
||||
Value::Abool(_) => !self + rhs,
|
||||
Value::Functio(f) => Value::Functio(match f {
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
let fraction = 1.0 / rhs.into_isize() as f64;
|
||||
let len = instructions.len();
|
||||
Functio::Bf {
|
||||
instructions: instructions
|
||||
.into_iter()
|
||||
.take((len as f64 * fraction) as usize)
|
||||
.collect(),
|
||||
tape_len,
|
||||
}
|
||||
}
|
||||
Functio::Able { params, body } => {
|
||||
let fraction = 1.0 / rhs.into_isize() as f64;
|
||||
let len = body.len();
|
||||
Functio::Able {
|
||||
params,
|
||||
body: body
|
||||
.into_iter()
|
||||
.take((len as f64 * fraction) as usize)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
Functio::Builtin(b) => Functio::Builtin(BuiltinFunctio {
|
||||
arity: b.arity + rhs.into_isize() as usize,
|
||||
..b
|
||||
}),
|
||||
Functio::Chain { functios, kind } => {
|
||||
let functios = *functios;
|
||||
Functio::Chain {
|
||||
functios: Box::new((
|
||||
(Value::Functio(functios.0) / rhs.clone()).into_functio(),
|
||||
(Value::Functio(functios.1) / rhs).into_functio(),
|
||||
)),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
Functio::Eval(s) => {
|
||||
let fraction = 1.0 / rhs.into_isize() as f64;
|
||||
let len = s.len();
|
||||
Functio::Eval(s.chars().take((len as f64 * fraction) as usize).collect())
|
||||
}
|
||||
}),
|
||||
Value::Cart(c) => {
|
||||
let cart_len = c.len();
|
||||
let chunk_len = rhs.into_isize() as usize;
|
||||
|
||||
Value::Cart(
|
||||
c.into_iter()
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(cart_len / chunk_len + (cart_len % chunk_len != 0) as usize)
|
||||
.enumerate()
|
||||
.map(|(k, v)| {
|
||||
(
|
||||
Value::Int(k as isize + 1),
|
||||
ValueRef::new(Value::Cart(v.iter().cloned().collect())),
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Not for Value {
|
||||
type Output = Value;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
match self {
|
||||
Value::Nul => Value::Nul,
|
||||
Value::Str(s) => Value::Str(s.chars().rev().collect()),
|
||||
Value::Int(i) => Value::Int(i.swap_bytes()),
|
||||
Value::Abool(a) => Value::Abool(match a {
|
||||
Abool::Never => Abool::Always,
|
||||
Abool::Sometimes => Abool::Sometimes,
|
||||
Abool::Always => Abool::Never,
|
||||
}),
|
||||
Value::Functio(f) => Value::Functio(match f {
|
||||
Functio::Bf {
|
||||
mut instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
instructions.reverse();
|
||||
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
}
|
||||
}
|
||||
Functio::Able {
|
||||
mut params,
|
||||
mut body,
|
||||
} => {
|
||||
params.reverse();
|
||||
body.reverse();
|
||||
|
||||
Functio::Able { params, body }
|
||||
}
|
||||
Functio::Builtin(b) => {
|
||||
let arity = b.arity;
|
||||
Functio::Builtin(BuiltinFunctio::new(
|
||||
move |args| b.call(&args.iter().cloned().rev().collect::<Vec<_>>()),
|
||||
arity,
|
||||
))
|
||||
}
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (a, b) = *functios;
|
||||
Functio::Chain {
|
||||
functios: Box::new((
|
||||
(!Value::Functio(b)).into_functio(),
|
||||
(!Value::Functio(a)).into_functio(),
|
||||
)),
|
||||
kind,
|
||||
}
|
||||
}
|
||||
Functio::Eval(code) => Functio::Eval(code.chars().rev().collect()),
|
||||
}),
|
||||
Value::Cart(c) => Value::Cart(
|
||||
c.into_iter()
|
||||
.map(|(k, v)| (v.borrow().clone(), ValueRef::new(k)))
|
||||
.collect(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let other = other.clone();
|
||||
|
||||
match self {
|
||||
Value::Nul => matches!(other, Value::Nul),
|
||||
Value::Str(s) => *s == other.to_string(),
|
||||
Value::Int(i) => *i == other.into_isize(),
|
||||
Value::Abool(a) => *a == other.into_abool(),
|
||||
Value::Functio(f) => *f == other.into_functio(),
|
||||
Value::Cart(c) => *c == other.into_cart(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Value {}
|
||||
|
||||
impl PartialOrd for Value {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
use std::cmp::Ordering::*;
|
||||
let other = other.clone();
|
||||
|
||||
match self {
|
||||
Value::Nul => {
|
||||
if other == Value::Nul {
|
||||
Some(Equal)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
Value::Str(s) => Some(s.cmp(&other.to_string())),
|
||||
Value::Int(i) => Some(i.cmp(&other.into_isize())),
|
||||
Value::Abool(a) => a.partial_cmp(&other.into_abool()),
|
||||
Value::Functio(_) => self.clone().into_isize().partial_cmp(&other.into_isize()),
|
||||
Value::Cart(c) => Some(c.len().cmp(&other.into_cart().len())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Value {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Value::Nul => write!(f, "nul"),
|
||||
Value::Str(v) => write!(f, "{}", v),
|
||||
Value::Int(v) => write!(f, "{}", v),
|
||||
Value::Abool(v) => write!(f, "{}", v),
|
||||
Value::Functio(v) => match v {
|
||||
Functio::Bf {
|
||||
instructions,
|
||||
tape_len,
|
||||
} => {
|
||||
write!(
|
||||
f,
|
||||
"({}) {}",
|
||||
tape_len,
|
||||
String::from_utf8(instructions.to_owned())
|
||||
.expect("Brainfuck functio source should be UTF-8")
|
||||
)
|
||||
}
|
||||
Functio::Able { params, body } => {
|
||||
write!(
|
||||
f,
|
||||
"({}) -> {:?}",
|
||||
params.join(", "),
|
||||
// Maybe we should have a pretty-printer for
|
||||
// statement blocks at some point?
|
||||
body,
|
||||
)
|
||||
}
|
||||
Functio::Builtin(b) => write!(f, "builtin @ {}", b.fn_addr()),
|
||||
Functio::Chain { functios, kind } => {
|
||||
let (a, b) = *functios.clone();
|
||||
write!(
|
||||
f,
|
||||
"{} {} {} ",
|
||||
Value::Functio(a),
|
||||
match kind {
|
||||
FunctioChainKind::Equal => '+',
|
||||
FunctioChainKind::ByArity => '*',
|
||||
},
|
||||
Value::Functio(b)
|
||||
)
|
||||
}
|
||||
Functio::Eval(s) => write!(f, "{}", s),
|
||||
},
|
||||
Value::Cart(c) => {
|
||||
write!(f, "[")?;
|
||||
let mut cart_vec = c.iter().collect::<Vec<_>>();
|
||||
cart_vec.sort_by(|x, y| x.0.partial_cmp(y.0).unwrap_or(std::cmp::Ordering::Less));
|
||||
|
||||
for (idx, (key, value)) in cart_vec.into_iter().enumerate() {
|
||||
write!(
|
||||
f,
|
||||
"{}{} <= {}",
|
||||
if idx != 0 { ", " } else { "" },
|
||||
value.borrow(),
|
||||
key
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ValueRef(Rc<RefCell<Value>>);
|
||||
|
||||
impl ValueRef {
|
||||
pub fn new(v: Value) -> Self {
|
||||
Self(Rc::new(RefCell::new(v)))
|
||||
}
|
||||
|
||||
pub fn borrow(&self) -> Ref<Value> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> RefMut<Value> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
|
||||
pub fn replace(&self, v: Value) -> Value {
|
||||
self.0.replace(v)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Variable {
|
||||
pub melo: bool,
|
||||
|
||||
// Multiple Variables can reference the same underlying Value when
|
||||
// pass-by-reference is used, therefore we use Rc here.
|
||||
pub value: ValueRef,
|
||||
}
|
||||
|
||||
impl Variable {
|
||||
pub fn from_value(value: Value) -> Self {
|
||||
Self {
|
||||
melo: false,
|
||||
value: ValueRef::new(value),
|
||||
}
|
||||
}
|
||||
}
|
18
ablescript_cli/Cargo.toml
Normal file
18
ablescript_cli/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "ablescript_cli"
|
||||
version = "0.3.0"
|
||||
authors = ["able <abl3theabove@gmail.com>"]
|
||||
edition = "2021"
|
||||
|
||||
description = "The best programming language"
|
||||
license = "MIT"
|
||||
documentation = "https://ablecorp.us/able-script-the-book/"
|
||||
repository = "https://github.com/AbleCorp/able-script"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
ablescript = { version = "0.3.0", path = "../ablescript" }
|
||||
|
||||
clap = "3.1"
|
||||
rustyline = "9.1"
|
64
ablescript_cli/src/main.rs
Normal file
64
ablescript_cli/src/main.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
#![forbid(unsafe_code, clippy::unwrap_used)]
|
||||
|
||||
mod repl;
|
||||
|
||||
use ablescript::interpret::ExecEnv;
|
||||
use ablescript::parser::parse;
|
||||
use clap::{Arg, Command};
|
||||
use std::process::exit;
|
||||
|
||||
fn main() {
|
||||
// variables::test(); // NOTE(Able): Add this as a test case
|
||||
let matches = Command::new("AbleScript")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Able <abl3theabove@gmail.com>")
|
||||
.about("AbleScript interpreter")
|
||||
.arg(
|
||||
Arg::new("file")
|
||||
.short('f')
|
||||
.long("file")
|
||||
.value_name("FILE")
|
||||
.help("Set the path to interpret from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("debug")
|
||||
.long("debug")
|
||||
.help("Enable debug AST printing"),
|
||||
)
|
||||
.get_matches();
|
||||
|
||||
let ast_print = matches.is_present("debug");
|
||||
|
||||
match matches.value_of("file") {
|
||||
Some(file_path) => {
|
||||
// Read file
|
||||
let source = match std::fs::read_to_string(file_path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Failed to read file \"{}\": {}", file_path, e);
|
||||
exit(1)
|
||||
}
|
||||
};
|
||||
|
||||
// Parse & evaluate
|
||||
if let Err(e) = parse(&source).and_then(|ast| {
|
||||
if ast_print {
|
||||
println!("{:#?}", ast);
|
||||
}
|
||||
ExecEnv::new().eval_stmts(&ast)
|
||||
}) {
|
||||
println!(
|
||||
"Error `{:?}` occurred at span: {:?} = `{:?}`",
|
||||
e.kind,
|
||||
e.span.clone(),
|
||||
&source[e.span]
|
||||
);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
println!("Hi [AbleScript {}]", env!("CARGO_PKG_VERSION"));
|
||||
repl::repl(ast_print);
|
||||
}
|
||||
}
|
||||
}
|
60
ablescript_cli/src/repl.rs
Normal file
60
ablescript_cli/src/repl.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use ablescript::interpret::ExecEnv;
|
||||
use ablescript::parser::parse;
|
||||
use rustyline::Editor;
|
||||
|
||||
pub fn repl(ast_print: bool) {
|
||||
let mut rl = Editor::<()>::new();
|
||||
let mut env = ExecEnv::new();
|
||||
|
||||
// If this is `Some`, the user has previously entered an
|
||||
// incomplete statement and is now completing it; otherwise, the
|
||||
// user is entering a completely new statement.
|
||||
let mut partial: Option<String> = None;
|
||||
loop {
|
||||
match rl.readline(if partial.is_some() { ">> " } else { ":: " }) {
|
||||
Ok(readline) => {
|
||||
let readline = readline.trim_end();
|
||||
rl.add_history_entry(readline);
|
||||
|
||||
let partial_data = match partial {
|
||||
Some(line) => line + readline,
|
||||
None => readline.to_owned(),
|
||||
};
|
||||
|
||||
partial = match parse(&partial_data).and_then(|ast| {
|
||||
if ast_print {
|
||||
println!("{:#?}", &ast);
|
||||
}
|
||||
env.eval_stmts(&ast)
|
||||
}) {
|
||||
Ok(_) => None,
|
||||
Err(ablescript::error::Error {
|
||||
// Treat "Unexpected EOF" errors as "we need
|
||||
// more data".
|
||||
kind: ablescript::error::ErrorKind::UnexpectedEof,
|
||||
..
|
||||
}) => Some(partial_data),
|
||||
Err(e) => {
|
||||
println!("{}", e);
|
||||
println!(" | {}", partial_data);
|
||||
println!(
|
||||
" {}{}",
|
||||
" ".repeat(e.span.start),
|
||||
"^".repeat((e.span.end - e.span.start).max(1))
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(rustyline::error::ReadlineError::Eof) => {
|
||||
println!("bye");
|
||||
break;
|
||||
}
|
||||
Err(rustyline::error::ReadlineError::Interrupted) => (),
|
||||
Err(e) => {
|
||||
println!("Error: {:?}", e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
44
examples/by-arity-chain.able
Normal file
44
examples/by-arity-chain.able
Normal file
|
@ -0,0 +1,44 @@
|
|||
functio arity_0() {
|
||||
/*this function has arity 0*/ print;
|
||||
}
|
||||
|
||||
functio arity_1(arg1) {
|
||||
/*this function has arity 1*/ print;
|
||||
arg1 print;
|
||||
}
|
||||
|
||||
functio arity_2(arg1, arg2) {
|
||||
/*this function has arity 2*/ print;
|
||||
arg1 print;
|
||||
arg2 print;
|
||||
}
|
||||
|
||||
functio arity_3(arg1, arg2, arg3) {
|
||||
/*this function has arity 3*/ print;
|
||||
arg1 print;
|
||||
arg2 print;
|
||||
arg3 print;
|
||||
}
|
||||
|
||||
owo arity_0();
|
||||
owo arity_1(/*foo*/);
|
||||
owo arity_2(/*foo*/, /*bar*/);
|
||||
owo arity_3(/*foo*/, /*bar*/, /*baz*/);
|
||||
|
||||
dim i1 arity_0 * arity_1;
|
||||
i1(/*second*/);
|
||||
|
||||
/*----*/ print;
|
||||
|
||||
dim i2 arity_1 * arity_0;
|
||||
i2(/*first*/);
|
||||
|
||||
/*----*/ print;
|
||||
|
||||
dim ifancy arity_3 * arity_3;
|
||||
ifancy(/*left1*/, /*right1*/, /*left2*/, /*right2*/, /*left3*/, /*right3*/);
|
||||
|
||||
/*---*/ print;
|
||||
|
||||
dim another arity_0 * arity_3;
|
||||
another(/*right1*/, /*right2*/, /*right3*/);
|
8
examples/carts.able
Normal file
8
examples/carts.able
Normal file
|
@ -0,0 +1,8 @@
|
|||
functio helloable() {
|
||||
/*Hello, Able!*/ print;
|
||||
}
|
||||
|
||||
dim cart [/*able*/ <= 42, helloable <= /*hello*/];
|
||||
|
||||
cart[42] print;
|
||||
cart[/*hello*/]();
|
4
examples/functio.able
Normal file
4
examples/functio.able
Normal file
|
@ -0,0 +1,4 @@
|
|||
functio hello(words){
|
||||
words print;
|
||||
}
|
||||
hello(/*wonk*/);
|
2
examples/hello-world.able
Normal file
2
examples/hello-world.able
Normal file
|
@ -0,0 +1,2 @@
|
|||
dim hello /*world*/;
|
||||
hello print;
|
5
examples/iotest.able
Normal file
5
examples/iotest.able
Normal file
|
@ -0,0 +1,5 @@
|
|||
dim data;
|
||||
loop {
|
||||
data read;
|
||||
data print;
|
||||
}
|
17
examples/lambda.able
Normal file
17
examples/lambda.able
Normal file
|
@ -0,0 +1,17 @@
|
|||
dim with_world λ (var) {
|
||||
var + /*world!*/ =: var;
|
||||
};
|
||||
|
||||
dim foo /*Hello, */;
|
||||
with_world(foo);
|
||||
foo print;
|
||||
|
||||
|
||||
|
||||
functio add1(var) {
|
||||
1 + var =: var;
|
||||
}
|
||||
|
||||
dim bar 0;
|
||||
(add1 * λ () { /*Goodbye, world!*/ print; })(bar);
|
||||
bar print;
|
3
examples/melo-hello.able
Normal file
3
examples/melo-hello.able
Normal file
|
@ -0,0 +1,3 @@
|
|||
dim hi /*wonk*/;
|
||||
melo hi;
|
||||
hi print; owo Should error out
|
23
examples/pass-by-reference.able
Normal file
23
examples/pass-by-reference.able
Normal file
|
@ -0,0 +1,23 @@
|
|||
owo Pass-by-reference test
|
||||
|
||||
owo Swap two variables.
|
||||
functio swap(left, right) {
|
||||
dim tmp left;
|
||||
right =: left;
|
||||
tmp =: right;
|
||||
}
|
||||
|
||||
dim foo /*hello*/;
|
||||
dim bar /*world*/;
|
||||
|
||||
swap(foo, bar);
|
||||
|
||||
unless (foo = /*world*/) {
|
||||
/*FAILED*/ print;
|
||||
/*foo should be 'world', is actually:*/ print;
|
||||
foo print;
|
||||
}
|
||||
|
||||
unless (foo ain't /*world*/) {
|
||||
/*OK*/ print;
|
||||
}
|
124
src/base_55.rs
124
src/base_55.rs
|
@ -1,124 +0,0 @@
|
|||
pub fn char2num(character: char) -> i32 {
|
||||
match character {
|
||||
'Z' => -26,
|
||||
'Y' => -25,
|
||||
'X' => -24,
|
||||
'W' => -23,
|
||||
'V' => -22,
|
||||
'U' => -21,
|
||||
'T' => -20,
|
||||
'R' => -18,
|
||||
'S' => -19,
|
||||
'Q' => -17,
|
||||
'P' => -16,
|
||||
'O' => -15,
|
||||
'N' => -14,
|
||||
'M' => -13,
|
||||
'L' => -12,
|
||||
'K' => -11,
|
||||
'J' => -10,
|
||||
'I' => -9,
|
||||
'H' => -8,
|
||||
'G' => -7,
|
||||
'F' => -6,
|
||||
'E' => -5,
|
||||
'D' => -4,
|
||||
'C' => -3,
|
||||
'B' => -2,
|
||||
'A' => -1,
|
||||
' ' => 0,
|
||||
'a' => 1,
|
||||
'b' => 2,
|
||||
'c' => 3,
|
||||
'd' => 4,
|
||||
'e' => 5,
|
||||
'f' => 6,
|
||||
'g' => 7,
|
||||
'h' => 8,
|
||||
'i' => 9,
|
||||
'j' => 10,
|
||||
'k' => 11,
|
||||
'l' => 12,
|
||||
'm' => 13,
|
||||
'n' => 14,
|
||||
'o' => 15,
|
||||
'p' => 16,
|
||||
'q' => 17,
|
||||
'r' => 18,
|
||||
's' => 19,
|
||||
't' => 20,
|
||||
'u' => 21,
|
||||
'v' => 22,
|
||||
'w' => 23,
|
||||
'x' => 24,
|
||||
'y' => 25,
|
||||
'z' => 26,
|
||||
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
|
||||
'/' => 53,
|
||||
'\\' => 54,
|
||||
'.' => 55,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
pub fn num2char(number: i32) -> char {
|
||||
match number {
|
||||
-26 => 'Z',
|
||||
-25 => 'Y',
|
||||
-24 => 'X',
|
||||
-23 => 'W',
|
||||
-22 => 'V',
|
||||
-210 => 'U',
|
||||
-20 => 'T',
|
||||
-18 => 'R',
|
||||
-19 => 'S',
|
||||
-17 => 'Q',
|
||||
-16 => 'P',
|
||||
-15 => 'O',
|
||||
-14 => 'N',
|
||||
-13 => 'M',
|
||||
-12 => 'L',
|
||||
-11 => 'K',
|
||||
-10 => 'J',
|
||||
-9 => 'I',
|
||||
-8 => 'H',
|
||||
-7 => 'G',
|
||||
-6 => 'F',
|
||||
-5 => 'E',
|
||||
-4 => 'D',
|
||||
-3 => 'C',
|
||||
-2 => 'B',
|
||||
-1 => 'A',
|
||||
0 => ' ',
|
||||
1 => 'a',
|
||||
2 => 'b',
|
||||
3 => 'c',
|
||||
4 => 'd',
|
||||
5 => 'e',
|
||||
6 => 'f',
|
||||
7 => 'g',
|
||||
8 => 'h',
|
||||
9 => 'i',
|
||||
10 => 'j',
|
||||
11 => 'k',
|
||||
12 => 'l',
|
||||
13 => 'm',
|
||||
14 => 'n',
|
||||
15 => 'o',
|
||||
16 => 'p',
|
||||
17 => 'q',
|
||||
18 => 'r',
|
||||
19 => 's',
|
||||
20 => 't',
|
||||
21 => 'u',
|
||||
22 => 'v',
|
||||
23 => 'w',
|
||||
24 => 'x',
|
||||
25 => 'y',
|
||||
26 => 'z',
|
||||
// NOTE(Able): Why does it jump to 53 here? MY REASONS ARE BEYOND YOUR UNDERSTANDING MORTAL
|
||||
53 => '/',
|
||||
54 => '\\',
|
||||
55 => '.',
|
||||
_ => ' ',
|
||||
}
|
||||
}
|
15
src/const.rs
15
src/const.rs
|
@ -1,15 +0,0 @@
|
|||
// NOTE(Able): Number constants
|
||||
pub const TAU: i32 = 6;
|
||||
pub const PI: i32 = 3;
|
||||
pub const E: i32 = 3;
|
||||
pub const M: i32 = 70; // @Kev#6900
|
||||
pub const PHI: i32 = 2;
|
||||
pub const GOLDEN_RATIO: i32 = 2;
|
||||
pub const WUA: i32 = 1;
|
||||
pub const EULERS_CONSTANT: i32 = 0;
|
||||
pub const GRAVITY: i32 = 10;
|
||||
pub const RNG: i32 = 12;
|
||||
pub const STD_RNG: i32 = 4; //The standard random number is 4 (source: https://xkcd.com/221/)
|
||||
pub const INF: i32 = i32::max_value();
|
||||
|
||||
pub const OCTOTHORPE: char = '#';
|
31
src/main.rs
31
src/main.rs
|
@ -1,31 +0,0 @@
|
|||
extern crate clap;
|
||||
use clap::{App, Arg};
|
||||
|
||||
mod base_55;
|
||||
mod parser;
|
||||
pub mod tokens;
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("AbleScript")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.author("Able <abl3theabove@gmail.com>")
|
||||
.about("Does awesome things")
|
||||
.arg(
|
||||
Arg::with_name("file")
|
||||
.short("f")
|
||||
.long("file")
|
||||
.value_name("FILE")
|
||||
.help("Set the path to interpret from")
|
||||
.takes_value(true),
|
||||
)
|
||||
.get_matches();
|
||||
match matches.value_of("file") {
|
||||
Some(file_path) => {
|
||||
// Start parsing that file
|
||||
}
|
||||
None => {
|
||||
println!("hi");
|
||||
//start the prompt
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
use crate::tokens::{ABOOL, TOKENS};
|
||||
|
||||
pub fn abool2num(abool: ABOOL) -> i32 {
|
||||
match abool {
|
||||
ABOOL::NEVER => -1,
|
||||
ABOOL::SOMETIMES => 0,
|
||||
ABOOL::ALWAYS => 1,
|
||||
}
|
||||
}
|
||||
pub fn num2abool(number: i32) -> ABOOL {
|
||||
match number {
|
||||
-1 => ABOOL::NEVER,
|
||||
0 => ABOOL::SOMETIMES,
|
||||
1 => ABOOL::ALWAYS,
|
||||
_ => ABOOL::SOMETIMES,
|
||||
}
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
pub enum TOKENS {
|
||||
LEFT_PARENTHESIS, // (
|
||||
RIGHT_PARENTHESIS, // )
|
||||
LEFT_BRACKET, // [
|
||||
RIGHT_BRACKET, // ]
|
||||
LEFT_BRACE, // {
|
||||
RIGHT_BRACE, // }
|
||||
COMMENT { value: String }, // #
|
||||
SUBTRACT, // -
|
||||
ADDITION, // +
|
||||
MULTIPLY, // *
|
||||
DIVIDE, // /
|
||||
CHAR, // Base52 based character
|
||||
FUNCTION, // functio
|
||||
BF_FUNCTION { name: String, functio: String }, // Brain fuck FFI
|
||||
VARIABLE, // Variable bro
|
||||
BOOLEAN { state: bool }, // True, False
|
||||
ABOOLEAN { state: ABOOL }, // Always, Sometimes, Never
|
||||
PRINT, // Prints the preceding things
|
||||
MELO, // Ban the following variable from ever being used again
|
||||
T_DARK,
|
||||
}
|
||||
pub enum ABOOL {
|
||||
NEVER = -1,
|
||||
SOMETIMES = 0,
|
||||
ALWAYS = 1,
|
||||
}
|
Loading…
Reference in a new issue