Compare commits
12 Commits
505a57abda
...
d5f70c6a7c
| Author | SHA1 | Date | |
|---|---|---|---|
| d5f70c6a7c | |||
| 1736582613 | |||
| 1f670a66a4 | |||
| 60f3572fec | |||
| f5d3809f37 | |||
| 1261c037f8 | |||
| fd8e1df696 | |||
| befdf63c7c | |||
| 679ac51602 | |||
| 7909fa3808 | |||
| 4b98ec1ce2 | |||
| 37ad3702d0 |
Generated
+50
-214
@@ -148,9 +148,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.11"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
@@ -410,9 +410,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.41"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be92d32e80243a54711e5d7ce823c35c41c9d929dc4ab58e1276f625841aadf9"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -429,9 +429,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.41"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707eab41e9622f9139419d573eca0900137718000c517d47da73045f54331c3d"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
@@ -439,9 +439,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.41"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ef4f52386a59ca4c860f7393bcf8abd8dfd91ecccc0f774635ff68e92eeef491"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -451,9 +451,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.5"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "cobs"
|
||||
@@ -461,7 +461,7 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
|
||||
dependencies = [
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -644,31 +644,6 @@ version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df"
|
||||
dependencies = [
|
||||
"bitflags 2.11.0",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crypt"
|
||||
version = "0.1.0"
|
||||
@@ -1057,15 +1032,6 @@ dependencies = [
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "error-chain"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
@@ -1349,17 +1315,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
@@ -1656,7 +1611,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bde5057d6143cc94e861d90f591b9303d6716c6b9602309150bd068853c10899"
|
||||
dependencies = [
|
||||
"hashbrown 0.16.1",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1803,10 +1758,14 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
name = "lysp"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"cross",
|
||||
"nom 8.0.0",
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "md2txt"
|
||||
@@ -1849,18 +1808,6 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
@@ -1922,6 +1869,15 @@ dependencies = [
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ntpc"
|
||||
version = "0.1.0"
|
||||
@@ -2010,15 +1966,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.5"
|
||||
@@ -2291,36 +2238,13 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.13",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pci-ids"
|
||||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d88ae3281b415d856e9c2ddbcdd5961e71c1a3e90138512c04d720241853a6af"
|
||||
dependencies = [
|
||||
"nom",
|
||||
"nom 7.1.3",
|
||||
"phf",
|
||||
"phf_codegen",
|
||||
"proc-macro2",
|
||||
@@ -2581,9 +2505,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -2610,9 +2534,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -2682,7 +2606,7 @@ dependencies = [
|
||||
"kasuari",
|
||||
"lru",
|
||||
"strum",
|
||||
"thiserror 2.0.12",
|
||||
"thiserror 2.0.18",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width 0.2.2",
|
||||
@@ -2745,10 +2669,11 @@ name = "red"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cross",
|
||||
"crossterm",
|
||||
"libc",
|
||||
"libterm",
|
||||
"syslog",
|
||||
"log",
|
||||
"logsink",
|
||||
"lysp",
|
||||
"regex",
|
||||
"thiserror 1.0.69",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
@@ -2773,9 +2698,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
version = "1.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
|
||||
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -2785,9 +2710,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.9"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -3088,7 +3013,7 @@ dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"logsink",
|
||||
"nom",
|
||||
"nom 7.1.3",
|
||||
"runtime",
|
||||
"stuff",
|
||||
"thiserror 1.0.69",
|
||||
@@ -3100,36 +3025,6 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signature"
|
||||
version = "2.2.0"
|
||||
@@ -3326,9 +3221,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3346,19 +3241,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syslog"
|
||||
version = "6.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfc7e95b5b795122fafe6519e27629b5ab4232c73ebb2428f568e82b1a457ad3"
|
||||
dependencies = [
|
||||
"error-chain",
|
||||
"hostname",
|
||||
"libc",
|
||||
"log",
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sysutils"
|
||||
version = "0.1.0"
|
||||
@@ -3426,11 +3308,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl 2.0.12",
|
||||
"thiserror-impl 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3446,9 +3328,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3462,14 +3344,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num-conv",
|
||||
"num_threads",
|
||||
"powerfmt",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3478,16 +3355,6 @@ version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-skia"
|
||||
version = "0.11.4"
|
||||
@@ -3942,22 +3809,6 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
@@ -3967,12 +3818,6 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[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.45.0"
|
||||
@@ -3982,15 +3827,6 @@ dependencies = [
|
||||
"windows-targets 0.42.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
||||
@@ -12,6 +12,7 @@ members = [
|
||||
"lib/libpsf",
|
||||
"lib/libterm",
|
||||
"lib/logsink",
|
||||
"lib/lysp",
|
||||
"lib/pixie",
|
||||
"lib/runtime",
|
||||
"lib/stuff",
|
||||
@@ -47,6 +48,7 @@ ratatui = { version = "0.30.0", features = ["all-widgets", "macros"], default-fe
|
||||
toml = "0.8.20"
|
||||
url = "2.5.0"
|
||||
http = "1.1.0"
|
||||
nom = "8.0.0"
|
||||
|
||||
# Cryptography
|
||||
rustls = { version = "0.23.29", default-features = false, features = ["std", "logging", "tls12", "custom-provider"] }
|
||||
@@ -89,6 +91,7 @@ cryptic.path = "lib/cryptic"
|
||||
hclient.path = "lib/hclient"
|
||||
pixie.path = "lib/pixie"
|
||||
stuff.path = "lib/stuff"
|
||||
lysp.path = "lib/lysp"
|
||||
|
||||
[workspace.lints.rust]
|
||||
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(rust_analyzer)'] }
|
||||
|
||||
@@ -45,7 +45,7 @@ extern "C" fn sigint_proxy(_: c_int) {
|
||||
|
||||
pub fn set_sigint_handler(handler: fn()) {
|
||||
*SIGINT_HANDLER.lock().unwrap() = handler;
|
||||
unsafe { libc::signal(libc::SIGINT, sigint_proxy as usize) };
|
||||
unsafe { libc::signal(libc::SIGINT, (sigint_proxy as *const ()).addr()) };
|
||||
}
|
||||
|
||||
pub fn send_kill(pid: u32) -> io::Result<()> {
|
||||
|
||||
@@ -9,14 +9,18 @@ pub struct RawMode {
|
||||
}
|
||||
|
||||
impl RawMode {
|
||||
/// # Safety
|
||||
///
|
||||
/// Modifies terminal state, if not correctly paired with a [RawMode::leave()] call,
|
||||
/// will ruin the terminal's behavior and appearance.
|
||||
pub unsafe fn enter<F: AsRawFd>(stdin: &F) -> Result<Self, io::Error> {
|
||||
let mut old = MaybeUninit::uninit();
|
||||
|
||||
if libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) != 0 {
|
||||
if unsafe { libc::tcgetattr(stdin.as_raw_fd(), old.as_mut_ptr()) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let old = old.assume_init();
|
||||
let old = unsafe { old.assume_init() };
|
||||
let mut new = old;
|
||||
new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ISIG | libc::ICANON | libc::IEXTEN);
|
||||
// new.c_lflag &= !(libc::ECHO | libc::ECHONL | libc::ICANON | libc::ISIG | libc::IEXTEN);
|
||||
@@ -32,23 +36,30 @@ impl RawMode {
|
||||
new.c_cflag &= !(libc::PARENB | libc::CSIZE);
|
||||
new.c_cflag |= libc::CS8;
|
||||
|
||||
if libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) != 0 {
|
||||
if unsafe { libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &new) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(Self { saved_termios: old })
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Modifies terminal state, if called multiple times or after modifying the terminal state
|
||||
/// will ruin the terminal's behavior and appearance.
|
||||
pub unsafe fn leave<F: AsRawFd>(&self, stdin: &F) {
|
||||
libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios);
|
||||
unsafe { libc::tcsetattr(stdin.as_raw_fd(), libc::TCSANOW, &self.saved_termios) };
|
||||
}
|
||||
}
|
||||
|
||||
/// # Safety
|
||||
///
|
||||
/// Does not check whether stdout is a terminal, so marked as semantically unsafe
|
||||
pub unsafe fn terminal_size(stdout: &Stdout) -> io::Result<(usize, usize)> {
|
||||
let mut size: MaybeUninit<libc::winsize> = MaybeUninit::uninit();
|
||||
if libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) != 0 {
|
||||
if unsafe { libc::ioctl(stdout.as_raw_fd(), libc::TIOCGWINSZ, size.as_mut_ptr()) } != 0 {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
let size = size.assume_init();
|
||||
let size = unsafe { size.assume_init() };
|
||||
Ok((size.ws_col as _, size.ws_row as _))
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
/target
|
||||
Generated
+300
@@ -0,0 +1,300 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is_terminal_polyfill",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"once_cell_polyfill",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lysp"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"dirs",
|
||||
"nom",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell_polyfill"
|
||||
version = "1.70.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.1+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "lysp"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
thiserror.workspace = true
|
||||
clap.workspace = true
|
||||
nom.workspace = true
|
||||
|
||||
cross.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,94 @@
|
||||
(defun err? (v) (= 'err (car v)))
|
||||
|
||||
(defun no-arguments () 1234)
|
||||
(defun only-required (a b c) (+ a b c))
|
||||
(defun only-optional (&optional a b c) (list a b c))
|
||||
(defun only-rest (&rest r) (length r))
|
||||
(defun required-and-optional (a b &optional c d) (list a b c d))
|
||||
(defun required-and-rest (a b &rest r) (list a b r))
|
||||
(defun optional-and-rest (&optional a b &rest r) (list a b r))
|
||||
(defun required-and-optional-and-rest (a b &optional c d &rest r) (list a b c d r))
|
||||
|
||||
; no-arguments
|
||||
(assert (= '(ok 1234) (eval '(no-arguments))))
|
||||
(assert (err? (eval '(no-arguments 1))))
|
||||
; only-required
|
||||
(assert (= '(ok 6) (eval '(only-required 1 2 3))))
|
||||
(assert (err? (eval '(only-required 1 2))))
|
||||
; only-optional
|
||||
(assert
|
||||
(=
|
||||
'(ok (nil nil nil))
|
||||
(eval '(only-optional))
|
||||
)
|
||||
)
|
||||
(assert
|
||||
(=
|
||||
'(ok (1 nil nil))
|
||||
(eval '(only-optional 1))
|
||||
)
|
||||
)
|
||||
(assert
|
||||
(=
|
||||
'(ok (1 2 nil))
|
||||
(eval '(only-optional 1 2))
|
||||
)
|
||||
)
|
||||
(assert
|
||||
(=
|
||||
'(ok (1 2 3))
|
||||
(eval '(only-optional 1 2 3))
|
||||
)
|
||||
)
|
||||
(assert (err? (eval '(only-optional 1 2 3 4))))
|
||||
; only-rest
|
||||
(assert (= 0 (only-rest)))
|
||||
(assert (= 1 (only-rest 1)))
|
||||
(assert (= 2 (only-rest 1 2)))
|
||||
(assert (= 3 (only-rest 1 2 3)))
|
||||
|
||||
; required-and-optional
|
||||
(assert (err? (eval '(required-and-optional))))
|
||||
(assert (err? (eval '(required-and-optional 1))))
|
||||
(assert
|
||||
(=
|
||||
'(1 2 nil nil)
|
||||
(required-and-optional 1 2)
|
||||
)
|
||||
)
|
||||
(assert
|
||||
(=
|
||||
'(1 2 3 nil)
|
||||
(required-and-optional 1 2 3)
|
||||
)
|
||||
)
|
||||
(assert
|
||||
(=
|
||||
'(1 2 3 4)
|
||||
(required-and-optional 1 2 3 4)
|
||||
)
|
||||
)
|
||||
(assert (err? (eval '(required-and-optional 1 2 3 4 5))))
|
||||
|
||||
; required-and-rest
|
||||
(assert (err? (eval '(required-and-rest))))
|
||||
(assert (err? (eval '(required-and-rest 1))))
|
||||
(assert (= '(1 2 nil) (required-and-rest 1 2)))
|
||||
(assert (= '(1 2 (3)) (required-and-rest 1 2 3)))
|
||||
(assert (= '(1 2 (3 4 5)) (required-and-rest 1 2 3 4 5)))
|
||||
|
||||
; optional-and-rest
|
||||
(assert (= '(nil nil nil) (optional-and-rest)))
|
||||
(assert (= '(1 nil nil) (optional-and-rest 1)))
|
||||
(assert (= '(1 2 nil) (optional-and-rest 1 2)))
|
||||
(assert (= '(1 2 (3)) (optional-and-rest 1 2 3)))
|
||||
(assert (= '(1 2 (3 4 5)) (optional-and-rest 1 2 3 4 5)))
|
||||
|
||||
; required-and-optional-and-rest
|
||||
(assert (err? (eval '(required-and-optional-and-rest))))
|
||||
(assert (err? (eval '(required-and-optional-and-rest 1))))
|
||||
(assert (= '(1 2 nil nil nil) (required-and-optional-and-rest 1 2)))
|
||||
(assert (= '(1 2 3 nil nil) (required-and-optional-and-rest 1 2 3)))
|
||||
(assert (= '(1 2 3 4 nil) (required-and-optional-and-rest 1 2 3 4)))
|
||||
(assert (= '(1 2 3 4 (5)) (required-and-optional-and-rest 1 2 3 4 5)))
|
||||
(assert (= '(1 2 3 4 (5 6 7 8)) (required-and-optional-and-rest 1 2 3 4 5 6 7 8)))
|
||||
@@ -0,0 +1,11 @@
|
||||
(assert (= (->string 1234) "1234"))
|
||||
(assert (= (->string '(1 2)) "(1 2)"))
|
||||
(assert (= (->string #t) "#T"))
|
||||
|
||||
(assert (= 1234 (string->number "1234")))
|
||||
(assert (= -1234 (string->number "-1234")))
|
||||
(assert (= 0x1234 (string->number "1234" 16)))
|
||||
(assert (= 0o1234 (string->number "1234" 8)))
|
||||
(assert (= (/ 1 2) (string->number "0.5")))
|
||||
(assert (= (/ -1 2) (string->number "-0.5")))
|
||||
(assert (= (/ -1 2) (string->number "-5e-1")))
|
||||
@@ -0,0 +1,12 @@
|
||||
(print "Argument count:" (length *args*))
|
||||
(print "Arguments as a list:" *args*)
|
||||
|
||||
(print "Iterated:")
|
||||
(let
|
||||
(index 0 current *args*)
|
||||
(while current
|
||||
(print index ":" (car current))
|
||||
(setq index (+ index 1))
|
||||
(setq current (cdr current))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
; empty env
|
||||
(setq custom-env (env/create nil))
|
||||
(setq
|
||||
custom-env-expr
|
||||
'(progn
|
||||
(defun my-function () 1234)
|
||||
)
|
||||
)
|
||||
|
||||
(defun my-function () 4321)
|
||||
(assert (= 'ok (car (eval custom-env custom-env-expr))))
|
||||
(assert (= 4321 (my-function)))
|
||||
(assert (= '(ok 1234) (eval custom-env '(my-function))))
|
||||
(assert (= 'err (car (eval custom-env '(print 1234))))) ;; print is not defined in custom-env
|
||||
(env/load-prelude custom-env)
|
||||
(assert (= '(ok nil) (eval custom-env '(print 1234))))
|
||||
@@ -0,0 +1,24 @@
|
||||
(defun factorial (x)
|
||||
(if (= x 0)
|
||||
1
|
||||
(* (factorial (- x 1)) x)
|
||||
)
|
||||
)
|
||||
|
||||
(defun loop-factorial (x)
|
||||
(let (i 0 acc 1)
|
||||
(while (< i x)
|
||||
(setq i (+ i 1))
|
||||
(setq acc (* acc i))
|
||||
)
|
||||
acc
|
||||
)
|
||||
)
|
||||
|
||||
;; TODO for loops?
|
||||
(let (i 0)
|
||||
(while (<= i 15)
|
||||
(print i "\t" (factorial i) "\t" (loop-factorial i))
|
||||
(setq i (+ i 1))
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,52 @@
|
||||
(setq h (hash/new
|
||||
'(key . value)
|
||||
'(1 . 2)
|
||||
'("string" . 3)
|
||||
'(3 . 4)))
|
||||
|
||||
(assert (= 'value (hash/get h 'key)))
|
||||
(assert (= 2 (hash/get h 1)))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(hash/map! (lambda (_ v) v) h)
|
||||
(assert (= 'value (hash/get h 'key)))
|
||||
(assert (= 2 (hash/get h 1)))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(hash/remove! h 'key)
|
||||
(assert (= NIL (hash/get h 'key)))
|
||||
|
||||
(hash/map! (lambda (_ v) (+ v 100)) h)
|
||||
(assert (= 102 (hash/get h 1)))
|
||||
(assert (= 103 (hash/get h "string")))
|
||||
(assert (= 100 (hash/get h 0 100)))
|
||||
(assert (= NIL (hash/get h 0)))
|
||||
|
||||
(setq hl (hash->list h))
|
||||
(assert (= '(3 . 104) (find (lambda (a) (= (car a) 3)) hl)))
|
||||
(assert (= '("string" . 103) (find (lambda (a) (= (car a) "string")) hl)))
|
||||
(assert (= '(1 . 102) (find (lambda (a) (= (car a) 1)) hl)))
|
||||
|
||||
(setq sum-1 0)
|
||||
(hash/for-each (lambda (_ v) (setq sum-1 (+ sum-1 v))) h)
|
||||
(setq sum-2 (hash/fold 0 (lambda (a _ v) (+ a v)) h))
|
||||
(assert (= sum-1 sum-2 309))
|
||||
|
||||
(hash/filter! (lambda (k _) (/= k "string")) h)
|
||||
;; Hash equality
|
||||
(assert (= h (hash/new '(1 . 102) '(3 . 104))))
|
||||
(assert (=
|
||||
(hash/new '(1 . 2) '(2 . 3) '("string" . "value"))
|
||||
(list->hash '((2 . 3) ("string" . "value") (1 . 2)))
|
||||
))
|
||||
|
||||
;; hash->list->hash idempotence
|
||||
(setq h (hash/new))
|
||||
(let (i 0)
|
||||
(while (< i 10000)
|
||||
(hash/put! h (+ "key" i) (+ "value" i))
|
||||
(setq i (+ i 1))
|
||||
))
|
||||
(assert (= h (list->hash (hash->list h))))
|
||||
@@ -0,0 +1 @@
|
||||
(import "io.lysp")
|
||||
@@ -0,0 +1,21 @@
|
||||
(print "FS API:")
|
||||
(runtime-debug (print (fs/exists? (car *args*))))
|
||||
(runtime-debug (print (fs/file? (car *args*))))
|
||||
(runtime-debug (print (fs/directory? (car *args*))))
|
||||
(runtime-debug (print (fs/read-to-string (car *args*))))
|
||||
(runtime-debug (print (fs/home-directory)))
|
||||
|
||||
(print "Stream API:")
|
||||
|
||||
(print (+ (car *args*) ":"))
|
||||
(setq handle (stream/open (car *args*)))
|
||||
|
||||
(loop
|
||||
(let (data (stream/read handle))
|
||||
(if data
|
||||
(stream/write stream/stdout data)
|
||||
(break)
|
||||
)
|
||||
)
|
||||
)
|
||||
(stream/close handle)
|
||||
@@ -0,0 +1,13 @@
|
||||
(setq xs '(1 2 3 4 5))
|
||||
(setq ys '((1 2) (3 4) (5 6)))
|
||||
|
||||
(assert (= (length xs) 5))
|
||||
(assert (= '(5 4 3 2 1) (reverse xs)))
|
||||
(assert (= 15 (apply + xs) (fold + 0 xs)))
|
||||
(assert (= 120 (apply * xs) (fold * 1 xs)))
|
||||
|
||||
(assert (= (length ys) 3))
|
||||
(assert (= '((5 6) (3 4) (1 2)) (reverse ys)))
|
||||
(assert (= '((2 1) (4 3) (6 5)) (map reverse ys)))
|
||||
(assert (= '(2 2 2) (map length ys)))
|
||||
(assert (= '(1 2 3 4 5 6) (flatmap identity ys)))
|
||||
@@ -0,0 +1,40 @@
|
||||
;; loop, broken by (return)
|
||||
(setq
|
||||
looped-loop
|
||||
(let (i 0)
|
||||
(loop
|
||||
(if (< i 10)
|
||||
(setq i (+ i 1))
|
||||
(break)
|
||||
)
|
||||
)
|
||||
i
|
||||
)
|
||||
)
|
||||
|
||||
;; while, broken prematurely
|
||||
(setq
|
||||
looped-while
|
||||
(let (i 0)
|
||||
(while (< i 20)
|
||||
(setq i (+ i 1))
|
||||
(if (= i 10) (break))
|
||||
)
|
||||
i
|
||||
)
|
||||
)
|
||||
|
||||
;; while, broken by condition
|
||||
(setq
|
||||
looped-while-full
|
||||
(let (i 0)
|
||||
(while (< i 10)
|
||||
(setq i (+ i 1))
|
||||
)
|
||||
i
|
||||
)
|
||||
)
|
||||
|
||||
;; All loops execute the same count of times
|
||||
(assert (= looped-loop looped-while looped-while-full))
|
||||
(print "Test succeeded")
|
||||
@@ -0,0 +1,68 @@
|
||||
; quoting rules
|
||||
|
||||
(setq glob0 123)
|
||||
(setq glob1 '(2 3 4))
|
||||
|
||||
(assert (= (list 1 2 3) '(1 2 3) `(1 2 3) (quote (1 2 3))))
|
||||
(assert (= '(1 glob0 3) `(1 glob0 3)))
|
||||
(assert (= '(1 123 3) `(1 ,glob0 3)))
|
||||
(assert (= '(1 glob1 5) `(1 glob1 5)))
|
||||
(assert (= '(1 (2 3 4) 5) `(1 ,glob1 5)))
|
||||
(assert (= '(1 2 3 4 5) `(1 ,@glob1 5)))
|
||||
(assert (= '(2 3 4 5) `(,@glob1 5)))
|
||||
(assert (= '(1 2 3 4) `(1 ,@glob1)))
|
||||
(assert (= '(2 3 4) `(,@glob1)))
|
||||
(assert (= '((2 3 4)) `(,glob1)))
|
||||
|
||||
; Nested
|
||||
(assert (= '((123 123) (123 123)) `((,glob0 ,glob0) (,glob0 ,glob0))))
|
||||
(assert (= '(2 3 4 2 3 4) `(,@glob1 ,@glob1)))
|
||||
(assert (= '((((2 3 4)))) `(((,glob1)))))
|
||||
|
||||
; Read value of a constructed symbol
|
||||
(defun read-indexed (prefix index) (get (symbol (+ (->string prefix) index))))
|
||||
(assert (= 123 (read-indexed 'glob 0)))
|
||||
(assert (= '(2 3 4) (read-indexed 'glob 1)))
|
||||
(assert (= nil (read-indexed 'glob 2)))
|
||||
|
||||
; those are prelude, but defined in lysp itself:
|
||||
|
||||
(print "The previously printed expression is AFTER this one in the code")
|
||||
(compile-debug
|
||||
(when #t
|
||||
(print "a")
|
||||
(print "b")
|
||||
)
|
||||
)
|
||||
(runtime-debug
|
||||
(when #t
|
||||
(print "a")
|
||||
(print "b")
|
||||
)
|
||||
)
|
||||
|
||||
(when 1
|
||||
(print "a")
|
||||
(print "b")
|
||||
)
|
||||
(unless nil
|
||||
(print "c")
|
||||
(print "d")
|
||||
)
|
||||
|
||||
|
||||
; catch macro
|
||||
(print "Failing in a catch block...")
|
||||
(catch
|
||||
(print a)
|
||||
e (print "Caught an error:" e)
|
||||
)
|
||||
|
||||
(print "... doesn't fail the execution")
|
||||
|
||||
(defmacro nested-0 (x) `(list 2 ,x))
|
||||
(defmacro nested-1 (x) `(list 1 (nested-0 ,x)))
|
||||
(assert (= '(1 (2 3)) (nested-1 3)))
|
||||
|
||||
;; (explain ...) must work on macros
|
||||
(assert (explain and))
|
||||
@@ -0,0 +1,72 @@
|
||||
; add
|
||||
(assert (= (+ 1 2 3) 6))
|
||||
(assert (= (+) 0))
|
||||
(assert (= (+ 1) 1))
|
||||
|
||||
; sub
|
||||
(assert (= (- 1) -1))
|
||||
(assert (= (- 1 2) -1))
|
||||
|
||||
; mul
|
||||
(assert (= (*) 1))
|
||||
(assert (= (* 2) 2))
|
||||
(assert (= (* 2 3 4) 24))
|
||||
|
||||
; div
|
||||
(assert (= (/ 1 0) #inf))
|
||||
(assert (= (/ -1 0) -#inf))
|
||||
(assert (= (/ 6 2) 3))
|
||||
(assert (= (/ 12 3 2) 2))
|
||||
(assert (= (/ 6 4) (/ 3 2)))
|
||||
(assert (= (/ 4) (/ 25 100))) ; reciprocal
|
||||
|
||||
; rem
|
||||
(assert (= (% 6 2) 0))
|
||||
(assert (= (% 6 4) 2))
|
||||
|
||||
;;;; NaN/infinity handling
|
||||
(assert (≠ #nan #nan))
|
||||
(assert (= #inf #inf))
|
||||
(assert (≠ -#inf #inf))
|
||||
(assert (> #inf 0 -#inf))
|
||||
|
||||
;;;; Ordering
|
||||
(assert (< 1 2 3))
|
||||
(assert (not (< 1 1 2 3)))
|
||||
(assert (<= 1 1 2 3 3))
|
||||
(assert (> 3 2 1))
|
||||
(assert (not (> 3 3 2 1)))
|
||||
(assert (>= 3 3 2 1))
|
||||
|
||||
(assert (= 1 1 1 1))
|
||||
(assert (not (= 1 1 2 1)))
|
||||
|
||||
(assert (≠ 1 2 3 4))
|
||||
(assert (not (≠ 1 2 2 3)))
|
||||
|
||||
;;;; Logic
|
||||
(assert (and #t #t 1))
|
||||
(assert (not (and #t #f 1)))
|
||||
(assert (not (or nil 0 #f)))
|
||||
; (and ...) and (or ...) must short-circuit
|
||||
; (and ...) and (or ...) must not double-evaluate
|
||||
(setq no-double-eval-and 0)
|
||||
(setq no-double-eval-or 0)
|
||||
(and
|
||||
#t
|
||||
(progn
|
||||
(setq no-double-eval-and (+ no-double-eval-and 1))
|
||||
#t
|
||||
)
|
||||
#f
|
||||
(error "(and ...) must short-circuit after #F"))
|
||||
(or
|
||||
#f
|
||||
(progn
|
||||
(setq no-double-eval-or (+ no-double-eval-or 1))
|
||||
#t
|
||||
)
|
||||
#t
|
||||
(error "(or ...) must short-circuit after #T"))
|
||||
(assert (= no-double-eval-and 1))
|
||||
(assert (= no-double-eval-or 1))
|
||||
@@ -0,0 +1,34 @@
|
||||
(defun cadr (x) (car (cdr x)))
|
||||
|
||||
(defun map-ok-err (f-ok f-err result)
|
||||
(if (= (car result) 'ok)
|
||||
`(ok ,(f-ok (cadr result)))
|
||||
`(err ,(f-err (cadr result)))
|
||||
)
|
||||
)
|
||||
(defun map-ok (f-ok result) (map-ok-err f-ok identity result))
|
||||
(defun map-err (f-err result) (map-ok-err identity f-err result))
|
||||
|
||||
(defun repl-print (value)
|
||||
(print "==>" value)
|
||||
)
|
||||
(defun repl-eval-print (expression)
|
||||
(map-ok-err repl-print repl-eval-error (eval expression))
|
||||
)
|
||||
|
||||
(defun repl-eval-error (error)
|
||||
(print "Evaluation error:")
|
||||
(print error)
|
||||
)
|
||||
(defun repl-read-error (error)
|
||||
(print "Parse error:")
|
||||
(print error)
|
||||
)
|
||||
|
||||
(loop
|
||||
(let (expression (read))
|
||||
(if expression NIL (break))
|
||||
(setq expression (unquote expression))
|
||||
(map-ok-err repl-eval-print repl-read-error expression)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
(defun replace-argument (x) (let (x 1) x))
|
||||
|
||||
(defun replace-let ()
|
||||
(let (x 1)
|
||||
(setq x 2) ;; mutate local x
|
||||
x
|
||||
)
|
||||
)
|
||||
|
||||
(defun reassignment-in-a-loop (iterations)
|
||||
(let (x 0 y 1)
|
||||
(while (< x iterations)
|
||||
(setq y (* y 2))
|
||||
(setq x (+ x 1))
|
||||
)
|
||||
y
|
||||
)
|
||||
)
|
||||
|
||||
(assert (= (replace-argument 2) 1))
|
||||
(assert (= (replace-argument 3) 1))
|
||||
(assert (= (replace-let) 2))
|
||||
(assert (= (reassignment-in-a-loop 4) 16))
|
||||
|
||||
;; capture upvalue
|
||||
(assert
|
||||
(=
|
||||
123
|
||||
(let
|
||||
(loc0 123)
|
||||
((lambda () loc0))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; mutate upvalue
|
||||
(assert
|
||||
(=
|
||||
321
|
||||
(let
|
||||
(loc0 123)
|
||||
((lambda () (setq loc0 321)))
|
||||
loc0
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,56 @@
|
||||
;; Conditional
|
||||
(if #t 1 2)
|
||||
(cond
|
||||
[(= 1 2 3) #f]
|
||||
[(= 1 1 1) #t]
|
||||
[`otherwise 1234]
|
||||
)
|
||||
|
||||
;; Loops
|
||||
(while #f
|
||||
1
|
||||
2
|
||||
3)
|
||||
(loop
|
||||
1
|
||||
2
|
||||
3
|
||||
(break))
|
||||
|
||||
;; Functions
|
||||
(defun a (x y z w) 1 2 3 x)
|
||||
(lambda (x y z w) 1 2 3 x)
|
||||
|
||||
;; Macros
|
||||
(defmacro my-quote (x) `(quote ,x))
|
||||
(print (my-quote (a b c)))
|
||||
|
||||
;; vectors
|
||||
#[1 2 3]
|
||||
|
||||
; alternate syntax for lists
|
||||
[] () nil NIL
|
||||
|
||||
;; progn
|
||||
(progn
|
||||
1
|
||||
2
|
||||
3)
|
||||
|
||||
;; setq
|
||||
(setq glob-value "my-global")
|
||||
|
||||
;; let
|
||||
(let
|
||||
(x 1 y 2)
|
||||
x
|
||||
)
|
||||
|
||||
(let* (x 1 y x) y)
|
||||
|
||||
;; Quoting
|
||||
`(ok ,glob-value)
|
||||
|
||||
;; Evaluation rules
|
||||
; (progn ...) must evaluate to the last expression
|
||||
(assert (= (progn 1 2 3) 3))
|
||||
@@ -0,0 +1,55 @@
|
||||
;; open upvalue get
|
||||
(setq res-1
|
||||
(let (a 123)
|
||||
((lambda (b) (+ a b)) 321)
|
||||
))
|
||||
;; shared open upvalue get
|
||||
(setq res-2
|
||||
(let (a 123)
|
||||
(let
|
||||
(
|
||||
x ((lambda (b) (+ a b) (+ a b)) 321)
|
||||
y ((lambda (b) (+ a b) (* a b)) 2)
|
||||
)
|
||||
(+ x y)
|
||||
)
|
||||
)
|
||||
)
|
||||
;; closed upvalue get
|
||||
(setq func-0 (let (a 123) (lambda (b) (+ a b))))
|
||||
|
||||
;; open upvalue set
|
||||
(setq res-3
|
||||
(let (a 123)
|
||||
((lambda (b) (setq a b)) 321)
|
||||
a
|
||||
)
|
||||
)
|
||||
;; shared open upvalue set
|
||||
(setq res-4
|
||||
(let (a 123)
|
||||
(let (
|
||||
funcs (list
|
||||
(lambda () (setq a 1))
|
||||
(lambda () (setq a (+ a 1)))
|
||||
(lambda () (setq a (+ a 2)))
|
||||
)
|
||||
)
|
||||
(while (not (nil? funcs))
|
||||
((car funcs))
|
||||
(setq funcs (cdr funcs))
|
||||
)
|
||||
|
||||
a
|
||||
)
|
||||
)
|
||||
)
|
||||
;; closed upvalue set
|
||||
(setq func-1 (let (a 123) (lambda (b) (setq a (+ a b)) a)))
|
||||
|
||||
(assert (= 444 res-1))
|
||||
(assert (= 690 res-2))
|
||||
(assert (= 444 (func-0 321)))
|
||||
(assert (= 321 res-3))
|
||||
(assert (= 4 res-4))
|
||||
(assert (= 444 (func-1 321)))
|
||||
@@ -0,0 +1,656 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile::CompileOptions,
|
||||
vm::{
|
||||
instruction::BranchOffset,
|
||||
value::{BytecodeFunction, StringValue},
|
||||
},
|
||||
};
|
||||
use crate::{
|
||||
compile::{
|
||||
error::CompileError,
|
||||
function::FunctionSignature,
|
||||
instruction::Emitted,
|
||||
syntax::{Expression, FunctionBody},
|
||||
},
|
||||
vm::{
|
||||
Value,
|
||||
instruction::{ConstantId, ImmediateInteger, Instruction, LocalId},
|
||||
value::{BooleanValue, IdentifierValue, NumberValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FunctionBlock {
|
||||
parent: Option<usize>,
|
||||
identifier: Option<IdentifierValue>,
|
||||
docstring: Option<StringValue>,
|
||||
signature: FunctionSignature,
|
||||
script_path: Option<Rc<Path>>,
|
||||
|
||||
// Data
|
||||
pub(crate) constants: Vec<Value>,
|
||||
locals: Vec<Local>,
|
||||
upvalues: Vec<UpvalueDef>,
|
||||
scope_depth: usize,
|
||||
|
||||
// Code
|
||||
pub(crate) instructions: Vec<Emitted>,
|
||||
labels: Vec<usize>,
|
||||
loop_stack: Vec<LoopStackEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Local {
|
||||
name: IdentifierValue,
|
||||
depth: isize,
|
||||
is_captured: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct UpvalueDef {
|
||||
pub(crate) index: LocalId,
|
||||
pub(crate) is_local: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct LoopStackEntry {
|
||||
label_entry: usize,
|
||||
label_exit: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CompileValue {
|
||||
Nil,
|
||||
Identifier(IdentifierValue),
|
||||
Integer(NumberValue),
|
||||
Boolean(BooleanValue),
|
||||
String(StringValue),
|
||||
LocalFunction(usize),
|
||||
Quote(Rc<Value>),
|
||||
Value(Value),
|
||||
Stack,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompileContext {
|
||||
pub function_blocks: Vec<FunctionBlock>,
|
||||
pub(crate) current: usize,
|
||||
pub(crate) options: CompileOptions,
|
||||
script_path: Option<Rc<Path>>,
|
||||
}
|
||||
|
||||
pub trait Compile {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError>;
|
||||
}
|
||||
|
||||
impl CompileContext {
|
||||
pub fn new(
|
||||
options: CompileOptions,
|
||||
root_name: Option<IdentifierValue>,
|
||||
script_path: Option<Rc<Path>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
function_blocks: vec![FunctionBlock::root(root_name, script_path.clone())],
|
||||
current: 0,
|
||||
options,
|
||||
script_path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile_value(
|
||||
options: CompileOptions,
|
||||
chunk_name: Option<IdentifierValue>,
|
||||
value: &Value,
|
||||
script_path: Option<Rc<Path>>,
|
||||
) -> Result<Rc<BytecodeFunction>, CompileError> {
|
||||
let mut cx = Self::new(options, chunk_name, script_path);
|
||||
let expression = Expression::parse(value).map_err(CompileError::Parse)?;
|
||||
let value = expression.compile(&mut cx)?;
|
||||
cx.compile_return_value(value)?;
|
||||
cx.to_bytecode()
|
||||
}
|
||||
|
||||
pub fn compile_function(
|
||||
&mut self,
|
||||
identifier: Option<IdentifierValue>,
|
||||
docstring: Option<StringValue>,
|
||||
signature: &FunctionSignature,
|
||||
body: &FunctionBody,
|
||||
) -> Result<usize, CompileError> {
|
||||
// TODO signature
|
||||
let index = self.push_lambda_context(identifier, docstring, signature)?;
|
||||
for expression in body.head.iter() {
|
||||
self.compile_statement(expression)?;
|
||||
}
|
||||
let value = body.tail.compile(self)?;
|
||||
self.compile_return_value(value)?;
|
||||
self.pop_context();
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn compile_return_value(&mut self, value: CompileValue) -> Result<(), CompileError> {
|
||||
self.push(value)?;
|
||||
self.emit(Instruction::Return);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push_lambda_context(
|
||||
&mut self,
|
||||
identifier: Option<IdentifierValue>,
|
||||
docstring: Option<StringValue>,
|
||||
signature: &FunctionSignature,
|
||||
) -> Result<usize, CompileError> {
|
||||
if self.options.trace_compile {
|
||||
eprintln!("COMPILE: push_lambda_context({identifier:?})");
|
||||
}
|
||||
let block = FunctionBlock::new(
|
||||
Some(self.current),
|
||||
identifier,
|
||||
docstring,
|
||||
signature,
|
||||
self.script_path.clone(),
|
||||
);
|
||||
let index = self.function_blocks.len();
|
||||
self.function_blocks.push(block);
|
||||
self.current = index;
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
pub fn pop_context(&mut self) {
|
||||
if self.options.trace_compile {
|
||||
let current_name = self.identifier.clone();
|
||||
eprintln!("COMPILE: pop_lambda_context({current_name:?})");
|
||||
}
|
||||
let index = self
|
||||
.parent
|
||||
.expect("cannot pop out of root function context");
|
||||
self.current = index;
|
||||
}
|
||||
|
||||
pub fn compile_statement(&mut self, expression: &Expression) -> Result<(), CompileError> {
|
||||
let value = expression.compile(self)?;
|
||||
self.discard(value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_set_global(
|
||||
&mut self,
|
||||
identifier: IdentifierValue,
|
||||
value: CompileValue,
|
||||
) -> Result<(), CompileError> {
|
||||
self.push(value)?;
|
||||
self.get_constant(Value::Identifier(identifier))?;
|
||||
self.emit(Instruction::SetGlobal);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_declare_macro(
|
||||
&mut self,
|
||||
identifier: IdentifierValue,
|
||||
value: CompileValue,
|
||||
) -> Result<(), CompileError> {
|
||||
self.push(value)?;
|
||||
self.get_constant(Value::Identifier(identifier))?;
|
||||
self.emit(Instruction::DeclareMacro);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn compile_assign(
|
||||
&mut self,
|
||||
identifier: IdentifierValue,
|
||||
value: CompileValue,
|
||||
) -> Result<(), CompileError> {
|
||||
self.push(value)?;
|
||||
|
||||
if let Some(local) = self.resolve_local(identifier.as_ref())? {
|
||||
self.emit(Instruction::SetLocal);
|
||||
self.emit(local);
|
||||
} else if let Some(upvalue) = self.resolve_upvalue(identifier.as_ref())? {
|
||||
self.emit(Instruction::SetUpvalue);
|
||||
self.emit(upvalue);
|
||||
} else {
|
||||
self.get_constant(Value::Identifier(identifier))?;
|
||||
self.emit(Instruction::SetGlobal);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_constant(&mut self, value: Value) -> Result<(), CompileError> {
|
||||
let index = self.constant(value)?;
|
||||
self.emit(Instruction::PushConstant);
|
||||
self.emit(index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: CompileValue) -> Result<(), CompileError> {
|
||||
match value {
|
||||
CompileValue::Nil => {
|
||||
self.emit(Instruction::PushNil);
|
||||
}
|
||||
CompileValue::String(value) => {
|
||||
self.get_constant(Value::String(value))?;
|
||||
}
|
||||
CompileValue::Quote(value) => {
|
||||
self.get_constant(value.as_ref().clone())?;
|
||||
}
|
||||
CompileValue::Value(value) => {
|
||||
self.get_constant(value)?;
|
||||
}
|
||||
CompileValue::Integer(value) => {
|
||||
if let Ok(immediate) = ImmediateInteger::try_from(value) {
|
||||
self.emit(Instruction::PushInteger);
|
||||
self.emit(immediate);
|
||||
} else {
|
||||
self.get_constant(Value::Number(value))?;
|
||||
}
|
||||
}
|
||||
CompileValue::Boolean(BooleanValue(value)) => match value {
|
||||
true => self.emit(Instruction::PushTrue),
|
||||
false => self.emit(Instruction::PushFalse),
|
||||
},
|
||||
CompileValue::Identifier(name) => {
|
||||
if let Some(local) = self.resolve_local(name.as_ref())? {
|
||||
self.emit(Instruction::GetLocal);
|
||||
self.emit(local);
|
||||
} else if let Some(upvalue) = self.resolve_upvalue(name.as_ref())? {
|
||||
self.emit(Instruction::GetUpvalue);
|
||||
self.emit(upvalue);
|
||||
} else {
|
||||
self.get_constant(Value::Identifier(name))?;
|
||||
self.emit(Instruction::GetGlobal);
|
||||
}
|
||||
}
|
||||
CompileValue::LocalFunction(index) => {
|
||||
let function = self.function_blocks[index].to_bytecode()?;
|
||||
self.get_constant(Value::Function(function.clone()))?;
|
||||
if !function.upvalues.is_empty() {
|
||||
self.emit(Instruction::MakeClosure);
|
||||
}
|
||||
}
|
||||
CompileValue::Stack => (),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn discard(&mut self, value: CompileValue) {
|
||||
if matches!(value, CompileValue::Stack) {
|
||||
self.emit(Instruction::Drop);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn guard_argument(&mut self) {
|
||||
let depth = self.scope_depth as isize;
|
||||
self.locals.push(Local {
|
||||
name: "".into(),
|
||||
depth,
|
||||
is_captured: false,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn unguard_argument(&mut self) {
|
||||
let guard = self
|
||||
.locals
|
||||
.pop()
|
||||
.expect("unguard_argument() called with an empty locals stack");
|
||||
let stack_index = self.locals.len();
|
||||
if guard.name.as_ref() != "" {
|
||||
panic!(
|
||||
"unguard_argument(): the local is not a guard: {:?} at stack index {}",
|
||||
guard.name, stack_index,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push_scope(&mut self) {
|
||||
self.scope_depth += 1;
|
||||
if self.options.trace_compile {
|
||||
eprintln!("COMPILE: push_scope({})", self.scope_depth);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop_scope(&mut self, out_value: Option<CompileValue>) -> Result<(), CompileError> {
|
||||
if self.options.trace_compile {
|
||||
eprintln!("COMPILE: pop_scope({})", self.scope_depth);
|
||||
}
|
||||
if self.scope_depth == 0 {
|
||||
panic!("Cannot pop out of root scope");
|
||||
}
|
||||
self.scope_depth -= 1;
|
||||
let Some(out_value) = out_value else { todo!() };
|
||||
self.push(out_value)?;
|
||||
self.emit(Instruction::SetTemp);
|
||||
for i in (0..self.locals.len()).rev() {
|
||||
if self.locals[i].depth <= self.scope_depth as isize {
|
||||
break;
|
||||
}
|
||||
if self.locals[i].is_captured {
|
||||
self.emit(Instruction::CloseUpvalue);
|
||||
} else {
|
||||
self.emit(Instruction::Drop);
|
||||
}
|
||||
self.locals.pop();
|
||||
}
|
||||
self.emit(Instruction::GetTemp);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn resolve_upvalue_inner(
|
||||
&mut self,
|
||||
at: Option<usize>,
|
||||
name: &str,
|
||||
) -> Result<Option<LocalId>, CompileError> {
|
||||
let Some(at) = at else {
|
||||
return Ok(None);
|
||||
};
|
||||
let block = &mut self.function_blocks[at];
|
||||
|
||||
if let Some(local) = block.resolve_local(name)? {
|
||||
block.locals[usize::from(local)].is_captured = true;
|
||||
|
||||
return self
|
||||
.add_upvalue(UpvalueDef {
|
||||
index: local,
|
||||
is_local: true,
|
||||
})
|
||||
.map(Some);
|
||||
}
|
||||
|
||||
let parent = block.parent;
|
||||
if let Some(upvalue) = self.resolve_upvalue_inner(parent, name)? {
|
||||
return self
|
||||
.add_upvalue(UpvalueDef {
|
||||
index: upvalue,
|
||||
is_local: false,
|
||||
})
|
||||
.map(Some);
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn resolve_upvalue(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
|
||||
self.resolve_upvalue_inner(self.parent, name)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CompileContext {
|
||||
type Target = FunctionBlock;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.function_blocks[self.current]
|
||||
}
|
||||
}
|
||||
impl DerefMut for CompileContext {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.function_blocks[self.current]
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionBlock {
|
||||
fn new(
|
||||
parent: Option<usize>,
|
||||
identifier: Option<IdentifierValue>,
|
||||
docstring: Option<StringValue>,
|
||||
signature: &FunctionSignature,
|
||||
script_path: Option<Rc<Path>>,
|
||||
) -> Self {
|
||||
let mut block = Self {
|
||||
parent,
|
||||
identifier,
|
||||
docstring,
|
||||
signature: signature.clone(),
|
||||
constants: vec![],
|
||||
locals: vec![],
|
||||
upvalues: vec![],
|
||||
scope_depth: 0,
|
||||
instructions: vec![],
|
||||
labels: vec![],
|
||||
loop_stack: vec![],
|
||||
script_path,
|
||||
};
|
||||
for required in signature.required_arguments.iter() {
|
||||
block
|
||||
.add_local(required.clone(), Some(-100))
|
||||
.expect("couldn't add an argument");
|
||||
}
|
||||
for optional in signature.optional_arguments.iter() {
|
||||
block
|
||||
.add_local(optional.clone(), Some(-100))
|
||||
.expect("couldn't add an argument");
|
||||
}
|
||||
if let Some(rest) = signature.rest_argument.as_ref() {
|
||||
block
|
||||
.add_local(rest.clone(), Some(-100))
|
||||
.expect("couldn't add an argument");
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn root(identifier: Option<IdentifierValue>, script_path: Option<Rc<Path>>) -> Self {
|
||||
Self::new(
|
||||
None,
|
||||
identifier,
|
||||
None,
|
||||
&FunctionSignature {
|
||||
required_arguments: vec![],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None,
|
||||
},
|
||||
script_path,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn enter_loop(&mut self, label_entry: usize, label_exit: usize) {
|
||||
self.loop_stack.push(LoopStackEntry {
|
||||
label_entry,
|
||||
label_exit,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn leave_loop(&mut self) {
|
||||
let v = self.loop_stack.pop();
|
||||
if v.is_none() {
|
||||
panic!("Compiler error: leave_loop() called outside any enclosing loop");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit_break(&mut self) -> Result<(), CompileError> {
|
||||
let Some(entry) = self.loop_stack.last() else {
|
||||
return Err(CompileError::BreakOutsideOfLoop);
|
||||
};
|
||||
self.emit(Emitted::Jump(entry.label_exit));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn emit_continue(&mut self) -> Result<(), CompileError> {
|
||||
let Some(entry) = self.loop_stack.last() else {
|
||||
return Err(CompileError::ContinueOutsideOfLoop);
|
||||
};
|
||||
self.emit(Emitted::Jump(entry.label_entry));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn to_bytecode(&self) -> Result<Rc<BytecodeFunction>, CompileError> {
|
||||
if !self.loop_stack.is_empty() {
|
||||
panic!("Compiler error: loop stack not empty on function completion");
|
||||
}
|
||||
|
||||
let mut instructions = vec![];
|
||||
let mut resolved_labels = HashMap::new();
|
||||
let mut patch_branches = HashMap::new();
|
||||
|
||||
for &offset in self.labels.iter() {
|
||||
resolved_labels.insert(offset, None);
|
||||
}
|
||||
|
||||
// Emit all code, with placeholders for branch targets
|
||||
for (offset, emitted) in self.instructions.iter().enumerate() {
|
||||
let ip = instructions.len();
|
||||
// Resolve real label address
|
||||
if let Some(real) = resolved_labels.get_mut(&offset) {
|
||||
assert!(real.is_none());
|
||||
*real = Some(ip);
|
||||
}
|
||||
match emitted {
|
||||
&Emitted::Jump(label) => {
|
||||
instructions.push(Instruction::Jump.into());
|
||||
patch_branches.insert(instructions.len(), label);
|
||||
instructions.push(0xFF);
|
||||
instructions.push(0xFF);
|
||||
}
|
||||
&Emitted::Branch(label) => {
|
||||
instructions.push(Instruction::Branch.into());
|
||||
patch_branches.insert(instructions.len(), label);
|
||||
instructions.push(0xFF);
|
||||
instructions.push(0xFF);
|
||||
}
|
||||
Emitted::LocalId(local) => {
|
||||
instructions.extend_from_slice(&local.to_bytes());
|
||||
}
|
||||
Emitted::ConstantId(index) => {
|
||||
instructions.extend_from_slice(&index.to_bytes());
|
||||
}
|
||||
Emitted::ArgumentCount(count) => {
|
||||
instructions.extend_from_slice(&count.to_bytes());
|
||||
}
|
||||
Emitted::ImmediateInteger(value) => {
|
||||
instructions.extend_from_slice(&value.to_bytes())
|
||||
}
|
||||
&Emitted::Bool(value) => instructions.push(value as u8),
|
||||
&Emitted::Instruction(instruction) => instructions.push(u8::from(instruction)),
|
||||
}
|
||||
}
|
||||
|
||||
// Patch branch targets
|
||||
for (position, label) in patch_branches {
|
||||
let label_input = self.labels.get(label).copied().unwrap();
|
||||
let branch_target = resolved_labels.get(&label_input).copied().unwrap().unwrap();
|
||||
let branch_source = position + 2;
|
||||
let branch_offset = branch_target
|
||||
.checked_signed_diff(branch_source)
|
||||
.and_then(|diff| BranchOffset::try_from(diff).ok())
|
||||
.ok_or_else(|| CompileError::CannotEmitBranch(branch_source, branch_target))?;
|
||||
instructions[position..position + 2].copy_from_slice(&branch_offset.to_bytes());
|
||||
}
|
||||
|
||||
Ok(Rc::new(BytecodeFunction {
|
||||
identifier: self.identifier.clone(),
|
||||
instructions: instructions.into(),
|
||||
docstring: self.docstring.clone(),
|
||||
constants: self.constants.iter().cloned().collect(),
|
||||
upvalues: self.upvalues.iter().copied().collect(),
|
||||
required_count: self.signature.required_arguments.len(),
|
||||
optional_count: self.signature.optional_arguments.len(),
|
||||
has_rest: self.signature.rest_argument.is_some(),
|
||||
script: self.script_path.clone(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn new_label(&mut self) -> usize {
|
||||
let index = self.labels.len();
|
||||
self.labels.push(usize::MAX);
|
||||
index
|
||||
}
|
||||
|
||||
pub fn adjust_label(&mut self, label: usize) {
|
||||
let address = self.instructions.len();
|
||||
self.labels[label] = address;
|
||||
}
|
||||
|
||||
pub fn emit<I: Into<Emitted>>(&mut self, insn: I) {
|
||||
let emitted = insn.into();
|
||||
self.instructions.push(emitted);
|
||||
}
|
||||
|
||||
pub fn constant(&mut self, value: Value) -> Result<ConstantId, CompileError> {
|
||||
if let Some(index) = self.constants.iter().position(|v| v == &value) {
|
||||
return Ok(index.try_into().unwrap());
|
||||
}
|
||||
let index = ConstantId::try_from(self.constants.len())
|
||||
.map_err(|_| CompileError::TooManyConstants)?;
|
||||
self.constants.push(value);
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
fn add_local(
|
||||
&mut self,
|
||||
name: IdentifierValue,
|
||||
depth: Option<isize>,
|
||||
) -> Result<LocalId, CompileError> {
|
||||
let index =
|
||||
LocalId::try_from(self.locals.len()).map_err(|_| CompileError::TooManyLocals)?;
|
||||
self.locals.push(Local {
|
||||
name,
|
||||
is_captured: false,
|
||||
depth: depth.unwrap_or(-1),
|
||||
});
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
fn add_upvalue(&mut self, upvalue: UpvalueDef) -> Result<LocalId, CompileError> {
|
||||
if let Some(index) = self.upvalues.iter().position(|uv| upvalue == *uv) {
|
||||
return Ok(index.try_into().unwrap());
|
||||
}
|
||||
// Add new
|
||||
let index =
|
||||
LocalId::try_from(self.upvalues.len()).map_err(|_| CompileError::TooManyLocals)?;
|
||||
self.upvalues.push(upvalue);
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
// Binding management
|
||||
|
||||
pub fn init_local(&mut self, index: LocalId) {
|
||||
self.locals[usize::from(index)].depth = self.scope_depth as isize;
|
||||
}
|
||||
|
||||
pub fn bind_local(&mut self, name: IdentifierValue) -> Result<LocalId, CompileError> {
|
||||
if self.scope_depth == 0 {
|
||||
todo!();
|
||||
}
|
||||
let mut i = self.locals.len();
|
||||
while i > 0 {
|
||||
i -= 1;
|
||||
let local = &self.locals[i];
|
||||
if local.depth != -1 && local.depth < self.scope_depth as isize {
|
||||
break;
|
||||
}
|
||||
if local.name == name {
|
||||
todo!("Same name, same scope");
|
||||
}
|
||||
}
|
||||
self.add_local(name, None)
|
||||
}
|
||||
|
||||
fn resolve_local(&mut self, name: &str) -> Result<Option<LocalId>, CompileError> {
|
||||
let Some(index) = self
|
||||
.locals
|
||||
.iter()
|
||||
.rposition(|local| local.name.as_ref() == name)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let local = &self.locals[index];
|
||||
if local.depth == -1 {
|
||||
return Err(CompileError::UndefinedLocalReference(name.into()));
|
||||
}
|
||||
|
||||
Ok(Some(index.try_into().unwrap()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_compile(
|
||||
expression: &Expression,
|
||||
) -> Result<(CompileContext, CompileValue), CompileError> {
|
||||
let mut cx = CompileContext::new(Default::default(), None, None);
|
||||
let value = expression.compile(&mut cx)?;
|
||||
Ok((cx, value))
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
use crate::compile::{
|
||||
block::{Compile, CompileContext, CompileValue},
|
||||
error::CompileError,
|
||||
syntax::{LetExpression, SetqExpression},
|
||||
};
|
||||
|
||||
impl Compile for LetExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
cx.push_scope();
|
||||
let mut binding_indices = vec![];
|
||||
for binding in self.bindings.iter() {
|
||||
let index = cx.bind_local(binding.identifier.clone())?;
|
||||
let value = binding.value.compile(cx)?;
|
||||
|
||||
cx.push(value)?;
|
||||
|
||||
if self.sequential {
|
||||
// Mark initialized
|
||||
cx.init_local(index);
|
||||
} else {
|
||||
binding_indices.push(index);
|
||||
}
|
||||
}
|
||||
if !self.sequential {
|
||||
for index in binding_indices {
|
||||
// Mark initialized
|
||||
cx.init_local(index);
|
||||
}
|
||||
}
|
||||
let value = self.body.compile(cx)?;
|
||||
cx.pop_scope(Some(value))?;
|
||||
Ok(CompileValue::Stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for SetqExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
for assignment in self.pairs.iter() {
|
||||
let value = assignment.value.compile(cx)?;
|
||||
cx.compile_assign(assignment.identifier.clone(), value)?;
|
||||
}
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
use crate::compile::{
|
||||
block::{Compile, CompileContext, CompileValue},
|
||||
error::CompileError,
|
||||
instruction::Emitted,
|
||||
syntax::{
|
||||
CondExpression, IfExpression, LoopExpression, PrognExpression, ReturnExpression,
|
||||
WhileExpression,
|
||||
},
|
||||
};
|
||||
|
||||
impl Compile for WhileExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let label_entry = cx.new_label();
|
||||
let label_exit = cx.new_label();
|
||||
|
||||
// Loop condition
|
||||
cx.adjust_label(label_entry);
|
||||
let condition_value = self.condition.compile(cx)?;
|
||||
cx.push(condition_value)?;
|
||||
cx.emit(Emitted::Branch(label_exit));
|
||||
|
||||
cx.enter_loop(label_entry, label_exit);
|
||||
let body_value = self.body.compile(cx)?;
|
||||
cx.discard(body_value);
|
||||
cx.leave_loop();
|
||||
cx.emit(Emitted::Jump(label_entry));
|
||||
cx.adjust_label(label_exit);
|
||||
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for LoopExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let label_entry = cx.new_label();
|
||||
let label_exit = cx.new_label();
|
||||
|
||||
cx.adjust_label(label_entry);
|
||||
cx.enter_loop(label_entry, label_exit);
|
||||
let body_value = self.body.compile(cx)?;
|
||||
cx.discard(body_value);
|
||||
cx.leave_loop();
|
||||
cx.emit(Emitted::Jump(label_entry));
|
||||
cx.adjust_label(label_exit);
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for IfExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let label_false = cx.new_label();
|
||||
let label_end = cx.new_label();
|
||||
|
||||
// Emit condition
|
||||
let condition_value = self.condition.compile(cx)?;
|
||||
cx.push(condition_value)?;
|
||||
cx.emit(Emitted::Branch(label_false));
|
||||
|
||||
// True branch
|
||||
let true_value = self.if_true.compile(cx)?;
|
||||
cx.push(true_value)?;
|
||||
cx.emit(Emitted::Jump(label_end));
|
||||
|
||||
// False branch
|
||||
cx.adjust_label(label_false);
|
||||
let false_value = match self.if_false.as_ref() {
|
||||
Some(expr) => expr.compile(cx)?,
|
||||
None => CompileValue::Nil,
|
||||
};
|
||||
cx.push(false_value)?;
|
||||
|
||||
cx.adjust_label(label_end);
|
||||
|
||||
Ok(CompileValue::Stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for CondExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let label_end = cx.new_label();
|
||||
|
||||
for condition in self.arms.iter() {
|
||||
let label_otherwise = cx.new_label();
|
||||
let condition_value = condition.condition.compile(cx)?;
|
||||
cx.push(condition_value)?;
|
||||
cx.emit(Emitted::Branch(label_otherwise));
|
||||
// Condition true
|
||||
let then_value = condition.then.compile(cx)?;
|
||||
cx.push(then_value)?;
|
||||
cx.emit(Emitted::Jump(label_end));
|
||||
// Condition false
|
||||
cx.adjust_label(label_otherwise);
|
||||
}
|
||||
|
||||
let otherwise_value = if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
|
||||
otherwise_arm.compile(cx)?
|
||||
} else {
|
||||
CompileValue::Nil
|
||||
};
|
||||
cx.push(otherwise_value)?;
|
||||
cx.adjust_label(label_end);
|
||||
|
||||
Ok(CompileValue::Stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for PrognExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
self.body.compile(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for ReturnExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let value = self.expression.compile(cx)?;
|
||||
cx.compile_return_value(value)?;
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn compile_break(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
cx.emit_break()?;
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
pub(super) fn compile_continue(cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
cx.emit_continue()?;
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
use crate::{
|
||||
compile::{
|
||||
block::{Compile, CompileContext, CompileValue},
|
||||
error::CompileError,
|
||||
syntax::{
|
||||
CallExpression, DefmacroExpression, DefunExpression, Expression, FunctionBody,
|
||||
LambdaExpression,
|
||||
},
|
||||
},
|
||||
vm::instruction::{ArgumentCount, Instruction},
|
||||
};
|
||||
|
||||
fn builtin_identifier_callee(identifier: &str) -> Option<Instruction> {
|
||||
match identifier {
|
||||
"+" => Some(Instruction::Add),
|
||||
"-" => Some(Instruction::Sub),
|
||||
"*" | "·" => Some(Instruction::Mul),
|
||||
"/" | "÷" => Some(Instruction::Div),
|
||||
"%" => Some(Instruction::Mod),
|
||||
"=" => Some(Instruction::Eq),
|
||||
"/=" | "≠" => Some(Instruction::Ne),
|
||||
">" => Some(Instruction::Gt),
|
||||
"<" => Some(Instruction::Lt),
|
||||
">=" | "≥" => Some(Instruction::Ge),
|
||||
"<=" | "≤" => Some(Instruction::Le),
|
||||
"not" => Some(Instruction::Not),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for FunctionBody {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
for expression in self.head.iter() {
|
||||
cx.compile_statement(expression)?;
|
||||
}
|
||||
self.tail.compile(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for CallExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let argument_count = ArgumentCount::try_from(self.arguments.len())
|
||||
.map_err(|_| CompileError::TooManyArgumentsInCall)?;
|
||||
let instruction = if let Expression::Identifier(callee) = self.callee.as_ref() {
|
||||
if let Some(instruction) = builtin_identifier_callee(callee.as_ref()) {
|
||||
instruction
|
||||
} else {
|
||||
Instruction::Call
|
||||
}
|
||||
} else {
|
||||
Instruction::Call
|
||||
};
|
||||
if instruction == Instruction::Call {
|
||||
let callee = self.callee.compile(cx)?;
|
||||
cx.push(callee)?;
|
||||
cx.guard_argument();
|
||||
}
|
||||
for expression in self.arguments.iter() {
|
||||
let value = expression.compile(cx)?;
|
||||
cx.push(value)?;
|
||||
cx.guard_argument();
|
||||
}
|
||||
cx.emit(instruction);
|
||||
cx.emit(argument_count);
|
||||
let mut unguard_count = usize::from(argument_count);
|
||||
if instruction == Instruction::Call {
|
||||
unguard_count += 1;
|
||||
}
|
||||
for _ in 0..unguard_count {
|
||||
cx.unguard_argument();
|
||||
}
|
||||
Ok(CompileValue::Stack)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for LambdaExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let function = cx.compile_function(
|
||||
Some("lambda".into()),
|
||||
self.docstring.clone(),
|
||||
&self.signature,
|
||||
&self.body,
|
||||
)?;
|
||||
Ok(CompileValue::LocalFunction(function))
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for DefunExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let function = cx.compile_function(
|
||||
Some(self.name.clone()),
|
||||
self.docstring.clone(),
|
||||
&self.signature,
|
||||
&self.body,
|
||||
)?;
|
||||
cx.compile_set_global(self.name.clone(), CompileValue::LocalFunction(function))?;
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
impl Compile for DefmacroExpression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
let function = cx.compile_function(
|
||||
Some(self.name.clone()),
|
||||
self.docstring.clone(),
|
||||
&self.signature,
|
||||
&self.body,
|
||||
)?;
|
||||
cx.compile_declare_macro(self.name.clone(), CompileValue::LocalFunction(function))?;
|
||||
Ok(CompileValue::Nil)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::{
|
||||
UpvalueDef,
|
||||
block::{CompileValue, test_compile},
|
||||
function::FunctionSignature,
|
||||
syntax::{
|
||||
Assignment, CallExpression, DefunExpression, Expression, FunctionBody,
|
||||
LambdaExpression, LetExpression,
|
||||
},
|
||||
},
|
||||
vm::instruction::{Instruction, LocalId},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_lambda_capture_compile() {
|
||||
// (let (x 1)
|
||||
// ((lambda (y) (+ x y)) 2))
|
||||
let e = Expression::Let(LetExpression {
|
||||
sequential: false,
|
||||
bindings: vec![Assignment {
|
||||
identifier: "x".into(),
|
||||
value: Rc::new(Expression::IntegerLiteral(1.into())),
|
||||
}],
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Call(CallExpression {
|
||||
callee: Rc::new(Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["y".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None,
|
||||
},
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Call(CallExpression {
|
||||
callee: Rc::new(Expression::Identifier("+".into())),
|
||||
arguments: vec![
|
||||
Rc::new(Expression::Identifier("x".into())),
|
||||
Rc::new(Expression::Identifier("y".into())),
|
||||
],
|
||||
})),
|
||||
},
|
||||
})),
|
||||
arguments: vec![Rc::new(Expression::IntegerLiteral(2.into()))],
|
||||
})),
|
||||
},
|
||||
});
|
||||
let (cx, _v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert_eq!(cx.function_blocks.len(), 2);
|
||||
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
|
||||
let lambda_function = cx.function_blocks[1].to_bytecode().unwrap();
|
||||
|
||||
// lambda
|
||||
assert_eq!(
|
||||
lambda_function.instructions.as_ref(),
|
||||
&[
|
||||
Instruction::GetUpvalue.into(),
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::Add.into(),
|
||||
2,
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
);
|
||||
assert_eq!(lambda_function.required_count, 1);
|
||||
assert_eq!(lambda_function.optional_count, 0);
|
||||
assert!(!lambda_function.has_rest);
|
||||
assert_eq!(
|
||||
lambda_function.upvalues.as_ref(),
|
||||
&[UpvalueDef {
|
||||
index: LocalId::from(0),
|
||||
is_local: true
|
||||
}]
|
||||
);
|
||||
|
||||
// root
|
||||
assert_eq!(
|
||||
root_function.instructions.as_ref(),
|
||||
&[
|
||||
// (let ...
|
||||
// ... (x 1) ...
|
||||
Instruction::PushInteger.into(),
|
||||
1,
|
||||
0,
|
||||
// ... ((lambda (y) (+ x y)) 2) ...
|
||||
// (lambda ...)
|
||||
Instruction::PushConstant.into(),
|
||||
0,
|
||||
0,
|
||||
Instruction::MakeClosure.into(),
|
||||
// 2
|
||||
Instruction::PushInteger.into(),
|
||||
2,
|
||||
0,
|
||||
// ((lambda ...) 2)
|
||||
Instruction::Call.into(),
|
||||
1,
|
||||
// (let ... ) exit
|
||||
Instruction::SetTemp.into(),
|
||||
Instruction::CloseUpvalue.into(),
|
||||
Instruction::GetTemp.into(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda_compile() {
|
||||
let e = Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None,
|
||||
},
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::IntegerLiteral(1234.into())),
|
||||
},
|
||||
});
|
||||
let (cx, v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert!(cx.function_blocks[0].instructions.is_empty());
|
||||
let CompileValue::LocalFunction(index) = v else {
|
||||
panic!("lambda did not compile to a function value")
|
||||
};
|
||||
let lambda_function = cx.function_blocks[index].to_bytecode().unwrap();
|
||||
assert_eq!(
|
||||
lambda_function.instructions.as_ref(),
|
||||
&[
|
||||
Instruction::PushInteger.into(),
|
||||
210,
|
||||
4,
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compile_defun() {
|
||||
let e = Expression::Defun(DefunExpression {
|
||||
name: "my-function".into(),
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None,
|
||||
},
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Identifier("a".into())),
|
||||
},
|
||||
});
|
||||
let (cx, _v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert_eq!(cx.function_blocks.len(), 2);
|
||||
let root_function = cx.function_blocks[0].to_bytecode().unwrap();
|
||||
let defun_function = cx.function_blocks[1].to_bytecode().unwrap();
|
||||
// identifier + function
|
||||
assert_eq!(root_function.constants.len(), 2);
|
||||
assert_eq!(
|
||||
root_function.instructions.as_ref(),
|
||||
&[
|
||||
// Identifier
|
||||
Instruction::PushConstant.into(),
|
||||
0,
|
||||
0,
|
||||
// Function
|
||||
Instruction::PushConstant.into(),
|
||||
1,
|
||||
0,
|
||||
Instruction::SetGlobal.into(),
|
||||
]
|
||||
);
|
||||
// inner function
|
||||
assert!(defun_function.constants.is_empty());
|
||||
assert_eq!(defun_function.required_count, 1);
|
||||
assert_eq!(defun_function.optional_count, 0);
|
||||
assert!(!defun_function.has_rest);
|
||||
assert_eq!(
|
||||
defun_function.instructions.as_ref(),
|
||||
&[
|
||||
// a
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
use crate::compile::{
|
||||
block::{Compile, CompileContext, CompileValue},
|
||||
error::CompileError,
|
||||
syntax::Expression,
|
||||
};
|
||||
|
||||
mod binding;
|
||||
mod flow;
|
||||
mod function;
|
||||
|
||||
impl Compile for Expression {
|
||||
fn compile(&self, cx: &mut CompileContext) -> Result<CompileValue, CompileError> {
|
||||
match self {
|
||||
Self::Nil => Ok(CompileValue::Nil),
|
||||
Self::Break => flow::compile_break(cx),
|
||||
Self::Continue => flow::compile_continue(cx),
|
||||
Self::Return(return_) => return_.compile(cx),
|
||||
Self::Call(call) => call.compile(cx),
|
||||
Self::If(if_) => if_.compile(cx),
|
||||
Self::Cond(cond) => cond.compile(cx),
|
||||
Self::Let(let_) => let_.compile(cx),
|
||||
Self::Setq(setq) => setq.compile(cx),
|
||||
Self::While(while_) => while_.compile(cx),
|
||||
Self::Loop(loop_) => loop_.compile(cx),
|
||||
Self::Defun(defun) => defun.compile(cx),
|
||||
Self::Lambda(lambda) => lambda.compile(cx),
|
||||
Self::Progn(progn) => progn.compile(cx),
|
||||
Self::Defmacro(defmacro) => defmacro.compile(cx),
|
||||
Self::Identifier(identifier) => Ok(CompileValue::Identifier(identifier.clone())),
|
||||
&Self::IntegerLiteral(value) => Ok(CompileValue::Integer(value)),
|
||||
&Self::BooleanLiteral(value) => Ok(CompileValue::Boolean(value)),
|
||||
Self::StringLiteral(value) => Ok(CompileValue::String(value.clone())),
|
||||
Self::Vector(vector) => Ok(CompileValue::Value(vector.clone().into())),
|
||||
Self::Quote(value) => Ok(CompileValue::Quote(value.clone())),
|
||||
Self::SyntaxError(_) => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::compile::{
|
||||
block::{CompileValue, test_compile},
|
||||
syntax::Expression,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_nil_compile() {
|
||||
let e = Expression::Nil;
|
||||
let (cx, v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert!(cx.function_blocks[0].instructions.is_empty());
|
||||
assert_eq!(v, CompileValue::Nil);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_literal_compile() {
|
||||
let e = Expression::IntegerLiteral(1.into());
|
||||
let (cx, v) = test_compile(&e).unwrap();
|
||||
assert_eq!(cx.current, 0);
|
||||
assert!(cx.function_blocks[0].instructions.is_empty());
|
||||
assert_eq!(v, CompileValue::Integer(1.into()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#![coverage(off)]
|
||||
|
||||
use crate::{compile::syntax::ParseError, vm::value::IdentifierValue};
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum CompileError {
|
||||
#[error("parse error: {0:?}")]
|
||||
Parse(Vec<ParseError>),
|
||||
#[error("block has too many local variables")]
|
||||
TooManyLocals,
|
||||
#[error("module has too many constants")]
|
||||
TooManyConstants,
|
||||
#[error("function call has too many arguments")]
|
||||
TooManyArgumentsInCall,
|
||||
#[error("(break) used outside of a loop")]
|
||||
BreakOutsideOfLoop,
|
||||
#[error("(continue) used outside of a loop")]
|
||||
ContinueOutsideOfLoop,
|
||||
#[error("cannot emit branch instruction from {0} to {1}")]
|
||||
CannotEmitBranch(usize, usize),
|
||||
#[error("undefined local value reference: {0}")]
|
||||
UndefinedLocalReference(IdentifierValue),
|
||||
}
|
||||
|
||||
impl From<Vec<ParseError>> for CompileError {
|
||||
fn from(value: Vec<ParseError>) -> Self {
|
||||
Self::Parse(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
use crate::vm::value::IdentifierValue;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct FunctionSignature {
|
||||
pub required_arguments: Vec<IdentifierValue>,
|
||||
pub optional_arguments: Vec<IdentifierValue>,
|
||||
pub rest_argument: Option<IdentifierValue>,
|
||||
}
|
||||
|
||||
impl FunctionSignature {
|
||||
pub const EMPTY: Self = Self {
|
||||
required_arguments: vec![],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None,
|
||||
};
|
||||
|
||||
pub fn argument(&self, name: &str) -> Option<usize> {
|
||||
if let Some(index) = self
|
||||
.required_arguments
|
||||
.iter()
|
||||
.position(|a| a.as_ref() == name)
|
||||
{
|
||||
Some(index)
|
||||
} else if let Some(index) = self
|
||||
.optional_arguments
|
||||
.iter()
|
||||
.position(|a| a.as_ref() == name)
|
||||
{
|
||||
Some(index + self.required_arguments.len())
|
||||
} else if self
|
||||
.rest_argument
|
||||
.as_ref()
|
||||
.is_some_and(|a| a.as_ref() == name)
|
||||
{
|
||||
Some(self.required_arguments.len() + self.optional_arguments.len())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_arity(&self) -> usize {
|
||||
self.required_arguments.len()
|
||||
}
|
||||
|
||||
pub fn max_arity(&self) -> usize {
|
||||
if self.rest_argument.is_none() {
|
||||
self.required_arguments.len() + self.optional_arguments.len()
|
||||
} else {
|
||||
usize::MAX
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
use crate::vm::instruction::{ArgumentCount, ConstantId, ImmediateInteger, Instruction, LocalId};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum Emitted {
|
||||
Branch(usize),
|
||||
Jump(usize),
|
||||
Instruction(Instruction),
|
||||
LocalId(LocalId),
|
||||
ConstantId(ConstantId),
|
||||
ArgumentCount(ArgumentCount),
|
||||
ImmediateInteger(ImmediateInteger),
|
||||
Bool(bool),
|
||||
// Instruction(Instruction),
|
||||
}
|
||||
|
||||
impl From<Instruction> for Emitted {
|
||||
fn from(value: Instruction) -> Self {
|
||||
Self::Instruction(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LocalId> for Emitted {
|
||||
fn from(value: LocalId) -> Self {
|
||||
Self::LocalId(value)
|
||||
}
|
||||
}
|
||||
impl From<ConstantId> for Emitted {
|
||||
fn from(value: ConstantId) -> Self {
|
||||
Self::ConstantId(value)
|
||||
}
|
||||
}
|
||||
impl From<ArgumentCount> for Emitted {
|
||||
fn from(value: ArgumentCount) -> Self {
|
||||
Self::ArgumentCount(value)
|
||||
}
|
||||
}
|
||||
impl From<ImmediateInteger> for Emitted {
|
||||
fn from(value: ImmediateInteger) -> Self {
|
||||
Self::ImmediateInteger(value)
|
||||
}
|
||||
}
|
||||
impl From<bool> for Emitted {
|
||||
fn from(value: bool) -> Self {
|
||||
Self::Bool(value)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
mod block;
|
||||
mod codegen;
|
||||
mod error;
|
||||
mod function;
|
||||
mod instruction;
|
||||
pub mod syntax;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CompileOptions {
|
||||
pub trace_compile: bool,
|
||||
}
|
||||
|
||||
pub use block::{Compile, CompileContext, UpvalueDef};
|
||||
pub use error::CompileError;
|
||||
@@ -0,0 +1,293 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CollectErrors, Expression, FunctionBody,
|
||||
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
},
|
||||
vm::value::{ConsCell, IdentifierValue, Keyword, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Assignment {
|
||||
pub identifier: IdentifierValue,
|
||||
pub value: Rc<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct SetqExpression {
|
||||
pub pairs: Vec<Assignment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LetExpression {
|
||||
pub sequential: bool,
|
||||
pub bindings: Vec<Assignment>,
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
impl Assignment {
|
||||
fn parse_list(value: &Value, input: &Value, after: Keyword) -> Result<Vec<Self>, ParseError> {
|
||||
let mut iter = value.syntax_iter(ExpectedWhere::InAssignment);
|
||||
let mut pairs = vec![];
|
||||
loop {
|
||||
let [identifier, value] = match iter.next_chunk::<2>() {
|
||||
Ok(pair) => pair,
|
||||
Err(rest) => {
|
||||
if !rest.is_empty() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::try_collect_extraneous(
|
||||
rest.map(|x| x.cloned()),
|
||||
)?,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
};
|
||||
let Value::Identifier(identifier) = identifier? else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Identifier,
|
||||
ExpectedWhere::InAssignment,
|
||||
),
|
||||
});
|
||||
};
|
||||
let value = Expression::parse_inner(value?);
|
||||
pairs.push(Self {
|
||||
identifier: identifier.clone(),
|
||||
value,
|
||||
});
|
||||
}
|
||||
if pairs.is_empty() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::AtLeastOneBinding,
|
||||
ExpectedWhere::AfterKeyword(after),
|
||||
),
|
||||
});
|
||||
}
|
||||
Ok(pairs)
|
||||
}
|
||||
}
|
||||
|
||||
impl LetExpression {
|
||||
pub(super) fn parse(
|
||||
value: &Value,
|
||||
input: &Value,
|
||||
keyword: Keyword,
|
||||
) -> Result<Self, ParseError> {
|
||||
let Value::Cons(value) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::AfterKeyword(keyword),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(bindings, cdr) = value.as_ref();
|
||||
let bindings = Assignment::parse_list(bindings, input, keyword)?;
|
||||
let body = FunctionBody::parse(cdr, input, keyword)?;
|
||||
let sequential = keyword == Keyword::LetStar;
|
||||
Ok(Self {
|
||||
sequential,
|
||||
bindings,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl SetqExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let pairs = Assignment::parse_list(value, input, Keyword::Setq)?;
|
||||
Ok(Self { pairs })
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for SetqExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.pairs.iter().fold(false, |acc, v| {
|
||||
let r = v.collect_errors(errors);
|
||||
acc | r
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for LetExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let r0 = self.bindings.iter().fold(false, |acc, v| {
|
||||
let r = v.collect_errors(errors);
|
||||
acc | r
|
||||
});
|
||||
let r1 = self.body.collect_errors(errors);
|
||||
r0 | r1
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for Assignment {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.value.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
Assignment, Expression, FunctionBody, LetExpression, SetqExpression,
|
||||
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
},
|
||||
vm::value::{ConsCell, Keyword, Value},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_let() {
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Let),
|
||||
Value::list_or_nil([Value::Identifier("a".into()), Value::Number(1.into())]),
|
||||
Value::Nil,
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Let(LetExpression {
|
||||
sequential: false,
|
||||
bindings: vec![Assignment {
|
||||
identifier: "a".into(),
|
||||
value: Rc::new(Expression::IntegerLiteral(1.into()))
|
||||
},],
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Nil)
|
||||
},
|
||||
})
|
||||
);
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::LetStar),
|
||||
Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Number(1.into()),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Number(2.into()),
|
||||
]),
|
||||
Value::Nil,
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Let(LetExpression {
|
||||
sequential: true,
|
||||
bindings: vec![
|
||||
Assignment {
|
||||
identifier: "a".into(),
|
||||
value: Rc::new(Expression::IntegerLiteral(1.into()))
|
||||
},
|
||||
Assignment {
|
||||
identifier: "b".into(),
|
||||
value: Rc::new(Expression::IntegerLiteral(2.into()))
|
||||
}
|
||||
],
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Nil)
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
// syntax errors
|
||||
let v = Value::Cons(Rc::new(ConsCell(
|
||||
Value::Keyword(Keyword::Let),
|
||||
Value::Identifier("a".into()),
|
||||
)));
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
&e[..],
|
||||
&[ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Let)
|
||||
)
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_setq() {
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Setq),
|
||||
Value::Identifier("a".into()),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Identifier("c".into()),
|
||||
Value::Identifier("d".into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Setq(SetqExpression {
|
||||
pairs: vec![
|
||||
Assignment {
|
||||
identifier: "a".into(),
|
||||
value: Rc::new(Expression::Identifier("b".into())),
|
||||
},
|
||||
Assignment {
|
||||
identifier: "c".into(),
|
||||
value: Rc::new(Expression::Identifier("d".into())),
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
|
||||
// syntax errors
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Setq)]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
&e[..],
|
||||
&[ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::AtLeastOneBinding,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Setq)
|
||||
)
|
||||
}]
|
||||
);
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Setq),
|
||||
Value::Identifier("a".into()),
|
||||
Value::Number(1.into()),
|
||||
Value::Identifier("b".into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
&e[..],
|
||||
&[ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell(
|
||||
Value::Identifier("b".into()),
|
||||
Value::Nil
|
||||
)))
|
||||
}]
|
||||
);
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Setq),
|
||||
Value::Number(1.into()),
|
||||
Value::Identifier("a".into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
&e[..],
|
||||
&[ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Identifier,
|
||||
ExpectedWhere::InAssignment
|
||||
)
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
|
||||
},
|
||||
vm::value::{ConsCell, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CallExpression {
|
||||
pub callee: Rc<Expression>,
|
||||
pub arguments: Vec<Rc<Expression>>,
|
||||
}
|
||||
|
||||
impl CallExpression {
|
||||
pub(super) fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
|
||||
let ConsCell(car, cdr) = cons;
|
||||
let callee = Expression::parse_inner(car);
|
||||
|
||||
let mut arguments = vec![];
|
||||
let mut list = cdr;
|
||||
while !list.is_nil() {
|
||||
let Value::Cons(cons) = list else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InCallExpression,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
let expression = Expression::parse_inner(car);
|
||||
arguments.push(expression);
|
||||
|
||||
list = cdr;
|
||||
}
|
||||
|
||||
Ok(Self { callee, arguments })
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for CallExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let mut r = self.callee.collect_errors(errors);
|
||||
for expr in self.arguments.iter() {
|
||||
r |= expr.collect_errors(errors);
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CallExpression, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
|
||||
},
|
||||
vm::value::Value,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_call() {
|
||||
let v = Value::list_or_nil([Value::Identifier("a".into())]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Call(CallExpression {
|
||||
callee: Rc::new(Expression::Identifier("a".into())),
|
||||
arguments: vec![]
|
||||
})
|
||||
);
|
||||
|
||||
// Syntax errors
|
||||
let v = Value::Identifier("a".into()).cons(Value::Number(1.into()));
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InCallExpression
|
||||
),
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,490 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
|
||||
},
|
||||
vm::value::{ConsCell, Keyword, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct IfExpression {
|
||||
pub condition: Rc<Expression>,
|
||||
pub if_true: Rc<Expression>,
|
||||
pub if_false: Option<Rc<Expression>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CondExpressionArm {
|
||||
pub condition: Rc<Expression>,
|
||||
pub then: Rc<Expression>,
|
||||
}
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct CondExpression {
|
||||
pub arms: Vec<CondExpressionArm>,
|
||||
pub otherwise_arm: Option<Rc<Expression>>,
|
||||
}
|
||||
|
||||
impl IfExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let Value::Cons(value) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::AfterKeyword(Keyword::If),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(condition, cdr) = value.as_ref();
|
||||
let condition = Expression::parse_inner(condition);
|
||||
let Value::Cons(cdr) = cdr else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterCondition,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(if_true, cdr) = cdr.as_ref();
|
||||
let if_true = Expression::parse_inner(if_true);
|
||||
let if_false = if cdr.is_nil() {
|
||||
None
|
||||
} else {
|
||||
let Value::Cons(cdr) = cdr else { todo!() };
|
||||
let ConsCell(if_false, cdr) = cdr.as_ref();
|
||||
if !cdr.is_nil() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::extraneous(cdr),
|
||||
});
|
||||
}
|
||||
Some(Expression::parse_inner(if_false))
|
||||
};
|
||||
Ok(Self {
|
||||
condition,
|
||||
if_true,
|
||||
if_false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CondExpressionArm {
|
||||
fn parse(cons: &ConsCell, input: &Value) -> Result<Self, ParseError> {
|
||||
let ConsCell(condition, cdr) = cons;
|
||||
let condition = Expression::parse_inner(condition);
|
||||
let Value::Cons(cons) = cdr else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::InConditionArm,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(then, cdr) = cons.as_ref();
|
||||
if !cdr.is_nil() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::extraneous(cdr),
|
||||
});
|
||||
}
|
||||
let then = Expression::parse_inner(then);
|
||||
|
||||
Ok(Self { condition, then })
|
||||
}
|
||||
}
|
||||
|
||||
impl CondExpression {
|
||||
pub(super) fn parse(mut value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let mut arms = vec![];
|
||||
let mut otherwise_arm = None;
|
||||
|
||||
while !value.is_nil() {
|
||||
let Value::Cons(cons) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ConditionList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Cond),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(arm, cdr) = cons.as_ref();
|
||||
|
||||
// Arm must be a list
|
||||
let Value::Cons(arm_cons) = arm else {
|
||||
return Err(ParseError {
|
||||
input: arm.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::InConditionList,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(arm_condition, arm_condition_cdr) = arm_cons.as_ref();
|
||||
|
||||
match arm_condition {
|
||||
Value::Keyword(Keyword::Otherwise) => {
|
||||
if otherwise_arm.is_some() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::extraneous(arm),
|
||||
});
|
||||
}
|
||||
let Value::Cons(otherwise_cons) = arm_condition_cdr else {
|
||||
return Err(ParseError {
|
||||
input: arm.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InConditionList,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(arm_expr, arm_cdr) = otherwise_cons.as_ref();
|
||||
if !arm_cdr.is_nil() {
|
||||
return Err(ParseError {
|
||||
input: arm.clone(),
|
||||
error: ParseErrorKind::extraneous(arm_cdr),
|
||||
});
|
||||
}
|
||||
let arm = Expression::parse_inner(arm_expr);
|
||||
otherwise_arm = Some(arm);
|
||||
}
|
||||
_ => {
|
||||
if otherwise_arm.is_some() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::extraneous(arm),
|
||||
});
|
||||
}
|
||||
let arm = CondExpressionArm::parse(arm_cons, arm)?;
|
||||
arms.push(arm);
|
||||
}
|
||||
}
|
||||
|
||||
value = cdr;
|
||||
}
|
||||
|
||||
if arms.is_empty() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ConditionList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Cond),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
arms,
|
||||
otherwise_arm,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for IfExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let a = self.condition.collect_errors(errors);
|
||||
let b = self.if_true.collect_errors(errors);
|
||||
let c = self
|
||||
.if_false
|
||||
.as_ref()
|
||||
.map(|x| x.collect_errors(errors))
|
||||
.unwrap_or_default();
|
||||
a | b | c
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for CondExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let mut r = false;
|
||||
for arm in self.arms.iter() {
|
||||
r |= arm.condition.collect_errors(errors);
|
||||
r |= arm.then.collect_errors(errors);
|
||||
}
|
||||
if let Some(otherwise_arm) = self.otherwise_arm.as_ref() {
|
||||
r |= otherwise_arm.collect_errors(errors);
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CondExpression, CondExpressionArm, ExpectedWhat, ExpectedWhere, Expression,
|
||||
IfExpression, ParseError, ParseErrorKind,
|
||||
},
|
||||
vm::value::{ConsCell, Keyword, Value},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_cond() {
|
||||
// without &otherwise branch
|
||||
let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
|
||||
let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Identifier("b".into())]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Cond(CondExpression {
|
||||
arms: vec![
|
||||
CondExpressionArm {
|
||||
condition: Rc::new(Expression::BooleanLiteral(true.into())),
|
||||
then: Rc::new(Expression::Identifier("a".into()))
|
||||
},
|
||||
CondExpressionArm {
|
||||
condition: Rc::new(Expression::BooleanLiteral(false.into())),
|
||||
then: Rc::new(Expression::Identifier("b".into()))
|
||||
}
|
||||
],
|
||||
otherwise_arm: None
|
||||
})
|
||||
);
|
||||
|
||||
// with &otherwise branch
|
||||
let b0 = Value::list_or_nil([Value::Boolean(true.into()), Value::Identifier("a".into())]);
|
||||
let b1 = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Otherwise),
|
||||
Value::Identifier("b".into()),
|
||||
]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::Cond(CondExpression {
|
||||
arms: vec![CondExpressionArm {
|
||||
condition: Rc::new(Expression::BooleanLiteral(true.into())),
|
||||
then: Rc::new(Expression::Identifier("a".into()))
|
||||
},],
|
||||
otherwise_arm: Some(Rc::new(Expression::Identifier("b".into())))
|
||||
})
|
||||
);
|
||||
|
||||
// syntax error
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond)]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ConditionList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Cond)
|
||||
)
|
||||
}]
|
||||
);
|
||||
|
||||
let v = Value::Keyword(Keyword::Cond).cons(Value::Identifier("a".into()));
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ConditionList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Cond)
|
||||
)
|
||||
}]
|
||||
);
|
||||
|
||||
let b0 = Value::list_or_nil([Value::Boolean(false.into())]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::InConditionArm
|
||||
)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::Boolean(false.into());
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::InConditionList
|
||||
)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::Keyword(Keyword::Otherwise);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::InConditionList
|
||||
)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::Keyword(Keyword::Otherwise).cons(Value::Number(1.into()));
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InConditionList
|
||||
)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::Boolean(false.into()).cons(Value::Boolean(false.into()));
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::InConditionArm
|
||||
)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::list_or_nil([
|
||||
Value::Boolean(false.into()),
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
|
||||
}]
|
||||
);
|
||||
let b0 = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Otherwise),
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: b0,
|
||||
error: ParseErrorKind::extraneous(&Value::list_or_nil([Value::Number(2.into())]))
|
||||
}]
|
||||
);
|
||||
let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0.clone(), b0.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::extraneous(&b0)
|
||||
}]
|
||||
);
|
||||
let b0 = Value::list_or_nil([Value::Keyword(Keyword::Otherwise), Value::Number(1.into())]);
|
||||
let b1 = Value::list_or_nil([Value::Boolean(false.into()), Value::Number(1.into())]);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::Cond), b0, b1.clone()]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::extraneous(&b1)
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_if() {
|
||||
// without false branch
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::If),
|
||||
Value::Boolean(true.into()),
|
||||
Value::Number(1.into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::If(IfExpression {
|
||||
condition: Rc::new(Expression::BooleanLiteral(true.into())),
|
||||
if_true: Rc::new(Expression::IntegerLiteral(1.into())),
|
||||
if_false: None
|
||||
})
|
||||
);
|
||||
|
||||
// with false branch
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::If),
|
||||
Value::Boolean(false.into()),
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(
|
||||
e.as_ref(),
|
||||
&Expression::If(IfExpression {
|
||||
condition: Rc::new(Expression::BooleanLiteral(false.into())),
|
||||
if_true: Rc::new(Expression::IntegerLiteral(1.into())),
|
||||
if_false: Some(Rc::new(Expression::IntegerLiteral(2.into())))
|
||||
})
|
||||
);
|
||||
|
||||
// invalid syntax
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::If)]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::AfterKeyword(Keyword::If)
|
||||
)
|
||||
}]
|
||||
);
|
||||
let v = Value::list_or_nil([Value::Keyword(Keyword::If), Value::Boolean(true.into())]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterCondition
|
||||
)
|
||||
}]
|
||||
);
|
||||
let v = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::If),
|
||||
Value::Boolean(true.into()),
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
]);
|
||||
let e = Expression::parse(&v).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::ExtraneousExpressions(Rc::new(ConsCell::end(
|
||||
Value::Number(3.into())
|
||||
)))
|
||||
}]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
#![coverage(off)]
|
||||
|
||||
use std::{error::Error as StdError, fmt, rc::Rc};
|
||||
|
||||
use crate::vm::value::{ConsCell, Keyword, Value};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ParseError {
|
||||
pub input: Value,
|
||||
pub error: ParseErrorKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum ParseErrorKind {
|
||||
#[error("Not an expression")]
|
||||
NotAnExpression,
|
||||
#[error("Expected {0} {1}")]
|
||||
Expected(ExpectedWhat, ExpectedWhere),
|
||||
#[error("Extraneous expression: {0}")]
|
||||
ExtraneousExpression(Value),
|
||||
#[error("Extraneous expression(s): {0}")]
|
||||
ExtraneousExpressions(Rc<ConsCell>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum ExpectedWhat {
|
||||
#[error("an expression")]
|
||||
Expression,
|
||||
#[error("an argument list")]
|
||||
ArgumentList,
|
||||
#[error("a condition")]
|
||||
Condition,
|
||||
#[error("a condition list")]
|
||||
ConditionList,
|
||||
#[error("at least one argument")]
|
||||
AtLeastOneArgument,
|
||||
#[error("exactly one argument")]
|
||||
ExactlyOneArgument,
|
||||
#[error("an argument, &optional or &rest keywords")]
|
||||
ArgumentSpec,
|
||||
#[error("a proper list")]
|
||||
ProperList,
|
||||
#[error("an identifier")]
|
||||
Identifier,
|
||||
#[error("at least one binding pair")]
|
||||
AtLeastOneBinding,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, thiserror::Error)]
|
||||
pub enum ExpectedWhere {
|
||||
#[error("after a condition")]
|
||||
AfterCondition,
|
||||
#[error("after the \"{0}\" keyword")]
|
||||
AfterKeyword(Keyword),
|
||||
#[error("after the argument list in a {0}")]
|
||||
AfterArgumentList(Keyword),
|
||||
#[error("in the argument list")]
|
||||
InArgumentList,
|
||||
#[error("in the call expression")]
|
||||
InCallExpression,
|
||||
#[error("in the condition list")]
|
||||
InConditionList,
|
||||
#[error("in the condition arm")]
|
||||
InConditionArm,
|
||||
#[error("in the function definition")]
|
||||
InFunctionDefinition,
|
||||
#[error("in assignment")]
|
||||
InAssignment,
|
||||
}
|
||||
|
||||
pub(super) trait CollectErrors<E> {
|
||||
fn collect_errors(&self, errors: &mut Vec<E>) -> bool;
|
||||
}
|
||||
|
||||
impl fmt::Display for ParseError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.error, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl StdError for ParseError {
|
||||
fn description(&self) -> &str {
|
||||
"Parse error"
|
||||
}
|
||||
}
|
||||
|
||||
impl ParseErrorKind {
|
||||
pub fn extraneous(value: &Value) -> Self {
|
||||
match value {
|
||||
Value::Nil => unreachable!(),
|
||||
Value::Cons(cons) => Self::ExtraneousExpressions(cons.clone()),
|
||||
_ => Self::ExtraneousExpression(value.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn collect_extraneous<I: IntoIterator<Item = Value>>(iter: I) -> Self
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
Self::extraneous(&Value::list_or_nil(iter))
|
||||
}
|
||||
|
||||
pub fn try_collect_extraneous<E, I: IntoIterator<Item = Result<Value, E>>>(
|
||||
iter: I,
|
||||
) -> Result<Self, E>
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
Ok(Self::extraneous(&Value::try_list_or_nil(iter)?))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::{
|
||||
function::FunctionSignature,
|
||||
syntax::{
|
||||
CollectErrors, ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind,
|
||||
maybe_docstring,
|
||||
},
|
||||
},
|
||||
vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct FunctionBody {
|
||||
pub head: Vec<Rc<Expression>>,
|
||||
pub tail: Rc<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LambdaExpression {
|
||||
pub docstring: Option<StringValue>,
|
||||
pub signature: FunctionSignature,
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct DefunExpression {
|
||||
pub name: IdentifierValue,
|
||||
pub docstring: Option<StringValue>,
|
||||
pub signature: FunctionSignature,
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ReturnExpression {
|
||||
pub expression: Rc<Expression>,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct PrognExpression {
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
impl FunctionSignature {
|
||||
pub(super) fn parse(mut value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
enum Mode {
|
||||
Required,
|
||||
Optional,
|
||||
Rest,
|
||||
}
|
||||
|
||||
let mut required_arguments = vec![];
|
||||
let mut optional_arguments = vec![];
|
||||
let mut rest_argument = None;
|
||||
let mut mode = Mode::Required;
|
||||
|
||||
while !value.is_nil() {
|
||||
let Value::Cons(cons) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentSpec,
|
||||
ExpectedWhere::InArgumentList,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
match (&mode, car) {
|
||||
(Mode::Required, Value::Identifier(arg)) => {
|
||||
required_arguments.push(arg.clone());
|
||||
}
|
||||
(Mode::Optional, Value::Identifier(arg)) => {
|
||||
optional_arguments.push(arg.clone());
|
||||
}
|
||||
(Mode::Rest, Value::Identifier(arg)) => {
|
||||
if rest_argument.is_some() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ExactlyOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Rest),
|
||||
),
|
||||
});
|
||||
}
|
||||
rest_argument = Some(arg.clone());
|
||||
}
|
||||
(Mode::Required, Value::Keyword(Keyword::Optional)) => {
|
||||
mode = Mode::Optional;
|
||||
}
|
||||
(Mode::Required, Value::Keyword(Keyword::Rest)) => {
|
||||
mode = Mode::Rest;
|
||||
}
|
||||
(Mode::Optional, Value::Keyword(Keyword::Rest)) => {
|
||||
if optional_arguments.is_empty() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::AtLeastOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Optional),
|
||||
),
|
||||
});
|
||||
}
|
||||
mode = Mode::Rest;
|
||||
}
|
||||
_ => {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentSpec,
|
||||
ExpectedWhere::InArgumentList,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
value = cdr;
|
||||
}
|
||||
|
||||
match mode {
|
||||
Mode::Required => (),
|
||||
Mode::Optional => {
|
||||
if optional_arguments.is_empty() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::AtLeastOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Optional),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
Mode::Rest => {
|
||||
if rest_argument.is_none() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ExactlyOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Rest),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
required_arguments,
|
||||
optional_arguments,
|
||||
rest_argument,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl FunctionBody {
|
||||
pub(super) fn parse(
|
||||
mut value: &Value,
|
||||
input: &Value,
|
||||
inside: Keyword,
|
||||
) -> Result<Self, ParseError> {
|
||||
let mut expressions = vec![];
|
||||
while !value.is_nil() {
|
||||
let Value::Cons(cons) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::AfterArgumentList(inside),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
let expression = Expression::parse_inner(car);
|
||||
expressions.push(expression);
|
||||
|
||||
value = cdr;
|
||||
}
|
||||
let Some(tail) = expressions.pop() else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterArgumentList(inside),
|
||||
),
|
||||
});
|
||||
};
|
||||
Ok(Self {
|
||||
head: expressions,
|
||||
tail,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PrognExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let body = FunctionBody::parse(value, input, Keyword::Progn)?;
|
||||
Ok(Self { body })
|
||||
}
|
||||
}
|
||||
|
||||
impl LambdaExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let Value::Cons(value) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Lambda),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = value.as_ref();
|
||||
let signature = FunctionSignature::parse(car, input)?;
|
||||
let (cdr, docstring) = maybe_docstring(cdr);
|
||||
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
|
||||
Ok(Self {
|
||||
docstring,
|
||||
signature,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DefunExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let Value::Cons(value) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InFunctionDefinition,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(identifier, cdr) = value.as_ref();
|
||||
|
||||
let Value::Identifier(identifier) = identifier else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Identifier,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Defun),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
let Value::Cons(cdr) = cdr else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InFunctionDefinition,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cdr.as_ref();
|
||||
let signature = FunctionSignature::parse(car, input)?;
|
||||
let (cdr, docstring) = maybe_docstring(cdr);
|
||||
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
|
||||
Ok(Self {
|
||||
name: identifier.clone(),
|
||||
docstring,
|
||||
signature,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ReturnExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
match value {
|
||||
Value::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
if !cdr.is_nil() {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::extraneous(cdr),
|
||||
});
|
||||
}
|
||||
|
||||
let expression = Expression::parse_inner(car);
|
||||
|
||||
Ok(Self { expression })
|
||||
}
|
||||
Value::Nil => Ok(Self {
|
||||
expression: Rc::new(Expression::Nil),
|
||||
}),
|
||||
_ => Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Return),
|
||||
),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for FunctionBody {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let mut r = false;
|
||||
for expr in self.head.iter() {
|
||||
r |= expr.collect_errors(errors);
|
||||
}
|
||||
r |= self.tail.collect_errors(errors);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for LambdaExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.body.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for DefunExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.body.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for PrognExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.body.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for ReturnExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.expression.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::{
|
||||
function::FunctionSignature,
|
||||
syntax::{
|
||||
CallExpression, ExpectedWhat, ExpectedWhere, Expression, FunctionBody,
|
||||
LambdaExpression, ParseError, ParseErrorKind,
|
||||
},
|
||||
},
|
||||
vm::value::{Keyword, Value},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_lambda() {
|
||||
let args = Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Keyword(Keyword::Optional),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Keyword(Keyword::Rest),
|
||||
Value::Identifier("c".into()),
|
||||
]);
|
||||
let body = Value::list_or_nil([
|
||||
Value::Identifier("+".into()),
|
||||
Value::Identifier("a".into()),
|
||||
Value::Number(1.into()),
|
||||
]);
|
||||
let lambda = Value::Keyword(Keyword::Lambda).cons(args.cons(body.cons(Value::Nil)));
|
||||
let expr = Expression::parse(&lambda).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
expr.as_ref(),
|
||||
&Expression::Lambda(LambdaExpression {
|
||||
docstring: None,
|
||||
signature: FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec!["b".into()],
|
||||
rest_argument: Some("c".into())
|
||||
},
|
||||
body: FunctionBody {
|
||||
head: vec![],
|
||||
tail: Expression::Call(CallExpression {
|
||||
callee: Expression::Identifier("+".into()).into(),
|
||||
arguments: vec![
|
||||
Rc::new(Expression::Identifier("a".into())),
|
||||
Rc::new(Expression::IntegerLiteral(1.into()))
|
||||
]
|
||||
})
|
||||
.into()
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
// syntax errors
|
||||
let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]);
|
||||
let e = Expression::parse(&lambda).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: lambda,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Lambda)
|
||||
)
|
||||
}]
|
||||
);
|
||||
let lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda), Value::Nil]);
|
||||
let e = Expression::parse(&lambda).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![ParseError {
|
||||
input: lambda,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
|
||||
)
|
||||
}]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_function_body() {
|
||||
let v = Value::list_or_nil([Value::Number(1.into())]);
|
||||
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap();
|
||||
assert_eq!(
|
||||
e,
|
||||
FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::IntegerLiteral(1.into()))
|
||||
}
|
||||
);
|
||||
|
||||
let v = Value::list_or_nil([
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
]);
|
||||
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap();
|
||||
assert_eq!(
|
||||
e,
|
||||
FunctionBody {
|
||||
head: vec![
|
||||
Rc::new(Expression::IntegerLiteral(1.into())),
|
||||
Rc::new(Expression::IntegerLiteral(2.into()))
|
||||
],
|
||||
tail: Rc::new(Expression::IntegerLiteral(3.into()))
|
||||
}
|
||||
);
|
||||
|
||||
// syntax error
|
||||
let v = Value::list_or_nil([]);
|
||||
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Expression,
|
||||
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
|
||||
),
|
||||
}
|
||||
);
|
||||
let v = Value::Identifier("a".into()).cons(Value::Identifier("b".into()));
|
||||
let e = FunctionBody::parse(&v, &v, Keyword::Lambda).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
ParseError {
|
||||
input: v,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::AfterArgumentList(Keyword::Lambda)
|
||||
),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_function_signature() {
|
||||
let args = Value::list_or_nil([]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec![],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None
|
||||
}
|
||||
);
|
||||
let args =
|
||||
Value::list_or_nil([Value::Identifier("a".into()), Value::Identifier("b".into())]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec!["a".into(), "b".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: None
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Keyword(Keyword::Optional),
|
||||
Value::Identifier("b".into()),
|
||||
]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec!["b".into()],
|
||||
rest_argument: None
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Keyword(Keyword::Rest),
|
||||
Value::Identifier("b".into()),
|
||||
]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: Some("b".into())
|
||||
}
|
||||
);
|
||||
let args =
|
||||
Value::list_or_nil([Value::Keyword(Keyword::Rest), Value::Identifier("c".into())]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec![],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: Some("c".into())
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Optional),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Keyword(Keyword::Rest),
|
||||
Value::Identifier("c".into()),
|
||||
]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec![],
|
||||
optional_arguments: vec!["b".into()],
|
||||
rest_argument: Some("c".into())
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Keyword(Keyword::Rest),
|
||||
Value::Identifier("c".into()),
|
||||
]);
|
||||
let v = FunctionSignature::parse(&args, &args).unwrap();
|
||||
assert_eq!(
|
||||
v,
|
||||
FunctionSignature {
|
||||
required_arguments: vec!["a".into()],
|
||||
optional_arguments: vec![],
|
||||
rest_argument: Some("c".into())
|
||||
}
|
||||
);
|
||||
|
||||
// syntax errors
|
||||
let args = Value::list_or_nil([Value::Keyword(Keyword::Optional)]);
|
||||
let e = FunctionSignature::parse(&args, &args).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
ParseError {
|
||||
input: args,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::AtLeastOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Optional)
|
||||
)
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::Rest),
|
||||
Value::Identifier("a".into()),
|
||||
Value::Identifier("b".into()),
|
||||
]);
|
||||
let e = FunctionSignature::parse(&args, &args).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
ParseError {
|
||||
input: args,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ExactlyOneArgument,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Rest)
|
||||
)
|
||||
}
|
||||
);
|
||||
let args = Value::list_or_nil([Value::Boolean(false.into())]);
|
||||
let e = FunctionSignature::parse(&args, &args).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
ParseError {
|
||||
input: args,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentSpec,
|
||||
ExpectedWhere::InArgumentList
|
||||
)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{
|
||||
CollectErrors, Expression, FunctionBody,
|
||||
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
},
|
||||
vm::value::{ConsCell, Keyword, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct WhileExpression {
|
||||
pub condition: Rc<Expression>,
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct LoopExpression {
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
impl WhileExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let Value::Cons(cons) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::While),
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
let condition = Expression::parse_inner(car);
|
||||
let body = FunctionBody::parse(cdr, input, Keyword::While)?;
|
||||
Ok(Self { condition, body })
|
||||
}
|
||||
}
|
||||
|
||||
impl LoopExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let body = FunctionBody::parse(value, input, Keyword::Loop)?;
|
||||
Ok(Self { body })
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for WhileExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
let a = self.condition.collect_errors(errors);
|
||||
let b = self.body.collect_errors(errors);
|
||||
a | b
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for LoopExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.body.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::{
|
||||
compile::{
|
||||
function::FunctionSignature,
|
||||
syntax::{
|
||||
CollectErrors, FunctionBody,
|
||||
error::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
maybe_docstring,
|
||||
},
|
||||
},
|
||||
vm::value::{ConsCell, IdentifierValue, Keyword, StringValue, Value},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct DefmacroExpression {
|
||||
pub name: IdentifierValue,
|
||||
pub docstring: Option<StringValue>,
|
||||
pub signature: FunctionSignature,
|
||||
pub body: FunctionBody,
|
||||
}
|
||||
|
||||
impl DefmacroExpression {
|
||||
pub(super) fn parse(value: &Value, input: &Value) -> Result<Self, ParseError> {
|
||||
let Value::Cons(value) = value else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InFunctionDefinition,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(identifier, cdr) = value.as_ref();
|
||||
|
||||
let Value::Identifier(identifier) = identifier else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Identifier,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Defun),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
let Value::Cons(cdr) = cdr else {
|
||||
return Err(ParseError {
|
||||
input: input.clone(),
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ProperList,
|
||||
ExpectedWhere::InFunctionDefinition,
|
||||
),
|
||||
});
|
||||
};
|
||||
let ConsCell(car, cdr) = cdr.as_ref();
|
||||
|
||||
let signature = FunctionSignature::parse(car, input)?;
|
||||
let (cdr, docstring) = maybe_docstring(cdr);
|
||||
let body = FunctionBody::parse(cdr, input, Keyword::Lambda)?;
|
||||
Ok(Self {
|
||||
name: identifier.clone(),
|
||||
docstring,
|
||||
signature,
|
||||
body,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for DefmacroExpression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
self.body.collect_errors(errors)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::value::{
|
||||
BooleanValue, ConsCell, IdentifierValue, Keyword, NumberValue, StringValue, Value, Vector,
|
||||
};
|
||||
|
||||
mod binding;
|
||||
mod call;
|
||||
mod condition;
|
||||
mod error;
|
||||
mod function;
|
||||
mod loops;
|
||||
mod macros;
|
||||
|
||||
pub use binding::*;
|
||||
pub use call::*;
|
||||
pub use condition::*;
|
||||
pub use error::*;
|
||||
pub use function::*;
|
||||
pub use loops::*;
|
||||
pub use macros::*;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Expression {
|
||||
Nil,
|
||||
BooleanLiteral(BooleanValue),
|
||||
StringLiteral(StringValue),
|
||||
IntegerLiteral(NumberValue),
|
||||
Identifier(IdentifierValue),
|
||||
Lambda(LambdaExpression),
|
||||
Defun(DefunExpression),
|
||||
Call(CallExpression),
|
||||
If(IfExpression),
|
||||
Cond(CondExpression),
|
||||
Setq(SetqExpression),
|
||||
Let(LetExpression),
|
||||
Defmacro(DefmacroExpression),
|
||||
SyntaxError(ParseError),
|
||||
Quote(Rc<Value>),
|
||||
While(WhileExpression),
|
||||
Loop(LoopExpression),
|
||||
Progn(PrognExpression),
|
||||
Vector(Rc<Vector>),
|
||||
Return(ReturnExpression),
|
||||
Break,
|
||||
Continue,
|
||||
}
|
||||
|
||||
impl Expression {
|
||||
fn map_or<T, F: FnOnce(T) -> Self>(result: Result<T, ParseError>, map: F) -> Rc<Self> {
|
||||
match result {
|
||||
Ok(r) => Rc::new(map(r)),
|
||||
Err(error) => Rc::new(Self::SyntaxError(error)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(value: &Value) -> Result<Rc<Self>, Vec<ParseError>> {
|
||||
let inner = Self::parse_inner(value);
|
||||
let mut errors = vec![];
|
||||
if inner.collect_errors(&mut errors) {
|
||||
Err(errors)
|
||||
} else {
|
||||
Ok(inner)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_inner(value: &Value) -> Rc<Self> {
|
||||
match value {
|
||||
Value::Vector(vector) => Rc::new(Self::Vector(vector.clone())),
|
||||
Value::HashTable(_) => todo!(),
|
||||
Value::String(value) => Rc::new(Self::StringLiteral(value.clone())),
|
||||
Value::Quasi(_value) => todo!("{value}"),
|
||||
Value::Unquote(_value) => todo!("Unquote {_value}"),
|
||||
Value::UnquoteSplice(_value) => todo!("UnquoteSplice {_value}"),
|
||||
Value::Quote(value) => Rc::new(Self::Quote(value.clone())),
|
||||
|
||||
Value::Nil => Rc::new(Self::Nil),
|
||||
&Value::Number(value) => Rc::new(Self::IntegerLiteral(value)),
|
||||
&Value::Boolean(value) => Rc::new(Self::BooleanLiteral(value)),
|
||||
Value::Identifier(value) => Rc::new(Self::Identifier(value.clone())),
|
||||
Value::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
match car {
|
||||
Value::Keyword(Keyword::Lambda) => {
|
||||
Self::map_or(LambdaExpression::parse(cdr, value), Expression::Lambda)
|
||||
}
|
||||
Value::Keyword(Keyword::If) => {
|
||||
Self::map_or(IfExpression::parse(cdr, value), Expression::If)
|
||||
}
|
||||
Value::Keyword(Keyword::Cond) => {
|
||||
Self::map_or(CondExpression::parse(cdr, value), Expression::Cond)
|
||||
}
|
||||
Value::Keyword(Keyword::Defun) => {
|
||||
Self::map_or(DefunExpression::parse(cdr, value), Expression::Defun)
|
||||
}
|
||||
Value::Keyword(keyword @ (Keyword::Let | Keyword::LetStar)) => {
|
||||
Self::map_or(LetExpression::parse(cdr, value, *keyword), Expression::Let)
|
||||
}
|
||||
Value::Keyword(Keyword::Setq) => {
|
||||
Self::map_or(SetqExpression::parse(cdr, value), Expression::Setq)
|
||||
}
|
||||
Value::Keyword(Keyword::Defmacro) => {
|
||||
Self::map_or(DefmacroExpression::parse(cdr, value), Expression::Defmacro)
|
||||
}
|
||||
Value::Keyword(Keyword::Quote) => {
|
||||
let value = match cdr {
|
||||
Value::Cons(cons) => cons.0.clone(),
|
||||
_ => Value::Nil,
|
||||
};
|
||||
Rc::new(Self::Quote(value.into()))
|
||||
}
|
||||
Value::Keyword(Keyword::Progn) => {
|
||||
Self::map_or(PrognExpression::parse(cdr, value), Expression::Progn)
|
||||
}
|
||||
Value::Keyword(Keyword::Loop) => {
|
||||
Self::map_or(LoopExpression::parse(cdr, value), Expression::Loop)
|
||||
}
|
||||
Value::Keyword(Keyword::While) => {
|
||||
Self::map_or(WhileExpression::parse(cdr, value), Expression::While)
|
||||
}
|
||||
Value::Keyword(Keyword::Break) => Rc::new(Self::Break),
|
||||
Value::Keyword(Keyword::Continue) => Rc::new(Self::Continue),
|
||||
Value::Keyword(Keyword::Return) => {
|
||||
Self::map_or(ReturnExpression::parse(cdr, value), Expression::Return)
|
||||
}
|
||||
_ => Self::map_or(CallExpression::parse(cons, value), Expression::Call),
|
||||
}
|
||||
}
|
||||
Value::Keyword(keyword) => todo!("Error here: keyword {keyword}"),
|
||||
Value::Closure(_) => todo!("Error here"),
|
||||
Value::Function(_) => todo!("Error here"),
|
||||
Value::NativeFunction(_) => todo!("Error here"),
|
||||
Value::NativeValue(_) => todo!("Error here"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CollectErrors<ParseError> for Expression {
|
||||
fn collect_errors(&self, errors: &mut Vec<ParseError>) -> bool {
|
||||
match self {
|
||||
Self::SyntaxError(error) => {
|
||||
errors.push(error.clone());
|
||||
true
|
||||
}
|
||||
Self::If(condition) => condition.collect_errors(errors),
|
||||
Self::Cond(condition) => condition.collect_errors(errors),
|
||||
Self::Lambda(lambda) => lambda.collect_errors(errors),
|
||||
Self::Defun(defun) => defun.collect_errors(errors),
|
||||
Self::Call(call) => call.collect_errors(errors),
|
||||
Self::Let(let_) => let_.collect_errors(errors),
|
||||
Self::Setq(setq) => setq.collect_errors(errors),
|
||||
Self::Defmacro(defmacro) => defmacro.collect_errors(errors),
|
||||
Self::While(cloop) => cloop.collect_errors(errors),
|
||||
Self::Loop(cloop) => cloop.collect_errors(errors),
|
||||
Self::Progn(progn) => progn.collect_errors(errors),
|
||||
Self::Return(return_) => return_.collect_errors(errors),
|
||||
Self::Nil
|
||||
| Self::Vector(_)
|
||||
| Self::Break
|
||||
| Self::Continue
|
||||
| Self::IntegerLiteral(_)
|
||||
| Self::Identifier(_)
|
||||
| Self::BooleanLiteral(_)
|
||||
| Self::Quote(_)
|
||||
| Self::StringLiteral(_) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn maybe_docstring(input: &Value) -> (&Value, Option<StringValue>) {
|
||||
if let Some((Value::String(docstring), cdr)) = input.uncons_ref() {
|
||||
(cdr, Some(docstring.clone()))
|
||||
} else {
|
||||
(input, None)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
compile::syntax::{ExpectedWhat, ExpectedWhere, Expression, ParseError, ParseErrorKind},
|
||||
vm::value::{Keyword, Value},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_parse_basic() {
|
||||
let v = Value::Nil;
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(e.as_ref(), &Expression::Nil);
|
||||
|
||||
let v = Value::Number(1234.into());
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(e.as_ref(), &Expression::IntegerLiteral(1234.into()));
|
||||
|
||||
let v = Value::Boolean(false.into());
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(e.as_ref(), &Expression::BooleanLiteral(false.into()));
|
||||
|
||||
let v = Value::Identifier("a".into());
|
||||
let e = Expression::parse(&v).unwrap();
|
||||
assert_eq!(e.as_ref(), &Expression::Identifier("a".into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_nested_syntax_error() {
|
||||
let inner_if = Value::list_or_nil([Value::Keyword(Keyword::If)]);
|
||||
let inner_lambda = Value::list_or_nil([Value::Keyword(Keyword::Lambda)]);
|
||||
let outer_if = Value::list_or_nil([
|
||||
Value::Keyword(Keyword::If),
|
||||
inner_if.clone(),
|
||||
inner_lambda.clone(),
|
||||
Value::Number(3.into()),
|
||||
]);
|
||||
let e = Expression::parse(&outer_if).unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
vec![
|
||||
ParseError {
|
||||
input: inner_if,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::Condition,
|
||||
ExpectedWhere::AfterKeyword(Keyword::If)
|
||||
)
|
||||
},
|
||||
ParseError {
|
||||
input: inner_lambda,
|
||||
error: ParseErrorKind::Expected(
|
||||
ExpectedWhat::ArgumentList,
|
||||
ExpectedWhere::AfterKeyword(Keyword::Lambda)
|
||||
)
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
use crate::vm::value::Value;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct VectorExpression {
|
||||
pub elements: Vec<Value>,
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::{
|
||||
instruction::{LocalId, MathInstruction},
|
||||
value::{Value, ValueString},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum CompileValue {
|
||||
Integer(i64),
|
||||
String(ValueString),
|
||||
Boolean(bool),
|
||||
LocalFunction(u32, usize),
|
||||
Local(LocalId),
|
||||
Argument(usize),
|
||||
Global(Rc<str>),
|
||||
Quote(Rc<Value>),
|
||||
Stack,
|
||||
Nil,
|
||||
}
|
||||
|
||||
// #[derive(Debug, Hash, PartialEq, Eq)]
|
||||
// pub enum CompileConstant {
|
||||
// Integer(i64),
|
||||
// LocalFunction(u32, usize),
|
||||
// Identifier(Rc<str>),
|
||||
// String(ValueString),
|
||||
// Value(Rc<Value>),
|
||||
// }
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum BuiltinFunction {
|
||||
Math(MathInstruction),
|
||||
}
|
||||
|
||||
impl BuiltinFunction {
|
||||
pub fn from_identifier(identifier: &str) -> Option<Self> {
|
||||
match identifier {
|
||||
"+" => Some(Self::Math(MathInstruction::Add)),
|
||||
"-" => Some(Self::Math(MathInstruction::Sub)),
|
||||
"*" => Some(Self::Math(MathInstruction::Mul)),
|
||||
"/" => Some(Self::Math(MathInstruction::Div)),
|
||||
"%" => Some(Self::Math(MathInstruction::Mod)),
|
||||
"&" => Some(Self::Math(MathInstruction::BitwiseAnd)),
|
||||
"|" => Some(Self::Math(MathInstruction::BitwiseOr)),
|
||||
"^" => Some(Self::Math(MathInstruction::BitwiseXor)),
|
||||
"&&" => Some(Self::Math(MathInstruction::And)),
|
||||
"||" => Some(Self::Math(MathInstruction::Or)),
|
||||
">" => Some(Self::Math(MathInstruction::Gt)),
|
||||
">=" => Some(Self::Math(MathInstruction::Ge)),
|
||||
"=" => Some(Self::Math(MathInstruction::Eq)),
|
||||
"/=" => Some(Self::Math(MathInstruction::Ne)),
|
||||
"<=" => Some(Self::Math(MathInstruction::Le)),
|
||||
"<" => Some(Self::Math(MathInstruction::Lt)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
use std::{error::Error as StdError, fmt, io, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
compile::{CompileError, syntax::ParseError},
|
||||
vm::{
|
||||
Value,
|
||||
instruction::{Instruction, InstructionDecodeError},
|
||||
value::{BytecodeFunction, IdentifierValue, StringValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("{error}")]
|
||||
pub struct MachineErrorAt {
|
||||
// TODO ip where the error occured
|
||||
pub error: MachineError,
|
||||
pub location: Option<MachineErrorLocation>,
|
||||
}
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct MachineErrorLocation {
|
||||
pub function: Rc<BytecodeFunction>,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
#[error("expected {expected}, got {got}")]
|
||||
pub struct ValueConversionError {
|
||||
pub expected: String,
|
||||
pub got: Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum ReadError {
|
||||
#[error("{0}")]
|
||||
Lexical(nom::Err<nom::error::Error<String>, nom::error::Error<String>>),
|
||||
#[error("{0:?}")]
|
||||
Parse(Vec<ParseError>),
|
||||
#[error("{0}")]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct ArgumentCountError {
|
||||
pub function: Rc<BytecodeFunction>,
|
||||
pub actual: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum MachineError {
|
||||
// VM itself
|
||||
#[error("instruction pointer is undefined")]
|
||||
InstructionPointerUndefined,
|
||||
#[error("instruction pointer is out of bounds")]
|
||||
InstructionPointerOutOfBounds,
|
||||
#[error("instruction fetch failed")]
|
||||
InstructionFetch,
|
||||
#[error("instruction decode error: {0}")]
|
||||
InstructionDecode(#[from] InstructionDecodeError),
|
||||
#[error("data stack overflowed")]
|
||||
DataStackOverflow,
|
||||
#[error("data stack underflowed")]
|
||||
DataStackUnderflow,
|
||||
#[error("call stack overflowed")]
|
||||
CallStackOverflow,
|
||||
#[error("call stack underflowed")]
|
||||
CallStackUnderflow,
|
||||
#[error("undefined upvalue reference")]
|
||||
UndefinedUpvalueReference,
|
||||
#[error("undefined local reference")]
|
||||
UndefinedLocalReference,
|
||||
#[error("undefined constant reference")]
|
||||
UndefinedConstantReference,
|
||||
#[error("unbound identifier reference: {0}")]
|
||||
UnboundIdentifier(IdentifierValue),
|
||||
#[error("invalid {0} argument: {1}")]
|
||||
InvalidInstructionArgument(Instruction, ValueConversionError),
|
||||
#[error("invalid branch target: {0}{1:+}")]
|
||||
InvalidBranchTarget(usize, isize),
|
||||
#[error("GET_TEMP with an empty temp register")]
|
||||
TempRegisterEmpty,
|
||||
#[error("{0}")]
|
||||
ArgumentCount(ArgumentCountError),
|
||||
|
||||
// Syntax+evaluation
|
||||
// #[error("evaluation error: {0}")]
|
||||
// EvaluationError(EvalError),
|
||||
#[error("invalid argument count")]
|
||||
InvalidArgumentCount,
|
||||
#[error("aborted: {0}")]
|
||||
Abort(Rc<str>),
|
||||
#[error("{0}: {1}")]
|
||||
LoadError(StringValue, Box<MachineErrorAt>),
|
||||
#[error("{0}")]
|
||||
Custom(Box<dyn StdError>),
|
||||
#[error("code-raised error: {0}")]
|
||||
Raised(String),
|
||||
|
||||
#[error("value conversion error: {0}")]
|
||||
ValueConversion(#[from] ValueConversionError),
|
||||
#[error("syntax error: {0}")]
|
||||
Read(ReadError),
|
||||
#[error("compile error: {0}")]
|
||||
Compile(#[from] CompileError),
|
||||
#[error("value cannot be used as a hashmap key: {0}")]
|
||||
InvalidHashTableKey(Value),
|
||||
}
|
||||
|
||||
impl MachineError {
|
||||
pub fn at(self, location: Option<MachineErrorLocation>) -> MachineErrorAt {
|
||||
MachineErrorAt {
|
||||
error: self,
|
||||
location,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn at_unknown(self) -> MachineErrorAt {
|
||||
self.at(None)
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineErrorAt {
|
||||
pub fn at_unknown(error: MachineError) -> Self {
|
||||
Self::at(error, None)
|
||||
}
|
||||
|
||||
pub fn at(error: MachineError, location: Option<MachineErrorLocation>) -> Self {
|
||||
Self { error, location }
|
||||
}
|
||||
}
|
||||
|
||||
impl MachineErrorLocation {
|
||||
pub fn disassemble_chunk(&self, address: usize, before: usize, after: usize, arrow: bool) {
|
||||
self.function.disassemble(address, before, after, arrow);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for MachineErrorLocation {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:p}+{}", self.function, self.offset)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ReadError {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Lexical(a), Self::Lexical(b)) => a == b,
|
||||
(Self::Io(a), Self::Io(b)) => a.raw_os_error() == b.raw_os_error(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ArgumentCountError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let too_few = self.actual < self.function.required_count;
|
||||
write!(
|
||||
f,
|
||||
"too {} arguments for function {}: expected ",
|
||||
if too_few { "few" } else { "many" },
|
||||
self.function
|
||||
)?;
|
||||
|
||||
if self.function.has_rest {
|
||||
let singular = self.function.required_count == 1;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"at least {} argument{}",
|
||||
self.function.required_count,
|
||||
if singular { "" } else { "s" }
|
||||
)?;
|
||||
} else if self.function.optional_count > 0 {
|
||||
write!(
|
||||
f,
|
||||
"{}-{} arguments",
|
||||
self.function.required_count,
|
||||
self.function.required_count + self.function.optional_count
|
||||
)?;
|
||||
} else {
|
||||
let singular = self.function.required_count == 1;
|
||||
|
||||
write!(
|
||||
f,
|
||||
"{} argument{}",
|
||||
self.function.required_count,
|
||||
if singular { "" } else { "s" }
|
||||
)?;
|
||||
}
|
||||
|
||||
write!(f, ", got {}", self.actual)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#![feature(
|
||||
if_let_guard,
|
||||
coverage_attribute,
|
||||
debug_closure_helpers,
|
||||
iter_next_chunk,
|
||||
exact_size_is_empty,
|
||||
exact_div
|
||||
)]
|
||||
//
|
||||
// pub mod error;
|
||||
|
||||
pub mod parse;
|
||||
pub mod read;
|
||||
|
||||
#[macro_use]
|
||||
pub mod util;
|
||||
|
||||
pub mod compile;
|
||||
pub mod error;
|
||||
pub mod vm;
|
||||
@@ -0,0 +1,233 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
rc::Rc,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use lysp::{
|
||||
compile::{CompileOptions, syntax::ParseError},
|
||||
error::{MachineError, MachineErrorAt},
|
||||
read::{InteractiveReader, ModuleReader, read},
|
||||
util::Either,
|
||||
vm::{
|
||||
env::Environment,
|
||||
machine::Machine,
|
||||
prelude,
|
||||
value::{ClosureValue, Value},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("{0}")]
|
||||
enum Error {
|
||||
Machine(#[from] MachineErrorAt),
|
||||
Io(#[from] io::Error),
|
||||
#[error("Error already printed")]
|
||||
Printed,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Trace {
|
||||
Compile,
|
||||
Execute,
|
||||
Call,
|
||||
Return,
|
||||
Stack,
|
||||
Macro,
|
||||
}
|
||||
|
||||
impl FromStr for Trace {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"compile" => Ok(Self::Compile),
|
||||
"execute" => Ok(Self::Execute),
|
||||
"call" => Ok(Self::Call),
|
||||
"return" => Ok(Self::Return),
|
||||
"stack" => Ok(Self::Stack),
|
||||
"macro" => Ok(Self::Macro),
|
||||
_ => Err(format!("Unknown trace flag: {s:?}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[clap(
|
||||
short = 'T',
|
||||
long = "trace",
|
||||
help = "Enable tracing of execution/compilation steps"
|
||||
)]
|
||||
trace: Vec<Trace>,
|
||||
module: Option<PathBuf>,
|
||||
arguments: Vec<String>,
|
||||
}
|
||||
|
||||
fn print_syntax_errors(errors: &[ParseError]) {
|
||||
if errors.len() > 1 {
|
||||
eprintln!("Syntax errors:");
|
||||
} else {
|
||||
eprintln!("Syntax error:");
|
||||
}
|
||||
eprintln!();
|
||||
for error in errors {
|
||||
let ParseError { input, error } = error;
|
||||
eprintln!(" * In expression:");
|
||||
eprintln!();
|
||||
eprintln!(" {input}");
|
||||
eprintln!();
|
||||
eprintln!(" :: {error}");
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_eval_error(value: Option<&Value>, input: MachineErrorAt) -> Error {
|
||||
if let Some(value) = value {
|
||||
eprintln!("Error in expression:");
|
||||
eprintln!();
|
||||
eprintln!(" {value}");
|
||||
eprintln!();
|
||||
}
|
||||
match input.error {
|
||||
MachineError::Compile(error) => {
|
||||
eprintln!("Compilation error:");
|
||||
eprintln!();
|
||||
eprintln!(":: {error}");
|
||||
}
|
||||
MachineError::Read(error) => {
|
||||
eprintln!("Syntax error:");
|
||||
eprintln!();
|
||||
eprintln!(":: {error}");
|
||||
}
|
||||
error => {
|
||||
if let Some(location) = input.location {
|
||||
eprintln!("At location {location}:");
|
||||
location.disassemble_chunk(location.offset, 5, 1, true);
|
||||
}
|
||||
eprintln!(":: {error}");
|
||||
}
|
||||
}
|
||||
Error::Printed
|
||||
}
|
||||
|
||||
fn handle_module_error(input: Either<MachineErrorAt, Vec<ParseError>>) -> Error {
|
||||
match input {
|
||||
Either::Left(error) => handle_eval_error(None, error),
|
||||
Either::Right(errors) => {
|
||||
print_syntax_errors(&errors);
|
||||
Error::Printed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(
|
||||
options: &CompileOptions,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
value: Value,
|
||||
) -> Option<Value> {
|
||||
let result = vm.evaluate_value(
|
||||
options.clone(),
|
||||
Some("interactive".into()),
|
||||
env,
|
||||
value.clone(),
|
||||
);
|
||||
match result {
|
||||
Ok(r) => Some(r),
|
||||
Err(error) => {
|
||||
handle_eval_error(Some(&value), error);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_interactive(
|
||||
compile_options: &CompileOptions,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
) -> Result<(), Error> {
|
||||
let mut reader = InteractiveReader::new("> ", ">> ");
|
||||
|
||||
loop {
|
||||
let value = match read(&mut reader, vm, env) {
|
||||
Ok(Some(value)) => value,
|
||||
Ok(None) => break,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if let Some(value) = eval(compile_options, vm, env, value) {
|
||||
println!("== {value}");
|
||||
} else {
|
||||
reader.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_module<P: AsRef<Path>>(
|
||||
compile_options: &CompileOptions,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
path: P,
|
||||
) -> Result<(), Error> {
|
||||
let path = path.as_ref();
|
||||
let name = format!("{}", path.display());
|
||||
let reader = BufReader::new(File::open(path)?);
|
||||
let module_reader = ModuleReader::new(reader, path, vm.trace_macros);
|
||||
let function = match module_reader.compile(Some(name.into()), compile_options, env) {
|
||||
Ok(function) => function,
|
||||
Err(error) => return Err(handle_module_error(error)),
|
||||
};
|
||||
let closure = ClosureValue {
|
||||
function,
|
||||
upvalues: vec![],
|
||||
};
|
||||
match vm.evaluate_closure(env, closure, 0) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(error) => Err(handle_eval_error(None, error)),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
let mut vm = Machine::default();
|
||||
let compile_options = CompileOptions {
|
||||
trace_compile: args.trace.contains(&Trace::Compile),
|
||||
};
|
||||
vm.trace_instructions = args.trace.contains(&Trace::Execute);
|
||||
vm.trace_calls = args.trace.contains(&Trace::Call);
|
||||
vm.trace_returns = args.trace.contains(&Trace::Return);
|
||||
vm.trace_stack = args.trace.contains(&Trace::Stack);
|
||||
vm.trace_macros = args.trace.contains(&Trace::Macro);
|
||||
let env = Rc::new(Environment::default());
|
||||
prelude::load(&env);
|
||||
let mut arguments = vec![];
|
||||
if let Some(script) = args.module.as_ref() {
|
||||
arguments.push(format!("{}", script.display()));
|
||||
}
|
||||
arguments.extend(args.arguments);
|
||||
env.set_global_value(
|
||||
"*args*",
|
||||
Value::list_or_nil(arguments.into_iter().map(|arg| Value::String(arg.into()))),
|
||||
)
|
||||
.ok();
|
||||
let result = match args.module.as_ref() {
|
||||
Some(module) => run_module(&compile_options, &mut vm, &env, module),
|
||||
None => run_interactive(&compile_options, &mut vm, &env),
|
||||
};
|
||||
match result {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(Error::Printed) => ExitCode::FAILURE,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use nom::{
|
||||
AsChar, FindToken, IResult, Input, Parser,
|
||||
branch::alt,
|
||||
bytes::streaming::{is_not, tag},
|
||||
character::{
|
||||
anychar,
|
||||
streaming::{char, one_of},
|
||||
},
|
||||
combinator::{map, map_res, opt, recognize, value, verify},
|
||||
error::{Error, ErrorKind, FromExternalError, ParseError},
|
||||
multi::{fold, fold_many1, many0},
|
||||
sequence::{delimited, pair, preceded},
|
||||
};
|
||||
|
||||
use crate::vm::value::{Keyword, NumberValue, Value, Vector};
|
||||
|
||||
struct IdentifierHead;
|
||||
struct IdentifierTail;
|
||||
|
||||
impl FindToken<char> for IdentifierHead {
|
||||
fn find_token(&self, token: char) -> bool {
|
||||
token.is_alphabetic() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
|
||||
}
|
||||
}
|
||||
|
||||
impl FindToken<char> for IdentifierTail {
|
||||
fn find_token(&self, token: char) -> bool {
|
||||
token.is_alphanumeric() || "~!@$%^&*-=_+<>?/|≠≥≤·:".contains(token)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_sign(input: &str) -> IResult<&str, bool> {
|
||||
map(
|
||||
opt(alt((value(true, char('-')), value(false, char('+'))))),
|
||||
|sign| sign.unwrap_or(false),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_hex_digit(input: &str) -> IResult<&str, char> {
|
||||
let (tail, ch) = anychar(input)?;
|
||||
match ch {
|
||||
'0'..='9' | 'a'..='f' | 'A'..='F' => Ok((tail, ch)),
|
||||
_ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
|
||||
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_dec_digit(input: &str) -> IResult<&str, char> {
|
||||
let (tail, ch) = anychar(input)?;
|
||||
match ch {
|
||||
'0'..='9' => Ok((tail, ch)),
|
||||
_ if ch.is_alphabetic() => Err(nom::Err::Failure(Error::from_char(input, ch))),
|
||||
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_oct_digit(input: &str) -> IResult<&str, char> {
|
||||
let (tail, ch) = anychar(input)?;
|
||||
match ch {
|
||||
'0'..='7' => Ok((tail, ch)),
|
||||
_ if ch.is_alphanumeric() => Err(nom::Err::Failure(Error::from_char(input, ch))),
|
||||
_ => Err(nom::Err::Error(Error::from_char(input, ch))),
|
||||
}
|
||||
}
|
||||
|
||||
struct OverflowError;
|
||||
|
||||
fn parse_integer_inner<I, E, P, D>(input: I, prefix: P, radix: u64, digit: D) -> IResult<I, u64, E>
|
||||
where
|
||||
I: Input,
|
||||
E: ParseError<I>,
|
||||
E: FromExternalError<I, OverflowError>,
|
||||
P: Parser<I, Error = E>,
|
||||
D: Parser<I, Output = char, Error = E>,
|
||||
{
|
||||
#[coverage(off)]
|
||||
fn digit_to_u8(ch: char) -> u8 {
|
||||
match ch {
|
||||
'0'..='9' => ch as u8 - b'0',
|
||||
'a'..='f' => ch as u8 - b'a' + 10,
|
||||
'A'..='F' => ch as u8 - b'A' + 10,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
let (output, result) = preceded(
|
||||
prefix,
|
||||
fold_many1(
|
||||
digit,
|
||||
|| Ok(0u64),
|
||||
move |acc, ch| match acc {
|
||||
Ok(acc)
|
||||
if let Some(acc) = acc
|
||||
.checked_mul(radix)
|
||||
.and_then(|acc| acc.checked_add(digit_to_u8(ch) as u64)) =>
|
||||
{
|
||||
Ok(acc)
|
||||
}
|
||||
Ok(_) => Err(OverflowError),
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
),
|
||||
)
|
||||
.parse(input.clone())?;
|
||||
|
||||
match result {
|
||||
Ok(result) => Ok((output, result)),
|
||||
Err(OverflowError) => Err(nom::Err::Failure(E::from_external_error(
|
||||
input,
|
||||
ErrorKind::Fold,
|
||||
OverflowError,
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_decimal(input: &str) -> IResult<&str, ()> {
|
||||
let (_, ch) = anychar(input)?;
|
||||
if ch.is_ascii_digit() {
|
||||
Ok((input, ()))
|
||||
} else {
|
||||
Err(nom::Err::Error(Error::from_char(input, ch)))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_integer_oct(input: &str) -> IResult<&str, u64> {
|
||||
parse_integer_inner(input, alt((tag("0o"), tag("0O"))), 8, parse_oct_digit)
|
||||
}
|
||||
|
||||
fn parse_integer_dec(input: &str) -> IResult<&str, u64> {
|
||||
parse_integer_inner(input, check_decimal, 10, parse_dec_digit)
|
||||
}
|
||||
|
||||
fn parse_integer_hex(input: &str) -> IResult<&str, u64> {
|
||||
parse_integer_inner(input, alt((tag("0x"), tag("0X"))), 16, parse_hex_digit)
|
||||
}
|
||||
|
||||
fn parse_integer(input: &str) -> IResult<&str, Value> {
|
||||
map_res(
|
||||
(
|
||||
parse_sign,
|
||||
alt((parse_integer_hex, parse_integer_oct, parse_integer_dec)),
|
||||
),
|
||||
|(minus, value)| {
|
||||
i64::try_from(value)
|
||||
.map(|value| Value::Number((if minus { -value } else { value }).into()))
|
||||
},
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_number(input: &str) -> IResult<&str, Value> {
|
||||
alt((
|
||||
value(Value::Number(NumberValue::nan()), tag("#NAN")),
|
||||
value(Value::Number(NumberValue::nan()), tag("#NaN")),
|
||||
value(Value::Number(NumberValue::nan()), tag("#nan")),
|
||||
value(Value::Number(NumberValue::infinity()), tag("#inf")),
|
||||
value(Value::Number(NumberValue::infinity()), tag("#INF")),
|
||||
value(Value::Number(NumberValue::neg_infinity()), tag("-#inf")),
|
||||
value(Value::Number(NumberValue::neg_infinity()), tag("-#INF")),
|
||||
parse_integer,
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_identifier(input: &str) -> IResult<&str, &str> {
|
||||
recognize(preceded(
|
||||
one_of(IdentifierHead),
|
||||
many0(one_of(IdentifierTail)),
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_identifier_or_keyword_or_nil(input: &str) -> IResult<&str, Value> {
|
||||
map(parse_identifier, |ident| match ident {
|
||||
"NIL" | "nil" => Value::Nil,
|
||||
_ if let Some(keyword) = Keyword::parse(ident) => Value::Keyword(keyword),
|
||||
_ => Value::Identifier(ident.into()),
|
||||
})
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_list_or_nil(input: &str) -> IResult<&str, Value> {
|
||||
let list_parens = delimited(
|
||||
char('('),
|
||||
many0(preceded(skip_comment_and_whitespace, parse_value)),
|
||||
preceded(skip_comment_and_whitespace, char(')')),
|
||||
);
|
||||
let list_brackets = delimited(
|
||||
char('['),
|
||||
many0(preceded(skip_comment_and_whitespace, parse_value)),
|
||||
preceded(skip_comment_and_whitespace, char(']')),
|
||||
);
|
||||
|
||||
alt((list_parens, list_brackets))
|
||||
.map(Value::list_or_nil)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_boolean(input: &str) -> IResult<&str, Value> {
|
||||
alt((
|
||||
value(true, tag("#t")),
|
||||
value(true, tag("#T")),
|
||||
value(false, tag("#f")),
|
||||
value(false, tag("#F")),
|
||||
))
|
||||
.map(Into::into)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
enum StringSegment<'a> {
|
||||
Literal(&'a str),
|
||||
Escape(char),
|
||||
}
|
||||
|
||||
fn parse_literal_string_segment(input: &str) -> IResult<&str, &str> {
|
||||
verify(is_not("\"\\"), |s: &str| !s.is_empty()).parse(input)
|
||||
}
|
||||
|
||||
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
|
||||
preceded(
|
||||
char('\\'),
|
||||
alt((
|
||||
value('\n', char('n')),
|
||||
value('\r', char('r')),
|
||||
value('\t', char('t')),
|
||||
value('\\', char('\\')),
|
||||
value('"', char('"')),
|
||||
)),
|
||||
)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_string_segment(input: &str) -> IResult<&str, StringSegment<'_>> {
|
||||
alt((
|
||||
map(parse_literal_string_segment, StringSegment::Literal),
|
||||
map(parse_escaped_char, StringSegment::Escape),
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_string(input: &str) -> IResult<&str, Value> {
|
||||
delimited(
|
||||
char('"'),
|
||||
fold(
|
||||
0..,
|
||||
parse_string_segment,
|
||||
String::new,
|
||||
|mut string, segment| {
|
||||
match segment {
|
||||
StringSegment::Literal(s) => string.push_str(s),
|
||||
StringSegment::Escape(c) => string.push(c),
|
||||
};
|
||||
string
|
||||
},
|
||||
),
|
||||
char('"'),
|
||||
)
|
||||
.map(|s| Value::String(s.into()))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
pub fn skip_comment_and_whitespace(mut input: &str) -> IResult<&str, ()> {
|
||||
let mut in_comment = false;
|
||||
while let Some(ch) = input.chars().next() {
|
||||
let next = input.ceil_char_boundary(1);
|
||||
if next >= input.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == ';' {
|
||||
in_comment = true;
|
||||
}
|
||||
|
||||
if in_comment {
|
||||
if ch.is_newline() {
|
||||
in_comment = false;
|
||||
}
|
||||
|
||||
input = &input[next..];
|
||||
continue;
|
||||
} else if ch.is_whitespace() || ch.is_newline() {
|
||||
input = &input[next..];
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
input = input.trim_start();
|
||||
Ok((input, ()))
|
||||
}
|
||||
|
||||
fn parse_quasi(input: &str) -> IResult<&str, Value> {
|
||||
preceded(char('`'), parse_value)
|
||||
.map(Rc::new)
|
||||
.map(Value::Quasi)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_unquote(input: &str) -> IResult<&str, Value> {
|
||||
preceded(char(','), parse_value)
|
||||
.map(Rc::new)
|
||||
.map(Value::Unquote)
|
||||
.parse(input)
|
||||
}
|
||||
fn parse_unquote_splice(input: &str) -> IResult<&str, Value> {
|
||||
preceded(tag(",@"), parse_value)
|
||||
.map(Rc::new)
|
||||
.map(Value::UnquoteSplice)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_quote(input: &str) -> IResult<&str, Value> {
|
||||
preceded(char('\''), parse_value)
|
||||
.map(Rc::new)
|
||||
.map(Value::Quote)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_vector(input: &str) -> IResult<&str, Value> {
|
||||
delimited(
|
||||
tag("#["),
|
||||
many0(preceded(skip_comment_and_whitespace, parse_value)),
|
||||
preceded(skip_comment_and_whitespace, char(']')),
|
||||
)
|
||||
.map(Vector::from_iter)
|
||||
.map(Into::into)
|
||||
.map(Value::Vector)
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
fn parse_dotted_cons(input: &str) -> IResult<&str, Value> {
|
||||
delimited(
|
||||
char('('),
|
||||
pair(
|
||||
preceded(skip_comment_and_whitespace, parse_value),
|
||||
preceded(
|
||||
pair(skip_comment_and_whitespace, char('.')),
|
||||
preceded(skip_comment_and_whitespace, parse_value),
|
||||
),
|
||||
),
|
||||
preceded(skip_comment_and_whitespace, char(')')),
|
||||
)
|
||||
.map(|(a, b)| a.cons(b))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
pub fn parse_value(input: &str) -> IResult<&str, Value> {
|
||||
alt((
|
||||
parse_dotted_cons,
|
||||
parse_list_or_nil,
|
||||
parse_vector,
|
||||
parse_boolean,
|
||||
parse_unquote_splice,
|
||||
parse_quote,
|
||||
parse_quasi,
|
||||
parse_unquote,
|
||||
parse_number,
|
||||
parse_string,
|
||||
parse_identifier_or_keyword_or_nil,
|
||||
))
|
||||
.parse(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use nom::error::{Error, FromExternalError, ParseError};
|
||||
|
||||
use crate::{
|
||||
parse::{
|
||||
OverflowError, parse_boolean, parse_dotted_cons, parse_identifier,
|
||||
parse_identifier_or_keyword_or_nil, parse_integer, parse_integer_dec,
|
||||
parse_integer_hex, parse_integer_oct, parse_list_or_nil, parse_value,
|
||||
},
|
||||
vm::value::{Keyword, Value},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_integer_dec() {
|
||||
let (r, v) = parse_integer_dec("1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, 1234);
|
||||
let (r, v) = parse_integer_dec("18446744073709551615\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, u64::MAX);
|
||||
|
||||
// illegal digit
|
||||
let e = parse_integer_dec("1234A").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("A", 'A')));
|
||||
|
||||
// overflow
|
||||
let e = parse_integer_dec("9999999999999999999999999").unwrap_err();
|
||||
assert_eq!(
|
||||
e,
|
||||
nom::Err::Failure(Error::from_external_error(
|
||||
"9999999999999999999999999",
|
||||
nom::error::ErrorKind::Fold,
|
||||
OverflowError
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_oct() {
|
||||
let (r, v) = parse_integer_oct("0o1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, 0o1234);
|
||||
let (r, v) = parse_integer_oct("0O1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, 0o1234);
|
||||
|
||||
// illegal digit
|
||||
let e = parse_integer_oct("0o12349").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("9", '9')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer_hex() {
|
||||
let (r, v) = parse_integer_hex("0x123Fa4\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, 0x123FA4);
|
||||
let (r, v) = parse_integer_hex("0X123Fa4\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, 0x123FA4);
|
||||
|
||||
// illegal digit
|
||||
let e = parse_integer_hex("0x1AG").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("G", 'G')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_integer() {
|
||||
// Dec path
|
||||
let (r, v) = parse_integer("1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(1234.into()));
|
||||
let (r, v) = parse_integer("+1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(1234.into()));
|
||||
let (r, v) = parse_integer("-1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number((-1234).into()));
|
||||
// Oct path
|
||||
let (r, v) = parse_integer("0o1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(0o1234.into()));
|
||||
let (r, v) = parse_integer("+0O1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(0o1234.into()));
|
||||
let (r, v) = parse_integer("-0o1234\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number((-0o1234).into()));
|
||||
// Hex path
|
||||
let (r, v) = parse_integer("0x1234AF\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(0x1234AF.into()));
|
||||
let (r, v) = parse_integer("+0X1234AF\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(0x1234AF.into()));
|
||||
let (r, v) = parse_integer("-0x1234AF\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number((-0x1234AF).into()));
|
||||
|
||||
// Illegal path
|
||||
let e = parse_integer("0x12V34").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
|
||||
let e = parse_integer("-0X12V34").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("V34", 'V')));
|
||||
let e = parse_integer("-0o81").unwrap_err();
|
||||
assert_eq!(e, nom::Err::Failure(Error::from_char("81", '8')));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identifier() {
|
||||
let (r, v) = parse_identifier("+\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, "+");
|
||||
let (r, v) = parse_identifier("a-1\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, "a-1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_identifier_or_keyword_or_nil() {
|
||||
let (r, v) = parse_identifier_or_keyword_or_nil("+\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Identifier("+".into()));
|
||||
let (r, v) = parse_identifier_or_keyword_or_nil("lambda\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Keyword(Keyword::Lambda));
|
||||
let (r, v) = parse_identifier_or_keyword_or_nil("NIL\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Nil);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_or_nil() {
|
||||
let (r, v) = parse_list_or_nil("()\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Nil);
|
||||
let (r, v) = parse_list_or_nil("( )\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Nil);
|
||||
let (r, v) = parse_list_or_nil("( a b -1 )\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(
|
||||
v,
|
||||
Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Number((-1).into()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_boolean() {
|
||||
let (r, v) = parse_boolean("#t\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Boolean(true.into()));
|
||||
let (r, v) = parse_boolean("#T\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Boolean(true.into()));
|
||||
let (r, v) = parse_boolean("#F\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Boolean(false.into()));
|
||||
let (r, v) = parse_boolean("#F\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Boolean(false.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dotted_pair() {
|
||||
let (r, v) = parse_dotted_cons("(a.b)").unwrap();
|
||||
assert_eq!(r, "");
|
||||
assert_eq!(
|
||||
v,
|
||||
Value::Identifier("a".into()).cons(Value::Identifier("b".into()))
|
||||
);
|
||||
let (r, v) = parse_dotted_cons("(a .(b. ( c . (d.NIL ) )))").unwrap();
|
||||
assert_eq!(r, "");
|
||||
assert_eq!(
|
||||
v,
|
||||
Value::list_or_nil([
|
||||
Value::Identifier("a".into()),
|
||||
Value::Identifier("b".into()),
|
||||
Value::Identifier("c".into()),
|
||||
Value::Identifier("d".into()),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_value() {
|
||||
let (r, v) = parse_value("+123\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number(123.into()));
|
||||
let (r, v) = parse_value("-0x123\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Number((-0x123).into()));
|
||||
let (r, v) = parse_value("abcdef-ghijkl\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Identifier("abcdef-ghijkl".into()));
|
||||
let (r, v) = parse_value("+\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Identifier("+".into()));
|
||||
let (r, v) = parse_value("+x\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Identifier("+x".into()));
|
||||
let (r, v) = parse_value("lambda-\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Identifier("lambda-".into()));
|
||||
let (r, v) = parse_value("lambda\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(v, Value::Keyword(Keyword::Lambda));
|
||||
let (r, v) = parse_value("(f #T -0x1)\n").unwrap();
|
||||
assert_eq!(r, "\n");
|
||||
assert_eq!(
|
||||
v,
|
||||
Value::list_or_nil([
|
||||
Value::Identifier("f".into()),
|
||||
Value::Boolean(true.into()),
|
||||
Value::Number((-0x1).into())
|
||||
])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
; Convenience flow control macros
|
||||
(defmacro when (condition body-head &rest body)
|
||||
"If condition is true, evaluates the expressions in body, otherwise returns nil"
|
||||
`(if ,condition (progn ,body-head ,@body))
|
||||
)
|
||||
|
||||
(defmacro unless (condition body-head &rest body)
|
||||
"If condition is false, evaluates the expressions in body, otherwise returns nil"
|
||||
`(if (not ,condition) (progn ,body-head ,@body))
|
||||
)
|
||||
|
||||
(defmacro when-let (bindings &rest body)
|
||||
"If expression evaluates to a trueish value, evaluates (let (binding expression) body...)"
|
||||
(let (output nil binding-names nil)
|
||||
(while bindings
|
||||
(setq binding-names (cons (car bindings) binding-names))
|
||||
(setq output (append output (list (car bindings) (cadr bindings))))
|
||||
(setq bindings (cddr bindings))
|
||||
)
|
||||
(unless binding-names
|
||||
(error "No bindings provided in the (when-let ...) form"))
|
||||
`(let ,output
|
||||
(if (and ,@binding-names)
|
||||
,@body
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
; List functions
|
||||
; TODO most of those could be ported to Rust for performance
|
||||
; TODO tail recursion
|
||||
(defun filter (f xs)
|
||||
"Returns a new list, formed from elements of xs matching predicate f"
|
||||
(cond
|
||||
((nil? xs) nil)
|
||||
((f (car xs)) (cons (car xs) (filter f (cdr xs))))
|
||||
(&otherwise (filter f (cdr xs)))
|
||||
)
|
||||
)
|
||||
(defun map (f xs)
|
||||
"Returns a new list, with f applied to elements of xs"
|
||||
(if (cons? xs)
|
||||
(cons (f (car xs)) (map f (cdr xs)))
|
||||
nil
|
||||
)
|
||||
)
|
||||
(defun flatmap (f xs)
|
||||
"Returns a new list, with f applied to list elements of xs, then appended together"
|
||||
(let (ys nil)
|
||||
(while (cons? xs)
|
||||
(setq ys (append ys (f (car xs))))
|
||||
(setq xs (cdr xs)))
|
||||
ys))
|
||||
(defun fold (f acc xs)
|
||||
"Returns the value calculated by sequentially applying f to acc and each element of xs"
|
||||
(while (cons? xs)
|
||||
(setq acc (f acc (car xs)))
|
||||
(setq xs (cdr xs)))
|
||||
acc)
|
||||
|
||||
(defun reverse (xs)
|
||||
"Returns the reversed list"
|
||||
(let (ys nil)
|
||||
(while (cons? xs)
|
||||
(setq ys (cons (car xs) ys))
|
||||
(setq xs (cdr xs)))
|
||||
ys))
|
||||
(defun unzip (xs)
|
||||
"Converts a list of lists into a single-level list"
|
||||
(flatmap identity xs))
|
||||
|
||||
; Logic functions
|
||||
(defun or_ (forms)
|
||||
(cond
|
||||
((nil? forms) #F)
|
||||
((nil? (cdr forms)) (car forms))
|
||||
(&otherwise
|
||||
(let (tmp (gensym))
|
||||
`(let (,tmp ,(car forms))
|
||||
(if ,tmp ,tmp ,(or_ (cdr forms)))
|
||||
)
|
||||
)
|
||||
)
|
||||
))
|
||||
(defmacro or (&rest forms)
|
||||
"Sequentially evaluates the forms, stopping and returning a form if it's trueish"
|
||||
;; (or x y z) -> (cond (x x) (y y) (z z) (&otherwise #t))
|
||||
(or_ forms))
|
||||
(defun and_ (forms)
|
||||
(cond
|
||||
((nil? forms) #T)
|
||||
((nil? (cdr forms)) (car forms))
|
||||
(&otherwise `(if ,(car forms) ,(and_ (cdr forms))))
|
||||
))
|
||||
(defmacro and (&rest forms)
|
||||
"Sequentially evaluates the forms, returning the last form if all the forms are trueish"
|
||||
;; (and x y z) -> (if x (if y (if z z)))
|
||||
(and_ forms)
|
||||
)
|
||||
|
||||
; Result handling functions
|
||||
(defun result/ok? (x)
|
||||
"Returns #t if x is an ok"
|
||||
(= 'ok (car x))
|
||||
)
|
||||
(defun result/err? (x)
|
||||
"Returns #t if x is an error"
|
||||
(= 'err (car x))
|
||||
)
|
||||
(defun result/map (f x)
|
||||
"If x is an ok, applies f to its value, otherwise does nothing"
|
||||
(if (result/ok? x)
|
||||
`(ok ,(f (cadr x)))
|
||||
x
|
||||
)
|
||||
)
|
||||
(defun result/map-err (f x)
|
||||
"If x is an error, applies f to its value, otherwise does nothing"
|
||||
(if (result/err? x)
|
||||
`(err ,(f (cadr x)))
|
||||
x
|
||||
)
|
||||
)
|
||||
|
||||
; Convenience error handling macros
|
||||
(defmacro catch (expression error-symbol handler)
|
||||
"Evaluates expression, running the handler if evaluation fails, introducing error-symbol as error"
|
||||
(let (eval-result-symbol (gensym))
|
||||
`(let (,eval-result-symbol (eval (quote ,expression)))
|
||||
(cond
|
||||
((result/ok? ,eval-result-symbol) (cadr ,eval-result-symbol))
|
||||
(&otherwise
|
||||
(let (,error-symbol (cadr ,eval-result-symbol))
|
||||
,handler
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro compile-debug (expression)
|
||||
"Prints the input expression during macro expansion/compile time and evaluates the expression in runtime"
|
||||
(eprint expression)
|
||||
expression
|
||||
)
|
||||
(defmacro runtime-debug (expression)
|
||||
"Prints the input expression and evaluates it in runtime"
|
||||
`(progn
|
||||
(eprint (quote ,expression))
|
||||
,expression
|
||||
)
|
||||
)
|
||||
|
||||
; Convenience list functions
|
||||
(defun caar (x) "Alias for (car (car x))" (car (car x)))
|
||||
(defun cadr (x) "Alias for (car (cdr x))" (car (cdr x)))
|
||||
(defun cdar (x) "Alias for (cdr (car x))" (cdr (car x)))
|
||||
(defun cddr (x) "Alias for (cdr (cdr x))" (cdr (cdr x)))
|
||||
|
||||
(defun caddr (x) "Alias for (car (cdr (cdr x)))" (car (cdr (cdr x))))
|
||||
(defun cadar (x) "Alias for (car (cdr (car x)))" (car (cdr (car x))))
|
||||
@@ -0,0 +1,265 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
io::{BufRead, Write, stdin, stdout},
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile::{
|
||||
Compile, CompileContext, CompileOptions,
|
||||
syntax::{Expression, FunctionBody, ParseError},
|
||||
},
|
||||
error::{MachineError, MachineErrorAt, ReadError},
|
||||
parse::{self, parse_value},
|
||||
util::Either,
|
||||
vm::{
|
||||
env::Environment,
|
||||
instruction::Instruction,
|
||||
machine::Machine,
|
||||
value::{BytecodeFunction, IdentifierValue, Value},
|
||||
},
|
||||
};
|
||||
|
||||
pub trait Reader {
|
||||
type Error: Into<MachineErrorAt>;
|
||||
|
||||
fn read(&mut self) -> Result<Option<Value>, Self::Error>;
|
||||
}
|
||||
|
||||
pub struct InteractiveReader {
|
||||
buffer: String,
|
||||
prompt_empty: Cow<'static, str>,
|
||||
prompt_continuation: Cow<'static, str>,
|
||||
}
|
||||
|
||||
pub struct FileReader<R: BufRead> {
|
||||
reader: R,
|
||||
buffer: String,
|
||||
}
|
||||
|
||||
pub struct ModuleReader<R: BufRead> {
|
||||
reader: FileReader<R>,
|
||||
path: PathBuf,
|
||||
macro_machine: Machine,
|
||||
}
|
||||
|
||||
impl InteractiveReader {
|
||||
pub fn new<T: Into<Cow<'static, str>>, U: Into<Cow<'static, str>>>(
|
||||
prompt_empty: T,
|
||||
prompt_continuation: U,
|
||||
) -> Self {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
prompt_empty: prompt_empty.into(),
|
||||
prompt_continuation: prompt_continuation.into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Reader for InteractiveReader {
|
||||
type Error = MachineErrorAt;
|
||||
|
||||
fn read(&mut self) -> Result<Option<Value>, Self::Error> {
|
||||
let stdin = stdin();
|
||||
read_inner(
|
||||
&mut stdin.lock(),
|
||||
&mut self.buffer,
|
||||
Some((
|
||||
self.prompt_empty.as_ref(),
|
||||
self.prompt_continuation.as_ref(),
|
||||
)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> FileReader<R> {
|
||||
pub fn new(reader: R) -> Self {
|
||||
Self {
|
||||
buffer: String::new(),
|
||||
reader,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> Reader for FileReader<R> {
|
||||
type Error = MachineErrorAt;
|
||||
|
||||
fn read(&mut self) -> Result<Option<Value>, Self::Error> {
|
||||
read_inner(&mut self.reader, &mut self.buffer, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: BufRead> ModuleReader<R> {
|
||||
pub fn new<P: Into<PathBuf>>(reader: R, path: P, trace_macros: bool) -> Self {
|
||||
let mut macro_machine = Machine::default();
|
||||
macro_machine.trace_macros = trace_macros;
|
||||
Self {
|
||||
reader: FileReader::new(reader),
|
||||
path: path.into(),
|
||||
macro_machine,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_expression(
|
||||
&mut self,
|
||||
options: &CompileOptions,
|
||||
env: &Rc<Environment>,
|
||||
) -> Result<Option<Rc<Expression>>, Either<MachineErrorAt, Vec<ParseError>>> {
|
||||
loop {
|
||||
let value =
|
||||
read(&mut self.reader, &mut self.macro_machine, env).map_err(Either::Left)?;
|
||||
let Some(value) = value else {
|
||||
return Ok(None);
|
||||
};
|
||||
let expression = Expression::parse(&value).map_err(Either::Right)?;
|
||||
if let Expression::Defmacro(_) = expression.as_ref() {
|
||||
self.macro_machine
|
||||
.evaluate_value(options.clone(), Some("defmacro".into()), env, value)
|
||||
.map_err(Either::Left)?;
|
||||
continue;
|
||||
}
|
||||
return Ok(Some(expression));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn compile(
|
||||
mut self,
|
||||
module_name: Option<IdentifierValue>,
|
||||
options: &CompileOptions,
|
||||
env: &Rc<Environment>,
|
||||
) -> Result<Rc<BytecodeFunction>, Either<MachineErrorAt, Vec<ParseError>>> {
|
||||
let mut cx =
|
||||
CompileContext::new(options.clone(), module_name, Some(self.path.clone().into()));
|
||||
let mut body = FunctionBody {
|
||||
head: vec![],
|
||||
tail: Rc::new(Expression::Nil),
|
||||
};
|
||||
|
||||
let mut syntax_errors = vec![];
|
||||
|
||||
loop {
|
||||
let expression = match self.read_expression(options, env) {
|
||||
Ok(Some(expression)) => expression,
|
||||
Ok(None) => break,
|
||||
Err(Either::Left(error)) => return Err(Either::Left(error)),
|
||||
Err(Either::Right(errors)) => {
|
||||
syntax_errors.extend(errors);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
body.head.push(expression);
|
||||
}
|
||||
|
||||
if !syntax_errors.is_empty() {
|
||||
return Err(Either::Right(syntax_errors));
|
||||
}
|
||||
|
||||
let value = body
|
||||
.compile(&mut cx)
|
||||
.map_err(MachineError::Compile)
|
||||
.map_err(MachineErrorAt::at_unknown)
|
||||
.map_err(Either::Left)?;
|
||||
cx.push(value)
|
||||
.map_err(MachineError::Compile)
|
||||
.map_err(MachineErrorAt::at_unknown)
|
||||
.map_err(Either::Left)?;
|
||||
cx.emit(Instruction::Return);
|
||||
|
||||
let function = cx
|
||||
.to_bytecode()
|
||||
.map_err(MachineError::Compile)
|
||||
.map_err(MachineErrorAt::at_unknown)
|
||||
.map_err(Either::Left)?;
|
||||
|
||||
Ok(function)
|
||||
}
|
||||
}
|
||||
|
||||
fn read_inner<R: BufRead>(
|
||||
reader: &mut R,
|
||||
buffer: &mut String,
|
||||
prompt: Option<(&str, &str)>,
|
||||
) -> Result<Option<Value>, MachineErrorAt> {
|
||||
loop {
|
||||
let mut incomplete = None;
|
||||
let mut i = buffer.trim_start();
|
||||
|
||||
while !i.is_empty() {
|
||||
i = match parse::skip_comment_and_whitespace(i) {
|
||||
Ok((i, _)) => i,
|
||||
Err(_error) => {
|
||||
buffer.clear();
|
||||
i = buffer.trim_start();
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if i.is_empty() {
|
||||
buffer.clear();
|
||||
break;
|
||||
}
|
||||
let result = parse_value(i);
|
||||
let (tail, value) = match result {
|
||||
Ok(r) => r,
|
||||
Err(nom::Err::Incomplete(error)) => {
|
||||
incomplete = Some(error);
|
||||
break;
|
||||
}
|
||||
Err(error) => {
|
||||
let error = ReadError::Lexical(error.map_input(|i| i.into()));
|
||||
let error = MachineError::Read(error);
|
||||
buffer.clear();
|
||||
return Err(error.at_unknown());
|
||||
}
|
||||
};
|
||||
|
||||
*buffer = tail.trim_start().into();
|
||||
return Ok(Some(value));
|
||||
}
|
||||
|
||||
*buffer = buffer.trim_start().into();
|
||||
|
||||
if let Some((prompt_empty, prompt_continuation)) = prompt {
|
||||
if buffer.is_empty() {
|
||||
print!("{prompt_empty}");
|
||||
} else {
|
||||
print!("{prompt_continuation}");
|
||||
}
|
||||
stdout().flush().ok();
|
||||
}
|
||||
|
||||
let len = reader
|
||||
.read_line(buffer)
|
||||
.map_err(ReadError::Io)
|
||||
.map_err(MachineError::Read)
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
if len == 0 {
|
||||
return if let Some(incomplete) = incomplete {
|
||||
let error = ReadError::Lexical(nom::Err::Incomplete(incomplete));
|
||||
let error = MachineError::Read(error);
|
||||
Err(error.at_unknown())
|
||||
} else {
|
||||
assert!(buffer.is_empty());
|
||||
Ok(None)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read<R: Reader>(
|
||||
reader: &mut R,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
) -> Result<Option<Value>, MachineErrorAt> {
|
||||
let raw_value = reader.read().map_err(Into::into)?;
|
||||
let Some(raw_value) = raw_value else {
|
||||
return Ok(None);
|
||||
};
|
||||
let exp_value = vm.macro_expand(env, &raw_value)?;
|
||||
Ok(Some(exp_value))
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
use std::fmt;
|
||||
|
||||
pub enum Either<A, B> {
|
||||
Left(A),
|
||||
Right(B),
|
||||
}
|
||||
|
||||
pub struct TryFilter<I, E1, E2, T, F>
|
||||
where
|
||||
I: Iterator<Item = Result<T, E1>>,
|
||||
F: FnMut(&T) -> Result<bool, E2>,
|
||||
E2: From<E1>,
|
||||
{
|
||||
iterator: I,
|
||||
filter: F,
|
||||
}
|
||||
|
||||
pub trait IteratorExt<I, E1, T> {
|
||||
fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
|
||||
where
|
||||
F: FnMut(&T) -> Result<bool, E2>,
|
||||
E2: From<E1>;
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! primitive_enum {
|
||||
(
|
||||
$(#[$meta:meta])*
|
||||
$vis:vis enum $ident:ident: $repr:ty [$conversion_err:ident] {
|
||||
$($variant:ident = $discriminant:literal),+ $(,)?
|
||||
}
|
||||
) => {
|
||||
$(#[$meta])*
|
||||
#[repr($repr)]
|
||||
$vis enum $ident {
|
||||
$($variant = $discriminant),+
|
||||
}
|
||||
|
||||
impl From<$ident> for $repr {
|
||||
fn from(value: $ident) -> $repr {
|
||||
value as $repr
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<$repr> for $ident {
|
||||
type Error = $conversion_err;
|
||||
|
||||
fn try_from(value: $repr) -> Result<$ident, $conversion_err> {
|
||||
match value {
|
||||
$($discriminant => Ok($ident::$variant),)+
|
||||
_ => Err($conversion_err(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl<A: fmt::Debug, B: fmt::Debug> fmt::Debug for Either<A, B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Left(a) => fmt::Debug::fmt(a, f),
|
||||
Self::Right(b) => fmt::Debug::fmt(b, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E1, T> IteratorExt<I, E1, T> for I
|
||||
where
|
||||
I: Iterator<Item = Result<T, E1>>,
|
||||
{
|
||||
fn try_filter<E2, F>(self, filter: F) -> impl Iterator<Item = Result<T, E2>>
|
||||
where
|
||||
F: FnMut(&T) -> Result<bool, E2>,
|
||||
E2: From<E1>,
|
||||
{
|
||||
TryFilter {
|
||||
iterator: self,
|
||||
filter,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, E1, E2, T, F> Iterator for TryFilter<I, E1, E2, T, F>
|
||||
where
|
||||
I: Iterator<Item = Result<T, E1>>,
|
||||
F: FnMut(&T) -> Result<bool, E2>,
|
||||
E2: From<E1>,
|
||||
{
|
||||
type Item = Result<T, E2>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
let value = self.iterator.next()?;
|
||||
let value = match value {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Some(Err(error.into())),
|
||||
};
|
||||
let cond = match (self.filter)(&value) {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Some(Err(error)),
|
||||
};
|
||||
|
||||
match cond {
|
||||
true => return Some(Ok(value)),
|
||||
false => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
use std::{
|
||||
borrow::Borrow,
|
||||
cell::RefCell,
|
||||
collections::HashMap,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
rc::Rc,
|
||||
sync::atomic::{AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
machine::Machine,
|
||||
value::{
|
||||
BytecodeFunction, IdentifierValue, NativeFunction, NativeObject, StringValue, Value,
|
||||
convert::{AnyFunction, TryFromValue},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum Macro {
|
||||
Native(NativeFunction),
|
||||
Bytecode(Rc<BytecodeFunction>),
|
||||
}
|
||||
|
||||
pub trait EnvironmentAccessHook {
|
||||
fn read_variable(&mut self, name: &str) -> Option<Value>;
|
||||
fn write_variable(
|
||||
&mut self,
|
||||
name: &IdentifierValue,
|
||||
value: &Value,
|
||||
) -> Result<bool, MachineError>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Environment {
|
||||
globals: RefCell<HashMap<IdentifierValue, Value>>,
|
||||
macros: RefCell<HashMap<IdentifierValue, Macro>>,
|
||||
parent: Option<Rc<Environment>>,
|
||||
gensym_index: AtomicU32,
|
||||
|
||||
access_hook: RefCell<Option<Box<dyn EnvironmentAccessHook>>>,
|
||||
}
|
||||
|
||||
impl Environment {
|
||||
pub fn new(parent: Option<Rc<Self>>) -> Self {
|
||||
Self {
|
||||
parent,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gensym(&self) -> IdentifierValue {
|
||||
// TODO do this in a root environment?
|
||||
let index = self.gensym_index.fetch_add(1, Ordering::SeqCst);
|
||||
format!("___gensym{index}").into()
|
||||
}
|
||||
|
||||
pub fn set_access_hook(
|
||||
&self,
|
||||
hook: Option<Box<dyn EnvironmentAccessHook>>,
|
||||
) -> Option<Box<dyn EnvironmentAccessHook>> {
|
||||
self.access_hook.replace(hook)
|
||||
}
|
||||
|
||||
pub fn defun_native<S, D, F>(&self, identifier: S, docstring: D, function: F) -> Value
|
||||
where
|
||||
S: Into<IdentifierValue>,
|
||||
D: Into<StringValue>,
|
||||
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
|
||||
{
|
||||
let identifier = identifier.into();
|
||||
let native = NativeFunction::new(identifier.clone(), docstring, function);
|
||||
let value = Value::NativeFunction(native);
|
||||
self.set_global_value(identifier, value.clone())
|
||||
.expect("defun_native failed");
|
||||
value
|
||||
}
|
||||
|
||||
pub fn defmacro_native<S, D, F>(&self, identifier: S, docstring: D, function: F)
|
||||
where
|
||||
S: Into<IdentifierValue>,
|
||||
D: Into<StringValue>,
|
||||
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
|
||||
{
|
||||
let identifier = identifier.into();
|
||||
let native = NativeFunction::new(identifier.clone(), docstring, function);
|
||||
self.macros
|
||||
.borrow_mut()
|
||||
.insert(identifier, Macro::Native(native));
|
||||
}
|
||||
|
||||
pub fn defmacro_bytecode<S: Into<IdentifierValue>>(
|
||||
&self,
|
||||
identifier: S,
|
||||
value: Rc<BytecodeFunction>,
|
||||
) {
|
||||
let identifier = identifier.into();
|
||||
// println!("Export macro: {identifier}: {value}");
|
||||
self.macros
|
||||
.borrow_mut()
|
||||
.insert(identifier, Macro::Bytecode(value));
|
||||
}
|
||||
|
||||
pub fn global_value<Q>(&self, identifier: &Q) -> Option<Value>
|
||||
where
|
||||
IdentifierValue: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized + AsRef<str>,
|
||||
{
|
||||
if let Some(hook) = &mut *self.access_hook.borrow_mut()
|
||||
&& let Some(value) = hook.read_variable(identifier.as_ref())
|
||||
{
|
||||
return Some(value);
|
||||
}
|
||||
|
||||
self.globals.borrow().get(identifier).cloned().or_else(|| {
|
||||
self.parent
|
||||
.as_ref()
|
||||
.and_then(|parent| parent.global_value(identifier))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_global_value<S: Into<IdentifierValue>, V: Into<Value>>(
|
||||
&self,
|
||||
identifier: S,
|
||||
value: V,
|
||||
) -> Result<(), MachineError> {
|
||||
let identifier = identifier.into();
|
||||
let value = value.into();
|
||||
|
||||
if let Some(hook) = &mut *self.access_hook.borrow_mut() {
|
||||
match hook.write_variable(&identifier, &value) {
|
||||
Ok(true) => return Ok(()),
|
||||
Ok(false) => {}
|
||||
Err(error) => return Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
self.globals.borrow_mut().insert(identifier, value);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn global_macro<Q>(&self, identifier: &Q) -> Option<Macro>
|
||||
where
|
||||
IdentifierValue: Borrow<Q>,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
self.macros.borrow().get(identifier).cloned().or_else(|| {
|
||||
self.parent
|
||||
.as_ref()
|
||||
.and_then(|parent| parent.global_macro(identifier))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn evaluate_global_function<Q>(
|
||||
self: &Rc<Self>,
|
||||
vm: &mut Machine,
|
||||
identifier: &Q,
|
||||
arguments: &[Value],
|
||||
) -> Result<Value, MachineError>
|
||||
where
|
||||
IdentifierValue: Borrow<Q>,
|
||||
Q: Hash + Eq + ?Sized + AsRef<str>,
|
||||
for<'a> &'a Q: Into<IdentifierValue>,
|
||||
{
|
||||
let value = self
|
||||
.global_value(identifier)
|
||||
.ok_or_else(|| MachineError::UnboundIdentifier(identifier.into()))?;
|
||||
let function = AnyFunction::try_from_value(&value)?;
|
||||
function.invoke(vm, self, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Environment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "environment")
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Environment {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Environment")
|
||||
.field("globals", &self.globals)
|
||||
.field("macros", &self.macros)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeObject for Environment {
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{machine::Machine, value::NumberValue},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
#[error("unrecognized instruction: {0}")]
|
||||
pub struct InstructionDecodeError(u8);
|
||||
|
||||
primitive_enum! {
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||
pub enum Instruction: u8 [InstructionDecodeError] {
|
||||
// Stack manipulation
|
||||
PushNil = 0,
|
||||
PushInteger = 1,
|
||||
PushTrue = 2,
|
||||
PushFalse = 3,
|
||||
PushConstant = 4,
|
||||
CloseUpvalue = 5,
|
||||
Drop = 6,
|
||||
SetTemp = 7,
|
||||
GetTemp = 8,
|
||||
// Binding manupulation
|
||||
GetLocal = 10,
|
||||
SetLocal = 11,
|
||||
GetUpvalue = 12,
|
||||
SetUpvalue = 13,
|
||||
GetGlobal = 14,
|
||||
SetGlobal = 15,
|
||||
DeclareGlobal = 16,
|
||||
DeclareMacro = 17,
|
||||
// Arithmetic
|
||||
Add = 20,
|
||||
Sub = 21,
|
||||
Mul = 22,
|
||||
Div = 23,
|
||||
Mod = 24,
|
||||
Gt = 25,
|
||||
Lt = 26,
|
||||
Eq = 27,
|
||||
Ge = 28,
|
||||
Le = 29,
|
||||
Ne = 30,
|
||||
Not = 31,
|
||||
// Branching
|
||||
Branch = 40,
|
||||
Jump = 41,
|
||||
// Functions and closures
|
||||
Call = 50,
|
||||
Return = 51,
|
||||
MakeClosure = 52,
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_fixed_unsigned {
|
||||
($vis:vis $ident:ident : $repr:ty, $bits:literal) => {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
$vis struct $ident($repr);
|
||||
|
||||
impl $ident {
|
||||
pub const BITS: usize = $bits;
|
||||
|
||||
pub fn to_bytes(&self) -> [u8; $bits / 8] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for $ident {
|
||||
type Error = FixedConvertError<usize, $bits>;
|
||||
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
if let Ok(value) = <$repr>::try_from(value) {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(FixedConvertError(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$repr> for $ident {
|
||||
fn from(value: $repr) -> $ident {
|
||||
$ident(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ident> for usize {
|
||||
fn from(value: $ident) -> usize {
|
||||
value.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ident> for $repr {
|
||||
fn from(value: $ident) -> $repr {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
macro_rules! impl_fixed {
|
||||
($vis:vis $ident:ident: u8) => {
|
||||
impl_fixed_unsigned!($vis $ident : u8, 8);
|
||||
|
||||
impl ReadEncoded for $ident {
|
||||
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
|
||||
let b0 = stream.fetch_byte()?;
|
||||
Ok(Self(b0))
|
||||
}
|
||||
}
|
||||
};
|
||||
($vis:vis $ident:ident: u16) => {
|
||||
impl_fixed_unsigned!($vis $ident : u16, 16);
|
||||
|
||||
impl ReadEncoded for $ident {
|
||||
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
|
||||
let b0 = stream.fetch_byte()?;
|
||||
let b1 = stream.fetch_byte()?;
|
||||
Ok(Self(u16::from_le_bytes([b0, b1])))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("cannot fit {0} into {N} bits")]
|
||||
pub struct FixedConvertError<T: fmt::Display, const N: usize>(T);
|
||||
|
||||
impl_fixed!(pub LocalId: u8);
|
||||
impl_fixed!(pub ConstantId: u16);
|
||||
impl_fixed!(pub ArgumentCount: u8);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct ImmediateInteger(i16);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
pub struct BranchOffset(i16);
|
||||
|
||||
impl ImmediateInteger {
|
||||
pub fn sign_extend_i64(self) -> i64 {
|
||||
self.0 as i64
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 2] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for ImmediateInteger {
|
||||
fn from(value: i16) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadEncoded for ImmediateInteger {
|
||||
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
|
||||
let b0 = stream.fetch_byte()?;
|
||||
let b1 = stream.fetch_byte()?;
|
||||
Ok(Self(i16::from_le_bytes([b0, b1])))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for ImmediateInteger {
|
||||
type Error = FixedConvertError<NumberValue, 16>;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(Self(value)),
|
||||
_ => Err(FixedConvertError(value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<i64> for ImmediateInteger {
|
||||
type Error = FixedConvertError<i64, 16>;
|
||||
|
||||
fn try_from(value: i64) -> Result<Self, Self::Error> {
|
||||
if let Ok(value) = value.try_into() {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(FixedConvertError(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BranchOffset {
|
||||
pub fn sign_extend_isize(self) -> isize {
|
||||
self.0 as isize
|
||||
}
|
||||
pub fn to_bytes(&self) -> [u8; 2] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<isize> for BranchOffset {
|
||||
type Error = FixedConvertError<isize, 16>;
|
||||
|
||||
fn try_from(value: isize) -> Result<Self, Self::Error> {
|
||||
if let Ok(value) = value.try_into() {
|
||||
Ok(Self(value))
|
||||
} else {
|
||||
Err(FixedConvertError(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for BranchOffset {
|
||||
fn from(value: i16) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadEncoded for BranchOffset {
|
||||
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError> {
|
||||
let b0 = stream.fetch_byte()?;
|
||||
let b1 = stream.fetch_byte()?;
|
||||
Ok(Self(i16::from_le_bytes([b0, b1])))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ReadEncoded: Sized {
|
||||
fn read_encoded(stream: &mut Machine) -> Result<Self, MachineError>;
|
||||
}
|
||||
|
||||
impl fmt::Display for Instruction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let name = match self {
|
||||
Self::PushNil => "PUSH_NIL",
|
||||
Self::PushInteger => "PUSH_INTEGER",
|
||||
Self::PushTrue => "PUSH_TRUE",
|
||||
Self::PushFalse => "PUSH_FALSE",
|
||||
Self::PushConstant => "PUSH_CONSTANT",
|
||||
Self::CloseUpvalue => "CLOSE_UPVALUE",
|
||||
Self::Drop => "DROP",
|
||||
Self::SetTemp => "SET_TEMP",
|
||||
Self::GetTemp => "GET_TEMP",
|
||||
Self::GetLocal => "GET_LOCAL",
|
||||
Self::SetLocal => "SET_LOCAL",
|
||||
Self::GetUpvalue => "GET_UPVALUE",
|
||||
Self::SetUpvalue => "SET_UPVALUE",
|
||||
Self::GetGlobal => "GET_GLOBAL",
|
||||
Self::SetGlobal => "SET_GLOBAL",
|
||||
Self::DeclareGlobal => "DECLARE_GLOBAL",
|
||||
Self::DeclareMacro => "DECLARE_MACRO",
|
||||
Self::Add => "ADD",
|
||||
Self::Sub => "SUB",
|
||||
Self::Mul => "MUL",
|
||||
Self::Div => "DIV",
|
||||
Self::Mod => "MOD",
|
||||
Self::Gt => "GT",
|
||||
Self::Lt => "LT",
|
||||
Self::Eq => "EQ",
|
||||
Self::Ge => "GE",
|
||||
Self::Le => "LE",
|
||||
Self::Ne => "NE",
|
||||
Self::Not => "NOT",
|
||||
Self::Branch => "BRANCH",
|
||||
Self::Jump => "JUMP",
|
||||
Self::Call => "CALL",
|
||||
Self::Return => "RETURN",
|
||||
Self::MakeClosure => "MAKE_CLOSURE",
|
||||
};
|
||||
f.write_str(name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,971 @@
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufReader, Read},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
compile::{CompileContext, CompileOptions},
|
||||
error::{
|
||||
ArgumentCountError, MachineError, MachineErrorAt, MachineErrorLocation, ReadError,
|
||||
ValueConversionError,
|
||||
},
|
||||
read::{self, FileReader, ModuleReader},
|
||||
util::Either,
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
instruction::{
|
||||
ArgumentCount, BranchOffset, ConstantId, ImmediateInteger, Instruction, LocalId,
|
||||
ReadEncoded,
|
||||
},
|
||||
macros::MacroExpand,
|
||||
prelude,
|
||||
stack::Stack,
|
||||
value::{
|
||||
BytecodeFunction, ClosureValue, IdentifierValue, NumberValue, Upvalue, UpvalueRef,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CallFrame {
|
||||
pub closure: ClosureValue,
|
||||
pub ip: usize,
|
||||
pub base_pointer: usize,
|
||||
open_upvalue_trackers: Vec<OpenUpvalueTracker>,
|
||||
}
|
||||
|
||||
pub struct Machine {
|
||||
data_stack: Stack<Value>,
|
||||
call_stack: Stack<CallFrame>,
|
||||
tmp: Option<Value>,
|
||||
|
||||
// upvalue_arena: Vec<UpvalueValue>,
|
||||
pub trace_instructions: bool,
|
||||
pub trace_returns: bool,
|
||||
pub trace_stack: bool,
|
||||
pub trace_calls: bool,
|
||||
pub trace_macros: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct OpenUpvalueTracker {
|
||||
sp: usize,
|
||||
upvalue_ref: UpvalueRef,
|
||||
}
|
||||
|
||||
impl From<usize> for OpenUpvalueTracker {
|
||||
fn from(value: usize) -> Self {
|
||||
Self {
|
||||
sp: value,
|
||||
upvalue_ref: UpvalueRef::open(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Machine {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
data_stack: Stack::new(1024),
|
||||
call_stack: Stack::new(64),
|
||||
tmp: None,
|
||||
// upvalue_arena: vec![],
|
||||
trace_calls: false,
|
||||
trace_stack: false,
|
||||
trace_returns: false,
|
||||
trace_instructions: false,
|
||||
trace_macros: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Machine {
|
||||
pub(crate) fn fetch_byte(&mut self) -> Result<u8, MachineError> {
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head_mut()
|
||||
.ok_or(MachineError::InstructionPointerUndefined)?;
|
||||
let byte = frame
|
||||
.closure
|
||||
.instruction_byte(frame.ip)
|
||||
.ok_or(MachineError::InstructionPointerOutOfBounds)?;
|
||||
frame.ip += 1;
|
||||
Ok(byte)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn fetch_opcode(&mut self) -> Result<Instruction, MachineError> {
|
||||
let byte = self.fetch_byte()?;
|
||||
Ok(byte.try_into()?)
|
||||
}
|
||||
|
||||
fn push(&mut self, value: Value) -> Result<(), MachineError> {
|
||||
self.data_stack
|
||||
.push(value)
|
||||
.map_err(|_| MachineError::DataStackOverflow)
|
||||
}
|
||||
|
||||
fn pop(&mut self) -> Result<Value, MachineError> {
|
||||
self.data_stack
|
||||
.pop()
|
||||
.ok_or(MachineError::DataStackUnderflow)
|
||||
}
|
||||
|
||||
pub fn read_call_stack(&self, depth: usize) -> Option<&CallFrame> {
|
||||
self.call_stack.nth(depth)
|
||||
}
|
||||
|
||||
pub fn current_location(&self) -> Option<MachineErrorLocation> {
|
||||
self.call_stack.head().map(|frame| MachineErrorLocation {
|
||||
function: frame.closure.function.clone(),
|
||||
offset: frame.ip,
|
||||
})
|
||||
}
|
||||
|
||||
fn local_slot(&mut self, id: LocalId) -> Result<&mut Value, MachineError> {
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
let sp = frame.base_pointer + 1 + usize::from(id);
|
||||
self.data_stack
|
||||
.get_mut(sp)
|
||||
.ok_or(MachineError::UndefinedLocalReference)
|
||||
}
|
||||
|
||||
fn execute_get_local(&mut self, id: LocalId) -> Result<(), MachineError> {
|
||||
let value = self.local_slot(id)?.clone();
|
||||
self.push(value)
|
||||
}
|
||||
|
||||
fn execute_set_local(&mut self, id: LocalId) -> Result<(), MachineError> {
|
||||
let value = self.pop()?;
|
||||
*self.local_slot(id)? = value;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_get_upvalue(&mut self, id: LocalId) -> Result<(), MachineError> {
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
let value = frame.closure.upvalues[usize::from(id)].read(&self.data_stack)?;
|
||||
self.push(value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_set_upvalue(&mut self, id: LocalId) -> Result<(), MachineError> {
|
||||
let value = self.pop()?;
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
frame.closure.upvalues[usize::from(id)].write(&mut self.data_stack, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_get_global(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
|
||||
let identifier = self.pop()?;
|
||||
let Value::Identifier(identifier) = identifier else {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::GetGlobal,
|
||||
ValueConversionError {
|
||||
expected: "identifier".into(),
|
||||
got: identifier,
|
||||
},
|
||||
));
|
||||
};
|
||||
let value = env
|
||||
.global_value(&identifier)
|
||||
.ok_or(MachineError::UnboundIdentifier(identifier))?;
|
||||
self.push(value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_set_global(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
|
||||
let identifier = self.pop()?;
|
||||
let Value::Identifier(identifier) = identifier else {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::SetGlobal,
|
||||
ValueConversionError {
|
||||
expected: "identifier".into(),
|
||||
got: identifier,
|
||||
},
|
||||
));
|
||||
};
|
||||
let value = self.pop()?;
|
||||
env.set_global_value(identifier, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_rest_argument(&mut self, count: usize) -> Result<Value, MachineError> {
|
||||
let mut rest = Value::Nil;
|
||||
for _ in 0..count {
|
||||
let value = self.pop()?;
|
||||
rest = value.cons(rest);
|
||||
}
|
||||
Ok(rest)
|
||||
}
|
||||
|
||||
fn collect_call_arguments(
|
||||
&mut self,
|
||||
function: &Rc<BytecodeFunction>,
|
||||
argument_count: usize,
|
||||
) -> Result<(), MachineError> {
|
||||
if !(function.min_arity()..=function.max_arity()).contains(&argument_count) {
|
||||
return Err(MachineError::ArgumentCount(ArgumentCountError {
|
||||
function: function.clone(),
|
||||
actual: argument_count,
|
||||
}));
|
||||
}
|
||||
|
||||
// Fill out missing optionals
|
||||
let mut remaining = argument_count - function.required_count;
|
||||
if remaining > function.optional_count {
|
||||
// Collect into &rest X
|
||||
remaining -= function.optional_count;
|
||||
} else {
|
||||
// Pad missing optionals
|
||||
for _ in remaining..function.optional_count {
|
||||
self.push(Value::Nil)?;
|
||||
}
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
if function.has_rest {
|
||||
let rest = self.collect_rest_argument(remaining)?;
|
||||
self.push(rest)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_call(
|
||||
&mut self,
|
||||
env: &Rc<Environment>,
|
||||
argument_count: usize,
|
||||
) -> Result<(), MachineError> {
|
||||
let base_pointer = self
|
||||
.data_stack
|
||||
.pointer()
|
||||
.checked_sub(argument_count + 1)
|
||||
.ok_or(MachineError::DataStackUnderflow)?;
|
||||
let callable = &self.data_stack[base_pointer];
|
||||
|
||||
let closure = match callable {
|
||||
Value::Closure(closure) => closure.clone(),
|
||||
// Make closure from just the function
|
||||
Value::Function(function) => ClosureValue {
|
||||
function: function.clone(),
|
||||
upvalues: vec![],
|
||||
},
|
||||
Value::NativeFunction(function) => {
|
||||
let function = function.clone();
|
||||
// TODO remove argument cloning
|
||||
let mut arguments = (0..argument_count)
|
||||
.map(|_| self.pop())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
arguments.reverse();
|
||||
|
||||
if self.trace_calls {
|
||||
eprintln!("TRACE: Call native");
|
||||
eprintln!("TRACE: {function}");
|
||||
if let Some(location) = self.current_location() {
|
||||
eprintln!("TRACE: From {location}");
|
||||
} else {
|
||||
eprintln!("TRACE: From <unknown>");
|
||||
}
|
||||
if !arguments.is_empty() {
|
||||
eprintln!("TRACE: With arguments:");
|
||||
for (i, arg) in arguments.iter().enumerate() {
|
||||
eprintln!("TRACE: [{i}] {arg}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let value = function.invoke(self, env, &arguments[..])?;
|
||||
// Drop native function itself from the stack
|
||||
self.pop()?;
|
||||
self.push(value)?;
|
||||
return Ok(());
|
||||
}
|
||||
_ => {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::Call,
|
||||
ValueConversionError {
|
||||
expected: "closure, function or native function".into(),
|
||||
got: callable.clone(),
|
||||
},
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
self.collect_call_arguments(&closure.function, argument_count)?;
|
||||
|
||||
if self.trace_calls {
|
||||
eprintln!("TRACE: Call closure");
|
||||
eprintln!("TRACE: {closure}");
|
||||
if let Some(location) = self.current_location() {
|
||||
eprintln!("TRACE: From {location}");
|
||||
} else {
|
||||
eprintln!("TRACE: From <unknown>");
|
||||
}
|
||||
if argument_count != 0 {
|
||||
eprintln!("TRACE: With arguments:");
|
||||
for i in 0..argument_count {
|
||||
let sp = base_pointer + 1 + i;
|
||||
if let Some(argument) = self.data_stack.get(sp) {
|
||||
eprintln!("TRACE: [{i}] {argument}");
|
||||
} else {
|
||||
eprintln!("TRACE: [{i}] <invalid>");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let frame = CallFrame {
|
||||
closure,
|
||||
base_pointer,
|
||||
ip: 0,
|
||||
open_upvalue_trackers: vec![],
|
||||
};
|
||||
self.call_stack
|
||||
.push(frame)
|
||||
.map_err(|_| MachineError::CallStackOverflow)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_make_closure(&mut self) -> Result<(), MachineError> {
|
||||
let value = self.pop()?;
|
||||
let Value::Function(function) = value else {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::MakeClosure,
|
||||
ValueConversionError {
|
||||
expected: "function".into(),
|
||||
got: value,
|
||||
},
|
||||
));
|
||||
};
|
||||
let mut closure = ClosureValue {
|
||||
function,
|
||||
upvalues: vec![],
|
||||
};
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head_mut()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
// eprintln!("BEGIN MAKE CLOSURE");
|
||||
for upvalue_def in closure.function.upvalues.iter() {
|
||||
let upvalue_ref = if upvalue_def.is_local {
|
||||
let sp = frame.base_pointer + 1 + usize::from(upvalue_def.index);
|
||||
let tracker_index = frame
|
||||
.open_upvalue_trackers
|
||||
.binary_search_by_key(&sp, |t| t.sp)
|
||||
// .inspect(|i| {
|
||||
// eprintln!("UPVALUE: found open tracker i={i}, sp={sp}");
|
||||
// })
|
||||
.unwrap_or_else(|i| {
|
||||
// eprintln!("UPVALUE: create new open tracker i={i}, sp={sp}");
|
||||
frame
|
||||
.open_upvalue_trackers
|
||||
.insert(i, OpenUpvalueTracker::from(sp));
|
||||
i
|
||||
});
|
||||
frame.open_upvalue_trackers[tracker_index]
|
||||
.upvalue_ref
|
||||
.clone()
|
||||
} else {
|
||||
todo!()
|
||||
};
|
||||
|
||||
closure.upvalues.push(upvalue_ref);
|
||||
}
|
||||
|
||||
// for (i, upvalue) in closure.upvalues.iter().enumerate() {
|
||||
// eprintln!(
|
||||
// "UPVALUE #{i}: {upvalue:?} => {}",
|
||||
// upvalue.read(&self.data_stack).unwrap()
|
||||
// );
|
||||
// }
|
||||
// eprintln!("END MAKE CLOSURE");
|
||||
|
||||
self.push(Value::Closure(closure))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_return(&mut self) -> Result<(), MachineError> {
|
||||
let return_value = self.pop()?;
|
||||
|
||||
if self.trace_returns {
|
||||
eprintln!("TRACE: Function return");
|
||||
if let Some(location) = self.current_location() {
|
||||
eprintln!("TRACE: From {location}");
|
||||
} else {
|
||||
eprintln!("TRACE: From <unknown>");
|
||||
}
|
||||
let csp = self.call_stack.pointer();
|
||||
if csp > 0
|
||||
&& let Some(frame) = self.call_stack.get(csp - 1)
|
||||
{
|
||||
eprintln!("TRACE: To {}+{}", frame.closure, frame.ip);
|
||||
} else {
|
||||
eprintln!("TRACE: To <unknown>");
|
||||
}
|
||||
eprintln!("TRACE: With value {return_value}");
|
||||
}
|
||||
|
||||
let frame = self
|
||||
.call_stack
|
||||
.pop()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
self.data_stack.set_pointer(frame.base_pointer);
|
||||
self.push(return_value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_declare_macro(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
|
||||
let identifier = self.pop()?;
|
||||
let function = self.pop()?;
|
||||
let Value::Identifier(identifier) = identifier else {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::DeclareMacro,
|
||||
ValueConversionError {
|
||||
expected: "identifier".into(),
|
||||
got: identifier,
|
||||
},
|
||||
));
|
||||
};
|
||||
let Value::Function(function) = function else {
|
||||
return Err(MachineError::InvalidInstructionArgument(
|
||||
Instruction::DeclareMacro,
|
||||
ValueConversionError {
|
||||
expected: "function".into(),
|
||||
got: function,
|
||||
},
|
||||
));
|
||||
};
|
||||
env.defmacro_bytecode(identifier, function);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn execute_branch(&mut self, check_condition: bool, target: isize) -> Result<(), MachineError> {
|
||||
let do_branch = if check_condition {
|
||||
let condition_value = self.pop()?;
|
||||
!condition_value.is_trueish()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if do_branch {
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head_mut()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
let ip = frame
|
||||
.ip
|
||||
.checked_add_signed(target)
|
||||
.ok_or(MachineError::InvalidBranchTarget(frame.ip, target))?;
|
||||
frame.ip = ip;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn trace_stack(&self) {
|
||||
eprint!("TRACE: [");
|
||||
for v in 0..self.data_stack.pointer() {
|
||||
if v != 0 {
|
||||
eprint!(" ");
|
||||
}
|
||||
eprint!("{}", self.data_stack[v]);
|
||||
}
|
||||
eprintln!("]");
|
||||
}
|
||||
|
||||
fn trace_instruction(&self) {
|
||||
let Some(frame) = self.call_stack.head() else {
|
||||
eprintln!("<undefined>");
|
||||
return;
|
||||
};
|
||||
|
||||
frame.closure.function.disassemble(frame.ip, 0, 0, false);
|
||||
}
|
||||
|
||||
fn execute_next(&mut self, env: &Rc<Environment>) -> Result<(), MachineError> {
|
||||
if self.trace_instructions {
|
||||
if self.trace_stack {
|
||||
self.trace_stack();
|
||||
}
|
||||
self.trace_instruction();
|
||||
}
|
||||
|
||||
let opcode = self.fetch_opcode()?;
|
||||
|
||||
match opcode {
|
||||
// values
|
||||
Instruction::PushNil => self.push(Value::Nil)?,
|
||||
Instruction::PushInteger => {
|
||||
let value = ImmediateInteger::read_encoded(self)?;
|
||||
self.push(Value::Number(NumberValue::Int(value.sign_extend_i64())))?;
|
||||
}
|
||||
Instruction::PushTrue => self.push(true.into())?,
|
||||
Instruction::PushFalse => self.push(false.into())?,
|
||||
Instruction::PushConstant => {
|
||||
let index = ConstantId::read_encoded(self)?;
|
||||
let frame = self.call_stack.head().expect("unreachable");
|
||||
let value = frame
|
||||
.closure
|
||||
.function
|
||||
.constants
|
||||
.get(usize::from(index))
|
||||
.cloned()
|
||||
.ok_or(MachineError::UndefinedConstantReference)?;
|
||||
self.push(value)?;
|
||||
}
|
||||
Instruction::Drop => {
|
||||
let _ = self.pop()?;
|
||||
}
|
||||
// binding
|
||||
Instruction::SetLocal => {
|
||||
let id = LocalId::read_encoded(self)?;
|
||||
self.execute_set_local(id)?;
|
||||
}
|
||||
Instruction::GetLocal => {
|
||||
let id = LocalId::read_encoded(self)?;
|
||||
self.execute_get_local(id)?;
|
||||
}
|
||||
Instruction::SetUpvalue => {
|
||||
let id = LocalId::read_encoded(self)?;
|
||||
self.execute_set_upvalue(id)?;
|
||||
}
|
||||
Instruction::GetUpvalue => {
|
||||
let id = LocalId::read_encoded(self)?;
|
||||
self.execute_get_upvalue(id)?;
|
||||
}
|
||||
Instruction::SetGlobal => self.execute_set_global(env)?,
|
||||
Instruction::GetGlobal => self.execute_get_global(env)?,
|
||||
Instruction::DeclareMacro => self.execute_declare_macro(env)?,
|
||||
// arithmetic
|
||||
Instruction::Gt
|
||||
| Instruction::Lt
|
||||
| Instruction::Eq
|
||||
| Instruction::Ge
|
||||
| Instruction::Le
|
||||
| Instruction::Ne
|
||||
| Instruction::Add
|
||||
| Instruction::Sub
|
||||
| Instruction::Mul
|
||||
| Instruction::Div
|
||||
| Instruction::Mod
|
||||
| Instruction::Not => {
|
||||
let argument_count = usize::from(ArgumentCount::read_encoded(self)?);
|
||||
let mut arguments = (0..argument_count)
|
||||
.map(|_| self.pop())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
arguments.reverse();
|
||||
let function = prelude::dispatch_arithmetic(opcode);
|
||||
let value = (function)(self, env, &arguments[..])?;
|
||||
self.push(value)?;
|
||||
}
|
||||
// function
|
||||
Instruction::Return => {
|
||||
self.execute_return()?;
|
||||
}
|
||||
Instruction::Call => {
|
||||
let argument_count = usize::from(ArgumentCount::read_encoded(self)?);
|
||||
self.execute_call(env, argument_count)?;
|
||||
}
|
||||
Instruction::MakeClosure => {
|
||||
self.execute_make_closure()?;
|
||||
}
|
||||
Instruction::SetTemp => {
|
||||
self.tmp = Some(self.pop()?);
|
||||
}
|
||||
Instruction::GetTemp => {
|
||||
let tmp = self.tmp.take().ok_or(MachineError::TempRegisterEmpty)?;
|
||||
self.push(tmp)?;
|
||||
}
|
||||
Instruction::CloseUpvalue => {
|
||||
let frame = self
|
||||
.call_stack
|
||||
.head_mut()
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
let upvalue_sp = self.data_stack.pointer() - 1;
|
||||
let start = frame
|
||||
.open_upvalue_trackers
|
||||
.binary_search_by_key(&upvalue_sp, |t| t.sp)
|
||||
.unwrap_or_else(|i| i);
|
||||
for tracker in frame.open_upvalue_trackers.drain(start..) {
|
||||
let value = self
|
||||
.data_stack
|
||||
.get(tracker.sp)
|
||||
.cloned()
|
||||
.ok_or(MachineError::UndefinedUpvalueReference)?;
|
||||
let old = tracker.upvalue_ref.close(value);
|
||||
// eprintln!("CLOSE UPVALUE sp={} -> {}", tracker.sp, value);
|
||||
assert_eq!(old, Upvalue::Open(tracker.sp));
|
||||
}
|
||||
self.pop()?;
|
||||
}
|
||||
Instruction::Branch => {
|
||||
let offset = BranchOffset::read_encoded(self)?;
|
||||
self.execute_branch(true, offset.sign_extend_isize())?;
|
||||
}
|
||||
Instruction::Jump => {
|
||||
let offset = BranchOffset::read_encoded(self)?;
|
||||
self.execute_branch(false, offset.sign_extend_isize())?;
|
||||
}
|
||||
Instruction::DeclareGlobal => todo!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unwind_to(&mut self, depth: usize) {
|
||||
// Data stack management
|
||||
while self.call_stack.pointer() != depth {
|
||||
let frame = self.call_stack.pop().unwrap();
|
||||
self.data_stack.set_pointer(frame.base_pointer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_closure(
|
||||
&mut self,
|
||||
env: &Rc<Environment>,
|
||||
closure: ClosureValue,
|
||||
argument_count: usize,
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
let unwind_target = self.call_stack.pointer();
|
||||
self.push(Value::Closure(closure))
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
self.execute_call(env, argument_count)
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
while self.call_stack.pointer() != unwind_target {
|
||||
let location = self.current_location();
|
||||
if let Err(error) = self.execute_next(env) {
|
||||
// Unwind up to entry depth
|
||||
self.unwind_to(unwind_target);
|
||||
return Err(error.at(location));
|
||||
}
|
||||
}
|
||||
self.pop().map_err(MachineErrorAt::at_unknown)
|
||||
}
|
||||
|
||||
pub fn evaluate_closure_args(
|
||||
&mut self,
|
||||
env: &Rc<Environment>,
|
||||
closure: ClosureValue,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
self.push(Value::Closure(closure))
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
for arg in args {
|
||||
self.push(arg.clone()).map_err(MachineErrorAt::at_unknown)?;
|
||||
}
|
||||
let unwind_target = self.call_stack.pointer();
|
||||
self.execute_call(env, args.len())
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
while self.call_stack.pointer() != unwind_target {
|
||||
let location = self.current_location();
|
||||
if let Err(error) = self.execute_next(env) {
|
||||
// Unwind up to entry depth
|
||||
self.unwind_to(unwind_target);
|
||||
return Err(error.at(location));
|
||||
}
|
||||
}
|
||||
self.pop().map_err(MachineErrorAt::at_unknown)
|
||||
}
|
||||
|
||||
pub fn macro_expand(
|
||||
&mut self,
|
||||
env: &Rc<Environment>,
|
||||
value: &Value,
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
match value.macro_expand(self, env, false) {
|
||||
Ok(result) => {
|
||||
if self.trace_macros && *value != result {
|
||||
eprintln!("TRACE: Macro expansion:");
|
||||
eprintln!("TRACE: {value}");
|
||||
eprintln!("TRACE: VVVVV");
|
||||
eprintln!("TRACE: {result}");
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
Err(error) => {
|
||||
if self.trace_macros {
|
||||
eprintln!("TRACE: Macro expansion:");
|
||||
eprintln!("TRACE: {value}");
|
||||
eprintln!("TRACE: VVVVV");
|
||||
eprintln!("TRACE: {error}");
|
||||
}
|
||||
Err(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_value(
|
||||
&mut self,
|
||||
compile_options: CompileOptions,
|
||||
chunk_name: Option<IdentifierValue>,
|
||||
env: &Rc<Environment>,
|
||||
value: Value,
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
let value_expanded = self.macro_expand(env, &value)?;
|
||||
let function =
|
||||
CompileContext::compile_value(compile_options, chunk_name, &value_expanded, None)
|
||||
.map_err(MachineError::Compile)
|
||||
.map_err(MachineErrorAt::at_unknown)?;
|
||||
|
||||
let closure = ClosureValue {
|
||||
function,
|
||||
upvalues: vec![],
|
||||
};
|
||||
|
||||
let value = self.evaluate_closure(env, closure, 0)?;
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn load_file<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
compile_options: CompileOptions,
|
||||
env: &Rc<Environment>,
|
||||
path: P,
|
||||
) -> Result<(), MachineErrorAt> {
|
||||
let path = path.as_ref();
|
||||
let name = format!("{}", path.display());
|
||||
let reader = BufReader::new(
|
||||
File::open(path)
|
||||
.map_err(ReadError::Io)
|
||||
.map_err(MachineError::Read)
|
||||
.map_err(MachineErrorAt::at_unknown)?,
|
||||
);
|
||||
let module_reader = ModuleReader::new(reader, path, self.trace_macros);
|
||||
let function = match module_reader.compile(Some(name.into()), &compile_options, env) {
|
||||
Ok(function) => function,
|
||||
Err(Either::Left(error)) => return Err(error),
|
||||
Err(Either::Right(syntax)) => {
|
||||
return Err(MachineError::Read(ReadError::Parse(syntax)).at_unknown());
|
||||
}
|
||||
};
|
||||
let closure = ClosureValue {
|
||||
function,
|
||||
upvalues: vec![],
|
||||
};
|
||||
self.evaluate_closure(env, closure, 0)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn evaluate_str(
|
||||
&mut self,
|
||||
compile_options: CompileOptions,
|
||||
chunk_name: Option<IdentifierValue>,
|
||||
env: &Rc<Environment>,
|
||||
text: &str,
|
||||
) -> Result<Value, MachineErrorAt> {
|
||||
struct SliceReader<'a>(&'a [u8]);
|
||||
|
||||
impl Read for SliceReader<'_> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let count = self.0.len().min(buf.len());
|
||||
buf[..count].copy_from_slice(&self.0[..count]);
|
||||
self.0 = &self.0[count..];
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
let reader = BufReader::new(SliceReader(text.as_bytes()));
|
||||
let mut reader = FileReader::new(reader);
|
||||
|
||||
let mut last_value = Value::Nil;
|
||||
loop {
|
||||
let value = match read::read(&mut reader, self, env)? {
|
||||
Some(value) => value,
|
||||
None => break,
|
||||
};
|
||||
|
||||
last_value =
|
||||
self.evaluate_value(compile_options.clone(), chunk_name.clone(), env, value)?;
|
||||
}
|
||||
|
||||
Ok(last_value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, MachineErrorAt},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
instruction::Instruction,
|
||||
machine::Machine,
|
||||
value::{BytecodeFunction, ClosureValue, NumberValue},
|
||||
},
|
||||
};
|
||||
|
||||
fn try_eval(
|
||||
env: &Rc<Environment>,
|
||||
mut instructions: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
) -> (Machine, Result<Value, MachineErrorAt>) {
|
||||
instructions.push(Instruction::Return.into());
|
||||
let closure = ClosureValue {
|
||||
upvalues: vec![],
|
||||
function: Rc::new(BytecodeFunction {
|
||||
identifier: Some("test-script".into()),
|
||||
docstring: None,
|
||||
instructions: instructions.into(),
|
||||
constants: constants.into(),
|
||||
upvalues: [].into(),
|
||||
required_count: 0,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
script: None,
|
||||
}),
|
||||
};
|
||||
let mut machine = Machine::default();
|
||||
let result = machine.evaluate_closure(env, closure, 0);
|
||||
(machine, result)
|
||||
}
|
||||
|
||||
fn eval0(
|
||||
env: &Rc<Environment>,
|
||||
instructions: Vec<u8>,
|
||||
constants: Vec<Value>,
|
||||
) -> (Machine, Value) {
|
||||
let (machine, value) = try_eval(env, instructions, constants);
|
||||
let value = value.expect("evaluation failed");
|
||||
(machine, value)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stack_execution() {
|
||||
let env = Rc::new(Environment::default());
|
||||
let (m, v) = eval0(&env, vec![Instruction::PushNil.into()], vec![]);
|
||||
assert_eq!(v, Value::Nil);
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
let (m, v) = eval0(&env, vec![Instruction::PushTrue.into()], vec![]);
|
||||
assert_eq!(v, true.into());
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
let (m, v) = eval0(&env, vec![Instruction::PushFalse.into()], vec![]);
|
||||
assert_eq!(v, false.into());
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_unwind() {
|
||||
let env = Rc::new(Environment::default());
|
||||
// Cause data stack underflow for unwind
|
||||
let (m, r) = try_eval(&env, vec![Instruction::Drop.into()], vec![]);
|
||||
let e = r.unwrap_err();
|
||||
assert_eq!(e.location.map(|a| a.offset), Some(1));
|
||||
assert!(matches!(e.error, MachineError::DataStackUnderflow));
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closure_call_no_upvalues_yes_locals() {
|
||||
let env = Rc::new(Environment::default());
|
||||
// (lambda (y) (let (x 123) (+ x y)))
|
||||
let lambda_function = Rc::new(BytecodeFunction {
|
||||
script: None,
|
||||
identifier: None,
|
||||
docstring: None,
|
||||
instructions: [
|
||||
// x 123
|
||||
Instruction::PushInteger.into(),
|
||||
123,
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
1,
|
||||
Instruction::Add.into(),
|
||||
2,
|
||||
Instruction::SetTemp.into(),
|
||||
Instruction::Drop.into(),
|
||||
Instruction::GetTemp.into(),
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
required_count: 1,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&env,
|
||||
vec![
|
||||
Instruction::PushConstant.into(),
|
||||
0,
|
||||
0,
|
||||
Instruction::PushInteger.into(),
|
||||
65,
|
||||
1,
|
||||
Instruction::Call.into(),
|
||||
1,
|
||||
],
|
||||
vec![Value::Function(lambda_function)],
|
||||
);
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
assert_eq!(r, Value::Number(NumberValue::Int(444)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_closure_call_no_upvalues_no_locals() {
|
||||
let env = Rc::new(Environment::default());
|
||||
// (lambda (x y) (+ x y))
|
||||
let lambda_function = Rc::new(BytecodeFunction {
|
||||
script: None,
|
||||
identifier: None,
|
||||
docstring: None,
|
||||
instructions: [
|
||||
Instruction::GetLocal.into(),
|
||||
0,
|
||||
Instruction::GetLocal.into(),
|
||||
1,
|
||||
Instruction::Add.into(),
|
||||
2,
|
||||
Instruction::Return.into(),
|
||||
]
|
||||
.into(),
|
||||
constants: [].into(),
|
||||
upvalues: [].into(),
|
||||
required_count: 2,
|
||||
optional_count: 0,
|
||||
has_rest: false,
|
||||
});
|
||||
let (m, r) = eval0(
|
||||
&env,
|
||||
vec![
|
||||
Instruction::PushConstant.into(),
|
||||
0,
|
||||
0,
|
||||
Instruction::PushInteger.into(),
|
||||
123,
|
||||
0,
|
||||
Instruction::PushInteger.into(),
|
||||
65,
|
||||
1,
|
||||
Instruction::Call.into(),
|
||||
2,
|
||||
],
|
||||
vec![Value::Function(lambda_function)],
|
||||
);
|
||||
assert!(m.data_stack.is_empty());
|
||||
assert!(m.call_stack.is_empty());
|
||||
assert_eq!(r, Value::Number(NumberValue::Int(444)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::MachineErrorAt,
|
||||
vm::{
|
||||
env::{Environment, Macro},
|
||||
machine::Machine,
|
||||
value::{ClosureValue, ConsCell, Value},
|
||||
},
|
||||
};
|
||||
|
||||
pub trait MacroExpand: Sized {
|
||||
fn macro_expand(
|
||||
&self,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
tail: bool,
|
||||
) -> Result<Self, MachineErrorAt>;
|
||||
}
|
||||
|
||||
impl MacroExpand for Value {
|
||||
fn macro_expand(
|
||||
&self,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
tail: bool,
|
||||
) -> Result<Self, MachineErrorAt> {
|
||||
match self {
|
||||
Self::Nil
|
||||
| Self::Identifier(_)
|
||||
| Self::Number(_)
|
||||
| Self::Boolean(_)
|
||||
| Self::String(_)
|
||||
| Self::Keyword(_)
|
||||
| Self::Quote(_)
|
||||
| Self::Closure(_)
|
||||
| Self::Function(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Vector(_)
|
||||
| Self::HashTable(_)
|
||||
| Self::UnquoteSplice(_)
|
||||
| Self::Unquote(_) => Ok(self.clone()),
|
||||
Self::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
let car = car.macro_expand(vm, env, false)?;
|
||||
let cdr = cdr.macro_expand(vm, env, true)?;
|
||||
|
||||
if tail {
|
||||
let cons = Rc::new(ConsCell(car, cdr.clone()));
|
||||
return Ok(Self::Cons(cons));
|
||||
}
|
||||
|
||||
let (Self::Identifier(identifier), Some(args)) = (&car, cdr.as_proper_list())
|
||||
else {
|
||||
let cons = Rc::new(ConsCell(car, cdr.clone()));
|
||||
return Ok(Self::Cons(cons));
|
||||
};
|
||||
let Some(mac) = env.global_macro(identifier) else {
|
||||
// eprintln!("{identifier} is not a macro");
|
||||
let cons = Rc::new(ConsCell(car, cdr.clone()));
|
||||
return Ok(Self::Cons(cons));
|
||||
};
|
||||
|
||||
match mac {
|
||||
Macro::Native(native) => {
|
||||
let result = native.invoke(vm, env, &args[..]).expect("TODO");
|
||||
result.macro_expand(vm, env, tail)
|
||||
}
|
||||
Macro::Bytecode(bytecode) => {
|
||||
let closure = ClosureValue {
|
||||
function: bytecode.clone(),
|
||||
upvalues: vec![],
|
||||
};
|
||||
let result = vm.evaluate_closure_args(env, closure, &args[..])?;
|
||||
result.macro_expand(vm, env, tail)
|
||||
}
|
||||
}
|
||||
}
|
||||
Self::Quasi(value) => {
|
||||
let value = expand_quasiquote(value);
|
||||
value.macro_expand(vm, env, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_quasiquote(value: &Value) -> Value {
|
||||
match value {
|
||||
Value::Nil => Value::Nil,
|
||||
// Toplevel-only
|
||||
Value::Unquote(inner) => inner.as_ref().clone(),
|
||||
Value::UnquoteSplice(inner) => inner.as_ref().clone(),
|
||||
Value::Cons(_) => {
|
||||
let mut elements = vec![];
|
||||
let mut current = value;
|
||||
|
||||
elements.push(Value::Identifier("append".into()));
|
||||
|
||||
while !current.is_nil() {
|
||||
let Value::Cons(cons) = current else { todo!() };
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
match car {
|
||||
Value::UnquoteSplice(splice) => {
|
||||
elements.push(splice.as_ref().clone());
|
||||
}
|
||||
_ => {
|
||||
let exp_car = expand_quasiquote(car);
|
||||
elements.push(Value::list_or_nil([
|
||||
Value::Identifier("list".into()),
|
||||
exp_car,
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
||||
current = cdr;
|
||||
}
|
||||
|
||||
Value::list_or_nil(elements)
|
||||
}
|
||||
_ => value.clone().quote(),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod env;
|
||||
pub mod instruction;
|
||||
pub mod machine;
|
||||
pub mod macros;
|
||||
pub mod prelude;
|
||||
pub mod stack;
|
||||
pub mod value;
|
||||
|
||||
pub use value::Value;
|
||||
@@ -0,0 +1,374 @@
|
||||
use std::{rc::Rc, slice};
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{
|
||||
ConsCell, HashTable, HashTableData,
|
||||
convert::{AnyFunction, TryFromValue},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
// // vectors
|
||||
// env.defun_native("getv", |vm, _, args| {
|
||||
// let [vec, index] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let Value::Vector(vec) = vec else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
|
||||
// let value = if index < 0 {
|
||||
// todo!()
|
||||
// } else {
|
||||
// vec.value_at(index as usize).unwrap_or(Value::Nil)
|
||||
// };
|
||||
// Ok(value)
|
||||
// });
|
||||
// env.defun_native("setv", |vm, _, args| {
|
||||
// let [vec, index, value] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let Value::Vector(vec) = vec else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let index = i64::try_from_value(index).map_err(|e| vm.error_at_ip(e))? as isize;
|
||||
// if index < 0 {
|
||||
// todo!()
|
||||
// } else {
|
||||
// vec.set_value_at(index as usize, value.clone());
|
||||
// }
|
||||
// Ok(Value::Nil)
|
||||
// });
|
||||
|
||||
// lists
|
||||
env.defun_native(
|
||||
"append",
|
||||
"Concatenates the lists into one list",
|
||||
|_, _, args| match args {
|
||||
[] => Ok(Value::Nil),
|
||||
[xs] => Ok(xs.clone()),
|
||||
[head @ .., tail] => {
|
||||
let mut elements = vec![];
|
||||
for arg in head {
|
||||
let iter = arg.proper_iter(ValueConversionError {
|
||||
expected: "proper list".into(),
|
||||
got: arg.clone(),
|
||||
});
|
||||
for element in iter {
|
||||
elements.push(element?.clone());
|
||||
}
|
||||
}
|
||||
let mut output = tail.clone();
|
||||
for element in elements.into_iter().rev() {
|
||||
output = element.cons(output);
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"car",
|
||||
"Returns the CAR value of a cons-cell",
|
||||
|_, _, args| {
|
||||
let [x] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let cons: &ConsCell = TryFromValue::try_from_value(x)?;
|
||||
Ok(cons.0.clone())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"cdr",
|
||||
"Returns the CDR value of a cons-cell",
|
||||
|_, _, args| {
|
||||
let [x] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let cons: &ConsCell = TryFromValue::try_from_value(x)?;
|
||||
Ok(cons.1.clone())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"cons",
|
||||
"Constructs a cons-cell from CAR and CDR arguments",
|
||||
|_, _, args| {
|
||||
let [car, cdr] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(Value::Cons(Rc::new(ConsCell(car.clone(), cdr.clone()))))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"list",
|
||||
"Constructs a list from the arguments",
|
||||
|_, _, args| {
|
||||
let out = Value::list_or_nil(args.iter().cloned());
|
||||
Ok(out)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"length",
|
||||
"Returns the length of the given list",
|
||||
|_, _, args| {
|
||||
let [xs] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
|
||||
let mut xs = xs;
|
||||
let mut count = 0;
|
||||
while !xs.is_nil() {
|
||||
let Value::Cons(cons) = xs else {
|
||||
break;
|
||||
};
|
||||
let ConsCell(_, cdr) = cons.as_ref();
|
||||
count += 1;
|
||||
xs = cdr;
|
||||
}
|
||||
Ok(count.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"find",
|
||||
"Finds an entry in the list matching given predicate",
|
||||
|vm, env, args| {
|
||||
let [predicate, list] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let predicate = AnyFunction::try_from_value(predicate)?;
|
||||
let mut list = list;
|
||||
while !list.is_nil() {
|
||||
let Value::Cons(cons) = list else {
|
||||
break;
|
||||
};
|
||||
let pred_value = predicate.invoke(vm, env, slice::from_ref(&cons.0))?;
|
||||
if pred_value.is_trueish() {
|
||||
return Ok(cons.0.clone());
|
||||
}
|
||||
list = &cons.1;
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
|
||||
// Hash table
|
||||
env.defun_native(
|
||||
"hash/new",
|
||||
"Creates a hash table from the list of pairs",
|
||||
|_, _, args| {
|
||||
let mut hash = HashTableData::new(16);
|
||||
for arg in args {
|
||||
let Value::Cons(cons) = arg else {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a pair".into(),
|
||||
got: arg.clone(),
|
||||
}));
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
hash.insert(car.clone(), cdr.clone())?;
|
||||
}
|
||||
let hash = HashTable::from(hash);
|
||||
Ok(hash.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/length",
|
||||
"Returns the number of associations in the hashtable",
|
||||
|_, _, args| {
|
||||
let [table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let len = table.borrow().len();
|
||||
Ok(len.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash->list",
|
||||
"Converts a hashtable into a list of pairs",
|
||||
|_, _, args| {
|
||||
let [table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let mut list = Value::Nil;
|
||||
for (key, value) in table.borrow().iter() {
|
||||
list = key.clone().cons(value.clone()).cons(list);
|
||||
}
|
||||
Ok(list)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"list->hash",
|
||||
"Converts a list of pairs into a hashtable",
|
||||
|_, _, args| {
|
||||
let [pairs] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let mut hash = HashTableData::new(16);
|
||||
let pair_iter =
|
||||
pairs.proper_iter(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a list of pairs".into(),
|
||||
got: pairs.clone(),
|
||||
}));
|
||||
for pair in pair_iter {
|
||||
let pair = pair?;
|
||||
let Value::Cons(cons) = pair else {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "a pair".into(),
|
||||
got: pair.clone(),
|
||||
}));
|
||||
};
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
|
||||
hash.insert(car.clone(), cdr.clone())?;
|
||||
}
|
||||
let hash = HashTable::from(hash);
|
||||
Ok(hash.into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/put!",
|
||||
"Inserts an association into the hashtable",
|
||||
|_, _, args| {
|
||||
let [table, key, value] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow_mut().insert(key.clone(), value.clone())?;
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/update!",
|
||||
"Applies a v -> v' function to the value referencing the key",
|
||||
|vm, env, args| {
|
||||
let [function, table, key] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let function = AnyFunction::try_from_value(function)?;
|
||||
let value = {
|
||||
let mut borrow = table.borrow_mut();
|
||||
match borrow.get_mut(key) {
|
||||
Some(old_value) => {
|
||||
let new_value = function.invoke(vm, env, slice::from_ref(old_value))?;
|
||||
*old_value = new_value.clone();
|
||||
new_value
|
||||
}
|
||||
None => {
|
||||
let new_value = function.invoke(vm, env, &[Value::Nil])?;
|
||||
borrow.insert(key.clone(), new_value.clone())?;
|
||||
new_value
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/remove!",
|
||||
"Removes an association from the hashtable",
|
||||
|_, _, args| {
|
||||
let [table, key] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow_mut().remove(key).unwrap_or(Value::Nil);
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/for-each",
|
||||
"Applies a (k, v) -> void function to all associations in the hashtable",
|
||||
|vm, env, args| {
|
||||
let [function, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let function = AnyFunction::try_from_value(function)?;
|
||||
for (key, value) in table.borrow().iter() {
|
||||
function.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/fold",
|
||||
"Applies a (acc, k, v) -> acc' function to all associations in the hashtable",
|
||||
|vm, env, args| {
|
||||
let [acc, function, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let function = AnyFunction::try_from_value(function)?;
|
||||
let mut acc = acc.clone();
|
||||
for (key, value) in table.borrow().iter() {
|
||||
acc = function.invoke(vm, env, &[acc, key.clone(), value.clone()])?;
|
||||
}
|
||||
Ok(acc)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/filter!",
|
||||
"Removes associations not matching the predicate from the hashtable",
|
||||
|vm, env, args| {
|
||||
let [predicate, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let predicate = AnyFunction::try_from_value(predicate)?;
|
||||
table.borrow_mut().retain_invoke(vm, env, &predicate)?;
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/map!",
|
||||
"Applies a (k, v) -> v' transform on the hashtable",
|
||||
|vm, env, args| {
|
||||
let [transform, table] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let transform = AnyFunction::try_from_value(transform)?;
|
||||
{
|
||||
let mut borrow = table.borrow_mut();
|
||||
borrow.iter_mut().try_for_each(|(key, value)| {
|
||||
let output = transform.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
*value = output;
|
||||
Ok::<_, MachineError>(())
|
||||
})?;
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/get",
|
||||
"Retrieves a value from the hashtable associated with given key",
|
||||
|_, _, args| {
|
||||
let (table, key, default) = match args {
|
||||
[table, key] => (table, key, &Value::Nil),
|
||||
[table, key, default] => (table, key, default),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
let value = table.borrow().get(key).unwrap_or(default).clone();
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"hash/has?",
|
||||
"Returns #t if the hashtable contains the key",
|
||||
|_, _, args| {
|
||||
let [table, key] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let table: Rc<HashTable> = TryFromValue::try_from_value(table)?;
|
||||
Ok(table.borrow().contains_key(key).into())
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{NumberValue, StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
env.defun_native(
|
||||
"symbol?",
|
||||
"Returns #t if the argument is an identifier",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Identifier(_)).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"quote?",
|
||||
"Returns #t if the argument is a quote",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Quote(_)).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"list?",
|
||||
"Returns #t if the argument is a list",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Cons(_) | Value::Nil).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"cons?",
|
||||
"Returns #t if the argument is a cons pair",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Cons(_)).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"number?",
|
||||
"Returns #t if the argument is a number",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Number(_)).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string?",
|
||||
"Returns #t if the argument is a string",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::String(_)).into())
|
||||
},
|
||||
);
|
||||
env.defun_native("nil?", "Returns #t if the argument is nil", |_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(matches!(arg, Value::Nil).into())
|
||||
});
|
||||
|
||||
env.defun_native(
|
||||
"symbol",
|
||||
"Constructs a symbol from a string",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(Value::Identifier((&*string).into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"->string",
|
||||
"Converts a value to string representation",
|
||||
|_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
match arg {
|
||||
Value::String(_) => Ok(arg.clone()),
|
||||
_ => Ok(Value::String(format!("{arg}").into())),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string->number",
|
||||
"Converts a string to a number with optional radix (default=10)",
|
||||
|_, _, args| {
|
||||
let (string, radix) = match args {
|
||||
[string] => (StringValue::try_from_value(string)?, 10),
|
||||
[string, radix] => (
|
||||
StringValue::try_from_value(string)?,
|
||||
i64::try_from_value(radix)?,
|
||||
),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let Ok(radix) = u32::try_from(radix) else {
|
||||
return Ok(Value::Nil);
|
||||
};
|
||||
|
||||
match NumberValue::from_str_radix(string.as_ref(), radix) {
|
||||
Some(value) => Ok(Value::Number(value)),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// // conversion
|
||||
// env.defun_native("string->int", |vm, _, args| {
|
||||
// let [arg] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let arg = ValueString::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?;
|
||||
// let result = arg.parse::<i64>().map(Value::Integer).unwrap_or(Value::Nil);
|
||||
// Ok(result)
|
||||
// });
|
||||
// env.defun_native("int->string", |vm, _, args| {
|
||||
// let [arg] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// let arg = i64::try_from_value(arg).map_err(|e| vm.error_at_ip(e))?;
|
||||
// let result = Value::String(format!("{arg}").into());
|
||||
// Ok(result)
|
||||
// });
|
||||
// env.defun_native("type", |vm, _, args| {
|
||||
// let [arg] = args else {
|
||||
// return Err(vm.error_at_ip(MachineErrorKind::InvalidArgument));
|
||||
// };
|
||||
// Ok(arg.typeid())
|
||||
// });
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
Value,
|
||||
env::{Environment, Macro},
|
||||
value::{Keyword, StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
env.defun_native(
|
||||
"import",
|
||||
"Evaluates the given script file",
|
||||
|vm, env, args| {
|
||||
if args.is_empty() {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
}
|
||||
|
||||
let caller = vm
|
||||
.read_call_stack(0)
|
||||
.ok_or(MachineError::CallStackUnderflow)?;
|
||||
let caller_directory = caller
|
||||
.closure
|
||||
.function
|
||||
.script
|
||||
.as_ref()
|
||||
.and_then(|path| path.parent())
|
||||
.map(Path::to_owned)
|
||||
.unwrap_or_else(|| PathBuf::from("."));
|
||||
|
||||
for arg in args {
|
||||
let path_str = StringValue::try_from_value(arg)?;
|
||||
let mut path = PathBuf::from(&*path_str);
|
||||
|
||||
if path.is_relative() {
|
||||
let in_caller_directory = caller_directory.join(&path);
|
||||
if in_caller_directory.exists() {
|
||||
path = in_caller_directory;
|
||||
}
|
||||
}
|
||||
|
||||
vm.load_file(Default::default(), env, &path)
|
||||
.map_err(|error| MachineError::LoadError(path_str, error.into()))?;
|
||||
}
|
||||
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
|
||||
env.defun_native("gensym", "Provides an unique symbol name", |_, env, _| {
|
||||
Ok(env.gensym().into())
|
||||
});
|
||||
env.defmacro_native(
|
||||
"explain",
|
||||
"Provides an explanation for a given value",
|
||||
|_, env, args| {
|
||||
let [argument] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
if let Value::Identifier(identifier) = argument
|
||||
&& let Some(mac) = env.global_macro(identifier)
|
||||
{
|
||||
return match mac {
|
||||
Macro::Native(mac) => Ok(Value::String(
|
||||
format!("built-in macro {}: {}", mac.name(), mac.docstring()).into(),
|
||||
)),
|
||||
Macro::Bytecode(mac) => Ok(Value::String(
|
||||
format!("macro {}: {}", mac.name(), mac.docstring()).into(),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(Value::list_or_nil([
|
||||
Value::Identifier("explain_".into()),
|
||||
argument.clone(),
|
||||
]))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"explain_",
|
||||
"Provides an explanation for a given value",
|
||||
|_, _, args| {
|
||||
let [argument] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let explanation = match argument {
|
||||
Value::Nil => "an empty list".into(),
|
||||
Value::Cons(_) => "a cons-cell".into(),
|
||||
Value::Number(_) => "a number".into(),
|
||||
Value::Boolean(_) => "a boolean value".into(),
|
||||
Value::Quasi(_) => "a quasi-quoted value".into(),
|
||||
Value::Quote(_) => "a quoted value".into(),
|
||||
Value::Unquote(_) => "an unquoted value".into(),
|
||||
Value::UnquoteSplice(_) => "an unquote-spliced value".into(),
|
||||
Value::Identifier(identifier) => format!("an identifier {:?}", identifier.as_ref()),
|
||||
Value::Vector(_) => "a vector".into(),
|
||||
Value::HashTable(_) => "a hash table".into(),
|
||||
Value::String(_) => "a string".into(),
|
||||
Value::Keyword(_) => "a keyword".into(),
|
||||
Value::Closure(closure) => {
|
||||
format!(
|
||||
"function {}: {}",
|
||||
closure.function.name(),
|
||||
closure.function.docstring()
|
||||
)
|
||||
}
|
||||
Value::Function(function) => {
|
||||
format!("function {}: {}", function.name(), function.docstring())
|
||||
}
|
||||
Value::NativeFunction(function) => {
|
||||
format!(
|
||||
"built-in function {}: {}",
|
||||
function.name(),
|
||||
function.docstring()
|
||||
)
|
||||
}
|
||||
Value::NativeValue(_) => "a native value".into(),
|
||||
};
|
||||
Ok(Value::String(explanation.into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"type",
|
||||
"Returns the data type of the argument",
|
||||
|_, _, args| {
|
||||
let [argument] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(argument.type_id())
|
||||
},
|
||||
);
|
||||
env.defmacro_native(
|
||||
"assert",
|
||||
"Checks that the condition is true, otherwise aborts the execution",
|
||||
|vm, _, args| match args {
|
||||
[] => Err(MachineError::InvalidArgumentCount),
|
||||
[cond] => {
|
||||
let assertion_failure_msg = match vm.current_location() {
|
||||
Some(ip) => format!("{ip}: assertion failed: {cond}"),
|
||||
None => format!("<undefined>: assertion failed: {cond}"),
|
||||
};
|
||||
let assertion_failure = Value::list_or_nil([
|
||||
Value::Identifier("abort".into()),
|
||||
Value::String(assertion_failure_msg.into()),
|
||||
]);
|
||||
Ok(Value::list_or_nil([
|
||||
Value::Keyword(Keyword::If),
|
||||
cond.clone(),
|
||||
Value::Nil,
|
||||
assertion_failure,
|
||||
]))
|
||||
}
|
||||
_ => Err(MachineError::InvalidArgumentCount),
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"abort",
|
||||
"Aborts the execution with the given reason message",
|
||||
|_, _, args| {
|
||||
let mut message = String::new();
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
message.push(' ');
|
||||
}
|
||||
message.push_str(&format!("{arg}"));
|
||||
}
|
||||
Err(MachineError::Abort(message.into()))
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
read::{self, InteractiveReader},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{IdentifierValue, NativeObject, StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
env.defun_native(
|
||||
"get",
|
||||
"Reads the value of a global variable",
|
||||
|_, env, args| {
|
||||
let (symbol, default) = match args {
|
||||
[symbol] => (symbol, &Value::Nil),
|
||||
[symbol, default] => (symbol, default),
|
||||
_ => todo!(),
|
||||
};
|
||||
let symbol = IdentifierValue::try_from_value(symbol)?;
|
||||
match env.global_value(&symbol) {
|
||||
Some(value) => Ok(value.clone()),
|
||||
None => Ok(default.clone()),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"set",
|
||||
"Sets the value of a global variable",
|
||||
|_, env, args| {
|
||||
let [symbol, value] = args else { todo!() };
|
||||
let symbol = match symbol {
|
||||
Value::Identifier(identifier) => identifier,
|
||||
Value::Quote(quoted) if let Value::Identifier(identifier) = quoted.as_ref() => {
|
||||
identifier
|
||||
}
|
||||
_ => todo!(),
|
||||
};
|
||||
env.set_global_value(symbol.clone(), value.clone())?;
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
|
||||
env.defun_native("eval", "Evaluates the given expression", |vm, env, args| {
|
||||
let (env, value) = match args {
|
||||
[value] => (env.clone(), value),
|
||||
[env, value] => (env.as_native()?, value),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let outcome =
|
||||
match vm.evaluate_value(Default::default(), Some("eval".into()), &env, value.clone()) {
|
||||
Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
|
||||
Err(error) => Value::list_or_nil([
|
||||
Value::Identifier("err".into()),
|
||||
Value::String(format!("{error}").into()),
|
||||
]),
|
||||
};
|
||||
Ok(outcome)
|
||||
});
|
||||
env.defun_native(
|
||||
"read-eval",
|
||||
"Evaluates the given expression, passed as a string",
|
||||
|vm, env, args| {
|
||||
let (env, input) = match args {
|
||||
[input] => (env.clone(), input),
|
||||
[env, input] => (env.as_native()?, input),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let input = StringValue::try_from_value(input)?;
|
||||
let mut input = String::from(&*input);
|
||||
input.push('\n');
|
||||
let outcome =
|
||||
match vm.evaluate_str(Default::default(), Some("eval".into()), &env, &input) {
|
||||
Ok(result) => Value::list_or_nil([Value::Identifier("ok".into()), result]),
|
||||
Err(error) => Value::list_or_nil([
|
||||
Value::Identifier("err".into()),
|
||||
Value::String(format!("{error}").into()),
|
||||
]),
|
||||
};
|
||||
|
||||
Ok(outcome)
|
||||
},
|
||||
);
|
||||
env.defun_native("env/create", "Create a new environment", |_, env, args| {
|
||||
let parent = match args {
|
||||
[] => Some(env.clone()),
|
||||
[env] if env.is_nil() => None,
|
||||
[env] => Some(env.as_native()?),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let environment: Rc<dyn NativeObject> = Rc::new(Environment::new(parent));
|
||||
Ok(Value::NativeValue(environment.into()))
|
||||
});
|
||||
env.defun_native(
|
||||
"env/load-prelude",
|
||||
"Adds prelude definitions to the environment",
|
||||
|_, _, args| {
|
||||
let [env] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let env = env.as_native::<Environment>()?;
|
||||
super::load(&env);
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"read",
|
||||
"Reads a form from standard input",
|
||||
|vm, env, _args| {
|
||||
let mut reader = InteractiveReader::new("> ", ">> ");
|
||||
let value = read::read(&mut reader, vm, env);
|
||||
let value = match value {
|
||||
Ok(Some(value)) => {
|
||||
Value::list_or_nil([Value::Identifier("ok".into()), value]).quote()
|
||||
}
|
||||
Ok(None) => Value::Nil,
|
||||
Err(error) => Value::list_or_nil([
|
||||
Value::Identifier("err".into()),
|
||||
Value::String(format!("{error}").into()),
|
||||
])
|
||||
.quote(),
|
||||
};
|
||||
Ok(value)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"unquote",
|
||||
"For quoted values, strips the quote and returns the inner value",
|
||||
|_, _, args| {
|
||||
let [x] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let unquoted = x.unquote()?;
|
||||
Ok(unquoted)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"print",
|
||||
"Prints the arguments to the standard output",
|
||||
|_, _, args| {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
print!(" ");
|
||||
}
|
||||
if let Value::String(string) = arg {
|
||||
print!("{}", &**string);
|
||||
} else {
|
||||
print!("{arg}");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"eprint",
|
||||
"Prints the arguments to the standard error output",
|
||||
|_, _, args| {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
eprint!(" ");
|
||||
}
|
||||
if let Value::String(string) = arg {
|
||||
eprint!("{}", &**string);
|
||||
} else {
|
||||
eprint!("{arg}");
|
||||
}
|
||||
}
|
||||
eprintln!();
|
||||
Ok(Value::Nil)
|
||||
},
|
||||
);
|
||||
env.defun_native("error", "Raises an error condition", |_, _, args| {
|
||||
let mut message = String::new();
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
message.push(' ');
|
||||
}
|
||||
if let Value::String(string) = arg {
|
||||
message.push_str(&format!("{}", *string));
|
||||
} else {
|
||||
message.push_str(&format!("{arg}"));
|
||||
}
|
||||
}
|
||||
Err(MachineError::Raised(message))
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
env::Environment,
|
||||
value::convert::{AnyFunction, TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
env.defun_native(
|
||||
"apply",
|
||||
"Applies the function to a given argument list",
|
||||
|vm, env, args| {
|
||||
let [f, xs] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let f = AnyFunction::try_from_value(f)?;
|
||||
let args = xs
|
||||
.proper_iter(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "proper list".into(),
|
||||
got: xs.clone(),
|
||||
}))
|
||||
.map(|x| x.cloned())
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
f.invoke(vm, env, &args[..])
|
||||
},
|
||||
);
|
||||
env.defun_native("identity", "Returns the argument as is", |_, _, args| {
|
||||
let [arg] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
Ok(arg.clone())
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::RefCell,
|
||||
fmt,
|
||||
fs::{self, File},
|
||||
io::{Read, Write, stdin, stdout},
|
||||
path::Path,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{NativeObject, NativeValue, StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Stream {
|
||||
Stdin,
|
||||
Stdout,
|
||||
File(RefCell<Option<File>>),
|
||||
}
|
||||
|
||||
impl NativeObject for Stream {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Stream {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("stream")
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream {
|
||||
pub fn read(&self, amount: usize) -> Option<Value> {
|
||||
let mut output = vec![];
|
||||
let mut remaining = amount;
|
||||
let mut buffer = [0; 4096];
|
||||
|
||||
while remaining != 0 {
|
||||
let want = remaining.min(buffer.len());
|
||||
let len = match self {
|
||||
Self::Stdin => stdin().read(&mut buffer[..want]).ok()?,
|
||||
Self::File(cell) => match &mut *cell.borrow_mut() {
|
||||
Some(file) => file.read(&mut buffer[..want]).ok()?,
|
||||
None => return None,
|
||||
},
|
||||
Self::Stdout => return None,
|
||||
};
|
||||
|
||||
if len == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
output.extend_from_slice(&buffer[..len]);
|
||||
remaining -= len;
|
||||
}
|
||||
|
||||
Some(output.into())
|
||||
}
|
||||
|
||||
fn write(&self, bytes: &[u8]) -> Option<Value> {
|
||||
let len = match self {
|
||||
Self::Stdin => return None,
|
||||
Self::Stdout => stdout().write(bytes).ok()?,
|
||||
Self::File(cell) => match &mut *cell.borrow_mut() {
|
||||
Some(file) => file.write(bytes).ok()?,
|
||||
None => return None,
|
||||
},
|
||||
};
|
||||
Some(Value::Number(len.into()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
let stdin: Rc<dyn NativeObject> = Rc::new(Stream::Stdin);
|
||||
let stdout: Rc<dyn NativeObject> = Rc::new(Stream::Stdout);
|
||||
|
||||
// FS API
|
||||
|
||||
env.defun_native(
|
||||
"fs/exists?",
|
||||
"Returns #t if the path exists",
|
||||
|_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
let path: &Path = (*path).as_ref();
|
||||
Ok(Value::Boolean(path.exists().into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"fs/directory?",
|
||||
"Returns #t if the path is a directory",
|
||||
|_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
let path: &Path = (*path).as_ref();
|
||||
Ok(Value::Boolean(path.is_dir().into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"fs/file?",
|
||||
"Returns #t if the path is a file",
|
||||
|_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
let path: &Path = (*path).as_ref();
|
||||
Ok(Value::Boolean(path.is_file().into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"fs/read-to-string",
|
||||
"Reads the file into a string",
|
||||
|_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
// TODO error handling
|
||||
match fs::read_to_string(&*path) {
|
||||
Ok(data) => Ok(Value::String(data.into())),
|
||||
Err(_error) => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"fs/read-to-vector",
|
||||
"Reads the file into a byte vector",
|
||||
|_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
// TODO error handling
|
||||
match fs::read(&*path) {
|
||||
Ok(data) => Ok(Value::Vector(Rc::new(data.into()))),
|
||||
Err(_error) => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"fs/home-directory",
|
||||
"Returns the path to user's home directory",
|
||||
|_, _, _| {
|
||||
if let Some(dir) = cross::path::home_dir() {
|
||||
Ok(Value::String(format!("{}", dir.display()).into()))
|
||||
} else {
|
||||
Ok(Value::Nil)
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// Stream API
|
||||
|
||||
env.set_global_value("stream/stdin", NativeValue::from(stdin))
|
||||
.expect("set_global_value failed");
|
||||
env.set_global_value("stream/stdout", NativeValue::from(stdout))
|
||||
.expect("set_global_value failed");
|
||||
|
||||
env.defun_native("stream/open", "Opens a file", |_, _, args| {
|
||||
let [path] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let path = StringValue::try_from_value(path)?;
|
||||
match File::open(&*path) {
|
||||
Ok(file) => {
|
||||
let object: Rc<dyn NativeObject> = Rc::new(Stream::File(RefCell::new(Some(file))));
|
||||
Ok(Value::NativeValue(object.into()))
|
||||
}
|
||||
Err(error) => {
|
||||
eprintln!("TODO: not sure whether to use result/exceptions");
|
||||
eprintln!("{path}: {error}");
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
});
|
||||
env.defun_native(
|
||||
"stream/read",
|
||||
"Reads data as a byte vector from the stream",
|
||||
|_, _, args| {
|
||||
let (stream, amount) = match args {
|
||||
// TODO read to end instead
|
||||
[stream] => (stream.as_native::<Stream>()?, 4096),
|
||||
[stream, amount] => (
|
||||
stream.as_native::<Stream>()?,
|
||||
usize::try_from_value(amount)?,
|
||||
),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
match stream.read(amount) {
|
||||
Some(value) => Ok(value),
|
||||
None => todo!(),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native("stream/write", "Writes data to the stream", |_, _, args| {
|
||||
let [stream, data] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let stream = stream.as_native::<Stream>()?;
|
||||
let result = match data {
|
||||
Value::Vector(value) if let Some(bytes) = value.as_bytes() => stream.write(&bytes[..]),
|
||||
Value::String(value) => stream.write(value.as_bytes()),
|
||||
_ => {
|
||||
return Err(MachineError::ValueConversion(ValueConversionError {
|
||||
expected: "byte vector or string".into(),
|
||||
got: data.clone(),
|
||||
}));
|
||||
}
|
||||
};
|
||||
match result {
|
||||
Some(value) => Ok(value),
|
||||
None => todo!(),
|
||||
}
|
||||
});
|
||||
env.defun_native("stream/close", "Closes the stream", |_, _, args| {
|
||||
let [stream] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let stream = stream.as_native::<Stream>()?;
|
||||
if let Stream::File(cell) = stream.as_ref() {
|
||||
cell.replace(None);
|
||||
}
|
||||
Ok(Value::Nil)
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,360 @@
|
||||
use std::{cmp::Ordering, ops::Mul, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
env::Environment,
|
||||
instruction::Instruction,
|
||||
machine::Machine,
|
||||
value::{NumberValue, Value, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub(crate) fn dispatch_arithmetic(
|
||||
instruction: Instruction,
|
||||
) -> fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> {
|
||||
match instruction {
|
||||
Instruction::Add => builtin_add,
|
||||
Instruction::Sub => builtin_sub,
|
||||
Instruction::Mul => builtin_mul,
|
||||
Instruction::Div => builtin_div,
|
||||
Instruction::Mod => builtin_mod,
|
||||
Instruction::Gt => builtin_cmp_gt,
|
||||
Instruction::Lt => builtin_cmp_lt,
|
||||
Instruction::Ge => builtin_cmp_ge,
|
||||
Instruction::Le => builtin_cmp_le,
|
||||
Instruction::Eq => builtin_cmp_eq,
|
||||
Instruction::Ne => builtin_cmp_ne,
|
||||
Instruction::Not => builtin_not,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn load(env: &Rc<Environment>) {
|
||||
env.defun_native("+", "Adds values", builtin_add);
|
||||
env.defun_native("-", "Subtracts values", builtin_sub);
|
||||
let mul = env.defun_native("*", "Multiplies values", builtin_mul);
|
||||
env.set_global_value("·", mul)
|
||||
.expect("set_global_value failed"); // alias for *
|
||||
let div = env.defun_native("/", "Divides values", builtin_div);
|
||||
env.set_global_value("÷", div)
|
||||
.expect("set_global_value failed"); // alias for /
|
||||
env.defun_native(
|
||||
"%",
|
||||
"Calculates a modulo/remainder of value divison",
|
||||
builtin_mod,
|
||||
);
|
||||
|
||||
env.defun_native(
|
||||
">",
|
||||
"Returns #t if the arguments are monotonically decreasing",
|
||||
builtin_cmp_gt,
|
||||
);
|
||||
let ge = env.defun_native(
|
||||
">=",
|
||||
"Returns #t if the arguments are monotonically non-increasing",
|
||||
builtin_cmp_ge,
|
||||
);
|
||||
env.set_global_value("≥", ge)
|
||||
.expect("set_global_value failed"); // alias for >=
|
||||
env.defun_native(
|
||||
"<",
|
||||
"Returns #t if the arguments are monotonically increasing",
|
||||
builtin_cmp_lt,
|
||||
);
|
||||
let le = env.defun_native(
|
||||
"<=",
|
||||
"Returns #t if the arguments are monotonically non-decreasing",
|
||||
builtin_cmp_le,
|
||||
);
|
||||
env.set_global_value("≤", le)
|
||||
.expect("set_global_value failed"); // alias for <=
|
||||
env.defun_native(
|
||||
"=",
|
||||
"Returns #t if all the arguments are equal",
|
||||
builtin_cmp_eq,
|
||||
);
|
||||
let ne = env.defun_native(
|
||||
"/=",
|
||||
"Returns #t if none of the arguments are equal",
|
||||
builtin_cmp_ne,
|
||||
);
|
||||
env.set_global_value("≠", ne)
|
||||
.expect("set_global_value failed"); // alias for /=
|
||||
|
||||
// env.defun_native("&", builtin_bitwise_and);
|
||||
// env.defun_native("|", builtin_bitwise_or);
|
||||
// env.defun_native("^", builtin_bitwise_xor);
|
||||
}
|
||||
|
||||
fn value_add(a: &Value, b: &Value) -> Result<Value, MachineError> {
|
||||
match (a, b) {
|
||||
(Value::String(a), Value::String(b)) => {
|
||||
Ok(Value::String(format!("{}{}", &**a, &**b).into()))
|
||||
}
|
||||
(Value::String(a), _) => Ok(Value::String(format!("{}{b}", &**a).into())),
|
||||
(_, Value::String(b)) => Ok(Value::String(format!("{a}{}", &**b).into())),
|
||||
(Value::Number(a), Value::Number(b)) => Ok(Value::Number(*a + *b)),
|
||||
(Value::Number(a), _) => Ok(Value::Number(*a + NumberValue::try_from_value(b)?)),
|
||||
(_, Value::Number(b)) => Ok(Value::Number(NumberValue::try_from_value(a)? + *b)),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => Ok(Value::Number(*a + *b)),
|
||||
_ => todo!("TODO error"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum CompareOperation {
|
||||
Eq,
|
||||
Ne,
|
||||
Lt,
|
||||
Gt,
|
||||
Le,
|
||||
Ge,
|
||||
}
|
||||
|
||||
fn builtin_fold<F>(fold: F, mut accumulator: Value, args: &[Value]) -> Result<Value, MachineError>
|
||||
where
|
||||
F: Fn(&Value, &Value) -> Result<Value, MachineError>,
|
||||
{
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i == 0 {
|
||||
accumulator = arg.clone();
|
||||
} else {
|
||||
accumulator = fold(&accumulator, arg)?;
|
||||
}
|
||||
}
|
||||
Ok(accumulator)
|
||||
}
|
||||
|
||||
fn builtin_fold_t<'a, T, F>(
|
||||
fold: F,
|
||||
mut accumulator: T,
|
||||
args: &'a [Value],
|
||||
) -> Result<Value, MachineError>
|
||||
where
|
||||
F: Fn(T, T) -> T,
|
||||
T: TryFromValue<'a> + Into<Value>,
|
||||
{
|
||||
for arg in args {
|
||||
let arg = T::try_from_value(arg)?;
|
||||
accumulator = fold(accumulator, arg);
|
||||
}
|
||||
Ok(accumulator.into())
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_add(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_fold(value_add, Value::Number(0.into()), args)
|
||||
}
|
||||
pub(crate) fn builtin_sub(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
match args {
|
||||
[] => Err(MachineError::InvalidArgumentCount),
|
||||
[x] => {
|
||||
let x = NumberValue::try_from_value(x)?;
|
||||
Ok((-x).into())
|
||||
}
|
||||
[x, rest @ ..] => {
|
||||
let mut accumulator = NumberValue::try_from_value(x)?;
|
||||
for argument in rest {
|
||||
let value = NumberValue::try_from_value(argument)?;
|
||||
accumulator -= value;
|
||||
}
|
||||
Ok(Value::Number(accumulator))
|
||||
}
|
||||
}
|
||||
}
|
||||
pub(crate) fn builtin_mul(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_fold_t(Mul::mul, NumberValue::from(1), args)
|
||||
}
|
||||
pub(crate) fn builtin_mod(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
let [x, y] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let x = NumberValue::try_from_value(x)?;
|
||||
let y = NumberValue::try_from_value(y)?;
|
||||
Ok(Value::Number(x % y))
|
||||
}
|
||||
pub(crate) fn builtin_div(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
match args {
|
||||
[] => Err(MachineError::InvalidArgumentCount),
|
||||
[x] => {
|
||||
let x = NumberValue::try_from_value(x)?;
|
||||
Ok(x.reciprocal().into())
|
||||
}
|
||||
[x, rest @ ..] => {
|
||||
let mut accumulator = NumberValue::try_from_value(x)?;
|
||||
for argument in rest {
|
||||
let argument = NumberValue::try_from_value(argument)?;
|
||||
accumulator /= argument;
|
||||
}
|
||||
Ok(accumulator.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pub(crate) fn builtin_and(
|
||||
// vm: &mut Machine,
|
||||
// _env: &Rc<Environment>,
|
||||
// args: &[Value],
|
||||
// ) -> Result<Value, MachineError> {
|
||||
// builtin_fold_t(BitAnd::bitand, true, args)
|
||||
// }
|
||||
// pub(crate) fn builtin_or(
|
||||
// vm: &mut Machine,
|
||||
// _env: &Rc<Environment>,
|
||||
// args: &[Value],
|
||||
// ) -> Result<Value, MachineError> {
|
||||
// builtin_fold_t(BitOr::bitor, false, args)
|
||||
// }
|
||||
// pub(crate) fn builtin_bitwise_and(
|
||||
// vm: &mut Machine,
|
||||
// _env: &Rc<Environment>,
|
||||
// args: &[Value],
|
||||
// ) -> Result<Value, MachineError> {
|
||||
// builtin_fold_t(BitAnd::bitand, 1, args)
|
||||
// }
|
||||
// pub(crate) fn builtin_bitwise_or(
|
||||
// vm: &mut Machine,
|
||||
// _env: &Rc<Environment>,
|
||||
// args: &[Value],
|
||||
// ) -> Result<Value, MachineError> {
|
||||
// builtin_fold_t(BitOr::bitor, 0, args)
|
||||
// }
|
||||
// pub(crate) fn builtin_bitwise_xor(
|
||||
// vm: &mut Machine,
|
||||
// _env: &Rc<Environment>,
|
||||
// args: &[Value],
|
||||
// ) -> Result<Value, MachineError> {
|
||||
// builtin_fold_t(BitXor::bitxor, 0, args)
|
||||
// }
|
||||
|
||||
pub(crate) fn builtin_cmp(
|
||||
_vm: &mut Machine,
|
||||
args: &[Value],
|
||||
op: CompareOperation,
|
||||
) -> Result<Value, MachineError> {
|
||||
fn ordering(a: &Value, b: &Value) -> Option<Ordering> {
|
||||
match (a, b) {
|
||||
(Value::Number(a), Value::Number(b)) => PartialOrd::partial_cmp(a, b),
|
||||
(Value::Boolean(a), Value::Boolean(b)) => Some(Ord::cmp(a, b)),
|
||||
(Value::String(a), Value::String(b)) => Some(Ord::cmp(a, b)),
|
||||
(Value::Identifier(a), Value::Identifier(b)) => Some(Ord::cmp(a, b)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
let mut accumulator = true;
|
||||
if !args.is_empty() {
|
||||
for i in 0..args.len() - 1 {
|
||||
let a = &args[i];
|
||||
let b = &args[i + 1];
|
||||
|
||||
match op {
|
||||
CompareOperation::Eq => {
|
||||
accumulator &= a == b;
|
||||
continue;
|
||||
}
|
||||
CompareOperation::Ne => {
|
||||
accumulator &= a != b;
|
||||
continue;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let ord = ordering(a, b);
|
||||
|
||||
// No ordering is specified
|
||||
let Some(ord) = ord else {
|
||||
accumulator = false;
|
||||
continue;
|
||||
};
|
||||
|
||||
let check = match op {
|
||||
CompareOperation::Eq => ord.is_eq(),
|
||||
CompareOperation::Ne => ord.is_ne(),
|
||||
CompareOperation::Lt => ord.is_lt(),
|
||||
CompareOperation::Gt => ord.is_gt(),
|
||||
CompareOperation::Le => ord.is_le(),
|
||||
CompareOperation::Ge => ord.is_ge(),
|
||||
};
|
||||
accumulator &= check;
|
||||
}
|
||||
}
|
||||
Ok(accumulator.into())
|
||||
}
|
||||
pub(crate) fn builtin_cmp_eq(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Eq)
|
||||
}
|
||||
pub(crate) fn builtin_cmp_ne(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Ne)
|
||||
}
|
||||
pub(crate) fn builtin_cmp_gt(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Gt)
|
||||
}
|
||||
pub(crate) fn builtin_cmp_lt(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Lt)
|
||||
}
|
||||
pub(crate) fn builtin_cmp_ge(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Ge)
|
||||
}
|
||||
pub(crate) fn builtin_cmp_le(
|
||||
vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
builtin_cmp(vm, args, CompareOperation::Le)
|
||||
}
|
||||
|
||||
pub(crate) fn builtin_not(
|
||||
_vm: &mut Machine,
|
||||
_env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
let [x] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let result = match x {
|
||||
Value::Boolean(value) => Value::Boolean(!*value),
|
||||
_ => Value::Boolean((!x.is_trueish()).into()),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::vm::{env::Environment, machine::Machine};
|
||||
|
||||
mod collections;
|
||||
mod convert;
|
||||
mod debug;
|
||||
mod eval;
|
||||
mod functional;
|
||||
mod io;
|
||||
mod math;
|
||||
mod string;
|
||||
|
||||
pub(crate) use math::*;
|
||||
|
||||
const PRELUDE_SOURCE: &str = include_str!("../../prelude.lysp");
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
let mut vm = Machine::default();
|
||||
|
||||
math::load(env);
|
||||
eval::load(env);
|
||||
functional::load(env);
|
||||
collections::load(env);
|
||||
convert::load(env);
|
||||
debug::load(env);
|
||||
io::load(env);
|
||||
string::load(env);
|
||||
|
||||
// Load the lysp part of the prelude
|
||||
vm.evaluate_str(Default::default(), None, env, PRELUDE_SOURCE)
|
||||
.expect("Couldn't evaluate prelude lysp part");
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
value::{StringValue, convert::TryFromValue},
|
||||
},
|
||||
};
|
||||
|
||||
fn classify<F: Fn(char) -> bool>(args: &[Value], predicate: F) -> Result<Value, MachineError> {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(string.chars().all(predicate).into())
|
||||
}
|
||||
|
||||
pub fn load(env: &Rc<Environment>) {
|
||||
env.defun_native(
|
||||
"string/trim",
|
||||
"Trims leading and trailing whitespace from a string",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(StringValue::from(string.trim()).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/pop",
|
||||
"Removes the last character from the string",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let mut string = (*StringValue::try_from_value(string)?).to_owned();
|
||||
string.pop();
|
||||
Ok(StringValue::from(string).into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/strip-prefix",
|
||||
"Removes the prefix from a string. Returns NIL if string does not start with the prefix",
|
||||
|_, _, args| {
|
||||
let [string, prefix] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
let prefix = StringValue::try_from_value(prefix)?;
|
||||
match string.strip_prefix(&*prefix) {
|
||||
Some(string) => Ok(StringValue::from(string).into()),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/strip-suffix",
|
||||
"Removes the suffix from a string. Returns NIL if string does not end with the suffix",
|
||||
|_, _, args| {
|
||||
let [string, suffix] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
let suffix = StringValue::try_from_value(suffix)?;
|
||||
match string.strip_suffix(&*suffix) {
|
||||
Some(string) => Ok(StringValue::from(string).into()),
|
||||
None => Ok(Value::Nil),
|
||||
}
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/split",
|
||||
"Splits a string by given separator",
|
||||
|_, _, args| {
|
||||
let (separator, string) = match args {
|
||||
[string] => (&StringValue::from(" "), string),
|
||||
[separator, string] => (&StringValue::try_from_value(separator)?, string),
|
||||
_ => return Err(MachineError::InvalidArgumentCount),
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
let mut items = Value::Nil;
|
||||
for item in string.rsplit(&**separator) {
|
||||
items = Value::String(item.into()).cons(items);
|
||||
}
|
||||
Ok(items)
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/length",
|
||||
"Returns the length of the string",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(string.len().into())
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/to-upper",
|
||||
"Converts a string to UPPERCASE",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(Value::String(string.to_uppercase().into()))
|
||||
},
|
||||
);
|
||||
env.defun_native(
|
||||
"string/to-lower",
|
||||
"Converts a string to lowercase",
|
||||
|_, _, args| {
|
||||
let [string] = args else {
|
||||
return Err(MachineError::InvalidArgumentCount);
|
||||
};
|
||||
let string = StringValue::try_from_value(string)?;
|
||||
Ok(Value::String(string.to_lowercase().into()))
|
||||
},
|
||||
);
|
||||
|
||||
env.defun_native(
|
||||
"string/alpha?",
|
||||
"Returns #t if all the characters in the string are alphabetic",
|
||||
|_, _, args| classify(args, char::is_alphabetic),
|
||||
);
|
||||
env.defun_native(
|
||||
"string/digit?",
|
||||
"Returns #t if all the characters in the string are digits (0-9)",
|
||||
|_, _, args| classify(args, |ch| ch.is_ascii_digit()),
|
||||
);
|
||||
env.defun_native(
|
||||
"string/ascii-graphic?",
|
||||
"Returns #t if all the characters in the string are ASCII graphic (non-control)",
|
||||
|_, _, args| classify(args, |ch| ch.is_ascii_graphic()),
|
||||
);
|
||||
env.defun_native(
|
||||
"string/ascii?",
|
||||
"Returns #t if all the characters in the string are ASCII",
|
||||
|_, _, args| classify(args, |ch| ch.is_ascii()),
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
use std::{
|
||||
mem::MaybeUninit,
|
||||
ops::{Bound, Index, RangeBounds},
|
||||
};
|
||||
|
||||
pub struct Stack<T> {
|
||||
data: Box<[MaybeUninit<T>]>,
|
||||
pointer: usize,
|
||||
}
|
||||
|
||||
impl<T> Stack<T> {
|
||||
pub fn new(limit: usize) -> Self {
|
||||
assert_ne!(limit, 0);
|
||||
Self {
|
||||
data: Box::new_uninit_slice(limit),
|
||||
pointer: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pointer == 0
|
||||
}
|
||||
|
||||
pub fn checked_slice<I: RangeBounds<usize>>(&self, range: I) -> Option<&[T]> {
|
||||
let start = match range.start_bound() {
|
||||
Bound::Unbounded => 0,
|
||||
Bound::Included(start) | Bound::Excluded(start) => *start,
|
||||
};
|
||||
let end = match range.end_bound() {
|
||||
Bound::Unbounded => self.pointer,
|
||||
Bound::Excluded(end) => *end,
|
||||
Bound::Included(end) => *end + 1,
|
||||
};
|
||||
|
||||
if start <= self.pointer && end <= self.pointer {
|
||||
Some(unsafe { self.data[start..end].assume_init_ref() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn checked_last(&self, n: usize) -> Option<&[T]> {
|
||||
if n < self.pointer {
|
||||
Some(unsafe { self.data[self.pointer - n..self.pointer].assume_init_ref() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
if index < self.pointer {
|
||||
Some(unsafe { self.data[index].assume_init_mut() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nth(&self, n: usize) -> Option<&T> {
|
||||
if n >= self.pointer {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { self.data[self.pointer - n - 1].assume_init_ref() })
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<&T> {
|
||||
if index < self.pointer {
|
||||
Some(unsafe { self.data[index].assume_init_ref() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn head(&self) -> Option<&T> {
|
||||
if self.pointer > 0 {
|
||||
Some(unsafe { self.data[self.pointer - 1].assume_init_ref() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn head_mut(&mut self) -> Option<&mut T> {
|
||||
if self.pointer > 0 {
|
||||
Some(unsafe { self.data[self.pointer - 1].assume_init_mut() })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pointer(&self) -> usize {
|
||||
self.pointer
|
||||
}
|
||||
|
||||
pub fn set_pointer(&mut self, pointer: usize) {
|
||||
if pointer < self.pointer {
|
||||
// Call drop for values between
|
||||
for slot in &mut self.data[pointer..self.pointer] {
|
||||
unsafe { slot.assume_init_drop() };
|
||||
}
|
||||
} else if pointer > self.pointer {
|
||||
panic!("Cannot set stack pointer higher than it currently is");
|
||||
}
|
||||
|
||||
self.pointer = pointer;
|
||||
}
|
||||
|
||||
pub fn pop(&mut self) -> Option<T> {
|
||||
if self.pointer > 0 {
|
||||
self.pointer -= 1;
|
||||
let value = unsafe { self.data[self.pointer].assume_init_read() };
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> Result<(), T> {
|
||||
if self.pointer < self.data.len() {
|
||||
self.data[self.pointer].write(value);
|
||||
self.pointer += 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for Stack<T> {
|
||||
type Output = T;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
self.get(index).expect("stack index out of bounds")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::vm::stack::Stack;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct DropTracker(&'static AtomicBool);
|
||||
|
||||
impl Drop for DropTracker {
|
||||
fn drop(&mut self) {
|
||||
self.0.store(true, Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_checked_slice() {
|
||||
let mut s = Stack::<u32>::new(32);
|
||||
s.push(1).unwrap();
|
||||
s.push(2).unwrap();
|
||||
s.push(3).unwrap();
|
||||
s.push(4).unwrap();
|
||||
s.push(5).unwrap();
|
||||
|
||||
// unbound..unbound
|
||||
assert_eq!(s.checked_slice(..).unwrap(), &[1, 2, 3, 4, 5]);
|
||||
// unbound..included
|
||||
assert_eq!(s.checked_slice(..=3).unwrap(), &[1, 2, 3, 4]);
|
||||
// unbound..excluded
|
||||
assert_eq!(s.checked_slice(..3).unwrap(), &[1, 2, 3]);
|
||||
|
||||
// included..unbound
|
||||
assert_eq!(s.checked_slice(2..).unwrap(), &[3, 4, 5]);
|
||||
// included..included
|
||||
assert_eq!(s.checked_slice(2..=3).unwrap(), &[3, 4]);
|
||||
// included..excluded
|
||||
assert_eq!(s.checked_slice(2..3).unwrap(), &[3]);
|
||||
|
||||
// Invalid
|
||||
assert!(s.checked_slice(..10).is_none());
|
||||
assert!(s.checked_slice(10..).is_none());
|
||||
assert!(s.checked_slice(10..9).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_underflow() {
|
||||
let mut s = Stack::<u32>::new(128);
|
||||
assert!(s.pop().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_overflow() {
|
||||
let mut s = Stack::<u32>::new(4);
|
||||
for i in 0..4 {
|
||||
s.push(i).unwrap();
|
||||
}
|
||||
assert_eq!(s.push(1234).unwrap_err(), 1234);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_no_drop_on_pop() {
|
||||
static DROP0: AtomicBool = AtomicBool::new(false);
|
||||
let mut s = Stack::<DropTracker>::new(4);
|
||||
s.push(DropTracker(&DROP0)).unwrap();
|
||||
assert!(!DROP0.load(Ordering::Acquire));
|
||||
let v = s.pop().unwrap();
|
||||
assert!(!DROP0.load(Ordering::Acquire));
|
||||
drop(v);
|
||||
assert!(DROP0.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_drop_on_set_pointer_lower() {
|
||||
static DROP0: AtomicBool = AtomicBool::new(false);
|
||||
static DROP1: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
let mut s = Stack::<DropTracker>::new(4);
|
||||
s.push(DropTracker(&DROP0)).unwrap();
|
||||
s.push(DropTracker(&DROP1)).unwrap();
|
||||
s.set_pointer(1);
|
||||
assert!(!DROP0.load(Ordering::Acquire));
|
||||
assert!(DROP1.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stack_head() {
|
||||
let mut s = Stack::<u32>::new(4);
|
||||
assert!(s.head().is_none());
|
||||
s.push(1234).unwrap();
|
||||
assert_eq!(s.head().unwrap(), &1234);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
use std::{
|
||||
fmt,
|
||||
ops::{Add, Not},
|
||||
};
|
||||
|
||||
use crate::vm::value::NumberValue;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct BooleanValue(pub bool);
|
||||
|
||||
impl From<bool> for BooleanValue {
|
||||
fn from(value: bool) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl Not for BooleanValue {
|
||||
type Output = Self;
|
||||
|
||||
fn not(self) -> Self::Output {
|
||||
Self(!self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for BooleanValue {
|
||||
type Output = NumberValue;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
NumberValue::Int(self.0 as i64 + rhs.0 as i64)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for BooleanValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.0 {
|
||||
true => write!(f, "#T"),
|
||||
false => write!(f, "#F"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use std::{cell::RefCell, fmt, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{Value, stack::Stack, value::BytecodeFunction},
|
||||
};
|
||||
|
||||
// #[derive(Debug, Clone, PartialEq)]
|
||||
// pub enum UpvalueValue {
|
||||
// Open(usize),
|
||||
// Closed(Box<Value>),
|
||||
// }
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Upvalue {
|
||||
Open(usize),
|
||||
Closed(Value),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct UpvalueRef(Rc<RefCell<Upvalue>>);
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub struct ClosureValue {
|
||||
pub function: Rc<BytecodeFunction>,
|
||||
pub upvalues: Vec<UpvalueRef>,
|
||||
}
|
||||
|
||||
impl UpvalueRef {
|
||||
pub fn open(sp: usize) -> Self {
|
||||
Self(Rc::new(RefCell::new(Upvalue::Open(sp))))
|
||||
}
|
||||
|
||||
pub fn read(&self, stack: &Stack<Value>) -> Result<Value, MachineError> {
|
||||
match &*self.0.borrow() {
|
||||
&Upvalue::Open(sp) => stack
|
||||
.get(sp)
|
||||
.cloned()
|
||||
.ok_or(MachineError::UndefinedUpvalueReference),
|
||||
Upvalue::Closed(value) => Ok(value.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, stack: &mut Stack<Value>, value: Value) -> Result<(), MachineError> {
|
||||
match &mut *self.0.borrow_mut() {
|
||||
&mut Upvalue::Open(sp) => {
|
||||
let slot = stack
|
||||
.get_mut(sp)
|
||||
.ok_or(MachineError::UndefinedUpvalueReference)?;
|
||||
*slot = value;
|
||||
}
|
||||
Upvalue::Closed(slot) => *slot = value,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn close(&self, value: Value) -> Upvalue {
|
||||
self.0.replace(Upvalue::Closed(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl ClosureValue {
|
||||
pub fn instruction_byte(&self, address: usize) -> Option<u8> {
|
||||
self.function.instructions.get(address).copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ClosureValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ClosureValue")
|
||||
.field("function", &self.function)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ClosureValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<closure {}@{:p}>", self.function.name(), self.function)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::fmt;
|
||||
|
||||
use crate::vm::value::Value;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ConsCell(pub Value, pub Value);
|
||||
|
||||
impl ConsCell {
|
||||
pub fn end(value: Value) -> Self {
|
||||
Self(value, Value::Nil)
|
||||
}
|
||||
|
||||
fn fmt_inner(&self, f: &mut fmt::Formatter<'_>, first: bool) -> fmt::Result {
|
||||
let Self(car, cdr) = self;
|
||||
if !first {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{car}")?;
|
||||
match cdr {
|
||||
Value::Nil => Ok(()),
|
||||
Value::Cons(cons) => cons.fmt_inner(f, false),
|
||||
_ => {
|
||||
write!(f, " . {cdr}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ConsCell {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.fmt_inner(f, true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
use std::{fmt, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
error::{MachineError, ValueConversionError},
|
||||
vm::{
|
||||
Value,
|
||||
env::Environment,
|
||||
machine::Machine,
|
||||
value::{
|
||||
BooleanValue, BytecodeFunction, ClosureValue, ConsCell, HashTable, IdentifierValue,
|
||||
NativeFunction, NativeValue, NumberValue, StringValue, Vector,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub trait TryFromValue<'a>: Sized {
|
||||
fn try_from_value(value: &'a Value) -> Result<Self, ValueConversionError>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AnyFunction {
|
||||
Native(NativeFunction),
|
||||
Function(Rc<BytecodeFunction>),
|
||||
Closure(ClosureValue),
|
||||
}
|
||||
|
||||
macro_rules! impl_integer {
|
||||
($($ty:ty : $bits:expr),+ $(,)?) => {
|
||||
$(
|
||||
impl TryFromValue<'_> for $ty {
|
||||
fn try_from_value(value: &Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
#[allow(irrefutable_let_patterns)]
|
||||
Value::Number(value) if let Ok(value) = <$ty>::try_from(*value) => Ok(value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: format!("{}-bit integer", $bits),
|
||||
got: value.clone()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$ty> for Value {
|
||||
fn from(value: $ty) -> Value {
|
||||
Value::Number(value.into())
|
||||
}
|
||||
}
|
||||
)+
|
||||
};
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for bool {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::Boolean(BooleanValue(value)) => Ok(*value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "#T or #F".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Value {
|
||||
fn from(value: bool) -> Self {
|
||||
Value::Boolean(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for NumberValue {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::Number(value) => Ok(*value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "integer value".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NumberValue> for Value {
|
||||
fn from(value: NumberValue) -> Self {
|
||||
Self::Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TryFromValue<'a> for &'a ConsCell {
|
||||
fn try_from_value(value: &'a Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::Cons(cons) => Ok(cons.as_ref()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "cons cell".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<Vector>> for Value {
|
||||
fn from(value: Rc<Vector>) -> Self {
|
||||
Self::Vector(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for StringValue {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::String(value) => Ok(value.clone()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "string".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NativeValue> for Value {
|
||||
fn from(value: NativeValue) -> Self {
|
||||
Self::NativeValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Value {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
Value::Vector(Rc::new(Vector::from_iter(value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IdentifierValue> for Value {
|
||||
fn from(value: IdentifierValue) -> Self {
|
||||
Value::Identifier(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StringValue> for Value {
|
||||
fn from(value: StringValue) -> Self {
|
||||
Value::String(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for Rc<HashTable> {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::HashTable(table) => Ok(table.clone()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "hash".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashTable> for Value {
|
||||
fn from(value: HashTable) -> Self {
|
||||
Value::HashTable(Rc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for IdentifierValue {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::Identifier(value) => Ok(value.clone()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "identifier".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromValue<'_> for AnyFunction {
|
||||
fn try_from_value(value: &'_ Value) -> Result<Self, ValueConversionError> {
|
||||
match value {
|
||||
Value::NativeFunction(function) => Ok(Self::Native(function.clone())),
|
||||
Value::Closure(closure) => Ok(Self::Closure(closure.clone())),
|
||||
Value::Function(function) => Ok(Self::Function(function.clone())),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "function".into(),
|
||||
got: value.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AnyFunction {
|
||||
pub fn name(&self) -> IdentifierValue {
|
||||
match self {
|
||||
Self::Native(native) => native.name().clone(),
|
||||
Self::Closure(closure) => closure.function.name().into(),
|
||||
Self::Function(function) => function.name().into(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
&self,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
args: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
match self {
|
||||
Self::Native(function) => function.invoke(vm, env, args),
|
||||
Self::Closure(closure) => vm
|
||||
.evaluate_closure_args(env, closure.clone(), args)
|
||||
.map_err(|error| error.error),
|
||||
Self::Function(function) => {
|
||||
let closure = ClosureValue {
|
||||
function: function.clone(),
|
||||
upvalues: vec![],
|
||||
};
|
||||
vm.evaluate_closure_args(env, closure, args)
|
||||
.map_err(|error| error.error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnyFunction> for Value {
|
||||
fn from(value: AnyFunction) -> Self {
|
||||
match value {
|
||||
AnyFunction::Function(value) => Value::Function(value),
|
||||
AnyFunction::Native(value) => Value::NativeFunction(value),
|
||||
AnyFunction::Closure(value) => Value::Closure(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for AnyFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Native(value) => fmt::Display::fmt(value, f),
|
||||
Self::Closure(value) => fmt::Display::fmt(value, f),
|
||||
Self::Function(value) => fmt::Display::fmt(value, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl_integer!(
|
||||
i8: 8,
|
||||
i16: 16,
|
||||
i32: 32,
|
||||
i64: 64,
|
||||
usize: usize::BITS,
|
||||
);
|
||||
@@ -0,0 +1,237 @@
|
||||
use std::{fmt, path::Path, rc::Rc};
|
||||
|
||||
use crate::{
|
||||
compile::UpvalueDef,
|
||||
vm::{
|
||||
Value,
|
||||
instruction::{BranchOffset, ConstantId, ImmediateInteger, Instruction},
|
||||
value::{IdentifierValue, StringValue},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct BytecodeFunction {
|
||||
pub identifier: Option<IdentifierValue>,
|
||||
pub docstring: Option<StringValue>,
|
||||
pub instructions: Box<[u8]>,
|
||||
pub constants: Box<[Value]>,
|
||||
pub upvalues: Box<[UpvalueDef]>,
|
||||
pub script: Option<Rc<Path>>,
|
||||
|
||||
pub required_count: usize,
|
||||
pub optional_count: usize,
|
||||
pub has_rest: bool,
|
||||
}
|
||||
|
||||
enum TraceArgument {
|
||||
Constant,
|
||||
ImmediateInteger,
|
||||
BranchTarget,
|
||||
Byte,
|
||||
None,
|
||||
}
|
||||
|
||||
impl fmt::Display for BytecodeFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<function {}@{:p}>", self.name(), self)
|
||||
}
|
||||
}
|
||||
|
||||
impl BytecodeFunction {
|
||||
pub fn name(&self) -> &str {
|
||||
match self.identifier.as_ref() {
|
||||
Some(name) => name.as_ref(),
|
||||
None => "unnamed",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn docstring(&self) -> &str {
|
||||
match self.docstring.as_ref() {
|
||||
Some(docstring) => docstring.as_ref(),
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn script_path(&self) -> Option<&Path> {
|
||||
self.script.as_deref()
|
||||
}
|
||||
|
||||
pub fn min_arity(&self) -> usize {
|
||||
self.required_count
|
||||
}
|
||||
|
||||
pub fn rest_argument_start(&self) -> Option<usize> {
|
||||
self.has_rest
|
||||
.then_some(self.required_count + self.optional_count)
|
||||
}
|
||||
|
||||
pub fn max_arity(&self) -> usize {
|
||||
if self.has_rest {
|
||||
usize::MAX
|
||||
} else {
|
||||
self.required_count + self.optional_count
|
||||
}
|
||||
}
|
||||
|
||||
fn trace_immediate_integer_at(&self, address: usize) -> Option<ImmediateInteger> {
|
||||
let Some(b0) = self.instructions.get(address).copied() else {
|
||||
eprint!(" <??> <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b0:02x}");
|
||||
let Some(b1) = self.instructions.get(address + 1).copied() else {
|
||||
eprint!(" <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b1:02x}");
|
||||
Some(ImmediateInteger::from(i16::from_le_bytes([b0, b1])))
|
||||
}
|
||||
|
||||
fn trace_branch_offset_at(&self, address: usize) -> Option<BranchOffset> {
|
||||
let Some(b0) = self.instructions.get(address).copied() else {
|
||||
eprint!(" <??> <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b0:02x}");
|
||||
let Some(b1) = self.instructions.get(address + 1).copied() else {
|
||||
eprint!(" <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b1:02x}");
|
||||
Some(BranchOffset::from(i16::from_le_bytes([b0, b1])))
|
||||
}
|
||||
|
||||
fn trace_constant_at(&self, address: usize) -> Option<Option<&Value>> {
|
||||
let Some(b0) = self.instructions.get(address).copied() else {
|
||||
eprint!(" <??> <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b0:02x}");
|
||||
let Some(b1) = self.instructions.get(address + 1).copied() else {
|
||||
eprint!(" <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b1:02x}");
|
||||
let id = usize::from(ConstantId::from(u16::from_le_bytes([b0, b1])));
|
||||
Some(self.constants.get(id))
|
||||
}
|
||||
|
||||
fn trace_byte_at(&self, address: usize) -> Option<u8> {
|
||||
let Some(b0) = self.instructions.get(address).copied() else {
|
||||
eprint!(" <??>");
|
||||
return None;
|
||||
};
|
||||
eprint!(" {b0:02x}");
|
||||
Some(b0)
|
||||
}
|
||||
|
||||
pub fn disassemble(&self, address: usize, before: usize, after: usize, arrow: bool) {
|
||||
let start = address.saturating_sub(before);
|
||||
let end = (address + after + 1).min(self.instructions.len());
|
||||
|
||||
let mut position = start;
|
||||
while position < end {
|
||||
let arrow = if arrow && position == address {
|
||||
"==>"
|
||||
} else {
|
||||
" "
|
||||
};
|
||||
eprint!("{self:p}:{position:<4}{arrow} ");
|
||||
|
||||
let opcode = self.instructions[position];
|
||||
|
||||
eprint!("{opcode:02x}");
|
||||
|
||||
let Ok(instruction) = Instruction::try_from(opcode) else {
|
||||
eprintln!(" <UNKNOWN>");
|
||||
position += 1;
|
||||
continue;
|
||||
};
|
||||
|
||||
let argument = match instruction {
|
||||
// Stack
|
||||
Instruction::PushNil => TraceArgument::None,
|
||||
Instruction::PushTrue | Instruction::PushFalse => TraceArgument::None,
|
||||
Instruction::PushInteger => TraceArgument::ImmediateInteger,
|
||||
Instruction::PushConstant => TraceArgument::Constant,
|
||||
Instruction::SetTemp | Instruction::GetTemp => TraceArgument::None,
|
||||
Instruction::SetLocal | Instruction::GetLocal => TraceArgument::Byte,
|
||||
Instruction::SetGlobal | Instruction::GetGlobal => TraceArgument::None,
|
||||
Instruction::SetUpvalue | Instruction::GetUpvalue => TraceArgument::Byte,
|
||||
Instruction::Drop => TraceArgument::None,
|
||||
Instruction::DeclareGlobal => TraceArgument::None,
|
||||
Instruction::DeclareMacro => TraceArgument::None,
|
||||
// Arithmetic
|
||||
Instruction::Ne
|
||||
| Instruction::Gt
|
||||
| Instruction::Lt
|
||||
| Instruction::Eq
|
||||
| Instruction::Ge
|
||||
| Instruction::Le
|
||||
| Instruction::Add
|
||||
| Instruction::Sub
|
||||
| Instruction::Mul
|
||||
| Instruction::Div
|
||||
| Instruction::Mod
|
||||
| Instruction::Not => TraceArgument::Byte,
|
||||
// Function
|
||||
Instruction::Call => TraceArgument::Byte,
|
||||
Instruction::Return => TraceArgument::None,
|
||||
Instruction::MakeClosure => TraceArgument::None,
|
||||
Instruction::CloseUpvalue => TraceArgument::Byte,
|
||||
// Branch
|
||||
Instruction::Branch => TraceArgument::BranchTarget,
|
||||
Instruction::Jump => TraceArgument::BranchTarget,
|
||||
};
|
||||
|
||||
position += 1;
|
||||
|
||||
match argument {
|
||||
TraceArgument::Byte => {
|
||||
let Some(byte) = self.trace_byte_at(position) else {
|
||||
eprintln!("\t\t{instruction} <invalid>");
|
||||
break;
|
||||
};
|
||||
position += 1;
|
||||
eprintln!("\t\t{instruction} {byte}");
|
||||
}
|
||||
TraceArgument::Constant => {
|
||||
let Some(constant) = self.trace_constant_at(position) else {
|
||||
eprintln!("\t\t{instruction} <invalid>");
|
||||
break;
|
||||
};
|
||||
position += 2;
|
||||
let Some(constant) = constant else {
|
||||
eprintln!("\t\t{instruction} <invalid>");
|
||||
continue;
|
||||
};
|
||||
eprintln!("\t\t{instruction} {constant}");
|
||||
}
|
||||
TraceArgument::ImmediateInteger => {
|
||||
let Some(value) = self.trace_immediate_integer_at(position) else {
|
||||
eprintln!("\t\t{instruction} <invalid>");
|
||||
break;
|
||||
};
|
||||
position += 2;
|
||||
eprintln!("\t\t{instruction} {}", value.sign_extend_i64());
|
||||
}
|
||||
TraceArgument::None => {
|
||||
eprintln!("\t\t{instruction}");
|
||||
}
|
||||
TraceArgument::BranchTarget => {
|
||||
let Some(offset) = self.trace_branch_offset_at(position) else {
|
||||
eprintln!("\t\t{instruction} <invalid>");
|
||||
break;
|
||||
};
|
||||
position += 2;
|
||||
let offset = offset.sign_extend_isize();
|
||||
eprintln!(
|
||||
"\t\t{instruction} {:+} (-> {})",
|
||||
offset,
|
||||
position.saturating_add_signed(offset)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
fmt,
|
||||
rc::Rc,
|
||||
slice,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{Value, env::Environment, machine::Machine, value::convert::AnyFunction},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct HashTable(RefCell<HashTableData>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct HashTableData {
|
||||
buckets: Box<[Vec<(Value, Value)>]>,
|
||||
length: usize,
|
||||
}
|
||||
|
||||
pub struct HashTableIter<'a> {
|
||||
buckets: slice::Iter<'a, Vec<(Value, Value)>>,
|
||||
inner: Option<slice::Iter<'a, (Value, Value)>>,
|
||||
}
|
||||
pub struct HashTableIterMut<'a> {
|
||||
buckets: slice::IterMut<'a, Vec<(Value, Value)>>,
|
||||
inner: Option<slice::IterMut<'a, (Value, Value)>>,
|
||||
}
|
||||
|
||||
impl HashTable {
|
||||
pub fn borrow(&self) -> Ref<'_, HashTableData> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> RefMut<'_, HashTableData> {
|
||||
self.0.borrow_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HashTable {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&*self.0.borrow(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl HashTableData {
|
||||
pub fn new(bucket_count: usize) -> Self {
|
||||
let buckets = vec![vec![]; bucket_count].into_boxed_slice();
|
||||
Self { buckets, length: 0 }
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.length == 0
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.length
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
for bucket in self.buckets.iter_mut() {
|
||||
bucket.clear();
|
||||
}
|
||||
self.length = 0;
|
||||
}
|
||||
|
||||
// Returns the value inserted
|
||||
pub fn insert(&mut self, key: Value, value: Value) -> Result<Value, MachineError> {
|
||||
let hash = key
|
||||
.hash()
|
||||
.ok_or_else(|| MachineError::InvalidHashTableKey(key.clone()))?;
|
||||
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
let slot_index = self.buckets[bucket_index]
|
||||
.iter()
|
||||
.position(|(k, _)| k == &key);
|
||||
if let Some(slot_index) = slot_index {
|
||||
self.buckets[bucket_index][slot_index].1 = value.clone();
|
||||
} else {
|
||||
self.buckets[bucket_index].push((key, value.clone()));
|
||||
self.length += 1;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
pub fn retain_invoke(
|
||||
&mut self,
|
||||
vm: &mut Machine,
|
||||
env: &Rc<Environment>,
|
||||
predicate: &AnyFunction,
|
||||
) -> Result<(), MachineError> {
|
||||
for bucket in self.buckets.iter_mut() {
|
||||
let mut index = 0;
|
||||
loop {
|
||||
if index >= bucket.len() {
|
||||
break;
|
||||
}
|
||||
|
||||
let (key, value) = &bucket[index];
|
||||
let output = predicate.invoke(vm, env, &[key.clone(), value.clone()])?;
|
||||
if !output.is_trueish() {
|
||||
bucket.remove(index);
|
||||
self.length -= 1;
|
||||
} else {
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &Value) -> bool {
|
||||
let Some(hash) = key.hash() else { return false };
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
|
||||
self.buckets[bucket_index]
|
||||
.iter()
|
||||
.find(|(k, _)| k == key)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &Value) -> Option<&Value> {
|
||||
let hash = key.hash()?;
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
|
||||
self.buckets[bucket_index]
|
||||
.iter()
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, key: &Value) -> Option<&mut Value> {
|
||||
let hash = key.hash()?;
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
|
||||
self.buckets[bucket_index]
|
||||
.iter_mut()
|
||||
.find(|(k, _)| k == key)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &Value) -> Option<Value> {
|
||||
let hash = key.hash()?;
|
||||
let bucket_index = (hash % self.buckets.len() as u64) as usize;
|
||||
let slot_index = self.buckets[bucket_index]
|
||||
.iter()
|
||||
.position(|(k, _)| k == key)?;
|
||||
let value = self.buckets[bucket_index].remove(slot_index);
|
||||
self.length -= 1;
|
||||
Some(value.1)
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> HashTableIter<'_> {
|
||||
HashTableIter {
|
||||
buckets: self.buckets.iter(),
|
||||
inner: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> HashTableIterMut<'_> {
|
||||
HashTableIterMut {
|
||||
buckets: self.buckets.iter_mut(),
|
||||
inner: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for HashTableData {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if self.length != other.length {
|
||||
return false;
|
||||
}
|
||||
for (key, value1) in self.iter() {
|
||||
let Some(value2) = other.get(key) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
if value1 != value2 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for HashTableData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut list = f.debug_list();
|
||||
for bucket in self.buckets.iter() {
|
||||
for cell in bucket.iter() {
|
||||
list.entry(cell);
|
||||
}
|
||||
}
|
||||
list.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HashTableData {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut index = 0;
|
||||
for bucket in self.buckets.iter() {
|
||||
for (key, value) in bucket.iter() {
|
||||
if index != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "({key} . {value})")?;
|
||||
index += 1;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<HashTableData> for HashTable {
|
||||
fn from(value: HashTableData) -> Self {
|
||||
Self(RefCell::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for HashTableIter<'a> {
|
||||
type Item = (&'a Value, &'a Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(inner) = &mut self.inner
|
||||
&& let Some((key, value)) = inner.next()
|
||||
{
|
||||
return Some((key, value));
|
||||
}
|
||||
|
||||
let next_bucket = self.buckets.next()?;
|
||||
self.inner = Some(next_bucket.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'a> Iterator for HashTableIterMut<'a> {
|
||||
type Item = (&'a Value, &'a mut Value);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
loop {
|
||||
if let Some(inner) = &mut self.inner
|
||||
&& let Some((key, value)) = inner.next()
|
||||
{
|
||||
return Some((key, value));
|
||||
}
|
||||
|
||||
let next_bucket = self.buckets.next()?;
|
||||
self.inner = Some(next_bucket.iter_mut());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
use std::{borrow::Borrow, fmt, rc::Rc};
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct IdentifierValue(Rc<str>);
|
||||
|
||||
impl From<&str> for IdentifierValue {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for IdentifierValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for IdentifierValue {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Borrow<str> for IdentifierValue {
|
||||
fn borrow(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for IdentifierValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
use crate::vm::value::{ConsCell, Value};
|
||||
|
||||
pub struct ProperListIter<'a, E> {
|
||||
pub(super) head: Option<&'a Value>,
|
||||
pub(super) error: Option<E>,
|
||||
}
|
||||
|
||||
impl<'a, E> Iterator for ProperListIter<'a, E> {
|
||||
type Item = Result<&'a Value, E>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let head = self.head.take()?;
|
||||
match head {
|
||||
Value::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
self.head = Some(cdr);
|
||||
Some(Ok(car))
|
||||
}
|
||||
Value::Nil => None,
|
||||
_ => self.error.take().map(Err),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
macro_rules! impl_keyword {
|
||||
(
|
||||
$vis:vis enum $name:ident {
|
||||
$($variant:ident => $s:literal),* $(,)?
|
||||
}
|
||||
) => {
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
$vis enum $name {
|
||||
$($variant),*
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub fn parse(s: &str) -> Option<Self> {
|
||||
match s {
|
||||
$(
|
||||
$s => Some(Self::$variant),
|
||||
)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
$(
|
||||
Self::$variant => $s
|
||||
),*
|
||||
})
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_keyword! {
|
||||
pub enum Keyword {
|
||||
Lambda => "lambda",
|
||||
Defun => "defun",
|
||||
If => "if",
|
||||
Cond => "cond",
|
||||
While => "while",
|
||||
Otherwise => "&otherwise",
|
||||
Optional => "&optional",
|
||||
Rest => "&rest",
|
||||
Setq => "setq",
|
||||
Let => "let",
|
||||
LetStar => "let*",
|
||||
Defmacro => "defmacro",
|
||||
Quote => "quote",
|
||||
Progn => "progn",
|
||||
Loop => "loop",
|
||||
Break => "break",
|
||||
Continue => "continue",
|
||||
Declare => "declare",
|
||||
Error => "&error",
|
||||
Return => "return",
|
||||
// Cons => "cons",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,326 @@
|
||||
use std::{
|
||||
fmt::{self, Write},
|
||||
hash::{DefaultHasher, Hash, Hasher},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
mod boolean;
|
||||
mod closure;
|
||||
mod cons;
|
||||
mod function;
|
||||
mod hashtable;
|
||||
mod identifier;
|
||||
mod iter;
|
||||
mod keyword;
|
||||
mod native;
|
||||
mod number;
|
||||
mod string;
|
||||
mod vector;
|
||||
|
||||
pub mod convert;
|
||||
|
||||
pub use boolean::BooleanValue;
|
||||
pub use closure::{ClosureValue, Upvalue, UpvalueRef};
|
||||
pub use cons::ConsCell;
|
||||
pub use function::BytecodeFunction;
|
||||
pub use hashtable::{HashTable, HashTableData};
|
||||
pub use identifier::IdentifierValue;
|
||||
pub use keyword::Keyword;
|
||||
pub use native::{NativeFunction, NativeObject, NativeValue};
|
||||
pub use number::NumberValue;
|
||||
pub use string::StringValue;
|
||||
pub use vector::Vector;
|
||||
|
||||
pub use iter::ProperListIter;
|
||||
|
||||
use crate::{
|
||||
compile::syntax::{ExpectedWhat, ExpectedWhere, ParseError, ParseErrorKind},
|
||||
error::ValueConversionError,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Value {
|
||||
// Syntactic
|
||||
Nil,
|
||||
Cons(Rc<ConsCell>),
|
||||
Number(NumberValue),
|
||||
Boolean(BooleanValue),
|
||||
Keyword(Keyword),
|
||||
Identifier(IdentifierValue),
|
||||
String(StringValue),
|
||||
Quasi(Rc<Value>),
|
||||
Quote(Rc<Value>),
|
||||
Unquote(Rc<Value>),
|
||||
UnquoteSplice(Rc<Value>),
|
||||
// Collections
|
||||
Vector(Rc<Vector>),
|
||||
HashTable(Rc<HashTable>),
|
||||
// Semantic
|
||||
Closure(ClosureValue),
|
||||
Function(Rc<BytecodeFunction>),
|
||||
// Native
|
||||
NativeFunction(NativeFunction),
|
||||
NativeValue(NativeValue),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
|
||||
match self {
|
||||
Self::Nil => state.write_u8(0),
|
||||
Self::Cons(cons) => {
|
||||
let ConsCell(car, cdr) = cons.as_ref();
|
||||
state.write_u8(1);
|
||||
if !car.hash_inner(state) {
|
||||
return false;
|
||||
}
|
||||
if !cdr.hash_inner(state) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Self::Number(value) => {
|
||||
state.write_u8(2);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Boolean(value) => {
|
||||
state.write_u8(3);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Keyword(value) => {
|
||||
state.write_u8(4);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Identifier(value) => {
|
||||
state.write_u8(5);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::String(value) => {
|
||||
state.write_u8(6);
|
||||
value.hash(state);
|
||||
}
|
||||
Self::Quasi(value) => {
|
||||
state.write_u8(7);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Quote(value) => {
|
||||
state.write_u8(8);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Unquote(value) => {
|
||||
state.write_u8(9);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::UnquoteSplice(value) => {
|
||||
state.write_u8(10);
|
||||
return value.hash_inner(state);
|
||||
}
|
||||
Self::Vector(_)
|
||||
| Self::HashTable(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::Function(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Closure(_) => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub fn hash(&self) -> Option<u64> {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
if !self.hash_inner(&mut hasher) {
|
||||
return None;
|
||||
}
|
||||
Some(hasher.finish())
|
||||
}
|
||||
|
||||
pub fn is_nil(&self) -> bool {
|
||||
matches!(self, Self::Nil)
|
||||
}
|
||||
|
||||
pub fn unquote(&self) -> Result<Value, ValueConversionError> {
|
||||
match self {
|
||||
Self::Quote(value) => Ok(value.as_ref().clone()),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "quoted value".into(),
|
||||
got: self.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn uncons_ref(&self) -> Option<(&Value, &Value)> {
|
||||
match self {
|
||||
Self::Cons(cons) => Some((&cons.0, &cons.1)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn quote(self) -> Value {
|
||||
Value::Quote(self.into())
|
||||
}
|
||||
|
||||
pub fn type_id(&self) -> Value {
|
||||
match self {
|
||||
Self::Nil | Self::Cons(_) => Value::Identifier("list".into()),
|
||||
Self::Identifier(_) => Value::Identifier("identifier".into()),
|
||||
Self::Number(_) => Value::Identifier("number".into()),
|
||||
Self::Boolean(_) => Value::Identifier("boolean".into()),
|
||||
Self::Quote(value) => {
|
||||
Value::list_or_nil([Self::Identifier("quote".into()), value.type_id()])
|
||||
}
|
||||
Self::Quasi(value) => {
|
||||
Value::list_or_nil([Self::Identifier("quasi".into()), value.type_id()])
|
||||
}
|
||||
Self::Unquote(value) => {
|
||||
Value::list_or_nil([Self::Identifier("unquote".into()), value.type_id()])
|
||||
}
|
||||
Self::UnquoteSplice(value) => {
|
||||
Value::list_or_nil([Self::Identifier("unquote-splice".into()), value.type_id()])
|
||||
}
|
||||
Self::Vector(_) => Self::Identifier("vector".into()),
|
||||
Self::HashTable(_) => Self::Identifier("hash".into()),
|
||||
Self::Keyword(_) => Self::Identifier("keyword".into()),
|
||||
Self::String(_) => Self::Identifier("string".into()),
|
||||
Self::NativeValue(_) => Self::Identifier("native".into()),
|
||||
Self::NativeFunction(_) | Self::Function(_) | Self::Closure(_) => {
|
||||
Self::Identifier("function".into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_trueish(&self) -> bool {
|
||||
match self {
|
||||
Self::Nil => false,
|
||||
Self::Cons(_) => true,
|
||||
Self::Vector(vector) => !vector.is_empty(),
|
||||
Self::HashTable(table) => !table.borrow().is_empty(),
|
||||
Self::Number(value) => value.is_trueish(),
|
||||
Self::String(value) => !value.is_empty(),
|
||||
Self::Boolean(BooleanValue(value)) => *value,
|
||||
Self::Keyword(_)
|
||||
| Self::Identifier(_)
|
||||
| Self::Quasi(_)
|
||||
| Self::Quote(_)
|
||||
| Self::Unquote(_)
|
||||
| Self::UnquoteSplice(_)
|
||||
| Self::Closure(_)
|
||||
| Self::NativeFunction(_)
|
||||
| Self::NativeValue(_)
|
||||
| Self::Function(_) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cons(self, cdr: Value) -> Self {
|
||||
Self::Cons(Rc::new(ConsCell(self, cdr)))
|
||||
}
|
||||
|
||||
pub fn try_list_or_nil<E, I: IntoIterator<Item = Result<Self, E>>>(items: I) -> Result<Self, E>
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
let iter = items.into_iter();
|
||||
let mut result = Value::Nil;
|
||||
for element in iter.rev() {
|
||||
result = element?.cons(result);
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn list_or_nil<I: IntoIterator<Item = Self>>(items: I) -> Self
|
||||
where
|
||||
I::IntoIter: ExactSizeIterator + DoubleEndedIterator,
|
||||
{
|
||||
let iter = items.into_iter();
|
||||
let mut result = Value::Nil;
|
||||
for element in iter.rev() {
|
||||
result = element.cons(result);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn proper_iter<E>(&self, error: E) -> ProperListIter<'_, E> {
|
||||
ProperListIter {
|
||||
head: Some(self),
|
||||
error: Some(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn syntax_iter(&self, location: ExpectedWhere) -> ProperListIter<'_, ParseError> {
|
||||
self.proper_iter(ParseError {
|
||||
input: self.clone(),
|
||||
error: ParseErrorKind::Expected(ExpectedWhat::ProperList, location),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn as_proper_list(&self) -> Option<Vec<Self>> {
|
||||
self.proper_iter(())
|
||||
.map(|x| x.cloned())
|
||||
.collect::<Result<_, _>>()
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn as_native<T: NativeObject>(&self) -> Result<Rc<T>, ValueConversionError> {
|
||||
match self {
|
||||
Self::NativeValue(value) if let Some(value) = value.cast_rc() => Ok(value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "native value".into(),
|
||||
got: self.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_native_ref<T: NativeObject>(&self) -> Result<&T, ValueConversionError> {
|
||||
match self {
|
||||
Self::NativeValue(value) if let Some(value) = value.cast_ref() => Ok(value),
|
||||
_ => Err(ValueConversionError {
|
||||
expected: "native value".into(),
|
||||
got: self.clone(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Nil => write!(f, "NIL"),
|
||||
Self::Cons(cons) => {
|
||||
f.write_char('(')?;
|
||||
fmt::Display::fmt(cons, f)?;
|
||||
f.write_char(')')
|
||||
}
|
||||
Self::Keyword(keyword) => write!(f, "{keyword}"),
|
||||
Self::Identifier(identifier) => write!(f, "{identifier}"),
|
||||
Self::String(value) => fmt::Display::fmt(value, f),
|
||||
Self::Quote(value) => {
|
||||
f.write_char('\'')?;
|
||||
fmt::Display::fmt(value, f)
|
||||
}
|
||||
Self::Quasi(value) => {
|
||||
f.write_char('`')?;
|
||||
fmt::Display::fmt(value, f)
|
||||
}
|
||||
Self::Unquote(value) => {
|
||||
f.write_char(',')?;
|
||||
fmt::Display::fmt(value, f)
|
||||
}
|
||||
Self::UnquoteSplice(value) => {
|
||||
f.write_str(",@")?;
|
||||
fmt::Display::fmt(value, f)
|
||||
}
|
||||
Self::Number(value) => fmt::Display::fmt(value, f),
|
||||
Self::Boolean(value) => fmt::Display::fmt(value, f),
|
||||
Self::Closure(value) => fmt::Display::fmt(value, f),
|
||||
Self::Function(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeValue(value) => fmt::Display::fmt(value, f),
|
||||
Self::NativeFunction(value) => fmt::Display::fmt(value, f),
|
||||
Self::Vector(vector) => {
|
||||
f.write_str("#[")?;
|
||||
fmt::Display::fmt(vector, f)?;
|
||||
f.write_char(']')
|
||||
}
|
||||
Self::HashTable(table) => {
|
||||
f.write_str("#hash{")?;
|
||||
fmt::Display::fmt(table, f)?;
|
||||
f.write_char('}')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
error::MachineError,
|
||||
vm::{
|
||||
env::Environment,
|
||||
machine::Machine,
|
||||
value::{IdentifierValue, StringValue, Value},
|
||||
},
|
||||
};
|
||||
|
||||
pub trait NativeObject: Any + fmt::Debug + fmt::Display {
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NativeValue(Rc<dyn NativeObject>);
|
||||
|
||||
pub type NativeFunctionImpl =
|
||||
Rc<dyn Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NativeFunction {
|
||||
name: IdentifierValue,
|
||||
inner: NativeFunctionImpl,
|
||||
docstring: StringValue,
|
||||
}
|
||||
|
||||
impl NativeFunction {
|
||||
pub fn new<S, D, F>(name: S, docstring: D, function: F) -> Self
|
||||
where
|
||||
S: Into<IdentifierValue>,
|
||||
D: Into<StringValue>,
|
||||
F: Fn(&mut Machine, &Rc<Environment>, &[Value]) -> Result<Value, MachineError> + 'static,
|
||||
{
|
||||
Self {
|
||||
name: name.into(),
|
||||
docstring: docstring.into(),
|
||||
inner: Rc::new(function),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &IdentifierValue {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn docstring(&self) -> &StringValue {
|
||||
&self.docstring
|
||||
}
|
||||
|
||||
pub fn invoke(
|
||||
&self,
|
||||
machine: &mut Machine,
|
||||
environment: &Rc<Environment>,
|
||||
arguments: &[Value],
|
||||
) -> Result<Value, MachineError> {
|
||||
(self.inner)(machine, environment, arguments)
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for NativeFunction {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
(&raw const *self.inner.as_ref()).addr().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NativeFunction {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.name == other.name && Rc::ptr_eq(&self.inner, &other.inner)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NativeFunction {}
|
||||
|
||||
impl fmt::Debug for NativeFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("NativeFunction")
|
||||
.field("name", &self.name)
|
||||
.field_with("inner", |f| write!(f, "{:p}", self.inner))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NativeFunction {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"<native {}@{:#x}>",
|
||||
self.name,
|
||||
(&raw const *self.inner.as_ref()).addr()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl NativeValue {
|
||||
pub fn cast_rc<T: NativeObject>(&self) -> Option<Rc<T>> {
|
||||
Rc::downcast(self.0.clone()).ok()
|
||||
}
|
||||
|
||||
pub fn cast_ref<T: NativeObject>(&self) -> Option<&T> {
|
||||
self.0.as_any().downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Rc<dyn NativeObject + 'static>> for NativeValue {
|
||||
fn from(value: Rc<dyn NativeObject>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NativeValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
Rc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NativeValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "<native {}>", self.0)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
use std::{
|
||||
cmp::Ordering,
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
num::TryFromIntError,
|
||||
ops::{Add, Div, DivAssign, Mul, Neg, Rem, RemAssign, Sub, SubAssign},
|
||||
};
|
||||
|
||||
pub struct NumberConvertError;
|
||||
|
||||
impl From<TryFromIntError> for NumberConvertError {
|
||||
fn from(_: TryFromIntError) -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum NumberValue {
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
}
|
||||
|
||||
impl NumberValue {
|
||||
pub(super) fn hash_inner<H: Hasher>(&self, state: &mut H) -> bool {
|
||||
match self {
|
||||
Self::Int(value) => {
|
||||
state.write_u8(1);
|
||||
value.hash(state)
|
||||
}
|
||||
Self::Float(value) if !value.is_nan() => {
|
||||
state.write_u8(2);
|
||||
state.write(&value.to_le_bytes());
|
||||
}
|
||||
_ => return false,
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
pub const fn nan() -> Self {
|
||||
Self::Float(f64::NAN)
|
||||
}
|
||||
|
||||
pub const fn infinity() -> Self {
|
||||
Self::Float(f64::INFINITY)
|
||||
}
|
||||
|
||||
pub const fn neg_infinity() -> Self {
|
||||
Self::Float(f64::NEG_INFINITY)
|
||||
}
|
||||
|
||||
pub fn reciprocal(self) -> Self {
|
||||
Self::Float(1.0 / self.as_float())
|
||||
}
|
||||
|
||||
pub fn from_str_radix(string: &str, radix: u32) -> Option<Self> {
|
||||
if !(2..=36).contains(&radix) {
|
||||
return None;
|
||||
}
|
||||
if let Ok(value) = i64::from_str_radix(string, radix) {
|
||||
Some(Self::Int(value))
|
||||
} else if radix == 10
|
||||
&& let Ok(value) = string.parse()
|
||||
{
|
||||
Some(Self::Float(value))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float(self) -> f64 {
|
||||
match self {
|
||||
Self::Int(value) => value as f64,
|
||||
Self::Float(value) => value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_trueish(&self) -> bool {
|
||||
match self {
|
||||
Self::Int(value) => *value != 0,
|
||||
Self::Float(value) => *value != 0.0 && !value.is_nan(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for NumberValue {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Int(a), Self::Int(b)) => a == b,
|
||||
_ => self.as_float() == other.as_float(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialOrd for NumberValue {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
match (self, other) {
|
||||
(Self::Int(a), Self::Int(b)) => PartialOrd::partial_cmp(a, b),
|
||||
_ => PartialOrd::partial_cmp(&self.as_float(), &other.as_float()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_add(b) => Self::Int(c),
|
||||
_ => todo!("TODO promote to bigint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_sub(b) => Self::Int(c),
|
||||
_ => todo!("TODO promote to bigint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for NumberValue {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
*self = *self - rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_mul(b) => Self::Int(c),
|
||||
_ => todo!("TODO promote to bigint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
// TODO convert inexact division to rational
|
||||
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_div_exact(b) => Self::Int(c),
|
||||
_ => Self::Float(self.as_float() / rhs.as_float()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign for NumberValue {
|
||||
fn div_assign(&mut self, rhs: Self) {
|
||||
*self = *self / rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Rem for NumberValue {
|
||||
type Output = Self;
|
||||
|
||||
fn rem(self, rhs: Self) -> Self::Output {
|
||||
match (self, rhs) {
|
||||
(Self::Int(a), Self::Int(b)) if let Some(c) = a.checked_rem(b) => Self::Int(c),
|
||||
_ => todo!("TODO NaN"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemAssign for NumberValue {
|
||||
fn rem_assign(&mut self, rhs: Self) {
|
||||
*self = *self % rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for NumberValue {
|
||||
type Output = NumberValue;
|
||||
|
||||
fn neg(self) -> Self::Output {
|
||||
match self {
|
||||
Self::Int(a) if let Some(b) = a.checked_neg() => Self::Int(b),
|
||||
_ => todo!("TODO promote to bigint"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for NumberValue {
|
||||
fn from(value: i8) -> Self {
|
||||
Self::Int(value.into())
|
||||
}
|
||||
}
|
||||
impl From<i16> for NumberValue {
|
||||
fn from(value: i16) -> Self {
|
||||
Self::Int(value.into())
|
||||
}
|
||||
}
|
||||
impl From<i32> for NumberValue {
|
||||
fn from(value: i32) -> Self {
|
||||
Self::Int(value.into())
|
||||
}
|
||||
}
|
||||
impl From<i64> for NumberValue {
|
||||
fn from(value: i64) -> Self {
|
||||
Self::Int(value)
|
||||
}
|
||||
}
|
||||
impl From<usize> for NumberValue {
|
||||
fn from(value: usize) -> Self {
|
||||
if let Ok(value) = i64::try_from(value) {
|
||||
Self::Int(value)
|
||||
} else {
|
||||
todo!("TODO: promote to bigint")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for i8 {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
let NumberValue::Int(value) = value else {
|
||||
return Err(NumberConvertError);
|
||||
};
|
||||
value.try_into().map_err(From::from)
|
||||
}
|
||||
}
|
||||
impl TryFrom<NumberValue> for i16 {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
let NumberValue::Int(value) = value else {
|
||||
return Err(NumberConvertError);
|
||||
};
|
||||
value.try_into().map_err(From::from)
|
||||
}
|
||||
}
|
||||
impl TryFrom<NumberValue> for i32 {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
let NumberValue::Int(value) = value else {
|
||||
return Err(NumberConvertError);
|
||||
};
|
||||
value.try_into().map_err(From::from)
|
||||
}
|
||||
}
|
||||
impl TryFrom<NumberValue> for i64 {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
let NumberValue::Int(value) = value else {
|
||||
return Err(NumberConvertError);
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<NumberValue> for usize {
|
||||
type Error = NumberConvertError;
|
||||
|
||||
fn try_from(value: NumberValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
NumberValue::Int(value) if let Ok(value) = value.try_into() => Ok(value),
|
||||
_ => Err(NumberConvertError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NumberValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::Int(value) => fmt::Display::fmt(value, f),
|
||||
Self::Float(value) if value.is_infinite() && value.is_sign_negative() => {
|
||||
write!(f, "-#inf")
|
||||
}
|
||||
Self::Float(value) if value.is_infinite() => write!(f, "#inf"),
|
||||
Self::Float(value) if value.is_nan() => write!(f, "#nan"),
|
||||
Self::Float(value) => fmt::Display::fmt(value, f),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
use std::{fmt, ops::Deref, rc::Rc};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct StringValue(Rc<str>);
|
||||
|
||||
impl StringValue {
|
||||
pub fn as_rc_str(&self) -> &Rc<str> {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for StringValue {
|
||||
fn from(value: &str) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for StringValue {
|
||||
fn from(value: String) -> Self {
|
||||
Self(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StringValue {
|
||||
type Target = str;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for StringValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(self.0.as_ref(), f)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,374 @@
|
||||
use std::{
|
||||
cell::{Ref, RefCell},
|
||||
fmt, mem,
|
||||
};
|
||||
|
||||
use crate::vm::value::{NumberValue, Value};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum VectorStorage {
|
||||
I8(Vec<i8>),
|
||||
I16(Vec<i16>),
|
||||
I32(Vec<i32>),
|
||||
I64(Vec<i64>),
|
||||
Any(Vec<Value>),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Vector(RefCell<VectorStorage>);
|
||||
|
||||
impl Vector {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.borrow().is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.borrow().len()
|
||||
}
|
||||
|
||||
pub fn borrow(&self) -> Ref<'_, VectorStorage> {
|
||||
self.0.borrow()
|
||||
}
|
||||
|
||||
pub fn value_at(&self, index: usize) -> Option<Value> {
|
||||
self.0.borrow().value_at(index)
|
||||
}
|
||||
pub fn set_value_at(&self, index: usize, value: Value) {
|
||||
self.0.borrow_mut().set_value_at(index, value);
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self) -> Option<Ref<'_, [u8]>> {
|
||||
Ref::filter_map(self.borrow(), |r| match r {
|
||||
VectorStorage::I8(values) => {
|
||||
Some(unsafe { mem::transmute::<&[i8], &[u8]>(&values[..]) })
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Vector {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&*self.0.borrow(), f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A> FromIterator<A> for Vector
|
||||
where
|
||||
VectorStorage: FromIterator<A>,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self {
|
||||
Self(RefCell::new(VectorStorage::from_iter(iter)))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for Vector {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
let (ptr, len, cap) = value.into_raw_parts();
|
||||
Self::from(unsafe { Vec::from_raw_parts(ptr.cast::<i8>(), len, cap) })
|
||||
}
|
||||
}
|
||||
impl From<Vec<i8>> for Vector {
|
||||
fn from(value: Vec<i8>) -> Self {
|
||||
Vector(RefCell::new(VectorStorage::I8(value)))
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! static_dispatch {
|
||||
([$input:expr] $value:ident => $body:expr) => {
|
||||
match $input {
|
||||
Self::I8($value) => $body,
|
||||
Self::I16($value) => $body,
|
||||
Self::I32($value) => $body,
|
||||
Self::I64($value) => $body,
|
||||
Self::Any($value) => $body,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! dispatch_integer_convert {
|
||||
($self:ident ($storage:ident, $value:ident) => $f:expr) => {
|
||||
match $self {
|
||||
Self::I8($storage) if let Ok($value) = i8::try_from($value) => {
|
||||
$f;
|
||||
}
|
||||
Self::I8($storage) if let Ok($value) = i16::try_from($value) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I16($storage);
|
||||
}
|
||||
Self::I8($storage) if let Ok($value) = i32::try_from($value) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I32($storage);
|
||||
}
|
||||
Self::I8($storage) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I64($storage);
|
||||
}
|
||||
Self::I16($storage) if let Ok($value) = i16::try_from($value) => {
|
||||
$f;
|
||||
}
|
||||
Self::I16($storage) if let Ok($value) = i32::try_from($value) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I32($storage);
|
||||
}
|
||||
Self::I16($storage) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I64($storage);
|
||||
}
|
||||
Self::I32($storage) if let Ok($value) = i32::try_from($value) => {
|
||||
$f;
|
||||
}
|
||||
Self::I32($storage) => {
|
||||
let mut $storage = $storage.iter().copied().map(Into::into).collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::I64($storage);
|
||||
}
|
||||
Self::I64($storage) => {
|
||||
$f;
|
||||
}
|
||||
Self::Any($storage) => {
|
||||
let $value = Value::Number(NumberValue::Int($value));
|
||||
$f;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! dispatch_any_convert {
|
||||
($self:ident ($storage:ident) => $f:expr) => {
|
||||
match $self {
|
||||
Self::I8($storage) => {
|
||||
let mut $storage = $storage
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.map(Value::Number)
|
||||
.collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::Any($storage);
|
||||
}
|
||||
Self::I16($storage) => {
|
||||
let mut $storage = $storage
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.map(Value::Number)
|
||||
.collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::Any($storage);
|
||||
}
|
||||
Self::I32($storage) => {
|
||||
let mut $storage = $storage
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.map(Value::Number)
|
||||
.collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::Any($storage);
|
||||
}
|
||||
Self::I64($storage) => {
|
||||
let mut $storage = $storage
|
||||
.iter()
|
||||
.copied()
|
||||
.map(Into::into)
|
||||
.map(Value::Number)
|
||||
.collect::<Vec<_>>();
|
||||
$f;
|
||||
*$self = Self::Any($storage);
|
||||
}
|
||||
Self::Any($storage) => {
|
||||
$f;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl VectorStorage {
|
||||
fn value_at(&self, index: usize) -> Option<Value> {
|
||||
match self {
|
||||
Self::I8(values) if let Some(value) = values.get(index).copied() => {
|
||||
Some(Value::Number(value.into()))
|
||||
}
|
||||
Self::I16(values) if let Some(value) = values.get(index).copied() => {
|
||||
Some(Value::Number(value.into()))
|
||||
}
|
||||
Self::I32(values) if let Some(value) = values.get(index).copied() => {
|
||||
Some(Value::Number(value.into()))
|
||||
}
|
||||
Self::I64(values) if let Some(value) = values.get(index).copied() => {
|
||||
Some(Value::Number(value.into()))
|
||||
}
|
||||
Self::Any(values) if let Some(value) = values.get(index).cloned() => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
static_dispatch!([self] vec => vec.len())
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
fn push(&mut self, value: Value) {
|
||||
match value {
|
||||
Value::Number(NumberValue::Int(value)) => self.push_integer(value),
|
||||
value => match self {
|
||||
Self::Any(vec) => vec.push(value),
|
||||
_ => self.push_any(value),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn set_value_at(&mut self, index: usize, value: Value) {
|
||||
if index >= self.len() {
|
||||
return;
|
||||
}
|
||||
match value {
|
||||
Value::Number(NumberValue::Int(value)) => self.set_integer(index, value),
|
||||
value => match self {
|
||||
Self::Any(vec) => vec[index] = value,
|
||||
_ => self.set_any(index, value),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn push_any(&mut self, value: Value) {
|
||||
dispatch_any_convert!(self (w) => {
|
||||
w.push(value);
|
||||
});
|
||||
}
|
||||
|
||||
fn set_any(&mut self, index: usize, value: Value) {
|
||||
dispatch_any_convert!(self (w) => {
|
||||
w[index] = value;
|
||||
});
|
||||
}
|
||||
|
||||
fn push_integer(&mut self, value: i64) {
|
||||
dispatch_integer_convert!(self (w, value) => {
|
||||
w.push(value);
|
||||
});
|
||||
}
|
||||
|
||||
fn set_integer(&mut self, index: usize, value: i64) {
|
||||
dispatch_integer_convert!(self (w, value) => {
|
||||
w[index] = value;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Value> for VectorStorage {
|
||||
fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
|
||||
let mut accumulator = Self::I8(vec![]);
|
||||
for item in iter {
|
||||
accumulator.push(item);
|
||||
}
|
||||
accumulator
|
||||
}
|
||||
}
|
||||
|
||||
fn display_vector<T: fmt::Display>(value: &[T], f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for (i, value) in value.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
write!(f, "{value}")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl fmt::Display for VectorStorage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "[")?;
|
||||
match self {
|
||||
Self::I8(vec) => display_vector(vec, f),
|
||||
Self::I16(vec) => display_vector(vec, f),
|
||||
Self::I32(vec) => display_vector(vec, f),
|
||||
Self::I64(vec) => display_vector(vec, f),
|
||||
Self::Any(vec) => display_vector(vec, f),
|
||||
}?;
|
||||
write!(f, "]")
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! impl_primitive_from_iter {
|
||||
($signed_ty:ty, $unsigned_ty:ty => $variant:ident) => {
|
||||
impl FromIterator<$signed_ty> for VectorStorage {
|
||||
fn from_iter<T: IntoIterator<Item = $signed_ty>>(iter: T) -> Self {
|
||||
Self::$variant(iter.into_iter().collect())
|
||||
}
|
||||
}
|
||||
impl FromIterator<$unsigned_ty> for VectorStorage {
|
||||
fn from_iter<T: IntoIterator<Item = $unsigned_ty>>(iter: T) -> Self {
|
||||
Self::$variant(iter.into_iter().map(|x| x as $signed_ty).collect())
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl_primitive_from_iter!(i8, u8 => I8);
|
||||
impl_primitive_from_iter!(i16, u16 => I16);
|
||||
impl_primitive_from_iter!(i32, u32 => I32);
|
||||
impl_primitive_from_iter!(i64, u64 => I64);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::vm::value::{Value, vector::VectorStorage};
|
||||
|
||||
#[test]
|
||||
fn test_vector_storage_from_iter() {
|
||||
let v = VectorStorage::from_iter([1i8, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u8, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i16, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u16, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i32, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u32, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1i64, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
let v = VectorStorage::from_iter([1u64, 2, 3, 4, 5, 6, 7]);
|
||||
assert_eq!(VectorStorage::I64(vec![1, 2, 3, 4, 5, 6, 7]), v);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_vector_unspecialize() {
|
||||
let mut v = VectorStorage::from_iter([1i8, 2, 3]);
|
||||
assert_eq!(VectorStorage::I8(vec![1, 2, 3]), v);
|
||||
v.push(Value::Number(1234.into()));
|
||||
assert_eq!(VectorStorage::I16(vec![1, 2, 3, 1234]), v);
|
||||
v.push(Value::Number(12341234.into()));
|
||||
assert_eq!(VectorStorage::I32(vec![1, 2, 3, 1234, 12341234]), v);
|
||||
v.push(Value::Number(1234123412341234i64.into()));
|
||||
assert_eq!(
|
||||
VectorStorage::I64(vec![1, 2, 3, 1234, 12341234, 1234123412341234]),
|
||||
v
|
||||
);
|
||||
v.push(Value::String("a".into()));
|
||||
assert_eq!(
|
||||
VectorStorage::Any(vec![
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
Value::Number(1234.into()),
|
||||
Value::Number(12341234.into()),
|
||||
Value::Number(1234123412341234i64.into()),
|
||||
Value::String("a".into())
|
||||
]),
|
||||
v
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
use std::{fs, path::Path, rc::Rc};
|
||||
|
||||
use lysp::{
|
||||
error::MachineErrorAt,
|
||||
vm::{env::Environment, machine::Machine, prelude, value::Value},
|
||||
};
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str_in(code: &str, env: &Rc<Environment>) -> Result<Value, MachineErrorAt> {
|
||||
let mut machine = Machine::default();
|
||||
machine.evaluate_str(Default::default(), None, env, code)
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str(code: &str) -> Value {
|
||||
let env = Rc::new(Environment::default());
|
||||
prelude::load(&env);
|
||||
match eval_str_in(code, &env) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
eprintln!("Couldn't evaluate expression:");
|
||||
eprintln!();
|
||||
eprintln!(" {code}");
|
||||
eprintln!();
|
||||
eprintln!(":: {error}");
|
||||
panic!("TEST FAILED");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_str_err(code: &str) -> MachineErrorAt {
|
||||
let env = Rc::new(Environment::default());
|
||||
prelude::load(&env);
|
||||
match eval_str_in(code, &env) {
|
||||
Ok(value) => {
|
||||
eprintln!("Expected the code to fail to evaluate, but it returned success:");
|
||||
eprintln!();
|
||||
eprintln!(" {code}");
|
||||
eprintln!();
|
||||
eprintln!("Returned");
|
||||
eprintln!();
|
||||
eprintln!(":: {value}");
|
||||
panic!("TEST FAILED");
|
||||
}
|
||||
Err(error) => error,
|
||||
}
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn eval_file<P: AsRef<Path>>(path: P) -> Value {
|
||||
let code = fs::read_to_string(path).expect("file read failed");
|
||||
eval_str(&code)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math() {
|
||||
// math
|
||||
assert_eq!(eval_str("(+ 1 2 3)"), Value::Number(6.into()));
|
||||
assert_eq!(eval_str("(- 3 2 1)"), Value::Number(0.into()));
|
||||
assert_eq!(eval_str("(* 2 3 4)"), Value::Number(24.into()));
|
||||
assert_eq!(eval_str("(/ 16 4 2)"), Value::Number(2.into()));
|
||||
assert_eq!(eval_str("(% 35 16)"), Value::Number(3.into()));
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(| 1 2 4)"), Value::Number(7.into()));
|
||||
// assert_eq!(eval_str("(& 1 2 4)"), Value::Number(0.into()));
|
||||
// assert_eq!(eval_str("(& 1 3 7)"), Value::Number(1.into()));
|
||||
// assert_eq!(eval_str("(^ 1 3 8)"), Value::Number(10.into()));
|
||||
// comparison
|
||||
assert_eq!(eval_str("(< 1 2 3 4 5)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(< 1 2 3 3 4 5)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(> 1 2 3 4 5)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(>= 5 5 4 3 2 1)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(> 5 5 4 3 2 1)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(<= 1 2 3 3 3 4)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(/= 1 2 3 4)"), Value::Boolean(true.into()));
|
||||
assert_eq!(eval_str("(/= 1 2 2 4)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(= 1 2 3 4)"), Value::Boolean(false.into()));
|
||||
assert_eq!(eval_str("(= 1 1 1 1)"), Value::Boolean(true.into()));
|
||||
// logic
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(&& 1 0)"), Value::Boolean(false.into()));
|
||||
// assert_eq!(eval_str("(&& #t 1 \"yes\")"), Value::Boolean(true.into()));
|
||||
// assert_eq!(
|
||||
// eval_str("(|| #f NIL \"\" ())"),
|
||||
// Value::Boolean(false.into())
|
||||
// );
|
||||
// assert_eq!(
|
||||
// eval_str("(|| #f '(1 2 3) \"\" ())"),
|
||||
// Value::Boolean(true.into())
|
||||
// );
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_abort() {
|
||||
// let err = eval_str_err("(assert (= 1 (+ 1 1)))");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "<undefined>: assertion failed: (= 1 (+ 1 1))");
|
||||
|
||||
// let err = eval_str_err("(abort 1234)");
|
||||
// let EvalError::Machine(MachineError {
|
||||
// error: MachineErrorKind::Aborted(message),
|
||||
// ..
|
||||
// }) = err
|
||||
// else {
|
||||
// panic!("Invalid error returned")
|
||||
// };
|
||||
// assert_eq!(&message, "1234");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_lambda() {
|
||||
assert_eq!(
|
||||
eval_str("((lambda (x) (+ x 1)) 1)"),
|
||||
Value::Number(2.into())
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("((lambda (x) (+ ((lambda () 2)) 1)) 1)"),
|
||||
Value::Number(3.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_math_behaves_the_same_inside_apply() {
|
||||
assert_eq!(eval_str("(apply + '(1 2 3 4))"), eval_str("(+ 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply * '(1 2 3 4))"), eval_str("(* 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply - '(1 2 3 4))"), eval_str("(- 1 2 3 4)"));
|
||||
assert_eq!(eval_str("(apply / '(100 25 2))"), eval_str("(/ 100 25 2)"));
|
||||
assert_eq!(eval_str("(apply % '(90 37))"), eval_str("(% 90 37)"));
|
||||
// TODO
|
||||
// assert_eq!(eval_str("(apply | '(1 2 4))"), eval_str("(| 1 2 4)"));
|
||||
// assert_eq!(eval_str("(apply & '(1 3 7))"), eval_str("(& 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply ^ '(1 3 7))"), eval_str("(^ 1 3 7)"));
|
||||
// assert_eq!(eval_str("(apply < '(1 2 3 4))"), eval_str("(< 1 2 3 4)"));
|
||||
// assert_eq!(eval_str("(apply > '(1 2 3 4))"), eval_str("(> 1 2 3 4)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_setq() {
|
||||
assert_eq!(eval_str("(setq a 1234) a\n"), Value::Number(1234.into()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_let() {
|
||||
// Should fail
|
||||
eval_str_err("(let (a 1234 b (+ a 1)) NIL)");
|
||||
eval_str_err("(let (a 1234) (let (a 9999 b (+ a 4321)) b))");
|
||||
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234 b 4321) (+ a b))"),
|
||||
Value::Number(5555.into())
|
||||
);
|
||||
assert_eq!(
|
||||
eval_str("(let* (a 1234 b (+ a 4321)) (+ b 4444))"),
|
||||
Value::Number(9999.into())
|
||||
);
|
||||
|
||||
// Nested let
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234) (let (b 4321) (+ a b)))"),
|
||||
Value::Number(5555.into())
|
||||
);
|
||||
// Does shadow
|
||||
assert_eq!(
|
||||
eval_str("(let (a 1234) (let* (a 9999 b (+ a 4321)) b))"),
|
||||
Value::Number(14320.into())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_macro() {
|
||||
assert_eq!(
|
||||
eval_str("(defmacro stringify (x) (cons 'list `,x)) (stringify (1 2 3 4))"),
|
||||
Value::list_or_nil([
|
||||
Value::Number(1.into()),
|
||||
Value::Number(2.into()),
|
||||
Value::Number(3.into()),
|
||||
Value::Number(4.into())
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_examples_work() {
|
||||
const EXCLUDE: &[&str] = &["repl.lysp", "echo.lysp", "io.lysp", "import.lysp"];
|
||||
|
||||
// None of them should crash at least
|
||||
for file in fs::read_dir("examples").unwrap() {
|
||||
let entry = file.unwrap();
|
||||
let filename = entry.file_name();
|
||||
let filename = filename.to_str().unwrap();
|
||||
|
||||
if !filename.ends_with(".lysp") || EXCLUDE.contains(&filename) {
|
||||
continue;
|
||||
}
|
||||
|
||||
eprintln!("Eval {}", entry.path().display());
|
||||
eval_file(entry.path());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/*.log
|
||||
Generated
+614
-211
File diff suppressed because it is too large
Load Diff
@@ -4,16 +4,25 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
authors = ["Mark Poliakov <mark@alnyan.me>"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libterm.workspace = true
|
||||
cross.workspace = true
|
||||
lysp.workspace = true
|
||||
logsink.workspace = true
|
||||
|
||||
thiserror.workspace = true
|
||||
log.workspace = true
|
||||
|
||||
unicode-width = "0.1.11"
|
||||
regex = "1.12.3"
|
||||
|
||||
[target.'cfg(not(target_os = "yggdrasil"))'.dependencies]
|
||||
libc = "0.2.150"
|
||||
crossterm = "0.27.0"
|
||||
syslog = "6.1.0"
|
||||
[features]
|
||||
default = []
|
||||
runtime = []
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
# [target.'cfg(not(target_os = "yggdrasil"))'.dependencies]
|
||||
# libc = "0.2.150"
|
||||
# syslog = "6.1.0"
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
(setq _red/command-table (hash/new))
|
||||
|
||||
;; Command text manipulation
|
||||
(setq _red/current-command nil)
|
||||
|
||||
(defun red/command/append! (text)
|
||||
(setq _red/current-command (+ (if _red/current-command _red/current-command "") text)))
|
||||
(defun red/command/erase-backward! ()
|
||||
(when _red/current-command
|
||||
(setq _red/current-command (string/pop _red/current-command))))
|
||||
(defun red/command/clear! () (setq _red/current-command nil))
|
||||
|
||||
;; Command macros
|
||||
(defun _red/declare-command
|
||||
(command handler)
|
||||
(hash/put! _red/command-table command handler))
|
||||
(defmacro declare-command
|
||||
(command args &rest body)
|
||||
(unless (cons? body)
|
||||
(error "No body provided in declare-command"))
|
||||
(unless (list? args)
|
||||
(error "Argument list must either be a NIL or a list"))
|
||||
(let*
|
||||
(
|
||||
args-has-rest (find (lambda (x) (= x '&rest)) args)
|
||||
lambda-args (if args-has-rest
|
||||
`(,@args)
|
||||
`(,@args &rest _)
|
||||
)
|
||||
)
|
||||
`(_red/declare-command ,command (lambda ,lambda-args ,@body))
|
||||
)
|
||||
)
|
||||
|
||||
;; Command handlers
|
||||
(defun _red/editor-command-hook (command)
|
||||
(when-let
|
||||
(words (filter identity (string/split command)))
|
||||
(let* (command (car words) args (cdr words) entry (hash/get _red/command-table command))
|
||||
(if (nil? entry)
|
||||
(red/message (+ "Unhandled command: :" command))
|
||||
(apply entry args))
|
||||
)))
|
||||
(defun _red/shell-command-hook (command)
|
||||
(setq command (string/trim command))
|
||||
(unless command (return))
|
||||
(let (words (filter identity (string/split command)))
|
||||
(apply red/shell-command words)
|
||||
)
|
||||
)
|
||||
|
||||
;; Root command handler
|
||||
(defun _red/root-command-hook (command)
|
||||
(red/set-top-mode 'normal)
|
||||
(setq command (string/trim command))
|
||||
(unless command (return))
|
||||
(let (shell-command (string/strip-prefix command "!"))
|
||||
(cond
|
||||
((nil? shell-command) (_red/editor-command-hook command))
|
||||
(shell-command (_red/shell-command-hook shell-command))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
;; Command definitions
|
||||
(declare-command "q" () (red/quit))
|
||||
(declare-command "q!" () (red/quit #t))
|
||||
(declare-command
|
||||
"w" (&optional filename)
|
||||
(if filename
|
||||
(red/buffer/write filename)
|
||||
(red/buffer/write)
|
||||
)
|
||||
)
|
||||
(declare-command
|
||||
"wq" ()
|
||||
(red/buffer/write)
|
||||
(red/quit)
|
||||
)
|
||||
(declare-command
|
||||
"e" (&optional filename)
|
||||
(if filename
|
||||
(red/buffer/open filename)
|
||||
)
|
||||
)
|
||||
(declare-command
|
||||
"e!" (&optional filename)
|
||||
(if filename
|
||||
(red/buffer/open filename #t)
|
||||
)
|
||||
)
|
||||
(declare-command
|
||||
"source" (filename)
|
||||
(import filename)
|
||||
)
|
||||
|
||||
;; TODO only allow some keys to be set
|
||||
;; TODO at least check that the key is defined
|
||||
(declare-command
|
||||
"set" (key &rest value)
|
||||
(let* (
|
||||
key-symbol (symbol (+ "red/" key))
|
||||
expression (string/join value)
|
||||
eval-result (if (nil? value) '(ok #t) (read-eval expression))
|
||||
)
|
||||
(cond
|
||||
((result/ok? eval-result) (set key-symbol (cadr eval-result)))
|
||||
(&otherwise (red/message (+ "error: " (cadr eval-result))))
|
||||
)
|
||||
)
|
||||
)
|
||||
(declare-command
|
||||
"unset" (key)
|
||||
(let (key-symbol (symbol (+ "red/" key)))
|
||||
(set key-symbol nil)
|
||||
)
|
||||
)
|
||||
(declare-command
|
||||
"eval" (&rest expressions)
|
||||
(let* (
|
||||
expression (string/join expressions)
|
||||
eval-result (read-eval expression)
|
||||
)
|
||||
(cond
|
||||
((result/ok? eval-result) (red/status (+ "=> " (cadr eval-result))))
|
||||
(&otherwise (red/message (+ "error: " (cadr eval-result))))
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,21 @@
|
||||
;; External API
|
||||
|
||||
(import "util.lysp")
|
||||
(import "message.lysp")
|
||||
|
||||
;; Bind the hooks
|
||||
(import "render.lysp")
|
||||
(red/bind-post-render-hook _red/root-post-render-hook)
|
||||
|
||||
;; Child modules
|
||||
(import "keyboard.lysp")
|
||||
(red/bind-key-hook _red/root-key-hook)
|
||||
|
||||
(import "command.lysp")
|
||||
(import "highlight.lysp")
|
||||
|
||||
;; User configuration
|
||||
(try-import "/etc/red/init.lysp")
|
||||
(try-import (+ (fs/home-directory) "/.red.d/init.lysp"))
|
||||
|
||||
(_red/update-render-params)
|
||||
@@ -0,0 +1,169 @@
|
||||
;; TODO
|
||||
(defun _map (f xs)
|
||||
(if (nil? xs)
|
||||
nil
|
||||
(cons (f (car xs)) (_map f (cdr xs))))
|
||||
)
|
||||
|
||||
(defun _red/syntax-make-keyword-rule (syntax pattern category prev-state next-state)
|
||||
`(red/syntax/define-keyword-rule ,syntax ,prev-state ,pattern ,category ,next-state))
|
||||
(defun _red/syntax-rule-keyword (syntax clause)
|
||||
;; (:keyword "PATTERNS"... :category "CATEGORY" :prev-state N :next-state M)
|
||||
(let (
|
||||
prev-state 0
|
||||
next-state nil
|
||||
patterns nil
|
||||
category nil
|
||||
)
|
||||
(while (not (nil? clause))
|
||||
(cond
|
||||
; :prev-state N
|
||||
((= (car clause) ':prev-state)
|
||||
(progn
|
||||
(setq prev-state (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; :next-state N
|
||||
((= (car clause) ':next-state)
|
||||
(progn
|
||||
(setq next-state (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; :category "CATEGORY"
|
||||
((= (car clause) ':category)
|
||||
(progn
|
||||
(setq category (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; "PATTERNS"...
|
||||
(&otherwise (setq patterns (cons (car clause) patterns)))
|
||||
)
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
|
||||
; (when (or (nil? category) (nil? patterns))
|
||||
; (error "Invalid clause")
|
||||
; )
|
||||
|
||||
(cons
|
||||
'progn
|
||||
(_map
|
||||
(lambda (pattern) (_red/syntax-make-keyword-rule syntax pattern category prev-state next-state))
|
||||
patterns
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
(defun _red/syntax-rule-regex (syntax clause)
|
||||
;; (:regex "REGEX" "CATEGORY" :prev-state N :next-state M)
|
||||
(let (
|
||||
prev-state 0
|
||||
next-state nil
|
||||
regex (car clause)
|
||||
category (cadr clause)
|
||||
)
|
||||
(setq clause (cdr (cdr clause)))
|
||||
(while (not (nil? clause))
|
||||
(cond
|
||||
; :prev-state N
|
||||
((= (car clause) ':prev-state)
|
||||
(progn
|
||||
(setq prev-state (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; :next-state M
|
||||
((= (car clause) ':next-state)
|
||||
(progn
|
||||
(setq next-state (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
)
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
`(red/syntax/define-regex-rule ,syntax ,prev-state ,regex ,category ,next-state)
|
||||
)
|
||||
)
|
||||
(defun _red/syntax-style-category (syntax clause)
|
||||
;; ("CATEGORY" :foreground 'COLOR :background 'COLOR :bold)
|
||||
(let (category (car clause) foreground nil background nil bold nil)
|
||||
(setq clause (cdr clause))
|
||||
(while (not (nil? clause))
|
||||
(cond
|
||||
; :foreground 'COLOR
|
||||
((or (= (car clause) ':foreground) (= (car clause) ':fg))
|
||||
(progn
|
||||
(setq foreground (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; :background 'COLOR
|
||||
((or (= (car clause) ':background) (= (car clause) ':bg))
|
||||
(progn
|
||||
(setq background (cadr clause))
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
)
|
||||
; :bold
|
||||
((= (car clause) ':bold) (setq bold #t))
|
||||
)
|
||||
(setq clause (cdr clause))
|
||||
)
|
||||
(when (not (or foreground background bold))
|
||||
(error "Empty clause")
|
||||
)
|
||||
`(red/syntax/define-category-style ,syntax ,category ,foreground ,background ,bold)
|
||||
)
|
||||
)
|
||||
|
||||
(defun _red/define-syntax (syntax clauses)
|
||||
(let (output nil current-state nil)
|
||||
(while (cons? clauses)
|
||||
(let (clause (car clauses))
|
||||
(cond
|
||||
((= clause ':styles) (setq current-state 'styles))
|
||||
((= clause ':rules) (setq current-state 'rules))
|
||||
;; ("style" ...) clauses
|
||||
((= current-state 'styles) (setq output (cons (_red/syntax-style-category syntax clause) output)))
|
||||
;; (:regex ...) clauses
|
||||
((and
|
||||
(= current-state 'rules)
|
||||
(cons? clause)
|
||||
(= (car clause) ':regex)
|
||||
(cons? (cdr clause))
|
||||
)
|
||||
(setq output (cons (_red/syntax-rule-regex syntax (cdr clause)) output))
|
||||
)
|
||||
;; (:keyword ...) clauses
|
||||
((and
|
||||
(= current-state 'rules)
|
||||
(cons? clause)
|
||||
(= (car clause) ':keyword)
|
||||
(cons? (cdr clause))
|
||||
)
|
||||
(setq output (cons (_red/syntax-rule-keyword syntax (cdr clause)) output))
|
||||
)
|
||||
)
|
||||
)
|
||||
(setq clauses (cdr clauses))
|
||||
)
|
||||
(if (nil? output)
|
||||
nil
|
||||
(cons 'progn output)
|
||||
)
|
||||
)
|
||||
)
|
||||
(defmacro define-syntax (syntax &rest clauses)
|
||||
(_red/define-syntax syntax clauses)
|
||||
)
|
||||
|
||||
;; Load syntax files
|
||||
;; TODO glob
|
||||
(import "syntax/lysp.lysp")
|
||||
(import "syntax/rust.lysp")
|
||||
|
||||
(red/syntax/reset)
|
||||
@@ -0,0 +1,158 @@
|
||||
;; Key sequence handling
|
||||
(setq _red/key-sequence nil)
|
||||
|
||||
(defun _red/push-key-seq
|
||||
(key)
|
||||
(setq _red/key-sequence (append _red/key-sequence (list key))))
|
||||
(defun _red/reset-key-seq
|
||||
()
|
||||
(setq _red/key-sequence nil))
|
||||
|
||||
;; Key map handling
|
||||
(setq _red/keymaps (hash/new))
|
||||
(setq _red/key-fallbacks (hash/new))
|
||||
(hash/put! _red/keymaps 'normal (red/keymap/new))
|
||||
(hash/put! _red/keymaps 'insert (red/keymap/new))
|
||||
(hash/put! _red/keymaps 'command (red/keymap/new))
|
||||
|
||||
(defun _red/declare-key
|
||||
(mode seq action)
|
||||
(when (not (hash/has? _red/keymaps mode))
|
||||
(hash/put! _red/keymaps mode (red/keymap/new))
|
||||
)
|
||||
(hash/update!
|
||||
(lambda (kmap)
|
||||
(red/keymap/put! kmap seq action)
|
||||
kmap)
|
||||
_red/keymaps
|
||||
mode
|
||||
)
|
||||
)
|
||||
(defun _red/declare-key-fallback
|
||||
(mode fallback)
|
||||
(hash/put! _red/key-fallbacks mode fallback)
|
||||
)
|
||||
|
||||
(defun _red/lookup-key
|
||||
(mode seq)
|
||||
(let (kmap (hash/get _red/keymaps mode))
|
||||
(if (not (nil? kmap))
|
||||
(red/keymap/get kmap seq)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro declare-keys
|
||||
(buffer-mode &rest clauses)
|
||||
;; (declare-keys
|
||||
;; normal
|
||||
;; ('k ...)
|
||||
;; ('h ...)
|
||||
;; :fallback
|
||||
;; (keyseq)
|
||||
;; ...
|
||||
;; )
|
||||
(let (output nil)
|
||||
(while (not (nil? clauses))
|
||||
(let (clause (car clauses))
|
||||
(cond
|
||||
((cons? clause)
|
||||
(let (key-seq (car clause) key-action (cdr clause))
|
||||
(when (nil? key-action)
|
||||
(error "Expected at least one expression after the key sequence")
|
||||
)
|
||||
(setq output (cons
|
||||
`(_red/declare-key (quote ,buffer-mode) ,key-seq (lambda () ,@key-action))
|
||||
output
|
||||
))
|
||||
)
|
||||
)
|
||||
;; Rest of clauses are fallback args + body
|
||||
((= clause ':fallback)
|
||||
(progn
|
||||
(setq
|
||||
output
|
||||
(cons
|
||||
`(_red/declare-key-fallback (quote ,buffer-mode) (lambda ,@(cdr clauses)))
|
||||
output)
|
||||
)
|
||||
(break)
|
||||
)
|
||||
)
|
||||
(&otherwise (error "Unexpected clause:" clause))
|
||||
)
|
||||
)
|
||||
(setq clauses (cdr clauses))
|
||||
)
|
||||
(when (nil? output)
|
||||
(error "No key sequence and no fallback action defined in the declare-keys invocation")
|
||||
)
|
||||
(cons 'progn output)
|
||||
)
|
||||
)
|
||||
|
||||
;; Key input handling
|
||||
|
||||
(defun _red/root-mapped-key-hook
|
||||
(mode key)
|
||||
;; 1. Push key into current sequence
|
||||
;; 2. Lookup current sequence in relevant key map
|
||||
;; 3.1. If present and leaf, reset current sequence and invoke the handler
|
||||
;; 3.2. If present and non-leaf, continue collecting the sequence
|
||||
;; 3.3. If non-present, reset the sequence
|
||||
(_red/push-key-seq key)
|
||||
(let (node (_red/lookup-key mode _red/key-sequence))
|
||||
(cond
|
||||
;; TODO invoke general fallback handler for the buffer-mode
|
||||
((nil? node)
|
||||
(progn
|
||||
(let (fallback (hash/get _red/key-fallbacks mode))
|
||||
(unless (nil? fallback)
|
||||
(fallback _red/key-sequence))
|
||||
)
|
||||
(_red/reset-key-seq)
|
||||
))
|
||||
((= (car node) 'prefix) nil)
|
||||
((= (car node) 'leaf)
|
||||
(progn
|
||||
(_red/reset-key-seq)
|
||||
((cadr node))
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(defun _red/root-key-hook
|
||||
(top-mode buffer-mode key)
|
||||
(cond
|
||||
((= top-mode 'command) (_red/root-mapped-key-hook 'command key))
|
||||
((= top-mode 'normal) (_red/root-mapped-key-hook buffer-mode key))
|
||||
(&otherwise (eprint "Unhandled" (list '_red/root-key-hook top-mode buffer-mode key)))
|
||||
)
|
||||
)
|
||||
|
||||
;; Helpers
|
||||
(defun red/as-insertable-key-seq
|
||||
(key-seq)
|
||||
(if (nil? (cdr key-seq))
|
||||
(let* (key (car key-seq) key-str (->string key))
|
||||
(cond
|
||||
((= key 'space) " ")
|
||||
((= key 'tab) "\t")
|
||||
((and
|
||||
(= (string/length key-str) 1)
|
||||
(or
|
||||
(not (string/ascii? key-str))
|
||||
(string/ascii-graphic? key-str)
|
||||
)
|
||||
)
|
||||
key-str
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(import "keymap/command.lysp")
|
||||
(import "keymap/insert.lysp")
|
||||
(import "keymap/normal.lysp")
|
||||
@@ -0,0 +1,24 @@
|
||||
(declare-keys
|
||||
command
|
||||
('escape (red/set-top-mode 'normal))
|
||||
('backspace
|
||||
(if _red/current-command
|
||||
(red/command/erase-backward!)
|
||||
(red/set-top-mode 'normal)
|
||||
))
|
||||
('newline
|
||||
(let (command _red/current-command)
|
||||
(red/command/clear!)
|
||||
(_red/root-command-hook command)
|
||||
)
|
||||
)
|
||||
:fallback
|
||||
(key-seq)
|
||||
(_red/command-mode-handler key-seq)
|
||||
)
|
||||
|
||||
(defun _red/command-mode-handler
|
||||
(key-seq)
|
||||
(let (insertable (red/as-insertable-key-seq key-seq))
|
||||
(unless (nil? insertable)
|
||||
(red/command/append! insertable))))
|
||||
@@ -0,0 +1,25 @@
|
||||
(declare-keys
|
||||
insert
|
||||
('left (red/buffer/move 'prev-char))
|
||||
('right (red/buffer/move 'next-char))
|
||||
('up (red/buffer/move 'prev-line))
|
||||
('down (red/buffer/move 'next-line))
|
||||
('newline
|
||||
(red/buffer/insert-line-after #T)
|
||||
(red/buffer/move 'next-line)
|
||||
(red/buffer/move 'line-start))
|
||||
('escape (red/buffer/set-mode 'normal))
|
||||
('backspace (red/buffer/erase-backward))
|
||||
:fallback
|
||||
(key-seq)
|
||||
(_red/insert-mode-handler key-seq)
|
||||
)
|
||||
|
||||
(defun _red/insert-mode-handler
|
||||
(key-seq)
|
||||
(let (insertable (red/as-insertable-key-seq key-seq))
|
||||
(unless (nil? insertable)
|
||||
(red/buffer/write-text insertable)
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,58 @@
|
||||
(declare-keys
|
||||
normal
|
||||
(':
|
||||
(red/clear-status)
|
||||
(red/clear-message)
|
||||
(red/set-top-mode 'command)
|
||||
)
|
||||
('i (red/buffer/set-mode 'insert-before))
|
||||
('a (red/buffer/set-mode 'insert-after))
|
||||
('I
|
||||
(red/buffer/move 'line-start)
|
||||
(red/buffer/set-mode 'insert-before)
|
||||
)
|
||||
('A
|
||||
(red/buffer/move 'line-end)
|
||||
(red/buffer/set-mode 'insert-after)
|
||||
)
|
||||
|
||||
('(g g) (red/buffer/move 'first-line))
|
||||
('G (red/buffer/move 'last-line))
|
||||
('h (red/buffer/move 'prev-char))
|
||||
('l (red/buffer/move 'next-char))
|
||||
('k (red/buffer/move 'prev-line))
|
||||
('j (red/buffer/move 'next-line))
|
||||
('K (red/buffer/move 'prev-page))
|
||||
('J (red/buffer/move 'next-page))
|
||||
('newline
|
||||
(red/buffer/move 'next-line)
|
||||
(red/buffer/move 'line-start))
|
||||
('left (red/buffer/move 'prev-char))
|
||||
('right (red/buffer/move 'next-char))
|
||||
('up (red/buffer/move 'prev-line))
|
||||
('down (red/buffer/move 'next-line))
|
||||
|
||||
('(F F F F F) nil)
|
||||
|
||||
('o
|
||||
(red/buffer/insert-line-after)
|
||||
(red/buffer/move 'next-line)
|
||||
(red/buffer/set-mode 'insert-before)
|
||||
)
|
||||
('O
|
||||
(red/buffer/insert-line-before)
|
||||
(red/buffer/set-mode 'insert-before)
|
||||
)
|
||||
|
||||
('(d d)
|
||||
(red/buffer/kill-line)
|
||||
(red/buffer/move 'prev-line))
|
||||
|
||||
('(z z)
|
||||
(when (red/buffer/path)
|
||||
(red/buffer/write)
|
||||
)
|
||||
(red/quit)
|
||||
)
|
||||
('(Z Z) (red/quit #T))
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
(setq _red/current-message nil)
|
||||
(setq _red/current-status nil)
|
||||
|
||||
(defun _red/set-message (var args)
|
||||
(let
|
||||
(value (if (nil? args)
|
||||
nil
|
||||
(string/join (map ->string args))))
|
||||
(set var value)))
|
||||
|
||||
(defun red/message (&rest args) (_red/set-message '_red/current-message args))
|
||||
(defun red/status (&rest args) (_red/set-message '_red/current-status args))
|
||||
|
||||
(defun red/clear-message () (setq _red/current-message nil))
|
||||
(defun red/clear-status () (setq _red/current-status nil))
|
||||
@@ -0,0 +1,112 @@
|
||||
;; Drawing options
|
||||
|
||||
(setq red/render/status-line #T)
|
||||
(setq red/render/status-line/key-sequence #T)
|
||||
|
||||
(setq red/render/mode-line #T)
|
||||
(setq red/render/mode-line/arrow #T)
|
||||
|
||||
;; Drawing functions
|
||||
|
||||
(defun _red/colorize-mode
|
||||
(mode)
|
||||
(cond
|
||||
((= mode 'normal) '(black cyan))
|
||||
((= mode 'insert) '(black yellow))
|
||||
((= mode 'command) '(black green))
|
||||
(&otherwise nil)))
|
||||
|
||||
(defun _red/render-mode-line
|
||||
(row mode name modified)
|
||||
(unless red/render/mode-line (return))
|
||||
(term/set-cursor row 0)
|
||||
(let (color (_red/colorize-mode mode))
|
||||
(when color
|
||||
(when (car color)
|
||||
(term/fg-color (car color)))
|
||||
(when (cadr color)
|
||||
(term/bg-color (cadr color))
|
||||
)
|
||||
)
|
||||
(term/write (+ " " (string/to-upper (->string mode)) " "))
|
||||
;; Invert colors and draw the arrow
|
||||
(when color
|
||||
(when (and red/render/mode-line/arrow (cadr color))
|
||||
(term/fg-color (cadr color))
|
||||
(term/bg-color 'black)
|
||||
(term/write "🭬"))
|
||||
(term/reset)
|
||||
)
|
||||
)
|
||||
;; Render bufer name
|
||||
(when name
|
||||
(term/write (+ " " name)))
|
||||
(when modified
|
||||
(term/write " [+]"))
|
||||
)
|
||||
|
||||
(defun _red/render-status-line
|
||||
(row)
|
||||
(let (message (cond
|
||||
(_red/current-message (list _red/current-message 'red))
|
||||
(_red/current-status (list _red/current-status 'white)))
|
||||
)
|
||||
(unless message (return))
|
||||
(term/set-cursor row 0)
|
||||
(when (cadr message)
|
||||
(term/fg-color (cadr message)))
|
||||
(term/write (car message))))
|
||||
|
||||
(defun _red/render-key-line
|
||||
(row width)
|
||||
(let (keys (string/join (map ->string _red/key-sequence) "+"))
|
||||
(unless keys (return))
|
||||
(term/set-cursor row (- width (+ (string/length keys) 1)))
|
||||
(term/write keys)
|
||||
))
|
||||
|
||||
(defun _red/render-command (row command)
|
||||
(term/set-cursor row 0)
|
||||
(term/write ":")
|
||||
(when command
|
||||
(term/write command)))
|
||||
|
||||
(defun _red/render-bottom-bar
|
||||
(width height)
|
||||
(let* (mode (red/buffer/mode) top-mode (car mode) buffer-mode (cadr mode))
|
||||
(when (= top-mode 'command) (setq buffer-mode 'command))
|
||||
|
||||
;; Display mode line
|
||||
(_red/render-mode-line
|
||||
(- height _red/mode-line-offset)
|
||||
buffer-mode
|
||||
(red/buffer/name)
|
||||
(red/buffer/modified?))
|
||||
|
||||
(when (= top-mode 'normal)
|
||||
;; Status text
|
||||
(_red/render-status-line (- height _red/status-line-offset))
|
||||
;; Current key sequence
|
||||
(_red/render-key-line (- height _red/status-line-offset) width)
|
||||
)
|
||||
(when (= top-mode 'command)
|
||||
;; Render command
|
||||
(_red/render-command (- height _red/status-line-offset) _red/current-command))
|
||||
)
|
||||
)
|
||||
|
||||
(defun _red/root-post-render-hook (width height)
|
||||
(_red/render-bottom-bar width height)
|
||||
(term/reset)
|
||||
;; If not in command mode, set cursor to buffer
|
||||
(when (= 'normal (car (red/buffer/mode)))
|
||||
(red/cursor-to-buffer))
|
||||
)
|
||||
|
||||
;; command line + status line
|
||||
(defun _red/update-render-params ()
|
||||
(setq _red/status-line-offset 1)
|
||||
(setq _red/mode-line-offset 2)
|
||||
(setq red/bottom-margin (+ (and red/render/mode-line 1) 1)))
|
||||
|
||||
(_red/update-render-params)
|
||||
@@ -0,0 +1,45 @@
|
||||
;; Test expression for syntax highlight
|
||||
(ignore (let print 1234 0x1234 "a string" empty-string "" after-empty-string #t #F))
|
||||
|
||||
(define-syntax
|
||||
"lysp"
|
||||
:styles
|
||||
("keyword" :fg 'red)
|
||||
("symbol" :fg 'cyan)
|
||||
("comment" :fg 'white :bold)
|
||||
("string" :fg 'yellow)
|
||||
("custom-syntax" :fg 'red :bold)
|
||||
("number" :fg 'cyan :bold)
|
||||
("constant" :fg 'cyan :bold)
|
||||
:rules
|
||||
;; keywords
|
||||
(:keyword
|
||||
"let" "let*" "progn" "defun" "defmacro" "setq"
|
||||
"when" "unless" "if" "while" "loop" "&optional"
|
||||
"&rest" "&otherwise" "cond" "nil" "lambda" "ignore"
|
||||
:category "keyword"
|
||||
)
|
||||
;; prelude functions
|
||||
(:keyword
|
||||
"car" "cdr" "caar" "cadr" "cdar" "cddr" "cadar"
|
||||
"caddr" "list?" "nil?" "cons?" "string?" "symbol?"
|
||||
"cons" "list" "print" "not" "or" "and" "=" ">="
|
||||
"<=" "/=" "error" "import" "apply" "find"
|
||||
:category "symbol"
|
||||
)
|
||||
(:keyword
|
||||
"#t" "#T" "#f" "#F"
|
||||
:category "constant"
|
||||
)
|
||||
;; strings
|
||||
(:regex "." "string" :prev-state 1)
|
||||
(:regex "[^\\\\]\"" "string" :prev-state 1 :next-state 0)
|
||||
(:regex "\"" "string" :next-state 1)
|
||||
(:regex "\"\"" "string")
|
||||
;; :keywords
|
||||
(:regex ":[\\-\\w]+" "custom-syntax")
|
||||
;; numbers
|
||||
(:regex "(0x|0o|0b)?\\d+" "number")
|
||||
;; comments
|
||||
(:regex ";.*$" "comment")
|
||||
)
|
||||
@@ -0,0 +1,37 @@
|
||||
;; TODO very WIP
|
||||
(define-syntax
|
||||
"rust"
|
||||
:styles
|
||||
("keyword" :fg 'red)
|
||||
("symbol" :fg 'cyan)
|
||||
("comment" :fg 'white :bold)
|
||||
("string" :fg 'yellow)
|
||||
("number" :fg 'cyan :bold)
|
||||
("constant" :fg 'cyan :bold)
|
||||
:rules
|
||||
;; Keywords
|
||||
(:keyword
|
||||
"use" "pub" "mod" "enum" "struct" "impl"
|
||||
"for" "=>" "->" "match" "for" "while" "loop"
|
||||
"if" "fn" "&mut" "&" "let" "where" "?"
|
||||
:category "keyword"
|
||||
)
|
||||
;; Identifiers
|
||||
(:keyword
|
||||
"Rc" "RefCell" "Clone" "Copy" "PartialEq" "Eq"
|
||||
"PartialOrd" "Ord" "Debug" "Result" "Option"
|
||||
"bool" "i8" "i16" "i32" "i64" "i128" "u8" "u16"
|
||||
"u32" "u64" "u128" "str" "&str"
|
||||
"Self" "self" "&self" "&mut self" "Ok" "Err"
|
||||
:category "symbol"
|
||||
)
|
||||
(:keyword "true" "false" :category "constant")
|
||||
;; strings
|
||||
(:regex "." "string" :prev-state 1)
|
||||
(:regex "[^\\\\]*\"" "string" :prev-state 1 :next-state 0)
|
||||
(:regex "\"" "string" :next-state 1)
|
||||
;; numbers
|
||||
(:regex "(0x|0o|0b)?\\d+" "number")
|
||||
;; Comments
|
||||
(:regex "//.*$" "comment")
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
;; TODO prelude
|
||||
(defun string/join (xs &optional separator)
|
||||
(when (nil? separator)
|
||||
(setq separator " "))
|
||||
(let (accumulator "")
|
||||
(while (cons? xs)
|
||||
(if accumulator
|
||||
(setq accumulator (+ accumulator separator (car xs)))
|
||||
(setq accumulator (car xs))
|
||||
)
|
||||
(setq xs (cdr xs))
|
||||
)
|
||||
accumulator
|
||||
)
|
||||
)
|
||||
|
||||
(defmacro ignore (&rest expressions) nil)
|
||||
|
||||
(defun try-import (path)
|
||||
(when (fs/file? path) (import path) #t))
|
||||
@@ -0,0 +1,104 @@
|
||||
use std::{
|
||||
fmt,
|
||||
ops::Index,
|
||||
slice::{self, SliceIndex},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
highlight::LineHighlight,
|
||||
text::{Span, Text, TextLike, TextLikeMut},
|
||||
};
|
||||
|
||||
pub struct Line {
|
||||
pub text: Text,
|
||||
pub highlight: LineHighlight,
|
||||
pub highlight_dirty: bool,
|
||||
}
|
||||
|
||||
impl Line {
|
||||
pub fn new() -> Self {
|
||||
Self::from(Text::new())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.text.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.text.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl TextLike for Line {
|
||||
type Iter<'a>
|
||||
= slice::Iter<'a, char>
|
||||
where
|
||||
Self: 'a;
|
||||
type Span<'a>
|
||||
= Span<'a>
|
||||
where
|
||||
Self: 'a;
|
||||
|
||||
#[inline]
|
||||
fn display_width(&self, tab_width: usize) -> usize {
|
||||
self.text.display_width(tab_width)
|
||||
}
|
||||
#[inline]
|
||||
fn iter(&self) -> Self::Iter<'_> {
|
||||
self.text.iter()
|
||||
}
|
||||
#[inline]
|
||||
fn span<R: SliceIndex<[char], Output = [char]>>(&self, range: R) -> Self::Span<'_> {
|
||||
self.text.span(range)
|
||||
}
|
||||
#[inline]
|
||||
fn skip_to_width(&self, offset: usize, tab_width: usize) -> (Self::Span<'_>, usize, usize) {
|
||||
self.text.skip_to_width(offset, tab_width)
|
||||
}
|
||||
}
|
||||
|
||||
impl TextLikeMut for Line {
|
||||
fn split_off(&mut self, at: usize) -> Self {
|
||||
let tail = self.text.split_off(at);
|
||||
self.highlight_dirty = true;
|
||||
Self::from(tail)
|
||||
}
|
||||
fn extend(&mut self, other: Self) {
|
||||
self.text.extend(other.text);
|
||||
self.highlight_dirty = true;
|
||||
}
|
||||
fn remove(&mut self, at: usize) {
|
||||
self.text.remove(at);
|
||||
self.highlight_dirty = true;
|
||||
}
|
||||
fn insert(&mut self, at: usize, ch: char) {
|
||||
self.text.insert(at, ch);
|
||||
self.highlight_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Text> for Line {
|
||||
fn from(text: Text) -> Self {
|
||||
Self {
|
||||
text,
|
||||
highlight: LineHighlight::default(),
|
||||
highlight_dirty: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Line {
|
||||
type Output = char;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.text[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Line {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.text, f)
|
||||
}
|
||||
}
|
||||
@@ -4,17 +4,23 @@ use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader, BufWriter, Write as IoWrite},
|
||||
path::{Path, PathBuf},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use libterm::{Color, CursorStyle, Term};
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
buffer::{line::Line, style::Style},
|
||||
config::EditorConfig,
|
||||
error::Error,
|
||||
line::{Line, TextLike},
|
||||
highlight::Highlighter,
|
||||
text::{Text, TextLike, TextLikeMut},
|
||||
};
|
||||
|
||||
pub mod line;
|
||||
pub mod style;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct View {
|
||||
cursor_column: usize,
|
||||
@@ -47,6 +53,7 @@ pub struct Buffer {
|
||||
name: Option<String>,
|
||||
path: Option<PathBuf>,
|
||||
modified: bool,
|
||||
filetype: Rc<str>,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
@@ -69,7 +76,7 @@ impl View {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_column(&mut self, config: &Config, col: usize, line: Option<&Line>) {
|
||||
pub fn set_column(&mut self, config: &EditorConfig, col: usize, line: Option<&Line>) {
|
||||
let Some(line) = line else {
|
||||
self.column_offset = 0;
|
||||
self.cursor_column = 0;
|
||||
@@ -100,14 +107,6 @@ impl View {
|
||||
self.cursor_row = 0;
|
||||
self.cursor_column = 0;
|
||||
}
|
||||
|
||||
// pub fn resize(&mut self, width: usize, height: usize) {
|
||||
// self.width = width;
|
||||
// self.height = height;
|
||||
|
||||
// self.column_offset = 0;
|
||||
// self.row_offset = 0;
|
||||
// }
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
@@ -121,6 +120,7 @@ impl Buffer {
|
||||
name: None,
|
||||
path: None,
|
||||
modified: false,
|
||||
filetype: "text".into(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,7 +129,8 @@ impl Buffer {
|
||||
let lines = input.lines().collect::<Result<Vec<_>, _>>()?;
|
||||
let lines = lines
|
||||
.into_iter()
|
||||
.map(|line| Line::from_str(line.trim_end_matches('\n')))
|
||||
.map(|line| Text::from_str(line.trim_end_matches('\n')))
|
||||
.map(Line::from)
|
||||
.collect();
|
||||
Ok(lines)
|
||||
}
|
||||
@@ -143,7 +144,7 @@ impl Buffer {
|
||||
vec![]
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
let mut this = Self {
|
||||
lines,
|
||||
name,
|
||||
path: Some(path.into()),
|
||||
@@ -152,7 +153,13 @@ impl Buffer {
|
||||
mode_dirty: true,
|
||||
view: View::default(),
|
||||
modified: false,
|
||||
})
|
||||
filetype: "text".into(),
|
||||
};
|
||||
|
||||
this.update_filetype();
|
||||
this.reset_highlight();
|
||||
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
pub fn reopen<P: AsRef<Path>>(&mut self, path: P) -> io::Result<()> {
|
||||
@@ -173,10 +180,34 @@ impl Buffer {
|
||||
|
||||
self.path = Some(path.into());
|
||||
self.name = name;
|
||||
self.update_filetype();
|
||||
self.reset_highlight();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_filetype(&mut self) {
|
||||
let extension = self
|
||||
.path
|
||||
.as_ref()
|
||||
.and_then(|path| path.extension())
|
||||
.and_then(|ext| ext.to_str());
|
||||
|
||||
let filetype = match extension {
|
||||
Some("lysp") => "lysp",
|
||||
Some("rs") => "rust",
|
||||
_ => "text",
|
||||
};
|
||||
|
||||
self.filetype = filetype.into();
|
||||
}
|
||||
|
||||
pub fn reset_highlight(&mut self) {
|
||||
for line in self.lines.iter_mut() {
|
||||
line.highlight_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn save(&mut self) -> Result<(), Error> {
|
||||
let path = self.path.as_ref().ok_or(Error::NoPath)?;
|
||||
let mut writer = BufWriter::new(File::create(path).map_err(Error::WriteError)?);
|
||||
@@ -201,7 +232,7 @@ impl Buffer {
|
||||
self.name = name;
|
||||
}
|
||||
|
||||
pub fn set_mode(&mut self, config: &Config, mode: SetMode) {
|
||||
pub fn set_mode(&mut self, config: &EditorConfig, mode: SetMode) {
|
||||
let dst_mode = match mode {
|
||||
SetMode::Normal => Mode::Normal,
|
||||
SetMode::InsertAfter | SetMode::InsertBefore => Mode::Insert,
|
||||
@@ -220,6 +251,10 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn filetype(&self) -> &str {
|
||||
&self.filetype
|
||||
}
|
||||
|
||||
pub fn mode(&self) -> Mode {
|
||||
self.mode
|
||||
}
|
||||
@@ -248,7 +283,7 @@ impl Buffer {
|
||||
self.view.cursor_row
|
||||
}
|
||||
|
||||
pub fn set_position(&mut self, config: &Config, px: usize, py: usize) {
|
||||
pub fn set_position(&mut self, config: &EditorConfig, px: usize, py: usize) {
|
||||
self.dirty = true;
|
||||
|
||||
if self.lines.is_empty() {
|
||||
@@ -275,7 +310,7 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_line_end(&mut self, config: &Config) {
|
||||
pub fn to_line_end(&mut self, config: &EditorConfig) {
|
||||
let len = self
|
||||
.lines
|
||||
.get(self.view.cursor_row)
|
||||
@@ -285,7 +320,7 @@ impl Buffer {
|
||||
self.set_position(config, len, self.view.cursor_row);
|
||||
}
|
||||
|
||||
pub fn to_first_line(&mut self, config: &Config) {
|
||||
pub fn to_first_line(&mut self, config: &EditorConfig) {
|
||||
let len = self
|
||||
.lines
|
||||
.get(self.view.cursor_row)
|
||||
@@ -295,7 +330,7 @@ impl Buffer {
|
||||
self.set_position(config, len, 0);
|
||||
}
|
||||
|
||||
pub fn to_last_line(&mut self, config: &Config) {
|
||||
pub fn to_last_line(&mut self, config: &EditorConfig) {
|
||||
if self.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -309,18 +344,18 @@ impl Buffer {
|
||||
self.set_position(config, len, self.lines.len() - 1);
|
||||
}
|
||||
|
||||
pub fn set_column(&mut self, config: &Config, x: usize) {
|
||||
pub fn set_column(&mut self, config: &EditorConfig, x: usize) {
|
||||
self.set_position(config, x, self.view.cursor_row);
|
||||
}
|
||||
|
||||
pub fn move_cursor(&mut self, config: &Config, dx: isize, dy: isize) {
|
||||
pub fn move_cursor(&mut self, config: &EditorConfig, dx: isize, dy: isize) {
|
||||
let px = (self.view.cursor_column as isize + dx).max(0) as usize;
|
||||
let py = (self.view.cursor_row as isize + dy).max(0) as usize;
|
||||
|
||||
self.set_position(config, px, py);
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, config: &Config, offset_x: usize, width: usize, height: usize) {
|
||||
pub fn resize(&mut self, config: &EditorConfig, offset_x: usize, width: usize, height: usize) {
|
||||
self.dirty = true;
|
||||
self.view.height = height;
|
||||
self.view.width = width;
|
||||
@@ -334,7 +369,7 @@ impl Buffer {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn display_cursor(&self, config: &Config) -> (usize, usize) {
|
||||
pub fn display_cursor(&self, config: &EditorConfig) -> (usize, usize) {
|
||||
if self.lines.is_empty() {
|
||||
return (0, 0);
|
||||
}
|
||||
@@ -374,23 +409,56 @@ impl Buffer {
|
||||
|
||||
fn display_line(
|
||||
&self,
|
||||
config: &Config,
|
||||
config: &EditorConfig,
|
||||
current_style: &mut Style,
|
||||
term: &mut Term,
|
||||
row: usize,
|
||||
line: &Line,
|
||||
hi: &Highlighter,
|
||||
) -> Result<(), Error> {
|
||||
let mut pos = 0;
|
||||
term.set_cursor_position(row, self.view.offset_x)
|
||||
.map_err(Error::TerminalError)?;
|
||||
|
||||
let span = line.skip_to_width(self.view.column_offset, config.tab_width);
|
||||
let (span, _skipped_display_cells, skipped_characters) =
|
||||
line.skip_to_width(self.view.column_offset, config.tab_width);
|
||||
let long_line = span.display_width(config.tab_width) > self.view.width;
|
||||
|
||||
for &ch in span.iter() {
|
||||
let mut current_category: Option<&str> = None;
|
||||
|
||||
// Reset style
|
||||
let reset_style = Style::default();
|
||||
reset_style
|
||||
.apply_delta(current_style, term)
|
||||
.map_err(Error::TerminalError)?;
|
||||
*current_style = reset_style;
|
||||
|
||||
for (index, &ch) in span.iter().enumerate() {
|
||||
if pos >= self.view.width {
|
||||
break;
|
||||
}
|
||||
|
||||
let character_index = skipped_characters + index;
|
||||
|
||||
let matching_token = line
|
||||
.highlight
|
||||
.tokens
|
||||
.iter()
|
||||
.find(|t| t.range.contains(&character_index));
|
||||
let token_category = matching_token.map(|t| t.category.as_ref());
|
||||
|
||||
if token_category != current_category {
|
||||
current_category = token_category;
|
||||
let style = token_category
|
||||
.and_then(|cat| hi.stylize(&self.filetype, cat, current_style))
|
||||
.unwrap_or_else(Style::default);
|
||||
|
||||
style
|
||||
.apply_delta(current_style, term)
|
||||
.map_err(Error::TerminalError)?;
|
||||
*current_style = style;
|
||||
}
|
||||
|
||||
if ch == '\t' {
|
||||
let old_pos = pos;
|
||||
let new_pos = (pos + config.tab_width) & !(config.tab_width - 1);
|
||||
@@ -406,6 +474,7 @@ impl Buffer {
|
||||
term.write_char('>').map_err(Error::TerminalFmtError)?;
|
||||
term.set_foreground(Color::Default)
|
||||
.map_err(Error::TerminalError)?;
|
||||
current_style.foreground = Color::Default;
|
||||
} else {
|
||||
term.write_char(' ').map_err(Error::TerminalFmtError)?;
|
||||
}
|
||||
@@ -433,7 +502,14 @@ impl Buffer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn display(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
|
||||
pub fn display(
|
||||
&mut self,
|
||||
config: &EditorConfig,
|
||||
term: &mut Term,
|
||||
hi: &Highlighter,
|
||||
) -> Result<(), Error> {
|
||||
hi.rehighlight(&mut self.lines, &self.filetype);
|
||||
|
||||
match self.mode {
|
||||
Mode::Normal => {
|
||||
term.set_cursor_style(CursorStyle::Default)
|
||||
@@ -445,6 +521,8 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
|
||||
let mut current_style = Style::default();
|
||||
|
||||
for (row, line) in self
|
||||
.lines
|
||||
.iter()
|
||||
@@ -452,13 +530,27 @@ impl Buffer {
|
||||
.take(self.view.height)
|
||||
.enumerate()
|
||||
{
|
||||
self.display_line(config, term, row, line)?;
|
||||
self.display_line(config, &mut current_style, term, row, line, hi)?;
|
||||
}
|
||||
|
||||
// Reset to default style
|
||||
Style::DEFAULT
|
||||
.apply_delta(¤t_style, term)
|
||||
.map_err(Error::TerminalError)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_terminal_cursor(&mut self, config: &Config, term: &mut Term) -> Result<(), Error> {
|
||||
pub fn get_terminal_cursor(&self, config: &EditorConfig) -> (usize, usize) {
|
||||
let (x, y) = self.display_cursor(config);
|
||||
(x + self.view.offset_x, y)
|
||||
}
|
||||
|
||||
pub fn set_terminal_cursor(
|
||||
&mut self,
|
||||
config: &EditorConfig,
|
||||
term: &mut Term,
|
||||
) -> Result<(), Error> {
|
||||
let (x, y) = self.display_cursor(config);
|
||||
if self.mode_dirty {
|
||||
match self.mode {
|
||||
@@ -497,7 +589,7 @@ impl Buffer {
|
||||
self.lines.insert(self.view.cursor_row + 1, newline);
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, config: &Config, ch: char) {
|
||||
pub fn insert(&mut self, config: &EditorConfig, ch: char) {
|
||||
if self.lines.is_empty() {
|
||||
assert_eq!(self.view.cursor_row, 0);
|
||||
self.lines.push(Line::new());
|
||||
@@ -509,7 +601,7 @@ impl Buffer {
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn erase_backward(&mut self, config: &Config) {
|
||||
pub fn erase_backward(&mut self, config: &EditorConfig) {
|
||||
if self.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -547,12 +639,15 @@ impl Buffer {
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
pub fn kill_line(&mut self, config: &Config) {
|
||||
pub fn kill_line(&mut self, config: &EditorConfig) {
|
||||
if self.lines.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
self.lines.remove(self.view.cursor_row);
|
||||
if self.view.cursor_row < self.lines.len() {
|
||||
self.lines[self.view.cursor_row].highlight_dirty = true;
|
||||
}
|
||||
self.move_cursor(config, 0, 1);
|
||||
self.modified = true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
use std::io;
|
||||
|
||||
use libterm::{Color, Term};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Style {
|
||||
pub foreground: Color,
|
||||
pub background: Color,
|
||||
pub bold: bool,
|
||||
}
|
||||
|
||||
impl Default for Style {
|
||||
fn default() -> Self {
|
||||
Self::DEFAULT
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub const DEFAULT: Self = Self {
|
||||
foreground: Color::White,
|
||||
background: Color::Black,
|
||||
bold: false,
|
||||
};
|
||||
|
||||
pub fn apply(&self, term: &mut Term) -> Result<(), io::Error> {
|
||||
self.apply_delta(&Self::DEFAULT, term)
|
||||
}
|
||||
|
||||
pub fn apply_delta(&self, original: &Self, term: &mut Term) -> Result<(), io::Error> {
|
||||
// TODO libterm Color PartialEq
|
||||
if self.foreground as u32 != original.foreground as u32 {
|
||||
// Apply foreground
|
||||
term.set_foreground(self.foreground)?;
|
||||
}
|
||||
if self.background as u32 != original.background as u32 {
|
||||
// Apply background
|
||||
term.set_background(self.background)?;
|
||||
}
|
||||
if self.bold != original.bold {
|
||||
// Apply bold
|
||||
term.set_bright(self.bold)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use crate::{
|
||||
State,
|
||||
buffer::{Buffer, SetMode},
|
||||
config::Config,
|
||||
error::Error,
|
||||
};
|
||||
|
||||
pub type CommandFn = fn(&mut State, &[&str]) -> Result<(), Error>;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum Action {
|
||||
// Editing
|
||||
EraseBackward,
|
||||
InsertBefore,
|
||||
InsertAfter,
|
||||
NewlineBefore,
|
||||
NewlineAfter,
|
||||
BreakLine,
|
||||
KillLine,
|
||||
|
||||
// Movement
|
||||
MoveFirstLine,
|
||||
MoveLastLine,
|
||||
MoveCharPrev,
|
||||
MoveCharNext,
|
||||
MoveLineBack(usize),
|
||||
MoveLineForward(usize),
|
||||
MoveLineStart,
|
||||
MoveLineEnd,
|
||||
}
|
||||
|
||||
static COMMANDS: &[(&str, RangeInclusive<usize>, CommandFn)] = &[
|
||||
("w", 0..=1, cmd_write),
|
||||
("w!", 0..=1, cmd_force_write),
|
||||
("q", 0..=0, cmd_exit),
|
||||
("q!", 0..=0, cmd_force_exit),
|
||||
("e", 1..=1, cmd_edit),
|
||||
("e!", 0..=1, cmd_force_edit),
|
||||
];
|
||||
|
||||
// Commands
|
||||
fn cmd_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||
if args.len() == 1 && state.buffer().is_modified() && state.buffer().path().is_some() {
|
||||
return Err(Error::UnsavedBuffer(
|
||||
"Use :w! FILE to force write to another file",
|
||||
));
|
||||
}
|
||||
|
||||
cmd_force_write(state, args)
|
||||
}
|
||||
|
||||
fn cmd_force_write(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||
let buffer = state.buffer_mut();
|
||||
if let Some(&path) = args.first() {
|
||||
buffer.set_path(path);
|
||||
}
|
||||
|
||||
buffer.save()?;
|
||||
|
||||
if let Some(name) = buffer.name() {
|
||||
let status = format!("{:?} written", name);
|
||||
state.set_status(status);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||
if state.buffer().is_modified() {
|
||||
return Err(Error::UnsavedBuffer("Use :e! [FILE] to open another file"));
|
||||
}
|
||||
|
||||
cmd_force_edit(state, args)
|
||||
}
|
||||
|
||||
fn cmd_force_edit(state: &mut State, args: &[&str]) -> Result<(), Error> {
|
||||
if let Some(&path) = args.first() {
|
||||
state.buffer_mut().reopen(path).map_err(Error::OpenError)
|
||||
} else if let Some(path) = state.buffer().path().cloned() {
|
||||
state.buffer_mut().reopen(path).map_err(Error::OpenError)
|
||||
} else {
|
||||
Err(Error::NoPath)
|
||||
}
|
||||
}
|
||||
|
||||
fn cmd_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
|
||||
let buffer = state.buffer();
|
||||
|
||||
if buffer.is_modified() {
|
||||
return Err(Error::UnsavedBuffer("Use :q! to force exit"));
|
||||
}
|
||||
|
||||
state.exit();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cmd_force_exit(state: &mut State, _args: &[&str]) -> Result<(), Error> {
|
||||
state.exit();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn execute(state: &mut State, command: String) -> Result<(), Error> {
|
||||
let words = command.split(' ').collect::<Vec<_>>();
|
||||
let Some((&cmd, args)) = words.split_first() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
for (name, nargs, f) in COMMANDS.iter() {
|
||||
if *name == cmd {
|
||||
if !nargs.contains(&args.len()) {
|
||||
todo!();
|
||||
}
|
||||
|
||||
return f(state, args);
|
||||
}
|
||||
}
|
||||
|
||||
Err(Error::UnknownCommand(cmd.into()))
|
||||
}
|
||||
|
||||
pub fn perform(buffer: &mut Buffer, config: &Config, action: Action) -> Result<(), Error> {
|
||||
match action {
|
||||
// Editing
|
||||
Action::EraseBackward => buffer.erase_backward(config),
|
||||
Action::InsertBefore => buffer.set_mode(config, SetMode::InsertBefore),
|
||||
Action::InsertAfter => buffer.set_mode(config, SetMode::InsertAfter),
|
||||
Action::NewlineBefore => buffer.newline_before(),
|
||||
Action::NewlineAfter => buffer.newline_after(false),
|
||||
Action::BreakLine => buffer.newline_after(true),
|
||||
Action::KillLine => buffer.kill_line(config),
|
||||
// Movement
|
||||
Action::MoveCharPrev => buffer.move_cursor(config, -1, 0),
|
||||
Action::MoveCharNext => buffer.move_cursor(config, 1, 0),
|
||||
Action::MoveLineBack(count) => buffer.move_cursor(config, 0, -(count as isize)),
|
||||
Action::MoveLineForward(count) => buffer.move_cursor(config, 0, count as isize),
|
||||
Action::MoveLineStart => buffer.set_column(config, 0),
|
||||
Action::MoveLineEnd => buffer.to_line_end(config),
|
||||
Action::MoveFirstLine => buffer.to_first_line(config),
|
||||
Action::MoveLastLine => buffer.to_last_line(config),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,80 +1,129 @@
|
||||
use libterm::TermKey;
|
||||
|
||||
use crate::{
|
||||
buffer::Mode,
|
||||
command::Action,
|
||||
keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn},
|
||||
use std::{
|
||||
mem,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
pub struct Config {
|
||||
// TODO must be a power of 2, lol
|
||||
pub struct EditorConfig {
|
||||
pub tab_width: usize,
|
||||
pub number: bool,
|
||||
|
||||
pub nmap: KeyMap,
|
||||
pub imap: KeyMap,
|
||||
pub number: Dirty<bool>,
|
||||
pub bottom_margin: Dirty<usize>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
pub struct Dirty<T>(T, bool);
|
||||
|
||||
impl Default for EditorConfig {
|
||||
fn default() -> Self {
|
||||
use Action::*;
|
||||
|
||||
let nmap = KeyMap::from_iter([
|
||||
bind1('i', [InsertBefore]),
|
||||
bind1('a', [InsertAfter]),
|
||||
bind1('h', [MoveCharPrev]),
|
||||
bind1('l', [MoveCharNext]),
|
||||
bind1('j', [MoveLineForward(1)]),
|
||||
bind1('J', [MoveLineForward(25)]),
|
||||
bind1('k', [MoveLineBack(1)]),
|
||||
bind1('K', [MoveLineBack(25)]),
|
||||
bind1(TermKey::Left, [MoveCharPrev]),
|
||||
bind1(TermKey::Right, [MoveCharNext]),
|
||||
bind1(TermKey::Up, [MoveLineBack(1)]),
|
||||
bind1(TermKey::Down, [MoveLineForward(1)]),
|
||||
bind1(TermKey::Home, [MoveLineStart]),
|
||||
bind1(TermKey::End, [MoveLineEnd]),
|
||||
bindn(['g', 'g'], [MoveFirstLine]),
|
||||
bind1('G', [MoveLastLine]),
|
||||
bind1('I', [MoveLineStart, InsertBefore]),
|
||||
bind1('A', [MoveLineEnd, InsertAfter]),
|
||||
bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]),
|
||||
bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]),
|
||||
bindn(['d', 'd'], [KillLine]),
|
||||
]);
|
||||
|
||||
let imap = KeyMap::from_iter([
|
||||
bind1('\x7F', [EraseBackward]),
|
||||
bind1(TermKey::Left, [MoveCharPrev]),
|
||||
bind1(TermKey::Right, [MoveCharNext]),
|
||||
bind1(TermKey::Up, [MoveLineBack(1)]),
|
||||
bind1(TermKey::Down, [MoveLineForward(1)]),
|
||||
bind1(TermKey::Home, [MoveLineStart]),
|
||||
bind1(TermKey::End, [MoveLineEnd]),
|
||||
bind1(
|
||||
'\n',
|
||||
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
),
|
||||
bind1(
|
||||
'\x0D',
|
||||
[BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
),
|
||||
]);
|
||||
|
||||
Self {
|
||||
tab_width: 4,
|
||||
number: true,
|
||||
nmap,
|
||||
imap,
|
||||
number: Dirty::new(false),
|
||||
bottom_margin: Dirty::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode<KeySeq, Vec<Action>>> {
|
||||
match mode {
|
||||
Mode::Normal => self.nmap.get(seq),
|
||||
Mode::Insert => self.imap.get(seq),
|
||||
}
|
||||
impl<T> Dirty<T> {
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self(value, true)
|
||||
}
|
||||
|
||||
pub fn clean(&mut self) -> bool {
|
||||
mem::replace(&mut self.1, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Dirty<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> DerefMut for Dirty<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.1 = true;
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// use libterm::TermKey;
|
||||
//
|
||||
// use crate::{
|
||||
// buffer::Mode,
|
||||
// command::Action,
|
||||
// keymap::{KeyMap, KeySeq, PrefixNode, bind1, bindn},
|
||||
// };
|
||||
//
|
||||
// pub struct Config {
|
||||
// // TODO must be a power of 2, lol
|
||||
// pub tab_width: usize,
|
||||
// pub number: bool,
|
||||
//
|
||||
// pub nmap: KeyMap,
|
||||
// pub imap: KeyMap,
|
||||
// }
|
||||
//
|
||||
// impl Default for Config {
|
||||
// fn default() -> Self {
|
||||
// use Action::*;
|
||||
//
|
||||
// let nmap = KeyMap::from_iter([
|
||||
// bind1('i', [InsertBefore]),
|
||||
// bind1('a', [InsertAfter]),
|
||||
// bind1('h', [MoveCharPrev]),
|
||||
// bind1('l', [MoveCharNext]),
|
||||
// bind1('j', [MoveLineForward(1)]),
|
||||
// bind1('J', [MoveLineForward(25)]),
|
||||
// bind1('k', [MoveLineBack(1)]),
|
||||
// bind1('K', [MoveLineBack(25)]),
|
||||
// bind1(TermKey::Left, [MoveCharPrev]),
|
||||
// bind1(TermKey::Right, [MoveCharNext]),
|
||||
// bind1(TermKey::Up, [MoveLineBack(1)]),
|
||||
// bind1(TermKey::Down, [MoveLineForward(1)]),
|
||||
// bind1(TermKey::Home, [MoveLineStart]),
|
||||
// bind1(TermKey::End, [MoveLineEnd]),
|
||||
// bindn(['g', 'g'], [MoveFirstLine]),
|
||||
// bind1('G', [MoveLastLine]),
|
||||
// bind1('I', [MoveLineStart, InsertBefore]),
|
||||
// bind1('A', [MoveLineEnd, InsertAfter]),
|
||||
// bind1('o', [NewlineAfter, MoveLineForward(1), InsertBefore]),
|
||||
// bind1('O', [NewlineBefore, MoveLineBack(1), InsertBefore]),
|
||||
// bindn(['d', 'd'], [KillLine]),
|
||||
// ]);
|
||||
//
|
||||
// let imap = KeyMap::from_iter([
|
||||
// bind1('\x7F', [EraseBackward]),
|
||||
// bind1(TermKey::Left, [MoveCharPrev]),
|
||||
// bind1(TermKey::Right, [MoveCharNext]),
|
||||
// bind1(TermKey::Up, [MoveLineBack(1)]),
|
||||
// bind1(TermKey::Down, [MoveLineForward(1)]),
|
||||
// bind1(TermKey::Home, [MoveLineStart]),
|
||||
// bind1(TermKey::End, [MoveLineEnd]),
|
||||
// bind1(
|
||||
// '\n',
|
||||
// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
// ),
|
||||
// bind1(
|
||||
// '\x0D',
|
||||
// [BreakLine, MoveLineForward(1), MoveLineStart, InsertBefore],
|
||||
// ),
|
||||
// ]);
|
||||
//
|
||||
// Self {
|
||||
// tab_width: 4,
|
||||
// number: true,
|
||||
// nmap,
|
||||
// imap,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl Config {
|
||||
// pub fn key_seq(&self, mode: Mode, seq: &KeySeq) -> Option<&PrefixNode<KeySeq, Vec<Action>>> {
|
||||
// match mode {
|
||||
// Mode::Normal => self.nmap.get(seq),
|
||||
// Mode::Insert => self.imap.get(seq),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use std::{fmt, io};
|
||||
|
||||
use lysp::error::{MachineError, ValueConversionError};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
// I/O errors
|
||||
@@ -19,4 +21,17 @@ pub enum Error {
|
||||
TerminalError(io::Error),
|
||||
#[error("Terminal error: {0:?}")]
|
||||
TerminalFmtError(fmt::Error),
|
||||
#[error("Command error: {0}")]
|
||||
Command(io::Error),
|
||||
#[error("Command error: {0:?}")]
|
||||
CommandStatus(Option<i32>),
|
||||
|
||||
#[error("Scripting error: {0:?}")]
|
||||
Script(#[from] MachineError),
|
||||
}
|
||||
|
||||
impl From<ValueConversionError> for Error {
|
||||
fn from(value: ValueConversionError) -> Self {
|
||||
Self::Script(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user