diff --git a/.gitignore b/.gitignore index cdd48dac..79deba5d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ +/Cargo.lock /target + /testing /docs/.obsidian -/.cargo \ No newline at end of file +/.cargo diff --git a/Cargo.lock b/Cargo.lock index 12ca8b6d..21a2caf3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,35 +4,42 @@ version = 3 [[package]] name = "ahash" -version = "0.7.8" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ - "getrandom", + "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] -name = "aho-corasick" -version = "1.1.2" +name = "allocator-api2" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" -dependencies = [ - "memchr", -] +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] -name = "anes" -version = "0.1.6" +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -44,9 +51,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -76,11 +83,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "arboard" +version = "3.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2041f1943049c7978768d84e6d0fd95de98b76d6c4727b09e78ec253d29fa58" +dependencies = [ + "clipboard-win", + "log", + "objc", + "objc-foundation", + "objc_id", + "parking_lot", + "thiserror", + "wl-clipboard-rs", + "x11rb", +] + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" [[package]] name = "bitflags" @@ -90,60 +114,70 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] -name = "cast" -version = "0.3.0" +name = "castaway" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "8a17ed5635fc8536268e5d4de1e22e81ac34419e5f052d4d51f4e01dcc263fcc" +dependencies = [ + "rustversion", +] [[package]] -name = "cfg-if" -version = "1.0.0" +name = "cc" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" [[package]] -name = "ciborium" -version = "0.2.2" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "ciborium-io" -version = "0.2.2" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" [[package]] -name = "ciborium-ll" -version = "0.2.2" +name = "chrono" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ - "ciborium-io", - "half", + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.5", ] [[package]] name = "clap" -version = "4.4.11" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -151,9 +185,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -163,11 +197,11 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn", @@ -175,15 +209,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "5.0.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc" +checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" dependencies = [ "error-code", ] @@ -205,82 +239,39 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" -dependencies = [ - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "is-terminal", - "itertools 0.10.5", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" +name = "compact_str" +version = "0.8.0-beta" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools 0.10.5", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c3242926edf34aec4ac3a77108ad4854bffaa2e4ddc1824124ce59231302d5" +checksum = "c2a2dc81369dde6d31456eedbb4fd3d320f0b9713573dfe06e569e2bce7607f2" dependencies = [ + "castaway", "cfg-if", - "crossbeam-utils", + "itoa", + "rustversion", + "ryu", + "static_assertions", ] [[package]] -name = "crossbeam-deque" -version = "0.8.4" +name = "core-foundation-sys" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fca89a0e215bab21874660c67903c5f143333cab1da83d041c7ded6053774751" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] -name = "crossbeam-epoch" -version = "0.9.16" +name = "crossbeam-channel" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d2fe95351b870527a5d09bf563ed3c97c0cffb87cf1c78a591bf48bb218d9aa" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", ] [[package]] name = "crossbeam-utils" -version = "0.8.17" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d96137f14f244c37f989d9fff8f95e6c18b918e71f36638f8c49112e4c78f" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crossterm" @@ -288,11 +279,12 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", "parking_lot", + "serde", "signal-hook", "signal-hook-mio", "winapi", @@ -308,22 +300,42 @@ dependencies = [ ] [[package]] -name = "crunchy" -version = "0.2.2" +name = "derive-new" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d150dea618e920167e5973d70ae6ece4385b7164e0d799fe7c122dd0a5d912ad" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dlib" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "either" -version = "1.9.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] -name = "endian-type" -version = "0.1.2" +name = "equivalent" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" @@ -337,15 +349,21 @@ dependencies = [ [[package]] name = "error-code" -version = "3.0.0" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" + +[[package]] +name = "fastrand" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" [[package]] name = "fd-lock" -version = "4.0.1" +version = "4.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93f7a0db71c99f68398f80653ed05afb0b00e062e1a20c7ff849c4edfabbbcc" +checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", "rustix", @@ -364,6 +382,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fsevent-sys" version = "4.1.0" @@ -374,33 +404,23 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.14" +name = "gethostname" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "0176e0459c2e4a1fe232f984bca6890e681076abb9934f6cea7c326f3fc47818" dependencies = [ - "cfg-if", "libc", - "wasi", -] - -[[package]] -name = "half" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872" -dependencies = [ - "cfg-if", - "crunchy", + "windows-targets 0.48.5", ] [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", + "allocator-api2", ] [[package]] @@ -410,10 +430,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] -name = "hermit-abi" -version = "0.3.5" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" @@ -424,6 +444,39 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "inotify" version = "0.9.6" @@ -446,54 +499,34 @@ dependencies = [ [[package]] name = "internment" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e976188335292f66a1533fd41d5c2ce24b32dc2c000569b8dccf4e57f489806" +checksum = "04e8e537b529b8674e97e9fb82c10ff168a290ac3867a0295f112061ffbca1ef" dependencies = [ "hashbrown", "parking_lot", ] -[[package]] -name = "is-terminal" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.68" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] @@ -520,26 +553,25 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.151" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] -name = "libredox" -version = "0.0.2" +name = "libloading" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3af92c55d7d839293953fcd0fda5ecfe93297cfde6ffbdec13b41d99c0ba6607" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ - "bitflags 2.4.1", - "libc", - "redox_syscall", + "cfg-if", + "windows-targets 0.52.5", ] [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -553,30 +585,36 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] -name = "memoffset" -version = "0.9.0" +name = "minimal-lexical" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", @@ -585,23 +623,25 @@ dependencies = [ ] [[package]] -name = "nibble_vec" -version = "0.1.0" +name = "nix" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "smallvec", + "bitflags 2.5.0", + "cfg-if", + "cfg_aliases", + "libc", ] [[package]] -name = "nix" -version = "0.27.1" +name = "nom" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "libc", + "memchr", + "minimal-lexical", ] [[package]] @@ -610,7 +650,7 @@ version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "crossbeam-channel", "filetime", "fsevent-sys", @@ -623,6 +663,15 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "nu-ansi-term" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd2800e1520bdc966782168a627aa5d1ad92e33b984bf7c7615d31280c83ff14" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -633,10 +682,33 @@ dependencies = [ ] [[package]] -name = "numtoa" -version = "0.1.0" +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "objc-foundation" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] [[package]] name = "once_cell" @@ -645,10 +717,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] -name = "oorandom" -version = "11.1.3" +name = "os_pipe" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "57119c3b893986491ec9aa85056780d3a0f3cf4da7cc09dd3650dbd6c6738fb9" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "parking_lot" @@ -674,79 +750,46 @@ dependencies = [ ] [[package]] -name = "plotters" -version = "0.3.5" +name = "petgraph" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", + "fixedbitset", + "indexmap", ] [[package]] -name = "plotters-backend" -version = "0.3.5" +name = "pkg-config" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" - -[[package]] -name = "plotters-svg" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" -dependencies = [ - "plotters-backend", -] +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "proc-macro2" -version = "1.0.70" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] -name = "quote" -version = "1.0.33" +name = "quick-xml" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" dependencies = [ - "proc-macro2", -] - -[[package]] -name = "radix_trie" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" -dependencies = [ - "endian-type", - "nibble_vec", -] - -[[package]] -name = "rayon" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" -dependencies = [ - "either", - "rayon-core", + "memchr", ] [[package]] -name = "rayon-core" -version = "1.12.1" +name = "quote" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "crossbeam-deque", - "crossbeam-utils", + "proc-macro2", ] [[package]] @@ -759,47 +802,33 @@ dependencies = [ ] [[package]] -name = "redox_termios" -version = "0.1.3" +name = "reedline" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" - -[[package]] -name = "regex" -version = "1.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "65ebc241ed0ccea0bbbd775a55a76f0dd9971ef084589dea938751a03ffedc14" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", + "arboard", + "chrono", + "crossterm", + "fd-lock", + "itertools", + "nu-ansi-term", + "serde", + "strip-ansi-escapes", + "strum", + "strum_macros", + "thiserror", + "unicode-segmentation", + "unicode-width", ] -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" - [[package]] name = "rustix" -version = "0.38.28" +version = "0.38.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316" +checksum = "e3cc72858054fcff6d7dea32df2aeaee6a7c24227366d7ea429aada2f26b16ad" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -807,32 +836,16 @@ dependencies = [ ] [[package]] -name = "rustyline" -version = "13.0.0" +name = "rustversion" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" -dependencies = [ - "bitflags 2.4.1", - "cfg-if", - "clipboard-win", - "fd-lock", - "home", - "libc", - "log", - "memchr", - "nix", - "radix_trie", - "unicode-segmentation", - "unicode-width", - "utf8parse", - "winapi", -] +checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" [[package]] name = "ryu" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" [[package]] name = "same-file" @@ -843,6 +856,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -851,35 +870,24 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "serde_json" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0652c533506ad7a2e353cce269330d6afd8bdfb6d75e0ace5b35aacbd7b9e9" -dependencies = [ - "itoa", - "ryu", - "serde", -] - [[package]] name = "signal-hook" version = "0.3.17" @@ -903,47 +911,97 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "stack" +name = "stack-cli" version = "0.1.0" dependencies = [ "clap", "codespan-reporting", - "criterion", "crossterm", - "internment", - "itertools 0.12.0", "notify", - "rustyline", - "termion", + "reedline", + "stack-core", + "stack-std", +] + +[[package]] +name = "stack-core" +version = "0.1.0" +dependencies = [ + "compact_str", + "internment", "test-case", - "thiserror", + "unicode-segmentation", + "yansi", +] + +[[package]] +name = "stack-std" +version = "0.1.0" +dependencies = [ + "compact_str", + "stack-core", + "unicode-segmentation", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa" +dependencies = [ + "vte", ] [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" + +[[package]] +name = "strum_macros" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6cf59daf282c0a494ba14fd21610a0325f9f90ec9d1231dea26bcb1d696c946" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn", +] [[package]] name = "syn" -version = "2.0.41" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c8b28c477cc3bf0e7966561e3460130e1255f7a1cf71931075f1c5e7a7e269" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -951,24 +1009,24 @@ dependencies = [ ] [[package]] -name = "termcolor" -version = "1.4.1" +name = "tempfile" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", ] [[package]] -name = "termion" -version = "3.0.0" +name = "termcolor" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "417813675a504dfbbf21bfde32c03e5bf9f2413999962b479023c02848c1c7a5" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ - "libc", - "libredox", - "numtoa", - "redox_termios", + "winapi-util", ] [[package]] @@ -1006,18 +1064,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" dependencies = [ "proc-macro2", "quote", @@ -1025,13 +1083,17 @@ dependencies = [ ] [[package]] -name = "tinytemplate" -version = "1.2.1" +name = "tree_magic_mini" +version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +checksum = "77ee137597cdb361b55a4746983e4ac1b35ab6024396a419944ad473bb915265" dependencies = [ - "serde", - "serde_json", + "fnv", + "home", + "memchr", + "nom", + "once_cell", + "petgraph", ] [[package]] @@ -1042,9 +1104,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -1064,11 +1126,31 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vte" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197" +dependencies = [ + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -1082,9 +1164,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -1092,9 +1174,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", @@ -1107,9 +1189,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1117,9 +1199,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", @@ -1130,18 +1212,81 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.91" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] -name = "web-sys" -version = "0.3.68" +name = "wayland-backend" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" +checksum = "9d50fa61ce90d76474c87f5fc002828d81b32677340112b4ef08079a9d459a40" dependencies = [ - "js-sys", - "wasm-bindgen", + "cc", + "downcast-rs", + "rustix", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82fb96ee935c2cea6668ccb470fb7771f6215d1691746c2d896b447a00ad3f1f" +dependencies = [ + "bitflags 2.5.0", + "rustix", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols" +version = "0.31.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +dependencies = [ + "bitflags 2.5.0", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b3a62929287001986fb58c789dce9b67604a397c15c611ad9f747300b6c283" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15a0c8eaff5216d07f226cb7a549159267f3467b289d9a2e52fd3ef5aae2b7af" +dependencies = [ + "dlib", + "log", + "pkg-config", ] [[package]] @@ -1175,6 +1320,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.5", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -1190,7 +1344,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.5", ] [[package]] @@ -1210,17 +1364,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1231,9 +1386,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1243,9 +1398,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1255,9 +1410,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1267,9 +1428,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1279,9 +1440,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1291,9 +1452,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1303,6 +1464,69 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "wl-clipboard-rs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12b41773911497b18ca8553c3daaf8ec9fe9819caf93d451d3055f69de028adb" +dependencies = [ + "derive-new", + "libc", + "log", + "nix", + "os_pipe", + "tempfile", + "thiserror", + "tree_magic_mini", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-wlr", +] + +[[package]] +name = "x11rb" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f25ead8c7e4cba123243a6367da5d3990e0d3affa708ea19dce96356bd9f1a" +dependencies = [ + "gethostname", + "rustix", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63e71c4b8bd9ffec2c963173a4dc4cbde9ee96961d4fcb4429db9929b606c34" + +[[package]] +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 8a25ba94..d8f85ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,37 +1,9 @@ -[package] -name = "stack" -version = "0.1.0" -edition = "2021" +[workspace] +resolver = "2" +members = ["stack-core", "stack-std", "stack-cli"] -[profile.release] -opt-level = 3 -strip = "debuginfo" -lto = "fat" -codegen-units = 1 +[workspace.dependencies] +unicode-segmentation = "1" +compact_str = "=0.8.0-beta" -[dependencies] -clap = { version = "4", features = ["derive"] } -codespan-reporting = "0.11.1" -crossterm = "0.27.0" -itertools = "0.12.0" -notify = "6" -rustyline = "13" -termion = "3.0.0" -thiserror = "1" -internment = "0.7.4" - -[dev-dependencies] test-case = "3" -criterion = "0.5.1" - -[[bench]] -name = "eval" -harness = false - -[[bin]] -name = "stack" -path = "src/main.rs" - -# [[bin]] -# name = "stack-generate" -# path = "src/generate_highlighter.rs" diff --git a/benches/eval.rs b/benches/eval.rs deleted file mode 100644 index bf3099fd..00000000 --- a/benches/eval.rs +++ /dev/null @@ -1,53 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; -use stack::Program; - -fn cold_start_no_core() { - let mut program = Program::new(); - program.eval_string("(2 2)").unwrap(); -} - -fn cold_start_with_core() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(2 2 + 'result def result)").unwrap(); -} - -fn running_complex_code() { - let mut program = Program::new().with_core().unwrap(); - - program - .eval_string( - " - '(fn - 0 'i def - - '(fn - i 1 + 'i set - ) 'inc def - - '(fn i) 'value def - - '() - 'inc export - 'value export - ) 'counter def - - counter 'my-counter use - - my-counter/inc - my-counter/inc - my-counter/inc - my-counter/value", - ) - .unwrap(); -} - -fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("cold start no core", |b| b.iter(cold_start_no_core)); - - c.bench_function("cold start with core", |b| b.iter(cold_start_with_core)); - - c.bench_function("running code", |b| b.iter(running_complex_code)); -} - -criterion_group!(benches, criterion_benchmark); -criterion_main!(benches); diff --git a/src/evaluator.rs b/src/evaluator.rs deleted file mode 100644 index bdf8143a..00000000 --- a/src/evaluator.rs +++ /dev/null @@ -1,633 +0,0 @@ -use codespan_reporting::diagnostic::{Diagnostic, Label}; -use codespan_reporting::files::SimpleFiles; -use codespan_reporting::term; -use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; -use internment::Intern; - -use crate::{ - module, Expr, ExprKind, Func, Journal, JournalOp, Lexer, Module, Parser, - Scanner, Scope, Span, Type, -}; -use core::{fmt, iter}; -use std::{collections::HashMap, fs, time::SystemTime}; - -#[derive(Debug, Clone)] -pub struct SourceFile { - pub contents: String, - pub mtime: SystemTime, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum EvalErrorKind { - Push, - StackUnderflow, - UnknownCall, - ParseError, - Message(String), - ExpectedFound(Type, Type), - Halt, - Panic(String), - UnableToRead(String, String), - Cast(Type), -} - -impl fmt::Display for EvalErrorKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Push => write!(f, "failed to push to the stack"), - Self::StackUnderflow => write!(f, "stack underflow"), - Self::UnknownCall => write!(f, "unknown call"), - Self::ParseError => write!(f, "parse error"), - Self::ExpectedFound(expected, found) => { - write!(f, "expected {}, found {}", expected, found) - } - Self::Message(message) => write!(f, "{}", message), - Self::Halt => write!(f, "halted"), - Self::Panic(message) => write!(f, "panic: {}", message), - Self::UnableToRead(filename, error) => { - write!(f, "unable to read {}: {}", filename, error) - } - Self::Cast(t) => { - write!(f, "unable to cast into {}", t) - } - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct EvalError { - pub kind: EvalErrorKind, - pub expr: Option, -} - -impl fmt::Display for EvalError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "Error: {}", self.kind)?; - writeln!( - f, - "Expr: {}", - match self.expr.clone() { - Some(expr) => expr.to_string(), - None => "no expr to display".into(), - } - ) - } -} - -impl EvalError { - pub fn print_report(&self, program: &Program) { - let source_file: Option<&str> = self.expr.as_ref().and_then(|expr| { - expr.debug_data.source_file.map(|x| x.as_ref().as_str()) - }); - let file_name = source_file.unwrap_or(""); - - let span: Option = - self.expr.as_ref().and_then(|expr| expr.debug_data.span); - let span = match span { - Some(span) => span.start..span.end, - None => 0..0, - }; - - let mut files = SimpleFiles::new(); - let mut file_id = 0; - - for (name, source) in program.sources.iter() { - let id = files.add(name, source.contents.clone()); - - if name == file_name { - file_id = id; - } - } - - let diagnostic = Diagnostic::error() - .with_message(self.to_string()) - .with_labels(vec![ - Label::primary(file_id, span).with_message("error occurs here") - ]); - - let writer = StandardStream::stderr(ColorChoice::Always); - let config = codespan_reporting::term::Config::default(); - - // TODO: Should we do anything for this error or can we just unwrap? - let _ = term::emit(&mut writer.lock(), &config, &files, &diagnostic); - } -} - -#[derive(Debug, Clone)] -pub struct Program { - pub stack: Vec, - pub scopes: Vec, - pub funcs: HashMap, Func>, - pub sources: HashMap, - pub journal: Journal, - pub debug: bool, -} - -impl Default for Program { - #[inline] - fn default() -> Self { - Self::new() - } -} - -impl fmt::Display for Program { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Stack: [")?; - - self.stack.iter().enumerate().try_for_each(|(i, expr)| { - if i == self.stack.len() - 1 { - write!(f, "{}", expr.to_pretty_string()) - } else { - write!(f, "{}, ", expr.to_pretty_string()) - } - })?; - write!(f, "]")?; - - // if !self.scopes.is_empty() { - // writeln!(f, "Scope:")?; - - // let layer = self.scopes.last().unwrap(); - // let items = layer.items.len(); - // for (item_i, (key, value)) in - // layer.items.iter().sorted_by_key(|(s, _)| *s).enumerate() - // { - // if item_i == items - 1 { - // write!( - // f, - // " + {}: {}", - // interner().resolve(key), - // match value.borrow().val() { - // Some(expr) => expr.to_string(), - // None => "None".to_owned(), - // } - // )?; - // } else { - // writeln!( - // f, - // " + {}: {}", - // interner().resolve(key), - // match value.borrow().val() { - // Some(expr) => expr.to_string(), - // None => "None".to_owned(), - // } - // )?; - // } - // } - // } - - let journal = self.journal.to_string(); - - if !journal.is_empty() { - write!(f, "\n\n")?; - write!(f, "{}", journal)?; - } - - Ok(()) - } -} - -impl Program { - #[inline] - pub fn new() -> Self { - Self { - stack: vec![], - scopes: vec![Scope::new()], - funcs: HashMap::new(), - sources: HashMap::new(), - journal: Journal::new(), - debug: false, - } - } - - pub fn with_core(self) -> Result { - let mut program = self; - module::core::Core::default().link(&mut program)?; - program.journal.clear(); - Ok(program) - } - - pub fn with_module(self, module: M) -> Result - where - M: Module, - { - let mut program = self; - module.link(&mut program)?; - program.journal.clear(); - Ok(program) - } - - pub fn with_debug(mut self) -> Self { - self.debug = true; - self - } - - pub fn loaded_files(&self) -> impl Iterator { - self.sources.keys().map(|s| s.as_str()) - } - - pub fn pop(&mut self, trace_expr: &Expr) -> Result { - match self.stack.pop() { - Some(expr) => { - if self.debug { - self.journal.op(JournalOp::Pop(expr.clone())); - } - Ok(expr) - } - None => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::StackUnderflow, - }), - } - } - - pub fn push(&mut self, expr: Expr) -> Result<(), EvalError> { - let expr = if expr.val.is_function() { - let mut scanner = - Scanner::new(self.scopes.last().unwrap().duplicate(), &self.funcs); - - match scanner.scan(expr.clone()) { - Ok(expr) => expr, - Err(message) => { - return Err(EvalError { - expr: Some(expr), - kind: EvalErrorKind::Message(message), - }) - } - } - } else { - expr - }; - - if self.debug { - self.journal.op(JournalOp::Push(expr.clone())); - } - - self.stack.push(expr); - // TODO: I don't think we need to commit after each push. - // self.journal.commit(); - - Ok(()) - } - - pub fn scope_item(&self, symbol: &str) -> Option { - self - .scopes - .last() - .and_then(|layer| layer.get_val(Intern::from_ref(symbol))) - } - - pub fn def_scope_item(&mut self, symbol: &str, value: Expr) { - if let Some(layer) = self.scopes.last_mut() { - layer.define(Intern::from_ref(symbol), value).unwrap(); - } - } - - pub fn set_scope_item( - &mut self, - symbol: &str, - value: Expr, - ) -> Result<(), EvalError> { - if let Some(layer) = self.scopes.last_mut() { - match layer.set(Intern::from_ref(symbol), value.clone()) { - Ok(_) => Ok(()), - Err(string) => Err(EvalError { - expr: Some(value), - kind: EvalErrorKind::Message(string), - }), - } - } else { - Err(EvalError { - expr: Some(value), - kind: EvalErrorKind::Message("no scopes to set to".into()), - }) - } - } - - pub fn remove_scope_item(&mut self, symbol: &str) { - if let Some(layer) = self.scopes.last_mut() { - layer.remove(Intern::from_ref(symbol)); - } - } - - pub fn push_scope(&mut self, scope: Scope) { - self.scopes.push(scope); - } - - pub fn pop_scope(&mut self) { - self.scopes.pop(); - } - - /// Reads, Lexes, Parses, and Evaluates a file - pub fn eval_file(&mut self, path: &str) -> Result<(), EvalError> { - let contents = fs::read_to_string(path); - - match contents { - Ok(contents) => { - self.sources.insert( - path.into(), - SourceFile { - mtime: SystemTime::now(), - contents: contents.clone(), - }, - ); - - let lexer = Lexer::new(&contents); - let parser = Parser::new(lexer, Intern::new(path.to_string())); - // TODO: It might be time to add a proper EvalError enum. - let exprs = parser.parse().map_err(|_| EvalError { - expr: None, - kind: EvalErrorKind::ParseError, - })?; - - self.eval(exprs) - } - Err(err) => Err(EvalError { - expr: None, - kind: EvalErrorKind::UnableToRead(path.into(), err.to_string()), - }), - } - } - - /// Lexes, Parses, and Evaluates a string, setting the source_file of the exprs to `name` - pub fn eval_string_with_name( - &mut self, - line: &str, - name: &str, - ) -> Result<(), EvalError> { - let lexer = Lexer::new(line); - let parser = Parser::new(lexer, Intern::new(name.to_string())); - // TODO: It might be time to add a proper EvalError enum. - let exprs = parser.parse().map_err(|_| EvalError { - expr: None, - kind: EvalErrorKind::ParseError, - })?; - - self.eval(exprs) - } - - /// Lexes, Parses, and Evaluates a string - pub fn eval_string(&mut self, line: &str) -> Result<(), EvalError> { - self.eval_string_with_name(line, "internal") - } - - /// Evaluates a vec of expressions - pub fn eval(&mut self, exprs: Vec) -> Result<(), EvalError> { - let result = exprs.into_iter().try_for_each(|expr| self.eval_expr(expr)); - match result { - Ok(_) => Ok(()), - Err(err) => { - self.journal.commit(); - Err(err) - } - } - } - - /// Evaluates an expression and makes decisions on how to evaluate it - /// - Lazy expressions don't get evaluated - /// - Lists get evaluated in order - /// - Calls get run through [`Self::eval_symbol`] - pub fn eval_expr(&mut self, expr: Expr) -> Result<(), EvalError> { - match expr.clone().val { - ExprKind::Call(call) => self.eval_symbol(&expr, call), - ExprKind::Lazy(block) => self.push(*block), - ExprKind::List(list) => { - let stack_len = self.stack.len(); - - self.eval(list)?; - - let list_len = self.stack.len() - stack_len; - - let mut list = iter::repeat_with(|| self.pop(&expr).unwrap()) - .take(list_len) - .collect::>(); - list.reverse(); - - self.push(Expr { - val: ExprKind::List(list), - debug_data: expr.debug_data, - }) - } - ExprKind::Fn(_) => Ok(()), - _ => self.push(expr), - } - } - - /// Makes decisions for how to evaluate a symbol (calls) such as - /// - Running an intrinsic - /// - Getting the value from the scope - /// - Calling functions through [`Self::auto_call`] - fn eval_symbol( - &mut self, - trace_expr: &Expr, - symbol: Intern, - ) -> Result<(), EvalError> { - let symbol_str = symbol.as_ref(); - - if self.debug { - self.journal.commit(); - } - - if let Some(func) = self.funcs.get(&symbol) { - if self.debug { - self.journal.op(JournalOp::FnCall(trace_expr.clone())); - } - let result = func(self, trace_expr); - if self.debug && result.is_ok() { - self.journal.commit(); - } - - return result; - } - - if let Some(value) = self.scope_item(symbol_str) { - if value.val.is_function() { - self.auto_call(trace_expr, value) - } else { - if self.debug { - self.journal.op(JournalOp::Call(trace_expr.clone())); - } - let result = self.push(value); - if self.debug { - self.journal.commit(); - } - - result - } - } else { - Err(EvalError { - kind: EvalErrorKind::UnknownCall, - expr: Some(trace_expr.clone()), - }) - } - } - - /// Handles auto-calling symbols (calls) when they're pushed to the stack - /// This is also triggered by the `call` keyword - pub fn auto_call( - &mut self, - trace_expr: &Expr, - expr: Expr, - ) -> Result<(), EvalError> { - match expr.val { - ExprKind::Call(_) => self.eval_expr(expr), - item @ ExprKind::List(_) => match item.is_function() { - true => { - if self.debug { - self.journal.op(JournalOp::FnCall(trace_expr.clone())); - } - - let fn_symbol = item.fn_symbol().unwrap(); - let fn_body = item.fn_body().unwrap(); - - if fn_symbol.scoped { - self.push_scope(fn_symbol.scope.clone()); - } - - if self.debug { - self.journal.commit(); - self.journal.op(JournalOp::FnStart(fn_symbol.scoped)); - } - - match self.eval(fn_body.to_vec()) { - Ok(_) => { - if self.debug { - self.journal.commit(); - self.journal.op(JournalOp::FnEnd); - } - - if fn_symbol.scoped { - self.pop_scope(); - } - - Ok(()) - } - Err(err) => Err(err), - } - } - false => { - let ExprKind::List(list) = item else { - unreachable!() - }; - self.eval(list) - } - }, - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::Set(vec![ - Type::Call, - Type::List(vec![Type::FnScope, Type::Any]), - ]), - expr.val.type_of(), - ), - }), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{simple_exprs, TestExpr}; - - #[test] - fn implicitly_adds_to_stack() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Integer(1), TestExpr::Integer(2)] - ); - } - - #[test] - fn add_two_numbers() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 +").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(3)]); - } - - #[test] - fn subtract_two_numbers() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 -").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(-1)]); - } - - #[test] - fn multiply_two_numbers() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 *").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(2)]); - } - - #[test] - fn divide_two_numbers() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 /").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(0)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 2.0 /").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Float(0.5)]); - } - - #[test] - fn modulo_two_numbers() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("10 5 %").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(0)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("11 5 %").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(1)]); - } - - #[test] - fn complex_operations() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 + 3 *").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(9)]); - } - - #[test] - fn eval_from_stack() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("'(1 2 +) unwrap call").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(3)]); - } - - #[test] - fn dont_eval_skips() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("6 'var def 'var").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Call(Intern::from_ref("var"))] - ); - } - - #[test] - fn eval_lists() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2 3)").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ])] - ); - } - - #[test] - fn eval_lists_eagerly() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("6 'var def (var)").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![TestExpr::Integer(6)])] - ); - } -} diff --git a/src/expr.rs b/src/expr.rs deleted file mode 100644 index 4840f830..00000000 --- a/src/expr.rs +++ /dev/null @@ -1,604 +0,0 @@ -use core::{ - any::Any, cell::RefCell, cmp::Ordering, fmt, iter, num::FpCategory, -}; -use std::{fmt::Debug, rc::Rc}; - -use internment::Intern; -use termion::color; - -use crate::{Scope, Span}; - -#[derive(Clone, PartialEq)] -pub struct FnSymbol { - pub scoped: bool, - pub scope: Scope, -} - -impl fmt::Debug for FnSymbol { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FnSymbol") - .field("scoped", &self.scoped) - .finish() - } -} - -#[derive(Debug, Clone)] -pub enum ExprKind { - Nil, - - Boolean(bool), - Integer(i64), - Float(f64), - - String(String), - List(Vec), - - Lazy(Box), - Call(Intern), - - /// Boolean denotes whether to create a new scope. - Fn(FnSymbol), - - UserData(Rc>), -} - -impl ExprKind { - pub fn unlazy(&self) -> &ExprKind { - match self { - ExprKind::Lazy(x) => x.val.unlazy(), - x => x, - } - } - - pub fn unlazy_mut(&mut self) -> &mut ExprKind { - match self { - ExprKind::Lazy(x) => x.val.unlazy_mut(), - x => x, - } - } - - pub fn is_truthy(&self) -> bool { - match self.to_boolean() { - Some(Self::Boolean(x)) => x, - _ => false, - } - } - - pub const fn is_nil(&self) -> bool { - matches!(*self, Self::Nil) - } - - pub fn is_function(&self) -> bool { - match self { - Self::List(list) => list - .first() - .map(|x| { - matches!( - x, - Expr { - val: Self::Fn(_), - .. - } - ) - }) - .unwrap_or(false), - _ => false, - } - } - - pub fn fn_symbol(&self) -> Option<&FnSymbol> { - match self { - Self::List(list) => list.first().and_then(|x| match &x.val { - Self::Fn(scope) => Some(scope), - _ => None, - }), - _ => None, - } - } - - pub fn fn_body(&self) -> Option<&[Expr]> { - match self { - Self::List(list) => list.first().and_then(|x| match x.val { - Self::Fn(_) => Some(&list[1..]), - _ => None, - }), - _ => None, - } - } - - pub fn type_of(&self) -> Type { - match self { - Self::Nil => Type::Nil, - - Self::Boolean(_) => Type::Boolean, - Self::Integer(_) => Type::Integer, - Self::Float(_) => Type::Float, - - Self::String(_) => Type::String, - - Self::List(list) => Type::List( - list - .iter() - .map(|expr| expr.val.type_of()) - .collect::>(), - ), - - Self::Lazy(x) => x.val.type_of(), - Self::Call(_) => Type::Call, - - Self::Fn(_) => Type::FnScope, - - Self::UserData(_) => Type::UserData, - } - } - - pub fn coerce_same(&self, other: &Self) -> Option<(ExprKind, ExprKind)> { - match self { - x @ Self::Boolean(_) => Some(x.clone()).zip(other.to_boolean()), - x @ Self::Integer(_) => Some(x.clone()).zip(other.to_integer()), - x @ Self::Float(_) => Some(x.clone()).zip(other.to_float()), - _ => None, - } - } - - pub fn coerce_same_float( - &self, - other: &Self, - ) -> Option<(ExprKind, ExprKind)> { - match (self, other) { - (lhs @ Self::Float(_), rhs) => Some(lhs.clone()).zip(rhs.to_float()), - (lhs, rhs @ Self::Float(_)) => lhs.to_float().zip(Some(rhs.clone())), - _ => self.coerce_same(other), - } - } - - pub fn to_boolean(&self) -> Option { - match self { - Self::Nil => Some(Self::Boolean(false)), - - x @ Self::Boolean(_) => Some(x.clone()), - Self::Integer(x) => Some(Self::Boolean(*x != 0)), - - _ => None, - } - } - - pub fn to_integer(&self) -> Option { - match self { - Self::Boolean(x) => Some(Self::Integer(if *x { 1 } else { 0 })), - x @ Self::Integer(_) => Some(x.clone()), - Self::Float(x) => { - let x = x.floor(); - - match x.classify() { - FpCategory::Zero => Some(Self::Integer(0)), - FpCategory::Normal => { - if x >= i64::MIN as f64 && x <= i64::MAX as f64 { - Some(Self::Integer(x as i64)) - } else { - None - } - } - _ => None, - } - } - - // Self::String(x) => x.parse().ok().map(Self::Integer), - _ => None, - } - } - - pub fn to_float(&self) -> Option { - match self { - Self::Integer(x) => Some(Self::Float(*x as f64)), - x @ Self::Float(_) => Some(x.clone()), - - // Self::String(x) => x.parse().ok().map(Self::Float), - _ => None, - } - } - - pub fn into_expr(self, debug_data: DebugData) -> Expr { - Expr { - val: self, - debug_data, - } - } - - // TODO: These might make more sense as intrinsics, since they might be too - // complicated for coercions. - - // pub const fn to_list(&self) -> Option { - // match self { - // x @ Self::List(_) => Some(x.clone()), - // // TODO: Implement a way to convert a string into a list of its characters - // // in the language itself. - // Self::String(x) => Some(Self::List( - // x.bytes() - // .map(|x| x as i64) - // .map(Self::Integer) - // .map(Expr::new) - // .collect_vec() - // .into(), - // )), - - // x => Some(Self::List([Expr::new(x.clone())].into())), - // } - // } - - // pub const fn to_string(&self) -> Option { - // match self { - // Self::List(x) => { - // x.iter() - // .map(|Expr(expr)| expr.borrow()) - // .map(|expr| match *expr { - // Self::Integer(x) => if x >= u8::MIN as i64 && x <= u8::MAX as i64 { - // Ok(x as u8) - // } else { - // Err(()) - // }, - // _ => Err(()), - // }) - // .try_collect::<_, Vec<_>, _>() - // .ok() - // .and_then(|bytes| core::str::from_utf8(&bytes).ok()) - // .map(|x| ExprKind::String(x.into())) - // }, - - // _ => None, - // } - // } -} - -impl PartialEq for ExprKind { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - // Same types. - (Self::Nil, Self::Nil) => true, - - (Self::Boolean(lhs), Self::Boolean(rhs)) => lhs == rhs, - (Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs, - (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs, - - (Self::String(lhs), Self::String(rhs)) => lhs == rhs, - - (Self::List(lhs), Self::List(rhs)) => { - if lhs.len() != rhs.len() { - return false; - } - - lhs.iter().zip(rhs).all(|(lhs, rhs)| lhs.val == rhs.val) - } - - (Self::Lazy(lhs), Self::Lazy(rhs)) => lhs.val == rhs.val, - (Self::Call(lhs), Self::Call(rhs)) => lhs == rhs, - - (Self::Fn(lhs), Self::Fn(rhs)) => lhs == rhs, - - (Self::UserData(lhs), Self::UserData(rhs)) => { - core::ptr::addr_eq(Rc::as_ptr(lhs), Rc::as_ptr(rhs)) - } - - // Different types. - (lhs @ Self::Boolean(_), rhs) => match rhs.to_boolean() { - Some(rhs) => *lhs == rhs, - None => false, - }, - (lhs, rhs @ Self::Float(_)) => match lhs.to_float() { - Some(lhs) => lhs == *rhs, - None => false, - }, - (lhs @ Self::Integer(_), rhs) => match rhs.to_integer() { - Some(rhs) => *lhs == rhs, - None => false, - }, - (lhs @ Self::Float(_), rhs) => match rhs.to_float() { - Some(rhs) => *lhs == rhs, - None => false, - }, - - _ => false, - } - } -} - -impl PartialOrd for ExprKind { - fn partial_cmp(&self, other: &Self) -> Option { - match (self, other) { - // Same types. - (Self::Nil, Self::Nil) => Some(Ordering::Equal), - (Self::Integer(lhs), Self::Integer(rhs)) => lhs.partial_cmp(rhs), - (Self::Float(lhs), Self::Float(rhs)) => lhs.partial_cmp(rhs), - - (Self::List(lhs), Self::List(rhs)) => lhs.partial_cmp(rhs), - (Self::String(lhs), Self::String(rhs)) => lhs.partial_cmp(rhs), - - (Self::Lazy(lhs), Self::Lazy(rhs)) => lhs.partial_cmp(rhs), - (Self::Call(lhs), Self::Call(rhs)) => lhs.partial_cmp(rhs), - - (Self::Fn(lhs), Self::Fn(rhs)) => lhs.scoped.partial_cmp(&rhs.scoped), - - // Different types. - (lhs @ Self::Boolean(_), rhs) => match rhs.to_boolean() { - Some(rhs) => lhs.partial_cmp(&rhs), - None => None, - }, - (lhs, rhs @ Self::Float(_)) => match lhs.to_float() { - Some(lhs) => lhs.partial_cmp(rhs), - None => None, - }, - (lhs @ Self::Integer(_), rhs) => match rhs.to_integer() { - Some(rhs) => lhs.partial_cmp(&rhs), - None => None, - }, - (lhs @ Self::Float(_), rhs) => match rhs.to_float() { - Some(rhs) => lhs.partial_cmp(&rhs), - None => None, - }, - - _ => None, - } - } -} - -impl fmt::Display for ExprKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Nil => f.write_str("nil"), - - Self::Boolean(x) => fmt::Display::fmt(x, f), - Self::Integer(x) => fmt::Display::fmt(x, f), - Self::Float(x) => fmt::Display::fmt(x, f), - - Self::String(x) => write!(f, "\"{}\"", x), - - Self::List(x) => { - f.write_str("(")?; - - iter::once("") - .chain(iter::repeat(" ")) - .zip(x.iter()) - .try_for_each(|(s, x)| { - f.write_str(s)?; - fmt::Display::fmt(x, f) - })?; - - f.write_str(")") - } - - Self::Lazy(x) => { - f.write_str("'")?; - fmt::Display::fmt(x, f) - } - Self::Call(x) => f.write_str(x.as_ref()), - - Self::Fn(_) => f.write_str("fn"), - - Self::UserData(_) => f.write_str("userdata"), - } - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Default)] -pub struct DebugData { - pub source_file: Option>, - pub span: Option, -} - -impl DebugData { - pub fn new(source_file: Option>, span: Option) -> Self { - Self { source_file, span } - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct Expr { - pub val: ExprKind, - pub debug_data: DebugData, -} - -impl fmt::Display for Expr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.val) - } -} - -impl From for ExprKind { - fn from(value: Expr) -> Self { - value.val - } -} - -impl Expr { - pub fn into_expr_kind(self) -> ExprKind { - self.into() - } - - pub fn to_pretty_string(&self) -> String { - let reset = color::Fg(color::Reset); - let result = match &self.val { - ExprKind::Nil => format!("{}nil", color::Fg(color::White)), - - ExprKind::Boolean(x) => { - format!("{}{}", color::Fg(color::Yellow), x) - } - ExprKind::Integer(x) => format!("{}{}", color::Fg(color::Yellow), x), - ExprKind::Float(x) => format!("{}{}", color::Fg(color::Yellow), x), - - ExprKind::String(x) => { - format!("{}\"{}\"", color::Fg(color::Green), x) - } - - ExprKind::List(x) => { - let mut string = String::new(); - string.push('('); - - iter::once("") - .chain(iter::repeat(" ")) - .zip(x.iter()) - .for_each(|(s, x)| { - string.push_str(s); - string.push_str(&x.to_pretty_string()) - }); - - string.push(')'); - - string - } - - ExprKind::Lazy(x) => { - format!("'{}", x.to_pretty_string()) - } - ExprKind::Call(x) => { - format!("{}{}", color::Fg(color::Blue), x.as_ref()) - } - - ExprKind::Fn(_) => format!("{}fn", color::Fg(color::Blue)), - - ExprKind::UserData(_) => format!("{}userdata", color::Fg(color::Blue)), - }; - - format!("{}{}", result, reset) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Type { - Nil, - - Boolean, - Integer, - Float, - - Pointer, - - String, - - List(Vec), - - Call, - - FnScope, - ScopePush, - ScopePop, - - Any, - Set(Vec), - - UserData, -} - -impl fmt::Display for Type { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Nil => f.write_str("nil"), - - Self::Boolean => f.write_str("boolean"), - Self::Integer => f.write_str("integer"), - Self::Float => f.write_str("float"), - - Self::Pointer => f.write_str("pointer"), - - Self::String => f.write_str("string"), - - Self::List(list) => { - f.write_str("(")?; - - iter::once("") - .chain(iter::repeat(" ")) - .zip(list.iter()) - .try_for_each(|(sep, ty)| { - f.write_str(sep)?; - fmt::Display::fmt(ty, f) - })?; - - f.write_str(")") - } - - Self::Call => f.write_str("call"), - - Self::FnScope => f.write_str("fn"), - Self::ScopePush => f.write_str("scope_push"), - Self::ScopePop => f.write_str("scope_pop"), - - Self::Any => f.write_str("any"), - Self::Set(set) => { - f.write_str("[")?; - - iter::once("") - .chain(iter::repeat(" ")) - .zip(set.iter()) - .try_for_each(|(sep, ty)| { - f.write_str(sep)?; - fmt::Display::fmt(ty, f) - })?; - - f.write_str("]") - } - - Self::UserData => f.write_str("userdata"), - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - use test_case::test_case; - - #[test_case(ExprKind::Nil => Some(ExprKind::Boolean(false)))] - #[test_case(ExprKind::Boolean(false) => Some(ExprKind::Boolean(false)))] - #[test_case(ExprKind::Boolean(true) => Some(ExprKind::Boolean(true)))] - #[test_case(ExprKind::Integer(0) => Some(ExprKind::Boolean(false)))] - #[test_case(ExprKind::Integer(1) => Some(ExprKind::Boolean(true)))] - #[test_case(ExprKind::Integer(i64::MIN) => Some(ExprKind::Boolean(true)))] - #[test_case(ExprKind::Integer(i64::MAX) => Some(ExprKind::Boolean(true)))] - #[test_case(ExprKind::Float(0.0) => None)] - #[test_case(ExprKind::Float(1.0) => None)] - #[test_case(ExprKind::Float(f64::MIN) => None)] - #[test_case(ExprKind::Float(f64::MAX) => None)] - #[test_case(ExprKind::Float(f64::NEG_INFINITY) => None)] - #[test_case(ExprKind::Float(f64::INFINITY) => None)] - #[test_case(ExprKind::Float(f64::NAN) => None)] - fn to_boolean(expr: ExprKind) -> Option { - expr.to_boolean() - } - - #[test_case(ExprKind::Nil => None)] - #[test_case(ExprKind::Boolean(false) => Some(ExprKind::Integer(0)))] - #[test_case(ExprKind::Boolean(true) => Some(ExprKind::Integer(1)))] - #[test_case(ExprKind::Integer(0) => Some(ExprKind::Integer(0)))] - #[test_case(ExprKind::Integer(1) => Some(ExprKind::Integer(1)))] - #[test_case(ExprKind::Integer(i64::MIN) => Some(ExprKind::Integer(i64::MIN)))] - #[test_case(ExprKind::Integer(i64::MAX) => Some(ExprKind::Integer(i64::MAX)))] - #[test_case(ExprKind::Float(f64::MIN) => None)] - #[test_case(ExprKind::Float(f64::MAX) => None)] - #[test_case(ExprKind::Float(f64::NEG_INFINITY) => None)] - #[test_case(ExprKind::Float(f64::INFINITY) => None)] - #[test_case(ExprKind::Float(f64::NAN) => None)] - #[test_case(ExprKind::Float(0.0) => Some(ExprKind::Integer(0)))] - #[test_case(ExprKind::Float(1.0) => Some(ExprKind::Integer(1)))] - fn to_integer(expr: ExprKind) -> Option { - expr.to_integer() - } - - #[test_case(ExprKind::Nil => None)] - #[test_case(ExprKind::Boolean(false) => None)] - #[test_case(ExprKind::Boolean(true) => None)] - #[test_case(ExprKind::Integer(0) => Some(ExprKind::Float(0.0)))] - #[test_case(ExprKind::Integer(1) => Some(ExprKind::Float(1.0)))] - #[test_case(ExprKind::Integer(i64::MIN) => Some(ExprKind::Float(i64::MIN as f64)))] - #[test_case(ExprKind::Integer(i64::MAX) => Some(ExprKind::Float(i64::MAX as f64)))] - #[test_case(ExprKind::Float(f64::MIN) => Some(ExprKind::Float(f64::MIN)))] - #[test_case(ExprKind::Float(f64::MAX) => Some(ExprKind::Float(f64::MAX)))] - #[test_case(ExprKind::Float(f64::NEG_INFINITY) => Some(ExprKind::Float(f64::NEG_INFINITY)))] - #[test_case(ExprKind::Float(f64::INFINITY) => Some(ExprKind::Float(f64::INFINITY)))] - // NOTE: NaN cannot be equality checked. - // #[test_case(ExprKind::Float(f64::NAN) => Some(ExprKind::Float(f64::NAN)))] - #[test_case(ExprKind::Float(0.0) => Some(ExprKind::Float(0.0)))] - #[test_case(ExprKind::Float(1.0) => Some(ExprKind::Float(1.0)))] - fn to_float(expr: ExprKind) -> Option { - expr.to_float() - } -} diff --git a/src/generate_highlighter.rs b/src/generate_highlighter.rs deleted file mode 100644 index 69a9a84c..00000000 --- a/src/generate_highlighter.rs +++ /dev/null @@ -1,26 +0,0 @@ -use std::fs; - -use enum_iterator::all; -use stack::Intrinsic; - -fn main() { - let string = include_str!("template.tmLanguage.json"); - // let items = all::().join("|"); - let mut items: Vec = Vec::new(); - - for intrinsic in all::() { - if let Intrinsic::Syscall { arity } = intrinsic { - if arity > 6 { - continue; - } - } - - items.push(intrinsic.as_str().to_string()); - } - - let items = items.join("|"); - - let string = string.replace("${template}", &items); - - fs::write("theme.tmLanguage.json", string).unwrap(); -} diff --git a/src/lexer.rs b/src/lexer.rs deleted file mode 100644 index b5651585..00000000 --- a/src/lexer.rs +++ /dev/null @@ -1,570 +0,0 @@ -use core::fmt; - -use internment::Intern; - -/// Converts a [`Lexer`] into a lazy [Vec]\<[Token]\>. -/// -/// This is useful in contexts where back-tracking or look-ahead is required, -/// since it collects [`Token`]s from the [`Lexer`] as needed. -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct TokenVec<'source> { - lexer: Lexer<'source>, - tokens: Vec, - eoi: Option, -} - -impl<'source> TokenVec<'source> { - /// Creates a new [`TokenVec`]. - /// - /// Prefer [`TokenVec::reuse`] where possible. - #[inline] - pub const fn new(lexer: Lexer<'source>) -> Self { - Self { - lexer, - tokens: Vec::new(), - eoi: None, - } - } - - /// Creates a [`TokenVec`] by re-using the allocations of an existing one. - #[inline] - pub fn reuse(&mut self, lexer: Lexer<'source>) { - self.lexer = lexer; - self.tokens.clear(); - } - - /// Returns a [`Token`] at the index. - /// - /// If the index is out of bounds, this returns the [`Token`] at the end index. - pub fn token(&mut self, mut index: usize) -> Token { - // Clamp the upper bound to the end of input index, if smaller. - index = index.min(self.eoi.unwrap_or(usize::MAX)); - - match self.tokens.get(index) { - Some(token) => token.clone(), - None => loop { - let token = self.lexer.next(); - self.tokens.push(token.clone()); - - if token.kind == TokenKind::Eoi { - self.eoi = Some(self.tokens.len() - 1); - break token; - } - - if self.tokens.len() - 1 == index { - break token; - } - }, - } - } -} - -/// Converts a source code &[str] into a stream of [`Token`]s. -/// -/// This produces a concrete [`Token`] stream, such that every character in the -/// source code is represented by a [`Token`], and none are disregarded. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Lexer<'source> { - pub source: &'source str, - cursor: usize, -} - -impl<'source> Lexer<'source> { - /// Creates a new [`Lexer`]. - #[inline] - pub fn new(source: &'source str) -> Self { - // Skip the UTF-8 BOM if present. - #[allow(clippy::obfuscated_if_else)] - let start = source - .as_bytes() - .starts_with(b"\xef\xbb\xbf") - .then_some(3) - .unwrap_or(0); - - Self { - source, - cursor: start, - } - } - - /// Returns the next [`Token`]. - /// - /// Once the first [`TokenKind::Eoi`] has been returned, it will continue to - /// return them thereafter, akin to a [`FusedIterator`]. - /// - /// [`FusedIterator`]: core::iter::FusedIterator - #[allow(clippy::should_implement_trait)] - // ^ This is fine. If it acts like an iterator, it's an iterator. - pub fn next(&mut self) -> Token { - let mut state = State::Start; - let mut chars = self.source[self.cursor..].chars(); - - let start = self.cursor; - - loop { - let char = chars.next().unwrap_or('\0'); - let char_width = char.len_utf8(); - - match state { - State::Start => match char { - '\0' if self.cursor == self.source.len() => { - break Token { - kind: TokenKind::Eoi, - span: Span { - start, - end: self.cursor, - }, - }; - } - // TODO: Remove square brackets from this once they become semantic. - ' ' | '\t' | '\r' | '\n' | '[' | ']' => state = State::Whitespace, - ';' => state = State::Comment, - '0'..='9' => state = State::Int, - '"' => state = State::String, - 'a'..='z' - | 'A'..='Z' - | '_' - | '+' - | '*' - | '/' - | ':' - | '%' - | '!' - | '=' - | '<' - | '>' - | '?' => state = State::Ident, - '\'' => { - self.cursor += char_width; - - break Token { - kind: TokenKind::Apostrophe, - span: Span { - start, - end: self.cursor, - }, - }; - } - '(' => { - self.cursor += char_width; - - break Token { - kind: TokenKind::ParenOpen, - span: Span { - start, - end: self.cursor, - }, - }; - } - ')' => { - self.cursor += char_width; - - break Token { - kind: TokenKind::ParenClose, - span: Span { - start, - end: self.cursor, - }, - }; - } - '-' => state = State::Hyphen, - _ => state = State::Invalid, - }, - State::Invalid => match char { - '\0' | ' ' | '\t' | '\r' | '\n' | '(' | ')' | '{' | '}' | '[' - | ']' | '"' => { - break Token { - kind: TokenKind::Invalid, - span: Span { - start, - end: self.cursor, - }, - }; - } - _ => {} - }, - State::Whitespace => match char { - ' ' | '\t' | '\r' | '\n' => {} - _ => { - break Token { - kind: TokenKind::Whitespace, - span: Span { - start, - end: self.cursor, - }, - }; - } - }, - State::Comment => match char { - '\0' | '\n' => { - break Token { - kind: TokenKind::Comment, - span: Span { - start, - end: self.cursor, - }, - }; - } - _ => {} - }, - State::Int => match char { - '0'..='9' => {} - '.' => state = State::Float, - _ => { - let slice = &self.source[start..self.cursor]; - let span = Span { - start, - end: self.cursor, - }; - - break match slice.parse() { - Ok(value) => Token { - kind: TokenKind::Integer(value), - span, - }, - Err(_) => Token { - kind: TokenKind::Invalid, - span, - }, - }; - } - }, - State::Float => match char { - '0'..='9' => {} - _ => { - let slice = &self.source[start..self.cursor]; - let span = Span { - start, - end: self.cursor, - }; - - break match slice.parse() { - Ok(value) => Token { - kind: TokenKind::Float(value), - span, - }, - Err(_) => Token { - kind: TokenKind::Invalid, - span, - }, - }; - } - }, - State::String => match char { - '\0' => { - break Token { - kind: TokenKind::Invalid, - span: Span { - start, - end: self.cursor, - }, - }; - } - '\\' => state = State::StringBackslash, - '"' => { - // Since this is a concrete token stream, the quotes are - // included in the length. However, we only want to - // intern the inner slice. - let slice = &self.source[start + 1..self.cursor]; - self.cursor += 1; - - break Token { - kind: TokenKind::String(slice.into()), - span: Span { - start, - end: self.cursor, - }, - }; - } - _ => {} - }, - State::StringBackslash => match char { - 'n' | 'r' | 't' | '0' => state = State::String, - _ => state = State::StringInvalid, - }, - State::StringInvalid => match char { - '\0' | '"' => { - break Token { - kind: TokenKind::Invalid, - span: Span { - start, - end: self.cursor, - }, - }; - } - _ => {} - }, - State::Ident => match char { - 'a'..='z' - | 'A'..='Z' - | '0'..='9' - | '_' - | '+' - | '-' - | '*' - | '/' - | '%' - | '=' - | '!' - | '&' - | '|' - | '<' - | '>' - | '?' - | '$' - | '~' - | '^' => {} - _ => { - let slice = &self.source[start..self.cursor]; - let ident = Intern::from_ref(slice); - - let kind = match ident { - ident if ident == Intern::from_ref("nil") => TokenKind::Nil, - ident if ident == Intern::from_ref("false") => { - TokenKind::Boolean(false) - } - ident if ident == Intern::from_ref("true") => { - TokenKind::Boolean(true) - } - ident if ident == Intern::from_ref("fn") => TokenKind::Fn, - ident if ident == Intern::from_ref("fn!") => { - TokenKind::FnExclamation - } - ident => TokenKind::Ident(ident), - }; - - break Token { - kind, - span: Span { - start, - end: self.cursor, - }, - }; - } - }, - State::Hyphen => match char { - '0'..='9' => state = State::Int, - 'a'..='z' - | 'A'..='Z' - | '_' - | '+' - | '-' - | '*' - | '/' - | '%' - | '=' - | '!' - | '&' - | '|' - | '<' - | '>' - | '?' - | '$' - | '~' - | '^' => state = State::Ident, - _ => { - break Token { - kind: TokenKind::Ident(Intern::from_ref("-")), - span: Span { - start, - end: self.cursor, - }, - }; - } - }, - } - - self.cursor += char_width; - } - } -} - -/// Contains information about a source code token. -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub struct Token { - /// The lexeme kind. - pub kind: TokenKind, - /// The [`Span`] in bytes that this token represents. - pub span: Span, -} - -/// [`Token`] lexeme kinds. -#[derive(Debug, Clone, PartialEq, PartialOrd)] -pub enum TokenKind { - /// Invalid sequence of [`char`]s. - Invalid, - /// End of input. - Eoi, - - /// Sequence of whitespace [`char`]s. - Whitespace, - /// Semicolon until the next newline or end of input. - Comment, - - /// Boolean literal. - Boolean(bool), - /// 64-bit signed integer literal. - Integer(i64), - /// 64-bit floating-point literal. - Float(f64), - /// Sequence of [`char`]s delimited by double-quotes (`"`). - String(String), - - /// Sequence of identifier [`char`]s. - Ident(Intern), - - /// `'` symbol. - Apostrophe, - /// `(` symbol. - ParenOpen, - /// `)` symbol. - ParenClose, - // /// `[` symbol. - // SquareOpen, - // /// `]` symbol. - // SquareClose, - /// `nil` keyword. - Nil, - /// `fn` keyword. - Fn, - /// `fn!` keyword. - FnExclamation, -} - -impl fmt::Display for TokenKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Invalid => f.write_str("invalid"), - Self::Eoi => f.write_str("end of input"), - - Self::Whitespace => f.write_str("whitespace"), - Self::Comment => f.write_str("comment"), - - // TODO: Should this display the kind of token too? - Self::Boolean(x) => fmt::Display::fmt(x, f), - // TODO: Should this display the kind of token too? - Self::Integer(x) => fmt::Display::fmt(x, f), - // TODO: Should this display the kind of token too? - Self::Float(x) => fmt::Display::fmt(x, f), - // TODO: Should this display the kind of token too? - Self::String(x) => fmt::Display::fmt(x, f), - - // TODO: Should this display the kind of token too? - Self::Ident(x) => fmt::Display::fmt(x.as_ref(), f), - - Self::Apostrophe => f.write_str("'"), - Self::ParenOpen => f.write_str("("), - Self::ParenClose => f.write_str(")"), - // Self::SquareOpen => f.write_str("["), - // Self::SquareClose => f.write_str("]"), - Self::Nil => f.write_str("nil"), - Self::Fn => f.write_str("fn"), - Self::FnExclamation => f.write_str("fn!"), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Span { - /// The lower bound (inclusive). - pub start: usize, - /// The upper bound (exclusive). - pub end: usize, -} - -impl Span { - pub fn new(start: usize, end: usize) -> Self { - Span { start, end } - } -} - -impl fmt::Display for Span { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}..{}", self.start, self.end) - } -} - -enum State { - Start, - Invalid, - Whitespace, - Comment, - Int, - Float, - String, - StringBackslash, - StringInvalid, - Ident, - Hyphen, -} - -#[cfg(test)] -mod test { - use super::*; - - use test_case::test_case; - - #[test_case("" => vec![Token { kind: TokenKind::Eoi, span: Span { start: 0, end: 0 } }] ; "empty")] - #[test_case(" \t\r\n" => vec![Token { kind: TokenKind::Whitespace, span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eoi, span: Span { start: 4, end: 4 } }] ; "whitespace")] - #[test_case("ßℝ💣" => vec![Token { kind: TokenKind::Invalid, span: Span { start: 0, end: 9 } }, Token { kind: TokenKind::Eoi, span: Span { start: 9, end: 9 } }] ; "invalid eoi")] - #[test_case("ßℝ💣\n" => vec![Token { kind: TokenKind::Invalid, span: Span { start: 0, end: 9 } }, Token { kind: TokenKind::Whitespace, span: Span { start: 9, end: 10 } }, Token { kind: TokenKind::Eoi, span: Span { start: 10, end: 10 } }] ; "invalid whitespace")] - #[test_case("; Comment" => vec![Token { kind: TokenKind::Comment, span: Span { start: 0, end: 9 } }, Token { kind: TokenKind::Eoi, span: Span { start: 9, end: 9 }}] ; "comment eoi")] - #[test_case("; Comment\n" => vec![Token { kind: TokenKind::Comment, span: Span { start: 0, end: 9 } }, Token { kind: TokenKind::Whitespace, span: Span { start: 9, end: 10 } }, Token { kind: TokenKind::Eoi, span: Span { start: 10, end: 10 } }] ; "comment whitespace")] - #[test_case("123" => vec![Token { kind: TokenKind::Integer(123), span: Span { start: 0, end: 3 } }, Token { kind: TokenKind::Eoi, span: Span { start: 3, end: 3 } }] ; "integer eoi")] - #[test_case("-123" => vec![Token { kind: TokenKind::Integer(-123), span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eoi, span: Span { start: 4, end: 4 } }] ; "negative integer eoi")] - #[test_case("123." => vec![Token { kind: TokenKind::Float(123.0), span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eoi, span: Span { start: 4, end: 4 } }] ; "float without fractional eoi")] - #[test_case("-123." => vec![Token { kind: TokenKind::Float(-123.0), span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eoi, span: Span { start: 5, end: 5 } }] ; "negative float without fractional eoi")] - #[test_case("123.456" => vec![Token { kind: TokenKind::Float(123.456), span: Span { start: 0, end: 7 } }, Token { kind: TokenKind::Eoi, span: Span { start: 7, end: 7 } }] ; "float with fractional eoi")] - #[test_case("-123.456" => vec![Token { kind: TokenKind::Float(-123.456), span: Span { start: 0, end: 8 } }, Token { kind: TokenKind::Eoi, span: Span { start: 8, end: 8 } }] ; "negative float with fractional eoi")] - #[test_case("\"hello\"" => vec![Token { kind: TokenKind::String("hello".into()), span: Span { start: 0, end: 7 } }, Token { kind: TokenKind::Eoi, span: Span { start: 7, end: 7 } }] ; "string eoi")] - #[test_case("\"he\\tlo\"" => vec![Token { kind: TokenKind::String("he\\tlo".into()), span: Span { start: 0, end: 8 } }, Token { kind: TokenKind::Eoi, span: Span { start: 8, end: 8 } }] ; "string escape eoi")] - #[test_case("\"hello" => vec![Token { kind: TokenKind::Invalid, span: Span { start: 0, end: 6 } }, Token { kind: TokenKind::Eoi, span: Span { start: 6, end: 6 } }] ; "string missing end quote eoi")] - #[test_case("\"hello\n" => vec![Token { kind: TokenKind::Invalid, span: Span { start: 0, end: 7 } }, Token { kind: TokenKind::Eoi, span: Span { start: 7, end: 7 } }] ; "string missing end quote whitespace")] - #[test_case("false" => vec![Token { kind: TokenKind::Boolean(false), span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eoi, span: Span { start: 5, end: 5 } }] ; "boolean false eoi")] - #[test_case("true" => vec![Token { kind: TokenKind::Boolean(true), span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eoi, span: Span { start: 4, end: 4 } }] ; "boolean true eoi")] - #[test_case("hello" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("hello")), span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eoi, span: Span { start: 5, end: 5 } }] ; "ident eoi")] - #[test_case("-hello" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("-hello")), span: Span { start: 0, end: 6 } }, Token { kind: TokenKind::Eoi, span: Span { start: 6, end: 6 } }] ; "ident starting hypen eoi")] - #[test_case("hey-o" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("hey-o")), span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eoi, span: Span { start: 5, end: 5 } }] ; "kebab ident eoi")] - #[test_case("+" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("+")), span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "plus ident eoi")] - #[test_case("-" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("-")), span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "minus ident eoi")] - #[test_case("!=" => vec![Token { kind: TokenKind::Ident(Intern::from_ref("!=")), span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eoi, span: Span { start: 2, end: 2 } }] ; "exclamation equals ident eoi")] - #[test_case("'" => vec![Token { kind: TokenKind::Apostrophe, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "apostrophe eoi")] - #[test_case("(" => vec![Token { kind: TokenKind::ParenOpen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "paren open eoi")] - #[test_case(")" => vec![Token { kind: TokenKind::ParenClose, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "paren close eoi")] - #[test_case("()" => vec![Token { kind: TokenKind::ParenOpen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::ParenClose, span: Span { start: 1, end: 2 } }, Token { kind: TokenKind::Eoi, span: Span { start: 2, end: 2 } }] ; "paren open close eoi")] - // #[test_case("[" => vec![Token { kind: TokenKind::SquareOpen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "square open eoi")] - // #[test_case("]" => vec![Token { kind: TokenKind::SquareClose, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eoi, span: Span { start: 1, end: 1 } }] ; "square close eoi")] - // #[test_case("[]" => vec![Token { kind: TokenKind::SquareOpen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::SquareClose, span: Span { start: 1, end: 2 } }, Token { kind: TokenKind::Eoi, span: Span { start: 2, end: 2 } }] ; "square open close eoi")] - fn lexer(source: &str) -> Vec { - let mut lexer = Lexer::new(source); - let mut tokens = Vec::with_capacity(8); - - loop { - let token = lexer.next(); - tokens.push(token.clone()); - - if token.kind == TokenKind::Eoi { - break; - } - } - - tokens - } - - #[test_case("", 0 => Token { kind: TokenKind::Eoi, span: Span { start: 0, end: 0 } } ; "empty at 0")] - #[test_case("", 1 => Token { kind: TokenKind::Eoi, span: Span { start: 0, end: 0 } } ; "empty at 1")] - #[test_case("", 3 => Token { kind: TokenKind::Eoi, span: Span { start: 0, end: 0 } } ; "empty at 3")] - #[test_case("123", 0 => Token { kind: TokenKind::Integer(123), span: Span { start: 0, end: 3 } } ; "single at 0")] - #[test_case("123", 1 => Token { kind: TokenKind::Eoi, span: Span { start: 3, end: 3 } } ; "single at 1")] - #[test_case("123", 3 => Token { kind: TokenKind::Eoi, span: Span { start: 3, end: 3 } } ; "single at 3")] - #[test_case("hello 123", 0 => Token { kind: TokenKind::Ident(Intern::from_ref("hello")), span: Span { start: 0, end: 5 } } ; "many at 0")] - #[test_case("hello 123", 1 => Token { kind: TokenKind::Whitespace, span: Span { start: 5, end: 6 } } ; "many at 1")] - #[test_case("hello 123", 2 => Token { kind: TokenKind::Integer(123), span: Span { start: 6, end: 9 } } ; "many at 2")] - #[test_case("hello 123", 5 => Token { kind: TokenKind::Eoi, span: Span { start: 9, end: 9 } } ; "many at 4")] - #[test_case("hello 123", 5 => Token { kind: TokenKind::Eoi, span: Span { start: 9, end: 9 } } ; "many at 5")] - fn token_vec(source: &str, index: usize) -> Token { - let lexer = Lexer::new(source); - let mut token_vec = TokenVec::new(lexer); - token_vec.token(index) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 637a43cc..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -mod chain; -mod evaluator; -mod expr; -mod journal; -mod lexer; -mod module; -mod parser; -mod scope; -#[cfg(test)] -mod test_utils; - -pub use chain::*; -pub use evaluator::*; -pub use expr::*; -pub use journal::*; -pub use lexer::*; -pub use module::*; -pub use parser::*; -pub use scope::*; -#[cfg(test)] -pub use test_utils::*; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 47faa625..00000000 --- a/src/main.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::io::stdout; -use std::path::{Path, PathBuf}; - -use clap::{Parser, Subcommand}; -use crossterm::terminal::{Clear, ClearType}; -use crossterm::{cursor, execute}; -use notify::event::AccessKind; -use notify::{ - Config, EventKind, INotifyWatcher, RecommendedWatcher, RecursiveMode, Watcher, -}; - -use rustyline::error::ReadlineError; -use rustyline::DefaultEditor; -use stack::{EvalError, Program}; - -#[derive(Parser)] -#[command(author, version, about, long_about = None)] -struct Cli { - #[command(subcommand)] - command: Option, - - #[arg(long)] - no_core: bool, -} - -#[derive(Subcommand)] -enum Commands { - #[command(about = "Run a file")] - Run { - path: PathBuf, - - #[arg(short, long)] - watch: bool, - - #[arg(short, long)] - debug: bool, - }, -} - -fn eval_string(program: &Program, result: Result<(), EvalError>) { - println!(); - if let Err(err) = result { - err.print_report(program); - eprintln!("{}", program); - } else { - println!("{}", program); - } -} - -fn repl(with_core: bool) -> rustyline::Result<()> { - let mut rl = DefaultEditor::new()?; - let mut program = Program::new(); - - if with_core { - program = program - .with_core() - // .unwrap() - // .with_module(map::module) - .unwrap(); - } - - loop { - let readline = rl.readline(">> "); - match readline { - Ok(line) => { - rl.add_history_entry(line.as_str()).unwrap(); - - let result = program.eval_string(line.as_str()); - program.journal.commit(); - eval_string(&program, result); - } - Err(ReadlineError::Interrupted) => { - println!("CTRL-C"); - break; - } - Err(ReadlineError::Eof) => { - println!("CTRL-D"); - break; - } - Err(err) => { - println!("Error: {:?}", err); - break; - } - } - } - - Ok(()) -} - -fn eval_file( - path: PathBuf, - watcher: Option<&mut INotifyWatcher>, - debug: bool, - with_core: bool, -) { - let mut stdout = stdout(); - - let mut program = Program::new(); - - if debug { - program = program.with_debug(); - } - - if with_core { - program = program - .with_core() - // .unwrap() - // .with_module(map::module) - .unwrap(); - } - - if watcher.is_some() { - execute!(stdout, Clear(ClearType::All)).unwrap(); - execute!(stdout, cursor::MoveTo(0, 0)).unwrap(); - } - - let path = match path.to_str() { - Some(str) => str, - None => panic!("Failed to read file"), - }; - - let result = program.eval_file(path); - program.journal.commit(); - eval_string(&program, result); - - if let Some(watcher) = watcher { - println!(); - println!("Watching files for changes..."); - - println!(" - {}", path); - for path in program.loaded_files().filter(|p| p.ends_with(".stack")) { - println!(" - {}", path); - watcher - .watch(Path::new(path), RecursiveMode::NonRecursive) - .unwrap(); - } - } -} - -fn main() { - let cli = Cli::parse(); - - match cli.command { - Some(Commands::Run { path, watch, debug }) => match watch { - true => { - let (tx, rx) = std::sync::mpsc::channel(); - - let mut watcher = - RecommendedWatcher::new(tx, Config::default()).unwrap(); - watcher.watch(&path, RecursiveMode::NonRecursive).unwrap(); - - eval_file(path.clone(), Some(&mut watcher), debug, !cli.no_core); - for res in rx { - match res { - Ok(event) => { - if let EventKind::Access(AccessKind::Close(_)) = event.kind { - eval_file( - path.clone(), - Some(&mut watcher), - debug, - !cli.no_core, - ); - } - } - Err(error) => eprintln!("Error: {error:?}"), - } - } - } - false => eval_file(path, None, debug, !cli.no_core), - }, - None => { - println!("Running REPL"); - repl(!cli.no_core).unwrap(); - } - } -} diff --git a/src/module/core/cast.rs b/src/module/core/cast.rs deleted file mode 100644 index a74ddd0a..00000000 --- a/src/module/core/cast.rs +++ /dev/null @@ -1,228 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, Expr, ExprKind, Program}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("toboolean"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Boolean(_) => program.push(item), - ExprKind::String(string) => program.push(Expr { - val: string - .parse() - .ok() - .map(ExprKind::Boolean) - .unwrap_or(ExprKind::Nil), - debug_data: DebugData::default(), - }), - found => program.push(Expr { - val: found.to_boolean().unwrap_or(ExprKind::Nil), - debug_data: DebugData::default(), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("tointeger"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Integer(_) => program.push(item), - ExprKind::String(string) => program.push( - string - .parse() - .ok() - .map(ExprKind::Integer) - .unwrap_or(ExprKind::Nil) - .into_expr(DebugData::default()), - ), - found => program.push( - found - .to_integer() - .unwrap_or(ExprKind::Nil) - .into_expr(item.debug_data), - ), - } - }); - - program - .funcs - .insert(Intern::from_ref("tofloat"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Float(_) => program.push(item), - ExprKind::String(string) => program.push( - string - .parse() - .ok() - .map(ExprKind::Float) - .unwrap_or(ExprKind::Nil) - .into_expr(DebugData::default()), - ), - found => program.push( - found - .to_float() - .unwrap_or(ExprKind::Nil) - .into_expr(DebugData::default()), - ), - } - }); - - program - .funcs - .insert(Intern::from_ref("tostring"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::String(_) => program.push(item), - found => { - let string = ExprKind::String(found.to_string()); - program.push(string.into_expr(DebugData::default())) - } - } - }); - - program - .funcs - .insert(Intern::from_ref("tolist"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::List(_) => program.push(item), - ExprKind::String(str) => program.push( - ExprKind::List( - str - .chars() - .map(|c| { - ExprKind::String(c.to_string()).into_expr(DebugData::default()) - }) - .collect::>(), - ) - .into_expr(DebugData::default()), - ), - found => program.push( - ExprKind::List(vec![found.into_expr(item.debug_data)]) - .into_expr(DebugData::default()), - ), - } - }); - - program - .funcs - .insert(Intern::from_ref("tocall"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Call(_) => program.push(item), - ExprKind::String(string) => program.push( - ExprKind::Call(Intern::new(string)).into_expr(DebugData::default()), - ), - found => { - let call = ExprKind::Call(Intern::new(found.to_string())); - program.push(call.into_expr(DebugData::default())) - } - } - }); - - program - .funcs - .insert(Intern::from_ref("typeof"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - let string = ExprKind::String(item.val.type_of().to_string()); - program.push(string.into_expr(DebugData::default())) - }); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::{simple_exprs, TestExpr}; - - use super::*; - - #[test] - fn to_string() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 tostring").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("1".into())] - ); - } - - #[test] - fn to_call() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("\"a\" tocall").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Call(Intern::from_ref("a"))] - ); - } - - #[test] - fn to_integer() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("\"1\" tointeger").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(1)]); - } - - #[test] - fn type_of() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 typeof").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("integer".into())] - ); - } - - #[test] - fn list_to_list() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2 3) tolist").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ])] - ); - } - - #[test] - fn list_into_lazy() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2 3) lazy").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Lazy( - TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ]) - .into() - )] - ); - } - - #[test] - fn call_into_lazy() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("'set lazy").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Lazy( - TestExpr::Call(Intern::from_ref("set")).into() - )] - ); - } -} diff --git a/src/module/core/compare.rs b/src/module/core/compare.rs deleted file mode 100644 index 27409db1..00000000 --- a/src/module/core/compare.rs +++ /dev/null @@ -1,287 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, ExprKind, Program}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("="), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val == rhs.val).into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref("!="), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val != rhs.val).into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref("<"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val < rhs.val).into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref(">"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val > rhs.val).into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref("<="), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val <= rhs.val).into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref(">="), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val >= rhs.val).into_expr(DebugData::default()), - ) - }); - - Ok(()) -} - -#[cfg(test)] - -mod tests { - use super::*; - use crate::{simple_exprs, TestExpr}; - - mod greater_than { - - use super::*; - - #[test] - fn greater_than_int() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("2 1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - } - - #[test] - fn greater_than_float() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1.0 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1.1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.1 1.0 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - } - - #[test] - fn greater_than_int_and_float() { - // Int first - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1.0 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1.1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("2 1.0 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - // Float first - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.1 1 >").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - } - } - - mod less_than { - use super::*; - - #[test] - fn less_than_int() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("2 1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - - #[test] - fn less_than_float() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1.0 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1.1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.1 1.0 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - - #[test] - fn less_than_int_and_float() { - // Int first - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1.0 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1.1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("2 1.0 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - // Float first - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.0 1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("0.9 1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1.1 1 <").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - } - - mod bitwise { - use super::*; - - #[test] - fn and_int() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1 and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 0 and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("0 1 and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("0 0 and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - - #[test] - fn and_bool() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("true true and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("true false and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("false true and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("false false and").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - - #[test] - fn or_int() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 1 or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 0 or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("0 1 or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("0 0 or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - - #[test] - fn or_bool() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("true true or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("true false or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("false true or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(true)]); - - let mut program = Program::new().with_core().unwrap(); - program.eval_string("false false or").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Boolean(false)]); - } - } -} diff --git a/src/module/core/control_flow.rs b/src/module/core/control_flow.rs deleted file mode 100644 index 4ff845b0..00000000 --- a/src/module/core/control_flow.rs +++ /dev/null @@ -1,208 +0,0 @@ -use internment::Intern; - -use crate::{EvalError, ExprKind, Program, Type}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("ifelse"), |program, trace_expr| { - let cond = program.pop(trace_expr)?; - let then = program.pop(trace_expr)?; - let r#else = program.pop(trace_expr)?; - - match (cond.val, then.val, r#else.val) { - ( - ExprKind::List(cond), - ExprKind::List(then), - ExprKind::List(r#else), - ) => { - program.eval(cond)?; - let cond = program.pop(trace_expr)?; - - if cond.val.is_truthy() { - program.eval(then) - } else { - program.eval(r#else) - } - } - (cond, then, r#else) => Err(EvalError { - kind: crate::EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::List(vec![Type::Boolean]), - Type::List(vec![]), - Type::List(vec![]), - ]), - Type::List(vec![cond.type_of(), then.type_of(), r#else.type_of()]), - ), - expr: Some(trace_expr.clone()), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("if"), |program, trace_expr| { - let cond = program.pop(trace_expr)?; - let then = program.pop(trace_expr)?; - - match (cond.val, then.val) { - (ExprKind::List(cond), ExprKind::List(then)) => { - program.eval(cond)?; - let cond = program.pop(trace_expr)?; - - if cond.val.is_truthy() { - program.eval(then) - } else { - Ok(()) - } - } - (cond, then) => Err(EvalError { - kind: crate::EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::List(vec![Type::Boolean]), - Type::List(vec![]), - ]), - Type::List(vec![cond.type_of(), then.type_of()]), - ), - expr: Some(trace_expr.clone()), - }), - } - - // program.push(ExprKind::List(vec![])); - // program.eval_intrinsic(trace_expr, Intrinsic::IfElse) - }); - - program - .funcs - .insert(Intern::from_ref("while"), |program, trace_expr| { - let cond = program.pop(trace_expr)?; - let block = program.pop(trace_expr)?; - - match (cond.val, block.val) { - (ExprKind::List(cond), ExprKind::List(block)) => loop { - program.eval(cond.clone())?; - let cond = program.pop(trace_expr)?; - - if cond.val.is_truthy() { - program.eval(block.clone())?; - } else { - break Ok(()); - } - }, - (cond, block) => Err(EvalError { - kind: crate::EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::List(vec![Type::Boolean]), - Type::List(vec![]), - ]), - Type::List(vec![cond.type_of(), block.type_of()]), - ), - expr: Some(trace_expr.clone()), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("halt"), |_, trace_expr| { - Err(EvalError { - kind: crate::EvalErrorKind::Halt, - expr: Some(trace_expr.clone()), - }) - }); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{simple_exprs, TestExpr}; - - mod control_flow { - use super::*; - - #[test] - fn if_true() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("1 2 + '(\"correct\") '(3 =) if") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("correct".into())] - ); - } - - #[test] - fn if_empty_condition() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("1 2 + 3 = '(\"correct\") '() if") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("correct".into())] - ); - } - - #[test] - fn if_else_true() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("1 2 + 3 = '(\"incorrect\") '(\"correct\") '() ifelse") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("correct".into())] - ); - } - - #[test] - fn if_else_false() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("1 2 + 2 = '(\"incorrect\") '(\"correct\") '() ifelse") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::String("incorrect".into())] - ); - } - } - - mod loops { - use super::*; - - #[test] - fn while_loop() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string( - ";; Set i to 3 - 3 'i def - - '( - ;; Decrement i by 1 - i 1 - - ;; Set i - 'i set - - i - ) '( - ;; If i is 0, break - i 0 != - ) while", - ) - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::Integer(2), - TestExpr::Integer(1), - TestExpr::Integer(0) - ] - ); - } - } -} diff --git a/src/module/core/core.rs b/src/module/core/core.rs deleted file mode 100644 index b7366352..00000000 --- a/src/module/core/core.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::time::SystemTime; - -use crate::{EvalError, Program, SourceFile}; - -// TODO: Split `core` into `list` and `module` modules -pub fn module(program: &mut Program) -> Result<(), EvalError> { - let source = include_str!("./core.stack"); - let source_name = "".to_owned(); - program.eval_string_with_name(source, &source_name)?; - program.sources.insert( - source_name, - SourceFile { - contents: source.into(), - mtime: SystemTime::now(), - }, - ); - - Ok(()) -} diff --git a/src/module/core/core.stack b/src/module/core/core.stack deleted file mode 100644 index ada0b01a..00000000 --- a/src/module/core/core.stack +++ /dev/null @@ -1,217 +0,0 @@ -'(fn! read-file parse call) 'import def -'(fn false =) 'not def - -;; List -;; ======================================== - -;; (list item -- 'list) -'(fn! wrap concat) 'push def -;; (list -- 'list item) -'(fn! len 1 - split unwrap) 'pop def - -;; (list item -- 'list) -'(fn! wrap swap concat) 'shift def -;; (list -- 'list item) -'(fn! 1 split swap unwrap) 'unshift def - -;; (list index item -- 'list) -'(fn! rot split swap rot rot wrap concat swap concat) 'insert def -;; (list index -- 'list item) -'(fn! 1 + split swap pop rot swap concat swap) 'remove def - -;; (list -- 'list) -'(fn! () '(pop rot rot swap push) '(swap len 0 !=) while drop) 'reverse def - -;; Let -;; ======================================== - -;; Creates a let block -;; {list(fn ...) list(vars)} -> {list(fn ...)} -'(fn - ['vars def] - ['func def] - - ['vars get len] ['var-len def drop] - - ['() 'new-fn def] - - ['func get] - [unshift dup typeof] - - '( - ;; Insert the `fn` call into the new function (preserving metadata) - ['new-fn get swap push] - ['new-fn set] - - ;; shift gives us the item and the list - ;; We want to update `func` to not include the `fn` call - ;; if it has it - ['func set] -) '( - ["fn" =] -) if - - [0 'i def] - - '( - ;; Get the new function - ['new-fn get] - - ;; Get the vars - [vars] - - ;; Index of the next var in reverse - [[var-len 1 -] i -] - - ;; Get the nth item, make it lazy, and insert it - [index swap drop lazy push] - - ;; Insert a `def` call - ['def push] - - ;; Update our new-fn - ['new-fn set] - - [i 1 + 'i set] - ) - '(i var-len <) - while - - ['new-fn get] - ['func get] - concat -) 'let-fn def - -;; QoL function to auto-call the created let block -;; {list(fn ...) list(vars)} -> {} -'(fn! let-fn call) 'let def - -;; QoL function to set a let block to a variable -;; {list(fn ...) list(vars) symbol} -> {} -'(fn! - ;; Creating a scoped function to keep `func`, `vars`, and `symbol` private - '(fn - ['func get] - ['vars get] - let-fn - - symbol - ;; Returning {list(fn ...) symbol} - ) '(func vars symbol) let - - ;; Setting the function to the symbol in the current scope - def -) 'defn def - -;; Modules -;; ======================================== -'(fn! - dup - get - - '() - [swap push] - [swap push] - [push] -) 'export def - -;; Run this function within the same scope -'(fn! - ;; Create a template function that will be used to set all of the imported variables - ;; in the current scope - '(fn!) - - ;; Create a let block to bring in our args - '(fn - "std/list.stack" import - - ['namespace get] - ['runner get] - ['scope get] - - ;; Create a let block for the for_each loop - '(fn - ['runner get] - ['item get] - - ;; Set the first item to be lazy so that it is not evaluated - [unwrap swap lazy] - ;; Reconstruct the pair - ['() swap push swap push] - - ;; Pop the symbol from the export {(val symbol)} -> {(val) symbol} - ;; Turn it into a string then into a list of chars - [pop tostring tolist] - - ;; Turn the namespace into a string then into a list of chars - ;; Add a "/" to the end of the namespace - ['namespace get tostring tolist "/" push] - [swap] - - ;; Join the namespace and the symbol - [concat "" join] - - ;; Push the item as a lazy symbol to the set code - [tocall lazy push] - - ;; Push a `'set` call to the code - ['def push] - - ;; Add the code to the template function and update it - [concat 'runner set] - - ;; Put the namespace and runner back on the stack - 'namespace get - 'runner get - ) '(namespace runner item) let-fn - list/for_each - ) '(scope namespace runner) let - - ;; Call our template function to set all of the imported variables - call - - ;; Pop namespace - drop -) 'use def - -;; Run this function within the same scope -'(fn! - ;; Create a template function that will be used to set all of the imported variables - ;; in the current scope - '(fn!) - - ;; Create a let block to bring in our args - '(fn - "std/list.stack" import - - ['runner get] - ['scope get] - - ;; Create a let block for the for_each loop - '(fn - ['runner get] - ['item get] - - ;; Set the first item to be lazy so that it is not evaluated - [unwrap swap lazy] - ;; Reconstruct the pair - ['() swap push swap push] - - ;; Push the item as a lazy symbol to the set code - [pop lazy push] - - ;; Push a `'set` call to the code - ['def push] - - ;; Add the code to the template function and update it - [concat 'runner def] - - ;; Put the runner back on the stack - 'runner get - ) '(runner item) let-fn - list/for_each - ) '(scope runner) let - - ;; Call our template function to set all of the imported variables - call -) 'use-all def diff --git a/src/module/core/debug.rs b/src/module/core/debug.rs deleted file mode 100644 index 837d06ec..00000000 --- a/src/module/core/debug.rs +++ /dev/null @@ -1,26 +0,0 @@ -use internment::Intern; - -use crate::{EvalError, EvalErrorKind, Program}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("panic"), |program, trace_expr| { - let string = program.pop(trace_expr)?; - - Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::Panic(format!("{}", string.val)), - }) - }); - - program - .funcs - .insert(Intern::from_ref("debug"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - println!("{}", item); - Ok(()) - }); - - Ok(()) -} diff --git a/src/module/core/eval.rs b/src/module/core/eval.rs deleted file mode 100644 index 3281307f..00000000 --- a/src/module/core/eval.rs +++ /dev/null @@ -1,33 +0,0 @@ -use internment::Intern; - -use crate::{ - DebugData, EvalError, EvalErrorKind, ExprKind, Lexer, Parser, Program, Type, -}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("parse"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::String(string) => { - let lexer = Lexer::new(&string); - let parser = Parser::new(lexer, Intern::from_ref("internal")); - let expr = parser - .parse() - .ok() - .map(ExprKind::List) - .unwrap_or(ExprKind::Nil); - - program.push(expr.into_expr(DebugData::default())) - } - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound(Type::String, item.val.type_of()), - }), - } - }); - - Ok(()) -} diff --git a/src/module/core/io.rs b/src/module/core/io.rs deleted file mode 100644 index 223f3d8f..00000000 --- a/src/module/core/io.rs +++ /dev/null @@ -1,59 +0,0 @@ -use internment::Intern; - -use crate::{ - DebugData, EvalError, EvalErrorKind, ExprKind, Program, SourceFile, Type, -}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("read-file"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::String(ref path) => { - let file_is_newer = - if let Some(loaded_file) = program.sources.get(path) { - let metadata = std::fs::metadata(path).ok().unwrap(); - let mtime = metadata.modified().ok().unwrap(); - mtime > loaded_file.mtime - } else { - true - }; - - if file_is_newer { - match std::fs::read_to_string(path.clone()) { - Ok(contents) => { - program.sources.insert( - path.to_string(), - SourceFile { - contents: contents.clone(), - mtime: std::fs::metadata(path).unwrap().modified().unwrap(), - }, - ); - program.push( - ExprKind::String(contents).into_expr(DebugData::default()), - ) - } - Err(e) => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::UnableToRead(path.into(), e.to_string()), - }), - } - } else { - let contents = &program.sources.get(path).unwrap().contents; - program.push( - ExprKind::String(contents.clone()) - .into_expr(DebugData::default()), - ) - } - } - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound(Type::String, item.val.type_of()), - }), - } - }); - - Ok(()) -} diff --git a/src/module/core/list.rs b/src/module/core/list.rs deleted file mode 100644 index a9f6c8f3..00000000 --- a/src/module/core/list.rs +++ /dev/null @@ -1,312 +0,0 @@ -use std::iter; - -use internment::Intern; -use itertools::Itertools; - -use crate::{DebugData, EvalError, EvalErrorKind, ExprKind, Program, Type}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("len"), |program, trace_expr| { - let list_expr = program.pop(trace_expr)?; - program.push(list_expr.clone())?; - - match list_expr.val { - ExprKind::List(list) => match i64::try_from(list.len()) { - Ok(i) => { - program.push(ExprKind::Integer(i).into_expr(DebugData::default())) - } - Err(_) => { - todo!("Create a list type to not exceed the i64 bounds") - } - }, - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![]), - list_expr.val.type_of(), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("index"), |program, trace_expr| { - let index_expr = program.pop(trace_expr)?; - let list_expr = program.pop(trace_expr)?; - program.push(list_expr.clone())?; - - match index_expr.val { - ExprKind::Integer(index) => match usize::try_from(index) { - Ok(i) => match list_expr.val { - ExprKind::List(list) => program.push( - list - .get(i) - .cloned() - .unwrap_or(ExprKind::Nil.into_expr(DebugData::default())), - ), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::Integer]), - Type::List(vec![ - list_expr.val.type_of(), - index_expr.val.type_of(), - ]), - ), - }), - }, - Err(_) => Err(EvalError { - kind: EvalErrorKind::Message( - "could not convert index into integer".into(), - ), - expr: Some(trace_expr.clone()), - }), - }, - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::Integer]), - Type::List(vec![list_expr.val.type_of(), index_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("split"), |program, trace_expr| { - let index_expr = program.pop(trace_expr)?; - let list_expr = program.pop(trace_expr)?; - - match index_expr.val { - ExprKind::Integer(index) if index >= 0 => { - let i = index as usize; - match list_expr.val { - ExprKind::List(mut list) => { - if i <= list.len() { - let rest = list.split_off(i); - program - .push(ExprKind::List(list).into_expr(DebugData::default()))?; - program - .push(ExprKind::List(rest).into_expr(DebugData::default())) - } else { - program.push(ExprKind::Nil.into_expr(DebugData::default())) - } - } - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::Integer]), - Type::List(vec![ - list_expr.val.type_of(), - index_expr.val.type_of(), - ]), - ), - }), - } - } - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::Integer]), - Type::List(vec![list_expr.val.type_of(), index_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("join"), |program, trace_expr| { - let delimiter_expr = program.pop(trace_expr)?; - let list_expr = program.pop(trace_expr)?; - - match (delimiter_expr.val, list_expr.val) { - (ExprKind::String(delimiter), ExprKind::List(list)) => { - let string = list - .into_iter() - .map(|expr| match expr.val { - ExprKind::String(string) => string, - expr_kind => expr_kind.to_string(), - }) - .join(&delimiter); - let string = ExprKind::String(string); - program.push(string.into_expr(DebugData::default())) - } - (delimiter, list) => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::String]), - Type::List(vec![list.type_of(), delimiter.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("concat"), |program, trace_expr| { - let list_rhs_expr = program.pop(trace_expr)?; - let list_lhs_expr = program.pop(trace_expr)?; - - match (list_lhs_expr.val, list_rhs_expr.val) { - (ExprKind::List(mut list_lhs), ExprKind::List(list_rhs)) => { - list_lhs.extend(list_rhs); - let list_expr = - ExprKind::List(list_lhs).into_expr(DebugData::default()); - program.push(list_expr) - } - (list_lhs, list_rhs) => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::List(vec![]), Type::List(vec![])]), - Type::List(vec![list_lhs.type_of(), list_rhs.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("unwrap"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::List(list) => { - program.stack.extend(list); - Ok(()) - } - _ => program.push(item), - } - }); - - program - .funcs - .insert(Intern::from_ref("wrap"), |program, trace_expr| { - let any = program.pop(trace_expr)?; - program.push(ExprKind::List(vec![any]).into_expr(DebugData::default())) - }); - - program - .funcs - .insert(Intern::from_ref("call-list"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - let item_clone = item.clone(); - - match item.val { - ExprKind::List(list) => { - let stack_len = program.stack.len(); - - program.eval(list)?; - - let list_len = program.stack.len() - stack_len; - - let mut list = iter::from_fn(|| program.pop(&item_clone).ok()) - .take(list_len) - .collect::>(); - list.reverse(); - - program.push(ExprKind::List(list).into_expr(DebugData::default())) - } - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::FnScope, Type::Any]), - Type::List(vec![item.val.type_of()]), - ), - }), - } - }); - - Ok(()) -} - -#[cfg(test)] -mod tests { - use crate::{simple_exprs, TestExpr}; - - use super::*; - - #[test] - fn concatenating_lists() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2) (3 \"4\") concat").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3), - TestExpr::String("4".into()) - ])] - ); - } - - #[test] - fn concatenating_blocks() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2) ('+) concat").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Call(Intern::from_ref("+")) - ])] - ); - } - - #[test] - fn getting_length_of_list() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2 3) len").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ]), - TestExpr::Integer(3) - ] - ); - } - - #[test] - fn getting_indexed_item_of_list() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("(1 2 3) 1 index").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ]), - TestExpr::Integer(2) - ] - ); - } - - #[test] - fn calling_lists() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("'(2 2 +) call").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(4)]); - } - - #[test] - fn calling_lists_special() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("'(2 2 +) call-list").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![TestExpr::Integer(4)])] - ); - } -} diff --git a/src/module/core/logical.rs b/src/module/core/logical.rs deleted file mode 100644 index df67889a..00000000 --- a/src/module/core/logical.rs +++ /dev/null @@ -1,31 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, ExprKind, Program}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("or"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val.is_truthy() || rhs.val.is_truthy()) - .into_expr(DebugData::default()), - ) - }); - - program - .funcs - .insert(Intern::from_ref("and"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push( - ExprKind::Boolean(lhs.val.is_truthy() && rhs.val.is_truthy()) - .into_expr(DebugData::default()), - ) - }); - - Ok(()) -} diff --git a/src/module/core/math.rs b/src/module/core/math.rs deleted file mode 100644 index 6d88dbab..00000000 --- a/src/module/core/math.rs +++ /dev/null @@ -1,127 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, EvalErrorKind, ExprKind, Program, Type}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("+"), |program, trace_expr| { - let rhs_expr = program.pop(trace_expr)?; - let lhs_expr = program.pop(trace_expr)?; - - match lhs_expr.val.coerce_same_float(&rhs_expr.val) { - Some((ExprKind::Integer(lhs), ExprKind::Integer(rhs))) => program - .push(ExprKind::Integer(lhs + rhs).into_expr(DebugData::default())), - Some((ExprKind::Float(lhs), ExprKind::Float(rhs))) => program - .push(ExprKind::Float(lhs + rhs).into_expr(DebugData::default())), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - ]), - Type::List(vec![lhs_expr.val.type_of(), rhs_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("-"), |program, trace_expr| { - let rhs_expr = program.pop(trace_expr)?; - let lhs_expr = program.pop(trace_expr)?; - - match lhs_expr.val.coerce_same_float(&rhs_expr.val) { - Some((ExprKind::Integer(lhs), ExprKind::Integer(rhs))) => program - .push(ExprKind::Integer(lhs - rhs).into_expr(DebugData::default())), - Some((ExprKind::Float(lhs), ExprKind::Float(rhs))) => program - .push(ExprKind::Float(lhs - rhs).into_expr(DebugData::default())), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - ]), - Type::List(vec![lhs_expr.val.type_of(), rhs_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("*"), |program, trace_expr| { - let rhs_expr = program.pop(trace_expr)?; - let lhs_expr = program.pop(trace_expr)?; - - match lhs_expr.val.coerce_same_float(&rhs_expr.val) { - Some((ExprKind::Integer(lhs), ExprKind::Integer(rhs))) => program - .push(ExprKind::Integer(lhs * rhs).into_expr(DebugData::default())), - Some((ExprKind::Float(lhs), ExprKind::Float(rhs))) => program - .push(ExprKind::Float(lhs * rhs).into_expr(DebugData::default())), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - ]), - Type::List(vec![lhs_expr.val.type_of(), rhs_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("/"), |program, trace_expr| { - let rhs_expr = program.pop(trace_expr)?; - let lhs_expr = program.pop(trace_expr)?; - - match lhs_expr.val.coerce_same_float(&rhs_expr.val) { - Some((ExprKind::Integer(lhs), ExprKind::Integer(rhs))) => program - .push(ExprKind::Integer(lhs / rhs).into_expr(DebugData::default())), - Some((ExprKind::Float(lhs), ExprKind::Float(rhs))) => program - .push(ExprKind::Float(lhs / rhs).into_expr(DebugData::default())), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - ]), - Type::List(vec![lhs_expr.val.type_of(), rhs_expr.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("%"), |program, trace_expr| { - let rhs_expr = program.pop(trace_expr)?; - let lhs_expr = program.pop(trace_expr)?; - - match lhs_expr.val.coerce_same_float(&rhs_expr.val) { - Some((ExprKind::Integer(lhs), ExprKind::Integer(rhs))) => program - .push(ExprKind::Integer(lhs % rhs).into_expr(DebugData::default())), - Some((ExprKind::Float(lhs), ExprKind::Float(rhs))) => program - .push(ExprKind::Float(lhs % rhs).into_expr(DebugData::default())), - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![ - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - Type::Set(vec![Type::Integer, Type::Float, Type::Pointer]), - ]), - Type::List(vec![lhs_expr.val.type_of(), rhs_expr.val.type_of()]), - ), - }), - } - }); - - Ok(()) -} diff --git a/src/module/core/mod.rs b/src/module/core/mod.rs deleted file mode 100644 index 3c6f5614..00000000 --- a/src/module/core/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -use crate::{EvalError, Module, Program}; - -pub mod cast; -pub mod compare; -pub mod control_flow; -#[allow(clippy::module_inception)] -pub mod core; -pub mod debug; -pub mod eval; -pub mod io; -pub mod list; -pub mod logical; -pub mod math; -pub mod scope; -pub mod stack; - -pub struct Core { - pub eval: bool, -} - -impl Module for Core { - fn link(&self, program: &mut Program) -> Result<(), EvalError> { - // Native Intrinsics - stack::module.link(program)?; - scope::module.link(program)?; - math::module.link(program)?; - compare::module.link(program)?; - logical::module.link(program)?; - list::module.link(program)?; - cast::module.link(program)?; - debug::module.link(program)?; - control_flow::module.link(program)?; - io::module.link(program)?; - eval::module.link(program)?; - - // In-Language Definitions - core::module(program)?; - - Ok(()) - } -} - -impl Default for Core { - #[inline] - fn default() -> Self { - Self { eval: true } - } -} diff --git a/src/module/core/scope.rs b/src/module/core/scope.rs deleted file mode 100644 index 95e5e2bc..00000000 --- a/src/module/core/scope.rs +++ /dev/null @@ -1,297 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, EvalErrorKind, ExprKind, Program, Type}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("def"), |program, trace_expr| { - let key = program.pop(trace_expr)?; - let val = program.pop(trace_expr)?; - - match key.val { - ExprKind::Call(ref key) => match program.funcs.contains_key(key) { - true => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::Message( - "cannot shadow a native function".into(), - ), - }), - false => { - program.def_scope_item(key.as_ref(), val); - Ok(()) - } - }, - _ => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::Any, Type::Call]), - Type::List(vec![val.val.type_of(), key.val.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("undef"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Call(key) => { - let key_str = &key.as_ref().to_owned(); - program.remove_scope_item(key_str); - - Ok(()) - } - item => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound(Type::Call, item.type_of()), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("set"), |program, trace_expr| { - let key = program.pop(trace_expr)?; - let val = program.pop(trace_expr)?; - - match key.val { - ExprKind::Call(ref key) => match program.funcs.contains_key(key) { - true => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::Message( - "cannot shadow a native function".into(), - ), - }), - false => { - program.set_scope_item(key.as_ref(), val)?; - Ok(()) - } - }, - key => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound( - Type::List(vec![Type::Any, Type::Call]), - Type::List(vec![val.val.type_of(), key.type_of()]), - ), - }), - } - }); - - program - .funcs - .insert(Intern::from_ref("get"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - match item.val { - ExprKind::Call(ref key) => { - if let Some(func) = program.funcs.get(key) { - func(program, trace_expr) - } else { - let key_str = key.as_ref(); - - // Always push something, otherwise it can get tricky to manage the - // stack in-langauge. - program.push( - program - .scope_item(key_str) - .unwrap_or(ExprKind::Nil.into_expr(DebugData::default())), - ) - } - } - item => Err(EvalError { - expr: Some(trace_expr.clone()), - kind: EvalErrorKind::ExpectedFound(Type::Call, item.type_of()), - }), - } - }); - - Ok(()) -} - -#[cfg(test)] - -mod tests { - use super::*; - use crate::{simple_expr, simple_exprs, TestExpr}; - use crate::{FnSymbol, Scope}; - - #[test] - fn storing_variables() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 'a def").unwrap(); - - let a = program - .scopes - .last() - .unwrap() - .get_val(Intern::from_ref("a")) - .unwrap(); - - assert_eq!(simple_expr(a), TestExpr::Integer(1)); - } - - #[test] - fn retrieving_variables() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 'a def a").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(1)]); - } - - #[test] - fn evaluating_variables() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 'a def a 2 +").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(3)]); - } - - #[test] - fn removing_variables() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 'a def 'a undef").unwrap(); - assert!(!program - .scopes - .iter() - .any(|scope| scope.has(Intern::from_ref("a")))) - } - - #[test] - fn auto_calling_functions() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("'(fn 1 2 +) 'is-three def is-three") - .unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(3)]); - } - - #[test] - fn only_auto_call_functions() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("'(1 2 +) 'is-three def is-three") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Call(Intern::from_ref("+")) - ])] - ); - } - - #[test] - fn getting_function_body() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("'(fn 1 2 +) 'is-three def 'is-three get") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Fn(FnSymbol { - scoped: true, - scope: Scope::new(), - }), - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Call(Intern::from_ref("+")) - ])] - ); - } - - #[test] - fn assembling_functions_in_code() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("'() 'fn tolist concat 1 tolist concat 2 tolist concat '+ tolist concat dup call") - .unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::List(vec![ - TestExpr::Fn(FnSymbol { - scoped: true, - scope: Scope::new(), - }), - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Call(Intern::from_ref("+")) - ]), - TestExpr::Integer(3) - ] - ); - } - - mod scope { - use super::*; - - #[test] - fn functions_are_isolated() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string( - "0 'a def - '(fn 5 'a def) - - '(fn 1 'a def call) call", - ) - .unwrap(); - - let a = program - .scopes - .last() - .unwrap() - .get_val(Intern::from_ref("a")) - .unwrap(); - - assert_eq!(simple_expr(a), TestExpr::Integer(0)); - } - - #[test] - fn functions_can_use_same_scope() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string( - "0 'a def - '(fn! 1 'a def) call", - ) - .unwrap(); - - let a = program - .scopes - .last() - .unwrap() - .get_val(Intern::from_ref("a")) - .unwrap(); - - assert_eq!(simple_expr(a), TestExpr::Integer(1)); - } - - #[test] - fn functions_can_shadow_vars() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string( - "0 'a def - '(fn 1 'a def a) call a", - ) - .unwrap(); - - let a = program - .scopes - .last() - .unwrap() - .get_val(Intern::from_ref("a")) - .unwrap(); - - assert_eq!(simple_expr(a), TestExpr::Integer(0)); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Integer(1), TestExpr::Integer(0)] - ) - } - } -} diff --git a/src/module/core/stack.rs b/src/module/core/stack.rs deleted file mode 100644 index f72fbdf5..00000000 --- a/src/module/core/stack.rs +++ /dev/null @@ -1,177 +0,0 @@ -use internment::Intern; - -use crate::{DebugData, EvalError, ExprKind, Program}; - -pub fn module(program: &mut Program) -> Result<(), EvalError> { - program - .funcs - .insert(Intern::from_ref("collect"), |program, _| { - let list = core::mem::take(&mut program.stack); - program.push(ExprKind::List(list).into_expr(DebugData::default())) - }); - - program - .funcs - .insert(Intern::from_ref("clear"), |program, _| { - program.stack.clear(); - Ok(()) - }); - - program - .funcs - .insert(Intern::from_ref("drop"), |program, trace_expr| { - program.pop(trace_expr)?; - Ok(()) - }); - - program - .funcs - .insert(Intern::from_ref("dup"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - program.push(item.clone())?; - program.push(item) - }); - - program - .funcs - .insert(Intern::from_ref("swap"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push(rhs)?; - program.push(lhs) - }); - - program - .funcs - .insert(Intern::from_ref("rot"), |program, trace_expr| { - let rhs = program.pop(trace_expr)?; - let mid = program.pop(trace_expr)?; - let lhs = program.pop(trace_expr)?; - - program.push(rhs)?; - program.push(lhs)?; - program.push(mid) - }); - - program - .funcs - .insert(Intern::from_ref("lazy"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - program - .push(ExprKind::Lazy(Box::new(item)).into_expr(DebugData::default())) - }); - - program - .funcs - .insert(Intern::from_ref("call"), |program, trace_expr| { - let item = program.pop(trace_expr)?; - - program.auto_call(trace_expr, item) - }); - - Ok(()) -} - -#[cfg(test)] - -mod tests { - use super::*; - use crate::{simple_expr, simple_exprs, TestExpr}; - - #[test] - fn clearing_stack() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 clear").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![]); - } - - #[test] - fn dropping_from_stack() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 drop").unwrap(); - assert_eq!(simple_exprs(program.stack), vec![TestExpr::Integer(1)]); - } - - #[test] - fn duplicating() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 dup").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Integer(1), TestExpr::Integer(1)] - ); - } - - #[test] - fn swapping() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 swap").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::Integer(2), TestExpr::Integer(1)] - ); - } - - #[test] - fn rotating() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 3 rot").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::Integer(3), - TestExpr::Integer(1), - TestExpr::Integer(2) - ] - ); - } - - #[test] - fn collect() { - let mut program = Program::new().with_core().unwrap(); - program.eval_string("1 2 3 collect").unwrap(); - assert_eq!( - simple_exprs(program.stack), - vec![TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ])] - ); - } - - #[test] - fn collect_and_unwrap() { - let mut program = Program::new().with_core().unwrap(); - program - .eval_string("1 2 3 collect 'a def 'a get unwrap") - .unwrap(); - - assert_eq!( - simple_exprs(program.stack), - vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ] - ); - - let a = program - .scopes - .last() - .unwrap() - .get_val(Intern::from_ref("a")) - .unwrap(); - - assert_eq!( - simple_expr(a), - TestExpr::List(vec![ - TestExpr::Integer(1), - TestExpr::Integer(2), - TestExpr::Integer(3) - ]) - ); - } -} diff --git a/src/module/map.rs b/src/module/map.rs deleted file mode 100644 index ff9f1497..00000000 --- a/src/module/map.rs +++ /dev/null @@ -1,170 +0,0 @@ -// TODO: reimplement this with the new errors and exprs with debug data. - -// use core::cell::RefCell; -// use std::{collections::HashMap, rc::Rc}; - -// use lasso::Spur; - -// use crate::{interner::interner, EvalError, Expr, ExprKind, Program}; - -// pub fn module(program: &mut Program) -> Result<(), EvalError> { -// program.funcs.insert( -// interner().get_or_intern_static("map/new"), -// |program, _| { -// program.push( -// ExprKind::UserData(Rc::new(RefCell::new(HashMap::::new()))) -// .into_expr(), -// )?; -// Ok(()) -// }, -// ); - -// program.funcs.insert( -// interner().get_or_intern_static("map/insert"), -// |program, trace_expr| { -// let key = program.pop(trace_expr)?; -// let item = program.pop(trace_expr)?; -// let map = program.pop(trace_expr)?; - -// program.push(map.clone())?; - -// match map.val { -// ExprKind::UserData(map) => { -// match map.borrow_mut().downcast_mut::>() { -// Some(map) => match key.val { -// ExprKind::Call(key) | ExprKind::String(key) => { -// map.insert(key, item); -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!( -// "expected call or string, found {}", -// found.type_of() -// ), -// }) -// } -// }, -// None => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: "unable to downcast userdata into map".into(), -// }) -// } -// } -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!("expected userdata, found {}", found.type_of()), -// }) -// } -// } - -// Ok(()) -// }, -// ); - -// program.funcs.insert( -// interner().get_or_intern_static("map/remove"), -// |program, trace_expr| { -// let key = program.pop(trace_expr)?; -// let map = program.pop(trace_expr)?; - -// program.push(map.clone())?; - -// match map.val { -// ExprKind::UserData(map) => { -// match map.borrow_mut().downcast_mut::>() { -// Some(map) => match key.val { -// ExprKind::Call(ref key) | ExprKind::String(ref key) => { -// map.remove(key); -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!( -// "expected call or string, found {}", -// found.type_of() -// ), -// }) -// } -// }, -// None => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: "unable to downcast userdata into map".into(), -// }) -// } -// } -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!("expected userdata, found {}", found.type_of()), -// }) -// } -// } - -// Ok(()) -// }, -// ); - -// program.funcs.insert( -// interner().get_or_intern_static("map/get"), -// |program, trace_expr| { -// let key = program.pop(trace_expr)?; -// let map = program.pop(trace_expr)?; - -// program.push(map.clone())?; - -// match map.val { -// ExprKind::UserData(map) => { -// match map.borrow().downcast_ref::>() { -// Some(map) => match key.val { -// ExprKind::Call(ref key) | ExprKind::String(ref key) => { -// program.push( -// map.get(key).cloned().unwrap_or(ExprKind::Nil.into_expr()), -// )?; -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!( -// "expected call or string, found {}", -// found.type_of() -// ), -// }) -// } -// }, -// None => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: "unable to downcast userdata into map".into(), -// }) -// } -// } -// } -// found => { -// return Err(EvalError { -// program: program.clone(), -// expr: trace_expr.clone(), -// message: format!("expected userdata, found {}", found.type_of()), -// }) -// } -// } - -// Ok(()) -// }, -// ); - -// Ok(()) -// } diff --git a/src/module/mod.rs b/src/module/mod.rs deleted file mode 100644 index 5d3d28ef..00000000 --- a/src/module/mod.rs +++ /dev/null @@ -1,22 +0,0 @@ -pub mod core; -pub mod map; - -use crate::{EvalError, Expr, Program}; - -// TODO: Check for name collisions with other modules. - -pub type Func = fn(&mut Program, &Expr) -> Result<(), EvalError>; - -pub trait Module { - fn link(&self, program: &mut Program) -> Result<(), EvalError>; -} - -impl Module for F -where - F: Fn(&mut Program) -> Result<(), EvalError>, -{ - #[inline] - fn link(&self, program: &mut Program) -> Result<(), EvalError> { - self(program) - } -} diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 6df0aebf..00000000 --- a/src/parser.rs +++ /dev/null @@ -1,287 +0,0 @@ -use internment::Intern; -use thiserror::Error; - -use crate::{ - DebugData, Expr, ExprKind, FnSymbol, Lexer, Scope, Span, TokenKind, TokenVec, -}; - -/// Converts a stream of [`Token`]s into a stream of [`Expr`]s. -/// -/// [`Token`]: crate::Token -#[derive(Debug, Clone, PartialEq)] -pub struct Parser<'source> { - filename: Intern, - tokens: TokenVec<'source>, - cursor: usize, -} - -impl<'source> Parser<'source> { - /// Creates a new [`Parser`]. - /// - /// Prefer [`Parser::reuse`] where possible. - #[inline] - pub const fn new(lexer: Lexer<'source>, filename: Intern) -> Self { - Self { - filename, - tokens: TokenVec::new(lexer), - cursor: 0, - } - } - - /// Creates a [`Parser`] by re-using the allocations of an existing one. - #[inline] - pub fn reuse(&mut self, lexer: Lexer<'source>) { - self.tokens.reuse(lexer); - } - - /// Parses all of the available [`Expr`]s into a [`Vec`]. - /// - /// If a [`ParseError`] is encountered, the whole collect fails. - #[inline] - pub fn parse(mut self) -> Result, ParseError> { - let mut exprs = Vec::new(); - - while let Some(result) = self.next().transpose() { - exprs.push(result?); - } - - Ok(exprs) - } - - /// Returns the next [`Expr`]. - /// - /// Once the first [Ok]\([None]\) has been returned, it will - /// continue to return them thereafter, akin to a [`FusedIterator`]. - /// - /// [`FusedIterator`]: core::iter::FusedIterator - #[allow(clippy::should_implement_trait)] - // ^ This is fine. If it acts like an iterator, it's an iterator. - pub fn next(&mut self) -> Result, ParseError> { - loop { - let token = self.tokens.token(self.cursor); - self.cursor += 1; - - match token.kind { - TokenKind::Invalid | TokenKind::ParenClose => { - break Err(ParseError { - reason: ParseErrorReason::UnexpectedToken { kind: token.kind }, - span: token.span, - }); - } - TokenKind::Eoi => { - break Ok(None); - } - - TokenKind::Whitespace | TokenKind::Comment => { - continue; - } - - TokenKind::Boolean(x) => { - break Ok(Some(ExprKind::Boolean(x).into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - TokenKind::Integer(x) => { - break Ok(Some(ExprKind::Integer(x).into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - TokenKind::Float(x) => { - break Ok(Some(ExprKind::Float(x).into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - TokenKind::String(x) => { - break Ok(Some(ExprKind::String(x).into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - - TokenKind::Ident(x) => { - break Ok(Some(ExprKind::Call(x).into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - - TokenKind::Apostrophe => { - break match self.next() { - Ok(Some(expr)) => { - Ok(Some(ExprKind::Lazy(Box::new(expr.clone())).into_expr( - DebugData { - source_file: Some(self.filename), - span: Some(Span { - start: token.span.start, - // TODO: We probably shouldn't be unwrapping here, though processed expressions should - // ALWAYS have a span in debug data, so this is fine if it hard-errors - end: expr.debug_data.span.unwrap().end, - }), - }, - ))) - } - Ok(None) => Err(ParseError { - reason: ParseErrorReason::UnexpectedToken { kind: token.kind }, - span: token.span, - }), - err @ Err(_) => err, - }; - } - TokenKind::ParenOpen => { - let mut list_items: Vec = Vec::new(); - - break loop { - let token_inner = self.tokens.token(self.cursor); - - match token_inner.kind { - TokenKind::Whitespace | TokenKind::Comment => { - self.cursor += 1; - continue; - } - TokenKind::ParenClose => { - self.cursor += 1; - break Ok(Some(ExprKind::List(list_items).into_expr( - DebugData { - source_file: Some(self.filename), - span: Some(Span { - start: token.span.start, - end: token_inner.span.end, - }), - }, - ))); - } - _ => match self.next()? { - Some(expr) => list_items.push(expr), - None => { - break Err(ParseError { - reason: ParseErrorReason::UnexpectedToken { - kind: token_inner.kind, - }, - span: token_inner.span, - }); - } - }, - } - }; - } - // // TODO: This should check to make sure there are matching brakets. - // TokenKind::SquareOpen | TokenKind::SquareClose => { - // continue; - // } - TokenKind::Nil => { - break Ok(Some(ExprKind::Nil.into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }))); - } - TokenKind::Fn => { - break Ok(Some( - ExprKind::Fn(FnSymbol { - scoped: true, - scope: Scope::new(), - }) - .into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }), - )); - } - TokenKind::FnExclamation => { - break Ok(Some( - ExprKind::Fn(FnSymbol { - scoped: false, - scope: Scope::new(), - }) - .into_expr(DebugData { - source_file: Some(self.filename), - span: Some(token.span), - }), - )); - } - } - } - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Error)] -#[error("{reason} at {span}")] -pub struct ParseError { - pub reason: ParseErrorReason, - pub span: Span, -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Error)] -pub enum ParseErrorReason { - #[error("unexpected token: {kind}")] - UnexpectedToken { kind: TokenKind }, -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - fn source_file() -> Intern { - Intern::from_ref("internal") - } - - fn construct(val: ExprKind, start: usize, end: usize) -> Expr { - Expr { - val, - debug_data: DebugData::new( - Some(source_file()), - Some(Span::new(start, end)), - ), - } - } - - #[test_case("" => Ok(Vec::::new()) ; "empty")] - #[test_case(" \t\r\n" => Ok(Vec::::new()) ; "whitespace")] - #[test_case("ßℝ💣" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 0, end: 9 } }) ; "invalid")] - #[test_case("'ßℝ💣" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 1, end: 10 } }) ; "lazy invalid")] - #[test_case("12 34 +" => Ok(vec![construct(ExprKind::Integer(12), 0, 2), construct(ExprKind::Integer(34), 3, 5), construct(ExprKind::Call(Intern::from_ref("+")), 6, 7)]) ; "int int add")] - #[test_case("æ 34 -" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 0, end: 2 } }) ; "invalid int sub")] - #[test_case("12 æ *" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 3, end: 5 } }) ; "int invalid mul")] - #[test_case("'" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Apostrophe }, span: Span { start: 0, end: 1 } }) ; "empty lazy")] - #[test_case("'12" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::Integer(12), 1, 3))), 0, 3)]) ; "lazy int")] - #[test_case("()" => Ok(vec![construct(ExprKind::List(vec![]), 0, 2)]) ; "empty list")] - #[test_case("(\n)" => Ok(vec![construct(ExprKind::List(vec![]), 0, 3)]) ; "empty list whitespace")] - #[test_case("'()" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 1, 3))), 0, 3)]) ; "lazy empty list")] - #[test_case("(')" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 2, end: 3 } }) ; "list empty lazy")] - #[test_case("('())" => Ok(vec![construct(ExprKind::List(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 2, 4))), 1, 4)]), 0, 5)]) ; "list lazy list")] - #[test_case("'('())" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 3, 5))), 2, 5)]), 1, 6))), 0, 6)]) ; "lazy list lazy list")] - #[test_case("('('))" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 4, end: 5 } }) ; "list lazy list empty lazy")] - #[test_case("'('('))" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 5, end: 6 } }) ; "lazy list lazy list empty lazy")] - #[test_case("(12 +)" => Ok(vec![construct(ExprKind::List(vec![construct(ExprKind::Integer(12), 1, 3), construct(ExprKind::Call(Intern::from_ref("+")), 4, 5)]), 0, 6)]) ; "list int add")] - #[test_case("'(12 +)" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![construct(ExprKind::Integer(12), 2, 4), construct(ExprKind::Call(Intern::from_ref("+")), 5, 6)]), 1, 7))), 0, 7)]) ; "lazy list int add")] - #[test_case("(æ +)" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 1, end: 3 } }) ; "invalid int add")] - #[test_case("'(æ +)" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 2, end: 4 } }) ; "lazy invalid int add")] - #[test_case("[]" => Ok(vec![]) ; "square empty")] - #[test_case("[ßℝ💣]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 1, end: 10 } }) ; "square invalid")] - #[test_case("['ßℝ💣]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 2, end: 11 } }) ; "square lazy invalid")] - #[test_case("[12 34 +]" => Ok(vec![construct(ExprKind::Integer(12), 1, 3), construct(ExprKind::Integer(34), 4, 6), construct(ExprKind::Call(Intern::from_ref("+")), 7, 8)]) ; "square int int add")] - #[test_case("[æ 34 -]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 1, end: 3 } }) ; "square invalid int sub")] - #[test_case("[12 æ *]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 4, end: 6 } }) ; "square int invalid mul")] - #[test_case("[']" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Apostrophe }, span: Span { start: 1, end: 2 } }) ; "square empty lazy")] - #[test_case("['12]" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::Integer(12), 2, 4))), 1, 4)]) ; "square lazy int")] - #[test_case("[()]" => Ok(vec![construct(ExprKind::List(vec![]), 1, 3)]) ; "square empty list")] - #[test_case("[(\n)]" => Ok(vec![construct(ExprKind::List(vec![]), 1, 4)]) ; "square empty list whitespace")] - #[test_case("['()]" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 2, 4))), 1, 4)]) ; "square lazy empty list")] - #[test_case("[(')]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 3, end: 4 } }) ; "square list empty lazy")] - #[test_case("[('())]" => Ok(vec![construct(ExprKind::List(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 3, 5))), 2, 5)]), 1, 6)]) ; "square list lazy list")] - #[test_case("['('())]" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![]), 4, 6))), 3, 6)]), 2, 7))), 1, 7)]) ; "square lazy list lazy list")] - #[test_case("[('('))]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 5, end: 6 } }) ; "square list lazy list empty lazy")] - #[test_case("['('('))]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::ParenClose }, span: Span { start: 6, end: 7 } }) ; "square lazy list lazy list empty lazy")] - #[test_case("[(12 +)]" => Ok(vec![construct(ExprKind::List(vec![construct(ExprKind::Integer(12), 2, 4), construct(ExprKind::Call(Intern::from_ref("+")), 5, 6)]), 1, 7)]) ; "square list int add")] - #[test_case("['(12 +)]" => Ok(vec![construct(ExprKind::Lazy(Box::new(construct(ExprKind::List(vec![construct(ExprKind::Integer(12), 3, 5), construct(ExprKind::Call(Intern::from_ref("+")), 6, 7)]), 2, 8))), 1, 8)]) ; "square lazy list int add")] - #[test_case("[(æ +)]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 2, end: 4 } }) ; "square invalid int add")] - #[test_case("['(æ +)]" => Err(ParseError { reason: ParseErrorReason::UnexpectedToken { kind: TokenKind::Invalid }, span: Span { start: 3, end: 5 } }) ; "square lazy invalid int add")] - fn parser(source: &str) -> Result, ParseError> { - let lexer = Lexer::new(source); - let parser = Parser::new(lexer, source_file()); - parser.parse() - } -} diff --git a/src/scope.rs b/src/scope.rs deleted file mode 100644 index ef6962e4..00000000 --- a/src/scope.rs +++ /dev/null @@ -1,339 +0,0 @@ -use internment::Intern; - -use crate::{Chain, Expr, ExprKind, FnSymbol, Func}; -use core::fmt; -use std::{cell::RefCell, collections::HashMap, fmt::Formatter, rc::Rc}; - -pub type Val = Rc>>>; - -#[derive(Default, PartialEq)] -pub struct Scope { - pub items: HashMap, Val>, -} - -impl fmt::Debug for Scope { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let iter = self - .items - .iter() - .map(|(name, item)| (name.as_ref().as_str(), item)); - write!(f, "{:?}", HashMap::<&str, &Val>::from_iter(iter)) - } -} - -impl Clone for Scope { - fn clone(&self) -> Self { - let mut items = HashMap::new(); - - for (name, item) in self.items.iter() { - items.insert(*name, item.clone()); - } - - Self { items } - } -} - -impl Scope { - pub fn new() -> Self { - Self::default() - } - - pub fn from(items: HashMap, Val>) -> Self { - Self { items } - } - - pub fn define( - &mut self, - name: Intern, - item: Expr, - ) -> Result<(), String> { - if let Some(chain) = self.items.get(&name) { - let mut chain = RefCell::borrow_mut(chain); - match chain.is_root() { - true => { - chain.set(Some(item)); - } - false => { - chain.unlink_with(Some(item)); - } - } - } else { - let val = Rc::new(RefCell::new(Chain::new(Some(item)))); - self.items.insert(name, val); - } - - Ok(()) - } - - pub fn reserve(&mut self, name: Intern) -> Result<(), String> { - if self.items.get(&name).is_none() { - let val = Rc::new(RefCell::new(Chain::new(None))); - self.items.insert(name, val); - Ok(()) - } else { - Err("Cannot reserve an already existing variable".to_owned()) - } - } - - pub fn set( - &mut self, - name: Intern, - item: Expr, - ) -> Result<(), String> { - if let Some(chain) = self.items.get_mut(&name) { - let mut chain = RefCell::borrow_mut(chain); - chain.set(Some(item)); - Ok(()) - } else { - Err("Cannot set to a nonexistent variable".to_owned()) - } - } - - pub fn remove(&mut self, name: Intern) { - self.items.remove(&name); - } - - pub fn has(&self, name: Intern) -> bool { - self.items.contains_key(&name) - } - - pub fn get_val(&self, name: Intern) -> Option { - self.items.get(&name).and_then(|item| item.borrow().val()) - } - - pub fn get_ref(&self, name: Intern) -> Option<&Val> { - self.items.get(&name) - } - - /// Merges another scope into this one, not overwriting any existing variables - pub fn merge(&mut self, other: Scope) { - for (name, item) in other.items { - if !self.has(name) - || (self.get_val(name).is_none() && item.borrow().val().is_some()) - { - self.items.insert(name, item); - } - } - } - - pub fn duplicate(&self) -> Self { - let mut items = HashMap::new(); - - for (name, item) in self.items.iter() { - let mut item = RefCell::borrow_mut(item); - items.insert(*name, item.link()); - } - - Self { items } - } -} - -#[derive(Debug)] -pub struct Scanner<'a> { - pub scope: Scope, - pub funcs: &'a HashMap, Func>, -} - -impl<'a> Scanner<'a> { - pub fn new(scope: Scope, funcs: &'a HashMap, Func>) -> Self { - Self { scope, funcs } - } - - pub fn scan(&mut self, expr: Expr) -> Result { - if expr.val.is_function() { - let expr = expr; - // We can unwrap here because we know the expression is a function - let fn_symbol = match expr.val.fn_symbol() { - Some(fn_symbol) => fn_symbol, - None => return Err("Invalid function".to_owned()), - }; - let mut fn_body = match expr.val.fn_body() { - Some(fn_body) => fn_body.to_vec(), - None => return Err("Invalid function".to_owned()), - }; - - for item in fn_body.iter_mut() { - if let ExprKind::Call(call) = item.val.unlazy() { - if !self.funcs.contains_key(call) && !self.scope.has(*call) { - self.scope.reserve(*call).unwrap(); - } - } else if item.val.unlazy().is_function() { - let mut scanner = Scanner::new(self.scope.clone(), self.funcs); - let unlazied_mut = item.val.unlazy_mut(); - *unlazied_mut = scanner - .scan(Expr { - val: unlazied_mut.clone(), - debug_data: item.debug_data.clone(), - }) - .unwrap() - .into_expr_kind(); - } - } - - let mut fn_scope = fn_symbol.scope.clone(); - fn_scope.merge(self.scope.clone()); - - let fn_symbol = ExprKind::Fn(FnSymbol { - scope: fn_scope, - scoped: fn_symbol.scoped, - }); - - let mut list_items = vec![Expr { - val: fn_symbol, - debug_data: expr.debug_data.clone(), - }]; - list_items.extend(fn_body); - - let new_expr = ExprKind::List(list_items); - - Ok(Expr { - val: new_expr, - debug_data: expr.debug_data, - }) - } else { - // If the expression is not a function, we just return it - Ok(expr) - } - } -} - -// #[cfg(test)] -// mod tests { -// use crate::{interner::interner, ExprKind, Program}; - -// #[test] -// fn top_level_scopes() { -// let mut program = Program::new().with_core().unwrap(); -// program.eval_string("0 'a def").unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// Some(ExprKind::Integer(0)) -// ); -// } - -// #[test] -// fn function_scopes_are_isolated() { -// let mut program = Program::new().with_core().unwrap(); -// program.eval_string("'(fn 0 'a def) call").unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// None -// ); -// } - -// #[test] -// fn functions_can_set_to_outer() { -// let mut program = Program::new().with_core().unwrap(); -// program.eval_string("0 'a def '(fn 1 'a set) call").unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// Some(ExprKind::Integer(1)) -// ); -// } - -// #[test] -// fn functions_can_shadow_outer() { -// let mut program = Program::new().with_core().unwrap(); -// program.eval_string("0 'a def '(fn 1 'a def) call").unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// Some(ExprKind::Integer(0)) -// ); -// } - -// #[test] -// fn closures_can_access_vars() { -// let mut program = Program::new().with_core().unwrap(); -// program -// .eval_string("0 'a def '(fn 1 'a def '(fn a)) call call") -// .unwrap(); - -// assert_eq!(program.stack, vec![ExprKind::Integer(1)]); -// } - -// #[test] -// fn closures_can_mutate_vars() { -// let mut program = Program::new().with_core().unwrap(); -// program -// .eval_string("0 'a def '(fn 1 'a def '(fn 2 'a set a)) call call") -// .unwrap(); - -// assert_eq!(program.stack, vec![ExprKind::Integer(2)],); -// } - -// #[test] -// fn scopeless_functions_can_def_outer() { -// let mut program = Program::new().with_core().unwrap(); -// program.eval_string("'(fn! 0 'a def) call").unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// Some(ExprKind::Integer(0)) -// ); -// } - -// #[test] -// fn scopeless_function_macro_test() { -// let mut program = Program::new().with_core().unwrap(); -// program -// .eval_string("'(fn! def) 'define def 0 'a define") -// .unwrap(); - -// assert_eq!( -// program -// .scopes -// .last() -// .unwrap() -// .get_val(interner().get_or_intern("a")), -// Some(ExprKind::Integer(0)) -// ); -// } - -// #[test] -// fn should_fail_on_invalid_symbol() { -// let mut program = Program::new().with_core().unwrap(); -// let result = program.eval_string("a").unwrap_err(); - -// assert_eq!(result.message, "unknown call a"); -// } - -// #[test] -// fn should_fail_on_invalid_symbol_in_fn() { -// let mut program = Program::new().with_core().unwrap(); -// let result = program.eval_string("'(fn a) call").unwrap_err(); - -// assert_eq!(result.message, "unknown call a"); -// } - -// #[test] -// fn variables_defined_from_scopeless_should_be_usable() { -// let mut program = Program::new().with_core().unwrap(); -// program -// .eval_string("'(fn! 0 'a def) '(fn call '(fn a)) call call") -// .unwrap(); -// } -// } diff --git a/src/template.tmLanguage.json b/src/template.tmLanguage.json deleted file mode 100644 index c8e4c304..00000000 --- a/src/template.tmLanguage.json +++ /dev/null @@ -1,431 +0,0 @@ -{ - "information_for_contributors": [ - "This file has been converted from https://github.com/atom/language-stack/blob/master/grammars/stack.cson", - "If you want to provide a fix or improvement, please create a pull request against the original repository.", - "Once accepted there, we are happy to receive an update request." - ], - "version": "https://github.com/atom/language-stack/commit/45bdb881501d0b8f8b707ca1d3fcc8b4b99fca03", - "name": "Stack", - "scopeName": "source.stack", - "patterns": [ - { - "include": "#comment" - }, - { - "include": "#shebang-comment" - }, - { - "include": "#quoted-sexp" - }, - { - "include": "#sexp" - }, - { - "include": "#keyfn" - }, - { - "include": "#string" - }, - { - "include": "#vector" - }, - { - "include": "#set" - }, - { - "include": "#map" - }, - { - "include": "#regexp" - }, - { - "include": "#var" - }, - { - "include": "#constants" - }, - { - "include": "#dynamic-variables" - }, - { - "include": "#metadata" - }, - { - "include": "#namespace-symbol" - }, - { - "include": "#symbol" - } - ], - "repository": { - "comment": { - "begin": "(?\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}|\\,))", - "name": "constant.keyword.stack" - }, - "keyfn": { - "patterns": [ - { - "match": "(?<=(\\s|\\(|\\[|\\{))(if(-[-\\p{Ll}\\?]*)?|let(-[-\\p{Ll}\\?]*)?|${template}|([\\p{Ll}]*case))(?=(\\s|\\)|\\]|\\}))", - "name": "storage.control.stack" - }, - { - "match": "(?<=(\\s|\\(|\\[|\\{))(declare-?|(in-)?ns|import|use|require|load|compile|set)(?=(\\s|\\)|\\]|\\}))", - "name": "keyword.control.stack" - } - ] - }, - "dynamic-variables": { - "match": "\\*[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\d]+\\*", - "name": "meta.symbol.dynamic.stack" - }, - "map": { - "begin": "(\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.section.map.begin.stack" - } - }, - "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.map.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.map.end.stack" - } - }, - "name": "meta.map.stack", - "patterns": [ - { - "include": "$self" - } - ] - }, - "metadata": { - "patterns": [ - { - "begin": "(\\^\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.section.metadata.map.begin.stack" - } - }, - "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.metadata.map.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.metadata.map.end.stack" - } - }, - "name": "meta.metadata.map.stack", - "patterns": [ - { - "include": "$self" - } - ] - }, - { - "begin": "(\\^)", - "end": "(\\s)", - "name": "meta.metadata.simple.stack", - "patterns": [ - { - "include": "#keyword" - }, - { - "include": "$self" - } - ] - } - ] - }, - "quoted-sexp": { - "begin": "(['``]\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.expression.begin.stack" - } - }, - "end": "(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.expression.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.expression.end.trailing.stack" - }, - "3": { - "name": "punctuation.section.expression.end.stack" - } - }, - "name": "meta.quoted-expression.stack", - "patterns": [ - { - "include": "$self" - } - ] - }, - "regexp": { - "begin": "#\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.regexp.begin.stack" - } - }, - "end": "\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.regexp.end.stack" - } - }, - "name": "string.regexp.stack", - "patterns": [ - { - "include": "#regexp_escaped_char" - } - ] - }, - "regexp_escaped_char": { - "match": "\\\\.", - "name": "constant.character.escape.stack" - }, - "set": { - "begin": "(\\#\\{)", - "beginCaptures": { - "1": { - "name": "punctuation.section.set.begin.stack" - } - }, - "end": "(\\}(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\})", - "endCaptures": { - "1": { - "name": "punctuation.section.set.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.set.end.stack" - } - }, - "name": "meta.set.stack", - "patterns": [ - { - "include": "$self" - } - ] - }, - "sexp": { - "begin": "(\\()", - "beginCaptures": { - "1": { - "name": "punctuation.section.expression.begin.stack" - } - }, - "end": "(\\))$|(\\)(?=[\\}\\]\\)\\s]*(?:;|$)))|(\\))", - "endCaptures": { - "1": { - "name": "punctuation.section.expression.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.expression.end.trailing.stack" - }, - "3": { - "name": "punctuation.section.expression.end.stack" - } - }, - "name": "meta.expression.stack", - "patterns": [ - { - "begin": "(?<=\\()(ns|declare|def[\\w\\d._:+=>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", - "name": "entity.global.stack" - }, - { - "include": "$self" - } - ] - }, - { - "include": "#keyfn" - }, - { - "include": "#constants" - }, - { - "include": "#vector" - }, - { - "include": "#map" - }, - { - "include": "#set" - }, - { - "include": "#sexp" - }, - { - "match": "(?<=\\()(.+?)(?=\\s|\\))", - "captures": { - "1": { - "name": "entity.name.function.stack" - } - }, - "patterns": [ - { - "include": "$self" - } - ] - }, - { - "include": "$self" - } - ] - }, - "shebang-comment": { - "begin": "^(#!)", - "beginCaptures": { - "1": { - "name": "punctuation.definition.comment.shebang.stack" - } - }, - "end": "$", - "name": "comment.line.shebang.stack" - }, - "string": { - "begin": "(?\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)/", - "captures": { - "1": { - "name": "meta.symbol.namespace.stack" - } - } - } - ] - }, - "symbol": { - "patterns": [ - { - "match": "([\\p{L}\\.\\-\\_\\+\\=\\>\\<\\!\\?\\*][\\w\\.\\-\\_\\:\\+\\=\\>\\<\\!\\?\\*\\d]*)", - "name": "meta.symbol.stack" - } - ] - }, - "var": { - "match": "(?<=(\\s|\\(|\\[|\\{)\\#)'[\\w\\.\\-\\_\\:\\+\\=\\>\\<\\/\\!\\?\\*]+(?=(\\s|\\)|\\]|\\}))", - "name": "meta.var.stack" - }, - "vector": { - "begin": "(\\[)", - "beginCaptures": { - "1": { - "name": "punctuation.section.vector.begin.stack" - } - }, - "end": "(\\](?=[\\}\\]\\)\\s]*(?:;|$)))|(\\])", - "endCaptures": { - "1": { - "name": "punctuation.section.vector.end.trailing.stack" - }, - "2": { - "name": "punctuation.section.vector.end.stack" - } - }, - "name": "meta.vector.stack", - "patterns": [ - { - "include": "$self" - } - ] - } - } -} \ No newline at end of file diff --git a/src/test_utils.rs b/src/test_utils.rs deleted file mode 100644 index da901769..00000000 --- a/src/test_utils.rs +++ /dev/null @@ -1,97 +0,0 @@ -use crate::{Expr, ExprKind, FnSymbol}; -use core::{any::Any, cell::RefCell}; -use internment::Intern; -use itertools::Itertools; -use std::{fmt::Debug, ops::Deref, rc::Rc}; - -#[derive(Debug, Clone)] -pub enum TestExpr { - Nil, - - Boolean(bool), - Integer(i64), - Float(f64), - - String(String), - List(Vec), - - Lazy(Box), - Call(Intern), - - /// Boolean denotes whether to create a new scope. - Fn(FnSymbol), - - UserData(Rc>), -} - -impl PartialEq for TestExpr { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - // Same types. - (Self::Nil, Self::Nil) => true, - - (Self::Boolean(lhs), Self::Boolean(rhs)) => lhs == rhs, - (Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs, - (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs, - - (Self::String(lhs), Self::String(rhs)) => lhs == rhs, - - (Self::List(lhs), Self::List(rhs)) => lhs == rhs, - - (Self::Lazy(lhs), Self::Lazy(rhs)) => lhs == rhs, - (Self::Call(lhs), Self::Call(rhs)) => lhs == rhs, - - // TODO: I removed `lhs.scope == rhs.scope &&` since it made asserting - // equality impossible in tests (without filling out the entire scope). - // Though, I think there's a better solution than removing comparability. - (Self::Fn(lhs), Self::Fn(rhs)) => lhs.scoped == rhs.scoped, - - (Self::UserData(lhs), Self::UserData(rhs)) => { - core::ptr::addr_eq(Rc::as_ptr(lhs), Rc::as_ptr(rhs)) - } - - _ => false, - } - } -} - -impl From for TestExpr { - fn from(value: Expr) -> Self { - let val = value.val; - match val { - ExprKind::Nil => TestExpr::Nil, - - ExprKind::Boolean(bool) => TestExpr::Boolean(bool), - ExprKind::Integer(int) => TestExpr::Integer(int), - ExprKind::Float(float) => TestExpr::Float(float), - - ExprKind::String(string) => TestExpr::String(string), - ExprKind::List(list) => { - let mut items: Vec = Vec::new(); - for item in list { - items.push(item.into()); - } - - TestExpr::List(items) - } - - ExprKind::Lazy(lazy) => { - let inner: TestExpr = lazy.deref().clone().into(); - TestExpr::Lazy(Box::new(inner)) - } - ExprKind::Call(call) => TestExpr::Call(call), - - ExprKind::Fn(fn_symbol) => TestExpr::Fn(fn_symbol), - - ExprKind::UserData(data) => TestExpr::UserData(data), - } - } -} - -pub fn simple_expr(expr: Expr) -> TestExpr { - expr.into() -} - -pub fn simple_exprs(exprs: Vec) -> Vec { - exprs.into_iter().map(simple_expr).collect_vec() -} diff --git a/stack-cli/Cargo.toml b/stack-cli/Cargo.toml new file mode 100644 index 00000000..e202f97c --- /dev/null +++ b/stack-cli/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "stack-cli" +version = "0.1.0" +edition = "2021" + +[features] +default = ["stack-std"] +stack-std = ["dep:stack-std"] + +[dependencies] +clap = { version = "4", features = ["derive"] } +reedline = { version = "0.31.0", features = ["system_clipboard"] } +notify = "6" +crossterm = "0.27.0" + +stack-core = { path = "../stack-core" } +stack-std = { path = "../stack-std", optional = true } +codespan-reporting = "0.11.1" + +[[bin]] +name = "stack" +path = "src/main.rs" diff --git a/stack-cli/src/main.rs b/stack-cli/src/main.rs new file mode 100644 index 00000000..11d442a0 --- /dev/null +++ b/stack-cli/src/main.rs @@ -0,0 +1,320 @@ +use core::fmt; +use std::{ + io::{self, prelude::Write, Read}, + path::{Path, PathBuf}, +}; + +use clap::Parser; +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files::SimpleFiles, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; +use crossterm::{ + cursor::{self, MoveTo}, + style::Print, + terminal, QueueableCommand, +}; +use notify::{ + Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher, +}; +use reedline::{DefaultPrompt, DefaultPromptSegment, Reedline, Signal}; +use stack_core::prelude::*; + +fn main() { + let cli = Cli::parse(); + + let new_context = || Context::new().with_journal(cli.journal); + + let mut engine = Engine::new(); + let mut context = new_context(); + + #[cfg(feature = "stack-std")] + { + if cli.enable_all || cli.enable_str { + engine.add_module(stack_std::str::module()); + } + + if cli.enable_all || cli.enable_fs { + engine.add_module(stack_std::fs::module(cli.sandbox)); + } + + if cli.enable_all || cli.enable_scope { + engine.add_module(stack_std::scope::module()); + } + } + + match cli.subcommand { + Subcommand::Stdin => { + let mut stdin = std::io::stdin(); + let mut source = String::new(); + + ok_or_exit(stdin.read_to_string(&mut source)); + + let source = Source::new("stdin", source); + let mut lexer = Lexer::new(source); + let exprs = ok_or_exit(parse(&mut lexer)); + + context = ok_or_exit(engine.run(context, exprs)); + print_stack(&context); + } + Subcommand::Repl => { + let mut repl = Reedline::create(); + let prompt = DefaultPrompt::new( + DefaultPromptSegment::Empty, + DefaultPromptSegment::Empty, + ); + + loop { + let signal = ok_or_exit(repl.read_line(&prompt)); + + match signal { + Signal::CtrlC | Signal::CtrlD => { + println!("aborted"); + break; + } + Signal::Success(line) => { + if line.starts_with(':') { + match &line.as_str()[1..] { + "exit" => break, + "clear" => { + ok_or_exit(repl.clear_screen()); + } + "reset" => { + context = new_context(); + println!("Reset context"); + } + command => eprintln!("error: unknown command '{command}'"), + } + } else { + let source = Source::new("repl", line); + let mut lexer = Lexer::new(source); + let exprs = ok_or_exit(parse(&mut lexer)); + + context = match engine.run(context, exprs) { + Ok(context) => { + print_stack(&context); + context + } + Err(e) => { + eprintln!("error: {e}"); + eprint_stack(&e.context); + e.context + } + } + } + } + } + } + } + Subcommand::Run { input, watch } => { + if !watch { + let source = ok_or_exit(Source::from_path(input)); + let mut lexer = Lexer::new(source); + let exprs = ok_or_exit(parse(&mut lexer)); + + context = ok_or_exit(engine.run(context, exprs)); + print_stack(&context); + } else { + let (tx, rx) = std::sync::mpsc::channel(); + + let mut watcher = + ok_or_exit(RecommendedWatcher::new(tx, Config::default())); + ok_or_exit(watcher.watch(&input, RecursiveMode::NonRecursive)); + + let run_file = |input| { + let mut context = new_context(); + + let source = match Source::from_path(input) { + Ok(source) => source, + Err(e) => { + eprintln!("error: {e}"); + return context; + } + }; + + context.add_source(source.clone()); + + let mut lexer = Lexer::new(source); + + let exprs = match parse(&mut lexer) { + Ok(exprs) => exprs, + Err(e) => { + eprintln!("error: {e}"); + return context; + } + }; + + match engine.run(context, exprs) { + Ok(context) => { + print_stack(&context); + if let Some(journal) = context.journal() { + eprintln!("{}", journal); + } + + context + } + Err(e) => { + if let Some(info) = &e.expr.info { + let span = info.span; + let span = span.start..span.end; + + let mut files = SimpleFiles::new(); + let mut file_id = 0; + for (name, source) in e.context.sources() { + let id = files.add(name, source.source()); + + if info.source.name() == name.as_str() { + file_id = id; + } + } + + let diagnostic = Diagnostic::error() + .with_message(e.clone().to_string()) + .with_labels(vec![Label::primary(file_id, span) + .with_message("error occurs here")]); + + let writer = StandardStream::stderr(ColorChoice::Always); + let config = codespan_reporting::term::Config::default(); + + // TODO: Should we do anything for this error or can we just unwrap? + let _ = + term::emit(&mut writer.lock(), &config, &files, &diagnostic); + } + + eprint_stack(&e.context); + if let Some(journal) = e.context.journal() { + eprintln!("{}", journal); + } + + e.context + } + } + }; + + ok_or_exit(clear_screen()); + let context = run_file(&input); + + ok_or_exit(context.sources().try_for_each(|source| { + watcher + .watch(Path::new(source.0.as_str()), RecursiveMode::NonRecursive) + })); + + for event in rx { + if let Event { + kind: EventKind::Modify(_), + .. + } = ok_or_exit(event) + { + ok_or_exit(clear_screen()); + run_file(&input); + } + } + } + } + } +} + +fn ok_or_exit(result: Result) -> T +where + E: fmt::Display, +{ + match result { + Ok(x) => x, + Err(e) => { + eprintln!("error: {e}"); + std::process::exit(1); + } + } +} + +fn print_stack(context: &Context) { + print!("stack:"); + + core::iter::repeat(" ") + .zip(context.stack()) + .for_each(|(sep, x)| print!("{sep}{x:#}")); + + println!() +} + +fn eprint_stack(context: &Context) { + eprint!("stack:"); + + core::iter::repeat(" ") + .zip(context.stack()) + .for_each(|(sep, x)| eprint!("{sep}{x:#}")); + + eprintln!() +} + +fn clear_screen() -> io::Result<()> { + let mut stdout = std::io::stdout(); + + stdout.queue(cursor::Hide)?; + let (_, num_lines) = terminal::size()?; + for _ in 0..2 * num_lines { + stdout.queue(Print("\n"))?; + } + stdout.queue(MoveTo(0, 0))?; + stdout.queue(cursor::Show)?; + + stdout.flush() +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, clap::Parser)] +#[command(author, version, about, long_about = None)] +#[command(propagate_version = true)] +struct Cli { + #[command(subcommand)] + subcommand: Subcommand, + + /// Whether to enable stack journaling. + #[arg(short, long)] + journal: bool, + + /// Whether to run a sandbox variant of the enabled standard modules. + #[arg(short, long)] + #[cfg(feature = "stack-std")] + sandbox: bool, + + /// Enable all standard modules. + #[arg(long)] + #[cfg(feature = "stack-std")] + enable_all: bool, + /// Enable the string standard module. + #[arg(long)] + #[cfg(feature = "stack-std")] + enable_str: bool, + /// Enable the file-system standard module. + #[arg(long)] + #[cfg(feature = "stack-std")] + enable_fs: bool, + /// Enable the scope standard module. + #[arg(long)] + #[cfg(feature = "stack-std")] + enable_scope: bool, +} + +#[derive(Debug, Clone, PartialEq, Eq, Default, clap::Subcommand)] +enum Subcommand { + /// Runs a REPL [alias >]. + #[default] + #[command(alias = ">")] + Repl, + /// Runs the code supplied via STDIN [alias -]. + #[command(alias = "-")] + Stdin, + /// Runs the code from an input file path. + Run { + /// The input file path. + input: PathBuf, + + /// Whether to watch the file and re-run it if there are changes. + #[arg(short, long)] + watch: bool, + }, +} diff --git a/stack-core/Cargo.toml b/stack-core/Cargo.toml new file mode 100644 index 00000000..e4a7effb --- /dev/null +++ b/stack-core/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "stack-core" +version = "0.1.0" +edition = "2021" + +[dependencies] +internment = "0.7.4" +unicode-segmentation.workspace = true +compact_str.workspace = true +yansi = "1" + +[dev-dependencies] +test-case.workspace = true diff --git a/src/chain.rs b/stack-core/src/chain.rs similarity index 100% rename from src/chain.rs rename to stack-core/src/chain.rs diff --git a/stack-core/src/context.rs b/stack-core/src/context.rs new file mode 100644 index 00000000..9270aebb --- /dev/null +++ b/stack-core/src/context.rs @@ -0,0 +1,278 @@ +use core::fmt; +use std::{cell::RefCell, collections::HashMap, rc::Rc}; + +use crate::{ + chain::Chain, + engine::{RunError, RunErrorReason}, + expr::Expr, + journal::{Journal, JournalOp}, + scope::{Scanner, Scope}, + source::Source, + symbol::Symbol, + vec_one::VecOne, +}; + +// TODO: This API could be a lot nicer. + +#[derive(Debug, Clone, PartialEq, Default)] +pub struct Context { + stack: Vec, + lets: Vec>, + scopes: VecOne, + journal: Option, + sources: HashMap, +} + +impl fmt::Display for Context { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Stack: [")?; + + // self.stack.iter().enumerate().try_for_each(|(i, expr)| { + // if i == self.stack.len() - 1 { + // write!(f, "{expr:#}") + // } else { + // write!(f, "{expr:#}, ") + // } + // })?; + + core::iter::once("") + .chain(core::iter::repeat(" ")) + .zip(self.stack.iter()) + .try_for_each(|(sep, x)| write!(f, "{sep}{x:#}"))?; + + write!(f, "]")?; + + // if !self.scopes.is_empty() { + // writeln!(f, "Scope:")?; + + // let layer = self.scopes.last().unwrap(); + // let items = layer.items.len(); + // for (item_i, (key, value)) in + // layer.items.iter().sorted_by_key(|(s, _)| *s).enumerate() + // { + // if item_i == items - 1 { + // write!( + // f, + // " + {}: {}", + // interner().resolve(key), + // match value.borrow().val() { + // Some(expr) => expr.to_string(), + // None => "None".to_owned(), + // } + // )?; + // } else { + // writeln!( + // f, + // " + {}: {}", + // interner().resolve(key), + // match value.borrow().val() { + // Some(expr) => expr.to_string(), + // None => "None".to_owned(), + // } + // )?; + // } + // } + // } + + if let Some(journal) = self.journal() { + let journal = journal.to_string(); + + if !journal.is_empty() { + write!(f, "\n\n")?; + write!(f, "{}", journal)?; + } + } + + Ok(()) + } +} + +impl Context { + #[inline] + pub fn new() -> Self { + Self { + stack: Vec::new(), + lets: Vec::new(), + scopes: VecOne::new(Scope::new()), + journal: None, + sources: HashMap::new(), + } + } + + #[inline] + pub fn with_stack_capacity(mut self, capacity: usize) -> Self { + self.stack = Vec::with_capacity(capacity); + self + } + + #[inline] + pub fn with_journal(mut self, journal: bool) -> Self { + self.journal = if journal { Some(Journal::new()) } else { None }; + self + } + + #[inline] + pub fn add_source(&mut self, source: Source) { + self.sources.insert(Symbol::from_ref(source.name()), source); + } + + #[inline] + pub fn source(&mut self, name: &Symbol) -> Option<&Source> { + self.sources.get(name) + } + + #[inline] + pub fn sources( + &self, + ) -> std::collections::hash_map::Iter<'_, Symbol, Source> { + self.sources.iter() + } + + #[inline] + pub fn remove_source(&mut self, name: &Symbol) { + self.sources.remove(name); + } + + #[inline] + pub fn stack(&self) -> &[Expr] { + &self.stack + } + + #[inline] + pub fn stack_mut(&mut self) -> &mut Vec { + &mut self.stack + } + + #[inline] + pub fn journal(&self) -> &Option { + &self.journal + } + + #[inline] + pub fn journal_mut(&mut self) -> &mut Option { + &mut self.journal + } + + pub fn stack_push(&mut self, expr: Expr) -> Result<(), RunError> { + let expr = if expr.kind.is_function() { + let mut scanner = Scanner::new(self.scopes.last().duplicate()); + + match scanner.scan(expr.clone()) { + Ok(expr) => expr, + Err(reason) => { + return Err(RunError { + reason, + context: self.clone(), + expr, + }) + } + } + } else { + expr + }; + + if let Some(journal) = self.journal_mut() { + journal.op(JournalOp::Push(expr.clone())); + } + + self.stack.push(expr); + // TODO: I don't think we need to commit after each push. + // self.journal.commit(); + + Ok(()) + } + + pub fn stack_pop(&mut self, expr: &Expr) -> Result { + match self.stack.pop() { + Some(expr) => { + if let Some(journal) = self.journal_mut() { + journal.op(JournalOp::Pop(expr.clone())); + } + Ok(expr) + } + None => Err(RunError { + reason: RunErrorReason::StackUnderflow, + context: self.clone(), + expr: expr.clone(), + }), + } + } + + #[inline] + pub fn scope_item(&self, symbol: Symbol) -> Option { + self.scopes.last().get_val(symbol) + } + + #[inline] + pub fn scope_items( + &self, + ) -> impl Iterator>>>)> { + self.scopes.last().items.iter() + } + + #[inline] + pub fn def_scope_item(&mut self, symbol: Symbol, value: Expr) { + let layer = self.scopes.last_mut(); + + layer.define(symbol, value); + // Remove the item from our let block since we've defined our own + self.let_remove(symbol); + } + + pub fn set_scope_item( + &mut self, + symbol: Symbol, + expr: Expr, + ) -> Result<(), RunError> { + let layer = self.scopes.last_mut(); + + match layer.set(symbol, expr.clone()) { + Ok(_) => Ok(()), + Err(reason) => Err(RunError { + reason, + context: self.clone(), + expr, + }), + } + } + + #[inline] + pub fn remove_scope_item(&mut self, symbol: Symbol) { + self.scopes.last_mut().remove(symbol); + } + + #[inline] + pub fn push_scope(&mut self, scope: Scope) { + self.scopes.push(scope); + } + + #[inline] + pub fn pop_scope(&mut self) { + self.scopes.try_pop(); + } + + #[inline] + pub fn let_push(&mut self) { + self.lets.push(HashMap::new()); + } + + #[inline] + pub fn let_pop(&mut self) -> Option> { + self.lets.pop() + } + + #[inline] + pub fn let_get(&self, name: Symbol) -> Option<&Expr> { + self.lets.iter().rev().find_map(|x| x.get(&name)) + } + + #[inline] + pub fn let_set(&mut self, name: Symbol, expr: Expr) -> Option { + self.lets.last_mut().and_then(|x| x.insert(name, expr)) + } + + #[inline] + pub fn let_remove(&mut self, name: Symbol) -> Option { + self.lets.last_mut().and_then(|x| x.remove(&name)) + } +} diff --git a/stack-core/src/engine.rs b/stack-core/src/engine.rs new file mode 100644 index 00000000..7140fb3b --- /dev/null +++ b/stack-core/src/engine.rs @@ -0,0 +1,478 @@ +use core::{fmt, str::FromStr}; +use std::collections::HashMap; + +use crate::{ + context::Context, + expr::{ + vec_fn_body, vec_fn_symbol, vec_is_function, Error, Expr, ExprKind, FnIdent, + }, + intrinsic::Intrinsic, + journal::JournalOp, + module::Module, + symbol::Symbol, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Default)] +pub struct Engine { + modules: HashMap, +} + +impl Engine { + #[inline] + pub fn new() -> Self { + Self { + modules: HashMap::new(), + } + } + + #[inline] + pub fn with_module(mut self, module: Module) -> Self { + self.add_module(module); + self + } + + #[inline] + pub fn add_module(&mut self, module: Module) -> &mut Self { + self.modules.insert(module.name(), module); + self + } + + #[inline] + pub fn module(&self, symbol: &Symbol) -> Option<&Module> { + self.modules.get(symbol) + } + + pub fn run( + &self, + mut context: Context, + exprs: Vec, + ) -> Result { + for expr in exprs { + context = self.run_expr(context, expr)?; + } + + Ok(context) + } + + #[allow(clippy::only_used_in_recursion)] + pub fn run_expr( + &self, + mut context: Context, + expr: Expr, + ) -> Result { + match expr.kind { + ExprKind::Nil + | ExprKind::Boolean(_) + | ExprKind::Integer(_) + | ExprKind::Float(_) + | ExprKind::String(_) => { + context.stack_push(expr)?; + Ok(context) + } + ExprKind::Error(_) => Err(RunError { + reason: RunErrorReason::DoubleError, + context, + expr, + }), + // TODO: This is temporary until a proper solution is created. + ExprKind::Symbol(x) => { + if let Some(journal) = context.journal_mut() { + journal.commit(); + } + + if let Ok(intrinsic) = Intrinsic::from_str(x.as_str()) { + if let Some(journal) = context.journal_mut() { + journal.commit(); + journal.op(JournalOp::FnCall(expr.clone())); + } + let mut context = intrinsic.run(self, context, expr)?; + if let Some(journal) = context.journal_mut() { + journal.commit(); + } + + Ok(context) + } else if let Some((namespace, func)) = x.as_str().split_once(':') { + if let Some(func) = self + .modules + .get(&Symbol::from_ref(namespace)) + .and_then(|module| module.func(Symbol::from_ref(func))) + { + if let Some(journal) = context.journal_mut() { + journal.op(JournalOp::FnCall(expr.clone())); + } + context = func(self, context, expr)?; + if let Some(journal) = context.journal_mut() { + journal.commit(); + } + Ok(context) + } else { + context.stack_push(Expr { + kind: ExprKind::Error(Error::new("unknown function".into())), + info: None, + })?; + + Ok(context) + } + } else if let Some(r#let) = context.let_get(x).cloned() { + context.stack_push(r#let)?; + Ok(context) + } else if let Some(item) = context.scope_item(x) { + if item.kind.is_function() { + let fn_ident = item.kind.fn_symbol().unwrap(); + let fn_body = item.kind.fn_body().unwrap(); + self.call_fn(&expr, fn_ident, fn_body, context) + } else { + if let Some(journal) = context.journal_mut() { + journal.op(JournalOp::Call(expr.clone())); + } + let result = context.stack_push(item); + if let Some(journal) = context.journal_mut() { + journal.commit(); + } + + result.map(|_| context) + } + } else { + Err(RunError { + context: context.clone(), + expr, + reason: RunErrorReason::UnknownCall, + }) + } + } + ExprKind::Lazy(x) => { + context.stack_push(*x)?; + Ok(context) + } + ExprKind::List(ref x) => match vec_is_function(x) { + true => { + let fn_ident = vec_fn_symbol(x).unwrap(); + let fn_body = vec_fn_body(x).unwrap(); + self.call_fn(&expr, fn_ident, fn_body, context) + } + false => self.run(context, x.to_vec()), + }, + ExprKind::Fn(_) => Ok(context), + } + } + + /// Handles auto-calling symbols (calls) when they're pushed to the stack + /// This is also triggered by the `call` keyword + pub fn call_fn( + &self, + expr: &Expr, + fn_ident: &FnIdent, + fn_body: &[Expr], + mut context: Context, + ) -> Result { + if let Some(journal) = context.journal_mut() { + journal.op(JournalOp::FnCall(expr.clone())); + } + + if fn_ident.scoped { + context.push_scope(fn_ident.scope.clone()); + } + + if let Some(journal) = context.journal_mut() { + journal.commit(); + journal.op(JournalOp::FnStart(fn_ident.scoped)); + } + + match self.run(context, fn_body.to_vec()) { + Ok(mut context) => { + if let Some(journal) = context.journal_mut() { + journal.commit(); + journal.op(JournalOp::FnEnd); + } + + if fn_ident.scoped { + context.pop_scope(); + } + + Ok(context) + } + Err(err) => Err(err), + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct RunError { + pub reason: RunErrorReason, + pub context: Context, + pub expr: Expr, +} + +impl std::error::Error for RunError {} + +impl fmt::Display for RunError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{} caused by ", self.reason)?; + + if let Some(ref info) = self.expr.info { + write!(f, "{}", info) + } else { + write!(f, "{}", self.expr) + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum RunErrorReason { + StackUnderflow, + DoubleError, + AssertionFailed, + Halt, + InvalidLet, + + // Scope Errors + UnknownCall, + InvalidDefinition, + InvalidFunction, + CannotSetBeforeDef, +} + +impl std::error::Error for RunErrorReason {} + +impl fmt::Display for RunErrorReason { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::StackUnderflow => write!(f, "stack underflow"), + Self::DoubleError => write!(f, "double error"), + Self::AssertionFailed => write!(f, "assertion failed"), + Self::Halt => write!(f, "halt"), + Self::InvalidLet => write!(f, "invalid let"), + Self::UnknownCall => write!(f, "unknown call"), + Self::InvalidDefinition => write!(f, "invalid definition"), + Self::InvalidFunction => write!(f, "invalid function"), + Self::CannotSetBeforeDef => { + write!(f, "cannot set to a nonexistent variable") + } + } + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + // TODO: Move test for scopes/vars into src/scope.rs? + #[test] + fn can_define_vars() { + let source = Source::new("", "0 'a def a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0),] + ); + } + + #[test] + fn can_redefine_vars() { + let source = Source::new("", "0 'a def a 1 'a def a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0), &ExprKind::Integer(1),] + ); + } + + #[test] + fn can_set_vars() { + let source = Source::new("", "0 'a def a 1 'a set a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0), &ExprKind::Integer(1),] + ); + } + + // TODO: Move test for lets into a better place? + #[test] + fn can_use_lets() { + let source = Source::new("", "10 2 '(a b -) '(a b) let"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(8),] + ); + } + + #[test] + fn lets_take_precedence_over_scope() { + let source = Source::new("", "0 'a def 1 '(a) '(a) let"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(1),] + ); + } + + #[test] + fn lets_act_as_overlays() { + let source = Source::new("", "0 'a def 1 '(a 2 'a def a) '(a) let a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(1), + &ExprKind::Integer(2), + &ExprKind::Integer(2), + ] + ); + } + + #[test] + fn functions_work_in_lets() { + let source = Source::new("", "0 'a def 1 '(fn a 2 'a def a) '(a) let a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(1), + &ExprKind::Integer(2), + &ExprKind::Integer(0), + ] + ); + } + + #[test] + fn scopeless_functions_work_in_lets() { + let source = Source::new("", "0 'a def 1 '(fn! a 2 'a def a) '(a) let a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(1), + &ExprKind::Integer(2), + &ExprKind::Integer(2), + ] + ); + } + + #[test] + fn lets_dont_leak() { + let source = Source::new( + "", + "0 'a def + 1 '(a) '(a) let + 1 '(fn! a) '(a) let + 1 '(fn a) '(a) let + a", + ); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(1), + &ExprKind::Integer(1), + &ExprKind::Integer(1), + &ExprKind::Integer(0), + ] + ); + } + + #[test] + fn lets_cant_set() { + let source = Source::new("", "1 '(2 'a set) '(a) let"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let context = Context::new().with_stack_capacity(32); + assert_eq!( + engine.run(context, exprs).map_err(|err| err.reason), + Err(RunErrorReason::CannotSetBeforeDef) + ); + } +} diff --git a/stack-core/src/expr.rs b/stack-core/src/expr.rs new file mode 100644 index 00000000..63a6e8d0 --- /dev/null +++ b/stack-core/src/expr.rs @@ -0,0 +1,404 @@ +use core::{cmp::Ordering, fmt, hash::Hash, ops}; + +use compact_str::CompactString; +use internment::Intern; +use yansi::Paint; + +use crate::{lexer::Span, scope::Scope, source::Source, symbol::Symbol}; + +#[derive(Debug, Clone)] +pub struct Expr { + pub kind: ExprKind, + pub info: Option, +} + +impl PartialEq for Expr { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.kind == other.kind + } +} + +impl PartialOrd for Expr { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + self.kind.partial_cmp(&other.kind) + } +} + +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if f.alternate() { + write!(f, "{:#}", self.kind) + } else { + write!(f, "{}", self.kind) + } + } +} + +#[derive(Debug, Clone)] +pub enum ExprKind { + Nil, + Error(Error), + + Boolean(bool), + Integer(i64), + Float(f64), + String(CompactString), + + Symbol(Symbol), + + Lazy(Box), + List(Vec), + + Fn(FnIdent), +} + +impl ExprKind { + #[inline] + pub const fn is_nil(&self) -> bool { + matches!(self, Self::Nil) + } + + #[inline] + pub const fn is_truthy(&self) -> bool { + matches!(self, Self::Boolean(true)) + } + + pub fn is_function(&self) -> bool { + match self { + Self::List(list) => vec_is_function(list), + _ => false, + } + } + + pub fn fn_symbol(&self) -> Option<&FnIdent> { + match self { + Self::List(list) => vec_fn_symbol(list), + _ => None, + } + } + + pub fn fn_symbol_mut(&mut self) -> Option<&mut FnIdent> { + match self { + Self::List(list) => vec_fn_symbol_mut(list), + _ => None, + } + } + + pub fn fn_body(&self) -> Option<&[Expr]> { + match self { + Self::List(list) => vec_fn_body(list), + _ => None, + } + } + + pub const fn unlazy(&self) -> &ExprKind { + match self { + ExprKind::Lazy(x) => x.kind.unlazy(), + x => x, + } + } + + pub fn unlazy_mut(&mut self) -> &mut ExprKind { + match self { + ExprKind::Lazy(x) => x.kind.unlazy_mut(), + x => x, + } + } +} + +impl PartialEq for ExprKind { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Nil, Self::Nil) => true, + (Self::Error(lhs), Self::Error(rhs)) => lhs == rhs, + + (Self::Boolean(lhs), Self::Boolean(rhs)) => lhs == rhs, + (Self::Integer(lhs), Self::Integer(rhs)) => lhs == rhs, + (Self::Float(lhs), Self::Float(rhs)) => lhs == rhs, + (Self::String(lhs), Self::String(rhs)) => lhs == rhs, + + (Self::Symbol(lhs), Self::Symbol(rhs)) => lhs == rhs, + + (Self::Lazy(lhs), Self::Lazy(rhs)) => lhs == rhs, + (Self::List(lhs), Self::List(rhs)) => lhs == rhs, + + _ => false, + } + } +} + +impl PartialOrd for ExprKind { + fn partial_cmp(&self, other: &Self) -> Option { + match (self, other) { + (Self::Nil, Self::Nil) => Some(Ordering::Equal), + (Self::Error(lhs), Self::Error(rhs)) => { + lhs.eq(rhs).then_some(Ordering::Equal) + } + + (Self::Boolean(lhs), Self::Boolean(rhs)) => { + lhs.eq(rhs).then_some(Ordering::Equal) + } + (Self::Integer(lhs), Self::Integer(rhs)) => lhs.partial_cmp(rhs), + (Self::Float(lhs), Self::Float(rhs)) => lhs.partial_cmp(rhs), + (Self::String(lhs), Self::String(rhs)) => { + lhs.eq(rhs).then_some(Ordering::Equal) + } + + (Self::Symbol(lhs), Self::Symbol(rhs)) => { + lhs.eq(rhs).then_some(Ordering::Equal) + } + + (Self::Lazy(lhs), Self::Lazy(rhs)) => lhs.partial_cmp(rhs), + (Self::List(lhs), Self::List(rhs)) => { + lhs.eq(rhs).then_some(Ordering::Equal) + } + + _ => None, + } + } +} + +impl ops::Add for ExprKind { + type Output = Result; + + fn add(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(lhs), Self::Integer(rhs)) => { + Ok(Self::Integer(lhs.saturating_add(rhs))) + } + (Self::Float(lhs), Self::Float(rhs)) => Ok(Self::Float(lhs + rhs)), + + (lhs, rhs) => Err((lhs, rhs)), + } + } +} + +impl ops::Sub for ExprKind { + type Output = Result; + + fn sub(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(lhs), Self::Integer(rhs)) => { + Ok(Self::Integer(lhs.saturating_sub(rhs))) + } + (Self::Float(lhs), Self::Float(rhs)) => Ok(Self::Float(lhs - rhs)), + + (lhs, rhs) => Err((lhs, rhs)), + } + } +} + +impl ops::Mul for ExprKind { + type Output = Result; + + fn mul(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(lhs), Self::Integer(rhs)) => { + Ok(Self::Integer(lhs.saturating_mul(rhs))) + } + (Self::Float(lhs), Self::Float(rhs)) => Ok(Self::Float(lhs * rhs)), + + (lhs, rhs) => Err((lhs, rhs)), + } + } +} + +impl ops::Div for ExprKind { + type Output = Result; + + fn div(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(lhs), Self::Integer(rhs)) => { + Ok(Self::Integer(lhs.saturating_div(rhs))) + } + (Self::Float(lhs), Self::Float(rhs)) => Ok(Self::Float(lhs / rhs)), + + (lhs, rhs) => Err((lhs, rhs)), + } + } +} + +impl ops::Rem for ExprKind { + type Output = Result; + + fn rem(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::Integer(lhs), Self::Integer(rhs)) => Ok(Self::Integer(lhs % rhs)), + (Self::Float(lhs), Self::Float(rhs)) => Ok(Self::Float(lhs % rhs)), + + (lhs, rhs) => Err((lhs, rhs)), + } + } +} + +impl fmt::Display for ExprKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Is there a nicer way to do this that avoids the duplication? + if f.alternate() { + match self { + Self::Nil => write!(f, "{}", "nil".green()), + Self::Error(x) => write!(f, "error({})", x.to_string().red()), + + Self::Boolean(x) => write!(f, "{}", x.to_string().green()), + Self::Integer(x) => write!(f, "{}", x.to_string().blue()), + Self::Float(x) => write!(f, "{}", x.to_string().blue()), + Self::String(x) => { + write!(f, "{}{}{}", "\"".green(), x.green(), "\"".green(),) + } + + Self::Symbol(x) => write!(f, "{}", x.as_str().blue()), + + Self::Lazy(x) => write!(f, "{}{x:#}", "'".yellow()), + Self::List(x) => { + write!(f, "{}", "(".yellow())?; + + core::iter::once("") + .chain(core::iter::repeat(" ")) + .zip(x.iter()) + .try_for_each(|(sep, x)| write!(f, "{sep}{x:#}"))?; + + write!(f, "{}", ")".yellow()) + } + + Self::Fn(x) => write!(f, "{}", x.to_string().yellow()), + } + } else { + match self { + Self::Nil => write!(f, "nil"), + Self::Error(x) => write!(f, "error({x})"), + + Self::Boolean(x) => write!(f, "{x}"), + Self::Integer(x) => write!(f, "{x}"), + Self::Float(x) => write!(f, "{x}"), + Self::String(x) => write!(f, "{x}"), + + Self::Symbol(x) => write!(f, "{}", x.as_str()), + + Self::Lazy(x) => write!(f, "{x}"), + Self::List(x) => { + write!(f, "(")?; + + core::iter::once("") + .chain(core::iter::repeat(" ")) + .zip(x.iter()) + .try_for_each(|(sep, x)| write!(f, "{sep}{x}"))?; + + write!(f, ")") + } + + Self::Fn(x) => write!(f, "{x}"), + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Error(Intern); + +impl Error { + /// Creates a [`Error`]. + #[inline] + pub fn new(value: String) -> Self { + Self(Intern::new(ErrorInner(value))) + } + + /// Returns the &[str] for this [`Error`]. + #[inline] + pub fn as_str(&self) -> &str { + self.0 .0.as_str() + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\"", self.as_str()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] +struct ErrorInner(String); + +impl fmt::Debug for ErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\"", self.0) + } +} + +impl fmt::Display for ErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "\"{}\"", self.0) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct FnIdent { + pub scoped: bool, + pub scope: Scope, +} + +impl fmt::Display for FnIdent { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "fn") + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ExprInfo { + pub source: Source, + pub span: Span, +} + +impl fmt::Display for ExprInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{}:{}", + self.source.name(), + self + .source + .location(self.span.start) + .map(|x| x.to_string()) + .unwrap_or_else(|| "?:?".into()) + ) + } +} + +pub fn vec_is_function(list: &[Expr]) -> bool { + list + .first() + .map(|x| { + matches!( + x, + Expr { + kind: ExprKind::Fn(_), + .. + } + ) + }) + .unwrap_or(false) +} + +pub fn vec_fn_symbol(list: &[Expr]) -> Option<&FnIdent> { + list.first().and_then(|x| match &x.kind { + ExprKind::Fn(scope) => Some(scope), + _ => None, + }) +} + +pub fn vec_fn_symbol_mut(list: &mut [Expr]) -> Option<&mut FnIdent> { + list.first_mut().and_then(|x| match &mut x.kind { + ExprKind::Fn(scope) => Some(scope), + _ => None, + }) +} + +pub fn vec_fn_body(list: &[Expr]) -> Option<&[Expr]> { + list.first().and_then(|x| match x.kind { + ExprKind::Fn(_) => Some(&list[1..]), + _ => None, + }) +} diff --git a/stack-core/src/intrinsic.rs b/stack-core/src/intrinsic.rs new file mode 100644 index 00000000..b93ca5d0 --- /dev/null +++ b/stack-core/src/intrinsic.rs @@ -0,0 +1,838 @@ +use core::{fmt, num::FpCategory, str::FromStr}; + +use compact_str::ToCompactString; +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + context::Context, + expr::{Expr, ExprKind}, + journal::JournalOp, + lexer::Lexer, + prelude::{parse, Engine, RunError, RunErrorReason}, + source::Source, + symbol::Symbol, +}; + +macro_rules! intrinsics { + ($($ident:ident => $s:literal),* $(,)?) => { + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub enum Intrinsic { + $($ident),* + } + + impl Intrinsic { + /// Returns the &[str] name of this [`Intrinsic`]. + pub const fn as_str(self) -> &'static str { + match self { + $(Self::$ident => $s),* + } + } + } + + impl FromStr for Intrinsic { + type Err = ParseIntrinsicError; + + fn from_str(s: &str) -> Result { + match s { + $($s => Ok(Self::$ident),)* + _ => Err(ParseIntrinsicError), + } + } + } + + impl fmt::Display for Intrinsic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } + } + }; +} + +intrinsics! { + Add => "+", + Sub => "-", + Mul => "*", + Div => "/", + Rem => "%", + + Eq => "=", + Ne => "!=", + Lt => "<", + Le => "<=", + Gt => ">", + Ge => ">=", + + Or => "or", + And => "and", + + Assert => "assert", + + Drop => "drop", + Dupe => "dupe", + Swap => "swap", + Rot => "rot", + + Len => "len", + Nth => "nth", + Split => "split", + Concat => "concat", + Push => "push", + Pop => "pop", + + Cast => "cast", + Lazy => "lazy", + + If => "if", + Halt => "halt", + + Call => "call", + + Let => "let", + Def => "def", + Set => "set", + Get => "get", + + Print => "print", + + OrElse => "orelse", + + Import => "import", +} + +impl Intrinsic { + pub fn run( + &self, + engine: &Engine, + mut context: Context, + expr: Expr, + ) -> Result { + match self { + // MARK: Add + Self::Add => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = match lhs.kind.clone() + rhs.kind.clone() { + Ok(res) => res, + Err(_) => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Sub + Self::Sub => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = match lhs.kind.clone() - rhs.kind.clone() { + Ok(res) => res, + Err(_) => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Mul + Self::Mul => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = match lhs.kind.clone() * rhs.kind.clone() { + Ok(res) => res, + Err(_) => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Div + Self::Div => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = match lhs.kind.clone() / rhs.kind.clone() { + Ok(res) => res, + Err(_) => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Rem + Self::Rem => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = match lhs.kind.clone() % rhs.kind.clone() { + Ok(res) => res, + Err(_) => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + + // MARK: Eq + Self::Eq => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind == rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Ne + Self::Ne => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind != rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Lt + Self::Lt => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind < rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Le + Self::Le => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind <= rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Gt + Self::Gt => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind > rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Ge + Self::Ge => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = ExprKind::Boolean(lhs.kind >= rhs.kind); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + + // MARK: Or + Self::Or => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = + ExprKind::Boolean(lhs.kind.is_truthy() || rhs.kind.is_truthy()); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: And + Self::And => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + let kind = + ExprKind::Boolean(lhs.kind.is_truthy() && rhs.kind.is_truthy()); + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + + // MARK: Assert + Self::Assert => { + let bool = context.stack_pop(&expr)?; + let message = context.stack_pop(&expr)?; + + if bool.kind.is_truthy() { + Ok(context) + } else { + Err(RunError { + reason: RunErrorReason::AssertionFailed, + context, + expr: Expr { + info: None, + kind: message.kind, + }, + }) + } + } + + // MARK: Drop + Self::Drop => { + context.stack_pop(&expr)?; + Ok(context) + } + // MARK: Dupe + Self::Dupe => { + let item = context.stack().last().cloned().ok_or_else(|| RunError { + reason: RunErrorReason::StackUnderflow, + context: context.clone(), + expr, + })?; + + context.stack_push(item)?; + Ok(context) + } + // MARK: Swap + Self::Swap => { + let len = context.stack().len(); + + if len >= 2 { + context.stack_mut().swap(len - 1, len - 2); + Ok(context) + } else { + Err(RunError { + reason: RunErrorReason::StackUnderflow, + context, + expr, + }) + } + } + // MARK: Rot + Self::Rot => { + let len = context.stack().len(); + + if len >= 3 { + context.stack_mut().swap(len - 1, len - 3); + context.stack_mut().swap(len - 2, len - 3); + + Ok(context) + } else { + Err(RunError { + reason: RunErrorReason::StackUnderflow, + context, + expr, + }) + } + } + + // MARK: Len + Self::Len => { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::List(ref x) => { + debug_assert!(x.len() <= i64::MAX as usize); + ExprKind::Integer(x.len() as i64) + } + ExprKind::String(ref x) => { + let len = x.graphemes(true).count(); + debug_assert!(len <= i64::MAX as usize); + ExprKind::Integer(len as i64) + } + _ => ExprKind::Nil, + }; + + context.stack_push(item.clone())?; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Nth + Self::Nth => { + let index = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + // TODO: Can these eager clones be removed? + let kind = match (item.kind.clone(), index.kind.clone()) { + (ExprKind::List(x), ExprKind::Integer(i)) if i >= 0 => x + .get(i as usize) + .map(|x| x.kind.clone()) + .unwrap_or(ExprKind::Nil), + (ExprKind::String(x), ExprKind::Integer(i)) if i >= 0 => x + .as_str() + .graphemes(true) + .nth(i as usize) + .map(|x| ExprKind::String(x.into())) + .unwrap_or(ExprKind::Nil), + _ => ExprKind::Nil, + }; + + context.stack_push(item.clone())?; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Split + Self::Split => { + let index = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + // TODO: Can these eager clones be removed? + match (item.kind.clone(), index.kind.clone()) { + (ExprKind::List(mut x), ExprKind::Integer(i)) if i >= 0 => { + if (i as usize) < x.len() { + let rest = x.split_off(i as usize); + + context.stack_push(Expr { + kind: ExprKind::List(x), + info: None, + })?; + + context.stack_push(Expr { + kind: ExprKind::List(rest), + info: None, + })?; + } else { + context.stack_push(Expr { + kind: ExprKind::List(x), + info: None, + })?; + + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + })?; + } + } + (ExprKind::String(mut x), ExprKind::Integer(i)) if i >= 0 => { + match x.as_str().grapheme_indices(true).nth(i as usize) { + Some((i, _)) => { + let rest = x.split_off(i); + + context.stack_push(Expr { + kind: ExprKind::String(x), + info: None, + })?; + + context.stack_push(Expr { + kind: ExprKind::String(rest), + info: None, + })?; + } + None => { + context.stack_push(Expr { + kind: ExprKind::String(x), + info: None, + })?; + + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + })?; + } + } + } + _ => { + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + })?; + + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + })?; + } + } + + Ok(context) + } + // MARK: Concat + Self::Concat => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + // TODO: Can these eager clones be removed? + let kind = match (lhs.kind.clone(), rhs.kind.clone()) { + (ExprKind::List(mut lhs), ExprKind::List(rhs)) => { + lhs.extend(rhs); + ExprKind::List(lhs) + } + (ExprKind::String(mut lhs), ExprKind::String(rhs)) => { + lhs.push_str(&rhs); + ExprKind::String(lhs) + } + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Push + Self::Push => { + let list = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + let kind = match (list.kind.clone(), item.kind.clone()) { + (ExprKind::List(mut x), i) => { + x.push(Expr { + kind: i, + info: item.info.clone(), + }); + ExprKind::List(x) + } + (ExprKind::String(mut x), ExprKind::String(s)) => { + x.push_str(&s); + ExprKind::String(x) + } + (ExprKind::String(mut x), ExprKind::Integer(c)) + if c >= 0 && c <= u32::MAX as i64 => + { + if let Some(c) = char::from_u32(c as u32) { + x.push(c); + ExprKind::String(x) + } else { + ExprKind::Nil + } + } + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + // MARK: Pop + Self::Pop => { + let list = context.stack_pop(&expr)?; + + match list.kind.clone() { + ExprKind::List(mut x) => { + let e = x.pop().unwrap_or(Expr { + kind: ExprKind::Nil, + info: None, + }); + + context.stack_push(Expr { + kind: ExprKind::List(x), + info: list.info, + })?; + context.stack_push(e)?; + } + ExprKind::String(mut x) => { + let e = x + .pop() + .map(|e| Expr { + kind: ExprKind::String(e.to_compact_string()), + info: None, + }) + .unwrap_or(Expr { + kind: ExprKind::Nil, + info: None, + }); + + context.stack_push(Expr { + kind: ExprKind::String(x), + info: list.info, + })?; + context.stack_push(e)?; + } + _ => { + context.stack_push(list.clone())?; + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + })?; + } + } + + Ok(context) + } + + // MARK: Cast + Self::Cast => { + let ty = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + // TODO: Can these eager clones be removed? + let kind = match ty.kind { + ExprKind::String(ref x) => match (item.kind.clone(), x.as_str()) { + (ExprKind::Nil, "boolean") => ExprKind::Boolean(false), + (ExprKind::Boolean(x), "boolean") => ExprKind::Boolean(x), + (ExprKind::Integer(x), "boolean") => ExprKind::Boolean(x != 0), + (ExprKind::Float(x), "boolean") => ExprKind::Boolean(x == 0.0), + + (ExprKind::Nil, "integer") => ExprKind::Integer(0), + (ExprKind::Boolean(x), "integer") => ExprKind::Integer(x as i64), + (ExprKind::Integer(x), "integer") => ExprKind::Integer(x), + (ExprKind::Float(x), "integer") => { + let x = x.floor(); + + match x.classify() { + FpCategory::Zero => ExprKind::Integer(0), + FpCategory::Normal + if x >= i64::MIN as f64 && x <= i64::MAX as f64 => + { + ExprKind::Integer(x as i64) + } + _ => ExprKind::Nil, + } + } + + (ExprKind::Nil, "float") => ExprKind::Float(0.0), + (ExprKind::Boolean(x), "float") => ExprKind::Float(x as i64 as f64), + (ExprKind::Integer(x), "float") => ExprKind::Float(x as f64), + (ExprKind::Float(x), "float") => ExprKind::Float(x), + + (ExprKind::Nil, "string") => ExprKind::String("nil".into()), + (ExprKind::Boolean(x), "string") => { + ExprKind::String(x.to_compact_string()) + } + (ExprKind::Integer(x), "string") => { + ExprKind::String(x.to_compact_string()) + } + (ExprKind::Float(x), "string") => { + ExprKind::String(x.to_compact_string()) + } + (ExprKind::String(x), "string") => ExprKind::String(x), + (ExprKind::Symbol(x), "string") => { + ExprKind::String(x.as_str().into()) + } + + // TODO: Make sure these are correct, because the logic is pretty + // nuanced in terms of when to choose a Symbol or Intrinsic. + (ExprKind::Nil, "symbol") => ExprKind::Nil, + (ExprKind::Boolean(x), "symbol") => ExprKind::Boolean(x), + // TODO: Handle conversion into `fn` and `fn!`. + (ExprKind::String(x), "symbol") => ExprKind::Symbol(Symbol::new(x)), + (ExprKind::Symbol(x), "symbol") => ExprKind::Symbol(x), + + _ => ExprKind::Nil, + }, + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + } + + // MARK: Lazy + Self::Lazy => { + let expr = context.stack_pop(&expr)?; + + context.stack_push(Expr { + kind: ExprKind::Lazy(Box::new(expr)), + info: None, + })?; + + Ok(context) + } + + // MARK: If + Self::If => { + let cond = context.stack_pop(&expr)?; + let body = context.stack_pop(&expr)?; + + if cond.kind.is_truthy() { + context = engine.run_expr(context, body)?; + } + + Ok(context) + } + // MARK: Halt + Self::Halt => Err(RunError { + reason: RunErrorReason::Halt, + context, + expr, + }), + + // MARK: Call + Self::Call => { + let item = context.stack_pop(&expr)?; + engine.run_expr(context, item) + } + + // MARK: Let + Self::Let => { + let names = context.stack_pop(&expr)?; + let body = context.stack_pop(&expr)?; + + match names.kind { + ExprKind::List(x) => { + let x_len = x.len(); + + let n = x.into_iter().try_fold( + Vec::with_capacity(x_len), + |mut v, x| match x.kind { + ExprKind::Symbol(x) => { + v.push(x); + Ok(v) + } + _ => Err(RunError { + reason: RunErrorReason::InvalidLet, + context: context.clone(), + expr: expr.clone(), + }), + }, + )?; + + context.let_push(); + + for name in n.into_iter().rev() { + let expr = context.stack_pop(&expr)?; + context.let_set(name, expr); + } + + if let Some(journal) = context.journal_mut() { + journal.commit(); + journal.op(JournalOp::FnStart(false)); + } + + context = engine.run_expr(context, body)?; + context.let_pop().unwrap(); + + if let Some(journal) = context.journal_mut() { + journal.commit(); + journal.op(JournalOp::FnEnd); + } + + Ok(context) + } + _ => Err(RunError { + reason: RunErrorReason::InvalidLet, + context: context.clone(), + expr: expr.clone(), + }), + } + } + + // MARK: Def + Self::Def => { + let name = context.stack_pop(&expr)?; + let value = context.stack_pop(&expr)?; + + match name.kind { + ExprKind::Symbol(symbol) => { + context.def_scope_item(symbol, value); + + Ok(context) + } + _ => Err(RunError { + reason: RunErrorReason::InvalidDefinition, + context: context.clone(), + expr: expr.clone(), + }), + } + } + + // MARK: Set + Self::Set => { + let name = context.stack_pop(&expr)?; + let value = context.stack_pop(&expr)?; + + match name.kind { + ExprKind::Symbol(symbol) => { + context.set_scope_item(symbol, value).map(|_| context) + } + _ => Err(RunError { + reason: RunErrorReason::InvalidDefinition, + context: context.clone(), + expr: expr.clone(), + }), + } + } + + // MARK: Get + Self::Get => { + let name = context.stack_pop(&expr)?; + + match name.kind { + ExprKind::Symbol(symbol) => context + .stack_push(context.scope_item(symbol).ok_or_else(|| RunError { + context: context.clone(), + expr, + reason: RunErrorReason::UnknownCall, + })?) + .map(|_| context), + _ => Err(RunError { + reason: RunErrorReason::UnknownCall, + context: context.clone(), + expr: expr.clone(), + }), + } + } + + // MARK: Print + Self::Print => { + let val = context.stack_pop(&expr)?; + + println!("{}", val); + + Ok(context) + } + + // MARK: OrElse + Self::OrElse => { + let rhs = context.stack_pop(&expr)?; + let lhs = context.stack_pop(&expr)?; + + match lhs.kind { + ExprKind::Nil => context.stack_push(rhs)?, + _ => context.stack_push(lhs)?, + } + + Ok(context) + } + + // MARK: Import + Self::Import => { + let path = context.stack_pop(&expr)?; + + match path.kind { + ExprKind::String(str) => { + if let Ok(source) = Source::from_path(str.as_str()) { + context.add_source(source.clone()); + let mut lexer = Lexer::new(source); + if let Ok(exprs) = parse(&mut lexer) { + return engine.run(context, exprs); + } + } + } + _ => { + todo!() + } + } + + Ok(context) + } + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ParseIntrinsicError; + +impl std::error::Error for ParseIntrinsicError {} + +impl fmt::Display for ParseIntrinsicError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "unknown intrinsic") + } +} diff --git a/src/journal.rs b/stack-core/src/journal.rs similarity index 85% rename from src/journal.rs rename to stack-core/src/journal.rs index 10b125be..37b2b35e 100644 --- a/src/journal.rs +++ b/stack-core/src/journal.rs @@ -1,9 +1,7 @@ use core::fmt; -use itertools::Itertools; use std::mem; -use termion::color; -use crate::Expr; +use crate::expr::Expr; #[derive(Debug, Clone, PartialEq, PartialOrd, Default)] struct JournalEntry { @@ -41,6 +39,8 @@ pub struct Journal { impl fmt::Display for Journal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use yansi::Paint; + let entries = self.entries(20); if !entries.is_empty() { writeln!(f, "Stack History (most recent first):")?; @@ -57,26 +57,22 @@ impl fmt::Display for Journal { match op { JournalOp::Call(call) => { - line.push_str(&format!("{}", color::Fg(color::White))); - line.push_str(&format!("{}", call)); + line.push_str(&format!("{}", call.white())); should_print = true; } JournalOp::FnCall(fn_call) => { - line.push_str(&format!("{}", color::Fg(color::Yellow))); - line.push_str(&format!("{}", fn_call)); + line.push_str(&format!("{}", fn_call.yellow())); should_print = true; } JournalOp::Push(push) => { - line.push_str(&format!("{}", color::Fg(color::Green))); - line.push_str(&format!("{}", push)); + line.push_str(&format!("{}", push.green())); should_print = true; } JournalOp::Pop(pop) => { - line.push_str(&format!("{}", color::Fg(color::Red))); - line.push_str(&format!("{}", pop)); + line.push_str(&format!("{}", pop.red())); should_print = true; } @@ -85,8 +81,6 @@ impl fmt::Display for Journal { } if should_print { - line.push_str(&format!("{}", color::Fg(color::Reset))); - let bullet_symbol = match entry.scoped { true => format!("{}*", " ".repeat(entry.scope)), false => { @@ -174,6 +168,6 @@ impl Journal { } } - entries.into_iter().rev().collect_vec() + entries.into_iter().rev().collect::>() } } diff --git a/stack-core/src/lexer.rs b/stack-core/src/lexer.rs new file mode 100644 index 00000000..2aaa35c0 --- /dev/null +++ b/stack-core/src/lexer.rs @@ -0,0 +1,484 @@ +use core::{fmt, ops::Range}; + +use crate::source::Source; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Token { + pub kind: TokenKind, + pub span: Span, +} + +impl fmt::Display for Token { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.kind) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Span { + /// The lower byte bound (inclusive). + pub start: usize, + /// The upper byte bound (exclusive). + pub end: usize, +} + +impl Span { + /// Returns the [Range]\<[usize]\> of this [`Span`]. + #[inline] + pub const fn to_range(self) -> Range { + Range { + start: self.start, + end: self.end, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TokenKind { + Invalid, + Eof, + + Apostrophe, + LeftParen, + RightParen, + // LeftSquare, + // RightSquare, + Integer, + Float, + String, + Symbol, +} + +impl fmt::Display for TokenKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Invalid => write!(f, "invalid characters"), + Self::Eof => write!(f, "end of file"), + + Self::Apostrophe => write!(f, "'"), + Self::LeftParen => write!(f, "("), + Self::RightParen => write!(f, ")"), + // Self::LeftSquare => write!(f, "["), + // Self::RightSquare => write!(f, "]"), + Self::Integer => write!(f, "an integer literal"), + Self::Float => write!(f, "a float literal"), + Self::String => write!(f, "a string literal"), + Self::Symbol => write!(f, "a symbol literal"), + } + } +} + +/// Converts a &[str] into a stream of [`Token`]s. +#[derive(Debug)] +pub struct Lexer { + source: Source, + cursor: usize, + peeked: Option, +} + +impl Lexer { + /// Creates a [`Lexer`] from a [`Source`]. + pub fn new(source: Source) -> Self { + Self { + // Skip the UTF-8 BOM, if present. + #[allow(clippy::obfuscated_if_else)] + cursor: source + .source() + .as_bytes() + .starts_with(b"\xef\xbb\xbf") + .then_some(3) + .unwrap_or(0), + source, + peeked: None, + } + } + + /// Returns a clone of the [`Source`]. + #[inline] + pub fn source(&self) -> Source { + self.source.clone() + } + + /// Returns the next [`Token`] in the stream without consuming it. + #[inline] + pub fn peek(&mut self) -> Token { + match self.peeked { + Some(token) => token, + None => { + let token = self.next(); + self.peeked = Some(token); + token + } + } + } + + /// Returns the next [`Token`] in the stream. + /// + /// Once the first [`TokenKind::Eof`] has been returned, it will continue to + /// return them thereafter, akin to a [`FusedIterator`]. + /// + /// [`FusedIterator`]: core::iter::FusedIterator + #[allow(clippy::should_implement_trait)] + pub fn next(&mut self) -> Token { + if let Some(token) = self.peeked.take() { + return token; + } + + let source = self.source.source(); + + let mut state = State::Start; + let mut start = self.cursor; + let mut chars = source[self.cursor..].chars(); + + loop { + let c = chars.next().unwrap_or('\0'); + let c_len = c.len_utf8(); + + match state { + State::Start => match c { + '\0' if self.cursor == source.len() => { + break Token { + kind: TokenKind::Eof, + span: Span { + start: self.cursor, + end: self.cursor, + }, + }; + } + '\'' => { + self.cursor += c_len; + + break Token { + kind: TokenKind::Apostrophe, + span: Span { + start, + end: self.cursor, + }, + }; + } + '(' => { + self.cursor += c_len; + + break Token { + kind: TokenKind::LeftParen, + span: Span { + start, + end: self.cursor, + }, + }; + } + ')' => { + self.cursor += c_len; + + break Token { + kind: TokenKind::RightParen, + span: Span { + start, + end: self.cursor, + }, + }; + } + // '[' => { + // self.cursor += c_len; + + // break Token { + // kind: TokenKind::LeftSquare, + // span: Span { + // start, + // end: self.cursor, + // }, + // }; + // } + // ']' => { + // self.cursor += c_len; + + // break Token { + // kind: TokenKind::RightSquare, + // span: Span { + // start, + // end: self.cursor, + // }, + // }; + // } + ';' => state = State::Comment, + '-' => state = State::Minus, + '0'..='9' => state = State::Integer, + '"' => state = State::String, + // NOTE: If this is modified, remember to change the other instances + // in the other State matches. + '_' + | '+' + | '*' + | '/' + | '%' + | ':' + | '!' + | '=' + | '<' + | '>' + | '?' + | 'a'..='z' + | 'A'..='Z' => state = State::Symbol, + // TODO: Square brackets should be checked in the parsing step. + ' ' | '\n' | '\t' | '\r' | '[' | ']' => start = self.cursor + c_len, + _ => state = State::Invalid, + }, + State::Invalid => match c { + '\0' | ' ' | '\n' | '\t' | '\r' | '(' | ')' | '[' | ']' | '"' => { + break Token { + kind: TokenKind::Invalid, + span: Span { + start, + end: self.cursor, + }, + }; + } + _ => {} + }, + State::Comment => match c { + '\0' => { + state = State::Start; + start = self.cursor; + self.cursor -= c_len; + } + '\n' => { + state = State::Start; + start = self.cursor + c_len; + } + _ => {} + }, + State::Minus => match c { + '0'..='9' => state = State::Integer, + '_' + | '+' + | '-' + | '*' + | '/' + | '%' + | ':' + | '!' + | '=' + | '<' + | '>' + | '?' + | 'a'..='z' + | 'A'..='Z' => state = State::Symbol, + _ => { + break Token { + kind: TokenKind::Symbol, + span: Span { + start, + end: self.cursor, + }, + }; + } + }, + State::Integer => match c { + '0'..='9' => {} + '.' => state = State::Float, + _ => { + break Token { + kind: TokenKind::Integer, + span: Span { + start, + end: self.cursor, + }, + }; + } + }, + State::Float => match c { + '0'..='9' => {} + _ => { + break Token { + kind: TokenKind::Float, + span: Span { + start, + end: self.cursor, + }, + }; + } + }, + State::String => match c { + '\0' if self.cursor == source.len() => { + break Token { + kind: TokenKind::Invalid, + span: Span { + start, + end: self.cursor, + }, + }; + } + '\n' => { + break Token { + kind: TokenKind::Invalid, + span: Span { + start, + end: self.cursor, + }, + }; + } + '\\' => state = State::StringBackslash, + '"' => { + self.cursor += c_len; + + break Token { + kind: TokenKind::String, + span: Span { + start, + end: self.cursor, + }, + }; + } + _ => {} + }, + State::StringBackslash => match c { + '\0' | '\n' => { + break Token { + kind: TokenKind::Invalid, + span: Span { + start, + end: self.cursor, + }, + }; + } + _ => state = State::String, + }, + State::Symbol => match c { + '_' + | '+' + | '-' + | '*' + | '/' + | '%' + | ':' + | '!' + | '=' + | '<' + | '>' + | '?' + | 'a'..='z' + | 'A'..='Z' + | '0'..='9' => {} + _ => { + break Token { + kind: TokenKind::Symbol, + span: Span { + start, + end: self.cursor, + }, + }; + } + }, + } + + self.cursor += c_len; + } + } +} + +enum State { + Start, + Invalid, + Comment, + Minus, + Integer, + Float, + String, + StringBackslash, + Symbol, +} + +#[cfg(test)] +mod test { + use super::*; + use test_case::case; + + #[case("" => vec![Token { kind: TokenKind::Eof, span: Span { start: 0, end: 0 } }] ; "eof")] + #[case(" \n\t\r" => vec![Token { kind: TokenKind::Eof, span: Span { start: 4, end: 4 } }] ; "whitespace eof")] + #[case("; comment" => vec![Token { kind: TokenKind::Eof, span: Span { start: 9, end: 9 } }] ; "comment eof")] + #[case("; comment\n" => vec![Token { kind: TokenKind::Eof, span: Span { start: 10, end: 10 } }] ; "comment whitespace eof")] + #[case("+" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "plus only")] + #[case("-" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "minus only")] + #[case("*" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "asterisk only")] + #[case("/" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "slash only")] + #[case("%" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "percent only")] + #[case("+a" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "plus symbol only")] + #[case("-a" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "minus symbol only")] + #[case("*a" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "asterisk symbol only")] + #[case("/a" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "slash symbol only")] + #[case("%a" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "percent symbol only")] + #[case("'" => vec![Token { kind: TokenKind::Apostrophe, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "apostrophe")] + #[case("(" => vec![Token { kind: TokenKind::LeftParen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "left paren")] + #[case(")" => vec![Token { kind: TokenKind::RightParen, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "right paren")] + // #[case("[" => vec![Token { kind: TokenKind::LeftSquare, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "left square")] + // #[case("]" => vec![Token { kind: TokenKind::RightSquare, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "right square")] + #[case("123" => vec![Token { kind: TokenKind::Integer, span: Span { start: 0, end: 3 } }, Token { kind: TokenKind::Eof, span: Span { start: 3, end: 3 } }] ; "integer")] + #[case("-123" => vec![Token { kind: TokenKind::Integer, span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eof, span: Span { start: 4, end: 4 } }] ; "negative integer")] + #[case("1.2" => vec![Token { kind: TokenKind::Float, span: Span { start: 0, end: 3 } }, Token { kind: TokenKind::Eof, span: Span { start: 3, end: 3 } }] ; "float")] + #[case("-1.2" => vec![Token { kind: TokenKind::Float, span: Span { start: 0, end: 4 } }, Token { kind: TokenKind::Eof, span: Span { start: 4, end: 4 } }] ; "negative float")] + #[case("hello" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eof, span: Span { start: 5, end: 5 } }] ; "symbol")] + #[case("h3l10" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eof, span: Span { start: 5, end: 5 } }] ; "alphanumeric symbol")] + #[case("he_lo" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eof, span: Span { start: 5, end: 5 } }] ; "underscore symbol")] + #[case("he-lo" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 5 } }, Token { kind: TokenKind::Eof, span: Span { start: 5, end: 5 } }] ; "hypen symbol")] + #[case("_" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "underscore symbol only")] + #[case(":" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "colon symbol only")] + #[case("!" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "exclamation symbol only")] + #[case("=" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "equals symbol only")] + #[case("<" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "left angle symbol only")] + #[case(">" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "right angle symbol only")] + #[case("?" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 1 } }, Token { kind: TokenKind::Eof, span: Span { start: 1, end: 1 } }] ; "question symbol only")] + #[case("nil" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 3 } }, Token { kind: TokenKind::Eof, span: Span { start: 3, end: 3 } }] ; "nil")] + #[case("fn" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 2 } }, Token { kind: TokenKind::Eof, span: Span { start: 2, end: 2 } }] ; "fn_")] + #[case("fn!" => vec![Token { kind: TokenKind::Symbol, span: Span { start: 0, end: 3 } }, Token { kind: TokenKind::Eof, span: Span { start: 3, end: 3 } }] ; "fn exclamation")] + #[case("\"hello\"" => vec![Token { kind: TokenKind::String, span: Span { start: 0, end: 7 } }, Token { kind: TokenKind::Eof, span: Span { start: 7, end: 7 } }] ; "string")] + fn lexer(source: &str) -> Vec { + let source = Source::new("", source); + let mut lexer = Lexer::new(source); + let mut tokens = Vec::new(); + + loop { + let token = lexer.next(); + tokens.push(token); + + if token.kind == TokenKind::Eof { + break tokens; + } + } + } + + #[test] + fn peek() { + let source = "1 2"; + let source = Source::new("", source); + let mut lexer = Lexer::new(source); + + assert_eq!( + lexer.next(), + Token { + kind: TokenKind::Integer, + span: Span { start: 0, end: 1 } + } + ); + assert_eq!( + lexer.peek(), + Token { + kind: TokenKind::Integer, + span: Span { start: 2, end: 3 } + } + ); + assert_eq!( + lexer.next(), + Token { + kind: TokenKind::Integer, + span: Span { start: 2, end: 3 } + } + ); + assert_eq!( + lexer.next(), + Token { + kind: TokenKind::Eof, + span: Span { start: 3, end: 3 } + } + ); + } +} diff --git a/stack-core/src/lib.rs b/stack-core/src/lib.rs new file mode 100644 index 00000000..1d940cfd --- /dev/null +++ b/stack-core/src/lib.rs @@ -0,0 +1,30 @@ +pub mod chain; +pub mod context; +pub mod engine; +pub mod expr; +pub mod intrinsic; +pub mod journal; +pub mod lexer; +pub mod module; +pub mod parser; +pub mod scope; +pub mod source; +pub mod symbol; + +pub mod prelude { + //! Re-exports commonly used items. + + use super::*; + + pub use context::Context; + pub use engine::{Engine, RunError, RunErrorReason}; + pub use expr::{Error, Expr, ExprInfo, ExprKind}; + pub use intrinsic::Intrinsic; + pub use lexer::Lexer; + pub use module::Module; + pub use parser::{parse, ParseError, ParseErrorKind}; + pub use source::Source; + pub use symbol::Symbol; +} + +mod vec_one; diff --git a/stack-core/src/module.rs b/stack-core/src/module.rs new file mode 100644 index 00000000..d71b6bf4 --- /dev/null +++ b/stack-core/src/module.rs @@ -0,0 +1,48 @@ +use std::collections::HashMap; + +use crate::{ + context::Context, + engine::{Engine, RunError}, + expr::Expr, + symbol::Symbol, +}; + +pub type Func = fn(&Engine, Context, Expr) -> Result; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Module { + name: Symbol, + funcs: HashMap, +} + +impl Module { + #[inline] + pub fn new(name: Symbol) -> Self { + Self { + name, + funcs: HashMap::new(), + } + } + + #[inline] + pub fn with_func(mut self, name: Symbol, func: Func) -> Self { + self.add_func(name, func); + self + } + + #[inline] + pub fn add_func(&mut self, name: Symbol, func: Func) -> &mut Self { + self.funcs.insert(name, func); + self + } + + #[inline] + pub const fn name(&self) -> Symbol { + self.name + } + + #[inline] + pub fn func(&self, name: Symbol) -> Option { + self.funcs.get(&name).copied() + } +} diff --git a/stack-core/src/parser.rs b/stack-core/src/parser.rs new file mode 100644 index 00000000..ed56be86 --- /dev/null +++ b/stack-core/src/parser.rs @@ -0,0 +1,219 @@ +use core::fmt; + +use compact_str::ToCompactString; + +use crate::{ + expr::{Expr, ExprInfo, ExprKind, FnIdent}, + lexer::{Lexer, Span, Token, TokenKind}, + source::{Location, Source}, + symbol::Symbol, +}; + +pub fn parse(lexer: &mut Lexer) -> Result, ParseError> { + let mut exprs = Vec::new(); + + loop { + let token = lexer.peek(); + + match token.kind { + TokenKind::Eof => break Ok(exprs), + _ => exprs.push(parse_expr(lexer)?), + } + } +} + +fn parse_expr(lexer: &mut Lexer) -> Result { + let source = lexer.source(); + let token = lexer.next(); + + match token.kind { + TokenKind::Invalid | TokenKind::Eof | TokenKind::RightParen => { + Err(ParseError { + source, + kind: ParseErrorKind::UnexpectedToken(token), + }) + } + + TokenKind::Apostrophe => { + let next_token = lexer.peek(); + let expr = parse_expr(lexer)?; + + Ok(Expr { + kind: ExprKind::Lazy(Box::new(expr)), + info: Some(ExprInfo { + source, + span: Span { + start: token.span.start, + end: next_token.span.end, + }, + }), + }) + } + TokenKind::LeftParen => { + let (list, end_span) = parse_list(lexer)?; + + Ok(Expr { + kind: ExprKind::List(list), + info: Some(ExprInfo { + source, + span: Span { + start: token.span.start, + end: end_span.end, + }, + }), + }) + } + + TokenKind::Integer => { + let slice = &source.source()[token.span.start..token.span.end]; + let literal = slice.parse().map_err(|_| ParseError { + source: source.clone(), + kind: ParseErrorKind::InvalidLiteral(token), + })?; + + Ok(Expr { + kind: ExprKind::Integer(literal), + info: Some(ExprInfo { + source, + span: token.span, + }), + }) + } + TokenKind::Float => { + let slice = &source.source()[token.span.start..token.span.end]; + let literal = slice.parse().map_err(|_| ParseError { + source: source.clone(), + kind: ParseErrorKind::InvalidLiteral(token), + })?; + + Ok(Expr { + kind: ExprKind::Float(literal), + info: Some(ExprInfo { + source, + span: token.span, + }), + }) + } + TokenKind::String => { + // // Discard the quotation marks from the slice. + let slice = &source.source()[token.span.start + 1..token.span.end - 1]; + + Ok(Expr { + kind: ExprKind::String( + slice + .replace("\\n", "\n") + .replace("\\t", "\t") + .replace("\\r", "\r") + .replace("\\0", "\0") + .to_compact_string(), + ), + info: Some(ExprInfo { + source, + span: token.span, + }), + }) + } + TokenKind::Symbol => { + let slice = &source.source()[token.span.start..token.span.end]; + + Ok(Expr { + kind: match slice { + "nil" => ExprKind::Nil, + "true" => ExprKind::Boolean(true), + "false" => ExprKind::Boolean(false), + "fn" => ExprKind::Fn(FnIdent { + scoped: true, + scope: Default::default(), + }), + "fn!" => ExprKind::Fn(FnIdent { + scoped: false, + scope: Default::default(), + }), + slice => ExprKind::Symbol(Symbol::from_ref(slice)), + }, + info: Some(ExprInfo { + source, + span: token.span, + }), + }) + } + } +} + +fn parse_list(lexer: &mut Lexer) -> Result<(Vec, Span), ParseError> { + let mut list = Vec::new(); + + loop { + let token = lexer.peek(); + + match token.kind { + TokenKind::RightParen => break Ok((list, lexer.next().span)), + _ => list.push(parse_expr(lexer)?), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ParseError { + pub source: Source, + pub kind: ParseErrorKind, +} + +impl std::error::Error for ParseError {} + +impl fmt::Display for ParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} at {}:{}", + self.kind, + self.source.name(), + self + .kind + .location(&self.source) + .map(|x| x.to_string()) + .unwrap_or_else(|| "?:?".into()) + ) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ParseErrorKind { + UnexpectedToken(Token), + InvalidLiteral(Token), +} + +impl ParseErrorKind { + pub fn location(self, source: &Source) -> Option { + match self { + Self::UnexpectedToken(x) => source.location(x.span.start), + Self::InvalidLiteral(x) => source.location(x.span.start), + } + } +} + +impl fmt::Display for ParseErrorKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::UnexpectedToken(x) => write!(f, "unexpected token {x}"), + Self::InvalidLiteral(x) => write!(f, "invalid literal {x}"), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use test_case::case; + + fn s(source: &str) -> Source { + Source::new("", source) + } + + #[case("" => Ok(Vec::::new()) ; "empty")] + #[case("1" => Ok(vec![Expr { kind: ExprKind::Integer(1), info: Some(ExprInfo { source: s("1"), span: Span { start: 0, end: 1 } }) }]))] + fn parse(source: &str) -> Result, ParseError> { + let mut lexer = Lexer::new(s(source)); + super::parse(&mut lexer) + } +} diff --git a/stack-core/src/scope.rs b/stack-core/src/scope.rs new file mode 100644 index 00000000..f2bf1ac9 --- /dev/null +++ b/stack-core/src/scope.rs @@ -0,0 +1,415 @@ +use core::fmt; +use std::{cell::RefCell, collections::HashMap, fmt::Formatter, rc::Rc}; + +use crate::{chain::Chain, expr::FnIdent, prelude::*}; + +pub type Val = Rc>>>; + +#[derive(Default, PartialEq)] +pub struct Scope { + pub items: HashMap, +} + +impl fmt::Debug for Scope { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let iter = self.items.iter().map(|(name, item)| (name.as_str(), item)); + write!(f, "{:?}", HashMap::<&str, &Val>::from_iter(iter)) + } +} + +impl Clone for Scope { + fn clone(&self) -> Self { + let mut items = HashMap::new(); + + for (name, item) in self.items.iter() { + items.insert(*name, item.clone()); + } + + Self { items } + } +} + +impl Scope { + pub fn new() -> Self { + Self::default() + } + + pub fn from(items: HashMap) -> Self { + Self { items } + } + + pub fn define(&mut self, name: Symbol, item: Expr) { + if let Some(chain) = self.items.get(&name) { + let mut chain = RefCell::borrow_mut(chain); + match chain.is_root() { + true => { + chain.set(Some(item)); + } + false => { + chain.unlink_with(Some(item)); + } + } + } else { + let val = Rc::new(RefCell::new(Chain::new(Some(item)))); + self.items.insert(name, val); + } + } + + pub fn reserve(&mut self, name: Symbol) { + if self.items.get(&name).is_none() { + let val = Rc::new(RefCell::new(Chain::new(None))); + self.items.insert(name, val); + } + } + + pub fn set( + &mut self, + name: Symbol, + item: Expr, + ) -> Result<(), RunErrorReason> { + if let Some(chain) = self.items.get_mut(&name) { + let mut chain = RefCell::borrow_mut(chain); + chain.set(Some(item)); + Ok(()) + } else { + Err(RunErrorReason::CannotSetBeforeDef) + } + } + + pub fn remove(&mut self, name: Symbol) { + self.items.remove(&name); + } + + pub fn has(&self, name: Symbol) -> bool { + self.items.contains_key(&name) + } + + pub fn get_val(&self, name: Symbol) -> Option { + self.items.get(&name).and_then(|item| item.borrow().val()) + } + + pub fn get_ref(&self, name: Symbol) -> Option<&Val> { + self.items.get(&name) + } + + /// Merges another scope into this one, not overwriting any existing variables + pub fn merge(&mut self, other: Scope) { + for (name, item) in other.items { + if !self.has(name) + || (self.get_val(name).is_none() && item.borrow().val().is_some()) + { + self.items.insert(name, item); + } + } + } + + pub fn duplicate(&self) -> Self { + let mut items = HashMap::new(); + + for (name, item) in self.items.iter() { + let mut item = RefCell::borrow_mut(item); + items.insert(*name, item.link()); + } + + Self { items } + } +} + +#[derive(Debug)] +pub struct Scanner { + pub scope: Scope, +} + +impl Scanner { + pub fn new(scope: Scope) -> Self { + Self { scope } + } + + pub fn scan(&mut self, expr: Expr) -> Result { + if expr.kind.is_function() { + let expr = expr; + // We can unwrap here because we know the expression is a function + let fn_symbol = match expr.kind.fn_symbol() { + Some(fn_symbol) => fn_symbol, + None => return Err(RunErrorReason::InvalidFunction), + }; + let mut fn_body = match expr.kind.fn_body() { + Some(fn_body) => fn_body.to_vec(), + None => return Err(RunErrorReason::InvalidFunction), + }; + + for item in fn_body.iter_mut() { + if let ExprKind::Symbol(call) = item.kind.unlazy() { + if !self.scope.has(*call) { + self.scope.reserve(*call); + } + } else if item.kind.unlazy().is_function() { + let mut scanner = Scanner::new(self.scope.duplicate()); + let unlazied_mut = item.kind.unlazy_mut(); + *unlazied_mut = scanner + .scan(Expr { + kind: unlazied_mut.clone(), + info: item.info.clone(), + }) + .unwrap() + .kind + } + } + + let mut fn_scope = fn_symbol.scope.clone(); + fn_scope.merge(self.scope.clone()); + + let fn_ident = ExprKind::Fn(FnIdent { + scope: fn_scope, + scoped: fn_symbol.scoped, + }); + + let mut list_items = vec![Expr { + kind: fn_ident, + info: expr.info.clone(), + }]; + list_items.extend(fn_body); + + let new_expr = ExprKind::List(list_items); + + Ok(Expr { + kind: new_expr, + info: expr.info, + }) + } else { + // If the expression is not a function, we just return it + Ok(expr) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn top_level_scopes() { + let source = Source::new("", "0 'a def"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .scope_item(Symbol::new("a".into())) + .map(|expr| expr.kind), + Some(ExprKind::Integer(0)) + ); + } + + #[test] + fn function_scopes_are_isolated() { + let source = Source::new("", "'(fn 0 'a def) call"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!(context.scope_item(Symbol::new("a".into())), None); + } + + #[test] + fn nested_function_scopes_are_isolated() { + let source = Source::new( + "", + "0 'a def a '(fn 1 'a def a '(fn 2 'a def a) call a) call a", + ); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(0), + &ExprKind::Integer(1), + &ExprKind::Integer(2), + &ExprKind::Integer(1), + &ExprKind::Integer(0) + ] + ); + } + + #[test] + fn functions_can_set_to_outer() { + let source = Source::new("", "0 'a def a '(fn a 1 'a set a) call a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(0), + &ExprKind::Integer(0), + &ExprKind::Integer(1), + &ExprKind::Integer(1), + ] + ); + } + + #[test] + fn closures_can_access_vars() { + let source = Source::new("", "0 'a def '(fn 1 'a def '(fn a)) call call a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(1), &ExprKind::Integer(0),] + ); + } + + #[test] + fn closures_can_mutate_vars() { + let source = + Source::new("", "0 'a def '(fn 1 'a def '(fn a 2 'a set a)) call call a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![ + &ExprKind::Integer(1), + &ExprKind::Integer(2), + &ExprKind::Integer(0), + ] + ); + } + + #[test] + fn scopeless_functions_can_def_outer() { + let source = Source::new("", "'(fn! 0 'a def) call a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0),] + ); + + let source = Source::new("", "0 'a def '(fn! a 1 'a def) call a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0), &ExprKind::Integer(1),] + ); + } + + #[test] + fn scopeless_function_can_reuse_define() { + let source = Source::new("", "'(fn! def) 'define def 0 'a define a"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0)] + ); + } + + // TODO: test failure cases + // #[test] + // fn should_fail_on_invalid_symbol() { + // let mut program = Program::new().with_core().unwrap(); + // let result = program.eval_string("a").unwrap_err(); + + // assert_eq!(result.message, "unknown call a"); + // } + + // #[test] + // fn should_fail_on_invalid_symbol_in_fn() { + // let mut program = Program::new().with_core().unwrap(); + // let result = program.eval_string("'(fn a) call").unwrap_err(); + + // assert_eq!(result.message, "unknown call a"); + // } + + #[test] + fn variables_defined_from_scopeless_should_be_usable() { + let source = + Source::new("", "'(fn! 0 'a def) '(fn call '(fn a)) call call"); + let mut lexer = Lexer::new(source); + let exprs = crate::parser::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs).unwrap(); + + assert_eq!(context.scope_item(Symbol::new("a".into())), None); + assert_eq!( + context + .stack() + .iter() + .map(|expr| &expr.kind) + .collect::>(), + vec![&ExprKind::Integer(0),] + ); + } +} diff --git a/stack-core/src/source.rs b/stack-core/src/source.rs new file mode 100644 index 00000000..2dc09bf9 --- /dev/null +++ b/stack-core/src/source.rs @@ -0,0 +1,246 @@ +use core::{fmt, num::NonZeroUsize}; +use std::{fs, io, path::Path, rc::Rc}; + +use unicode_segmentation::UnicodeSegmentation; + +/// Contains metadata for a source. +/// +/// This internally stores an [`Rc`], hence it is *cheap* to clone. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Source(Rc); + +impl Source { + /// Creates a new [`Source`]. + pub fn new(name: N, source: S) -> Self + where + N: Into, + S: Into, + { + let name = name.into(); + let source = source.into(); + let line_starts = core::iter::once(0) + .chain( + source + .char_indices() + .filter(|&(_, c)| c == '\n') + .map(|(i, _)| i + 1), + ) + .collect::>(); + + Self(Rc::new(SourceInner { + name, + source, + line_starts, + })) + } + + /// Creates a new [`Source`] from the contents read from a file. + pub fn from_path

(path: P) -> io::Result + where + P: AsRef, + { + let source = fs::read_to_string(&path)?; + let name = path.as_ref().to_string_lossy().into_owned(); + + Ok(Self::new(name, source)) + } + + /// Returns the name as a &[str]. + #[inline] + #[must_use] + pub fn name(&self) -> &str { + self.0.name.as_str() + } + + /// Returns the source as a &[str]. + #[inline] + #[must_use] + pub fn source(&self) -> &str { + self.0.source.as_str() + } + + /// Returns the [`Location`] calculated from a byte index. + /// + /// [`None`] is returned when `index` is out-of-bounds, or `index` is not on + /// UTF-8 sequence boundaries. + /// + /// This internally uses a binary search. + #[must_use] + pub fn location(&self, index: usize) -> Option { + if index > self.0.source.len() { + None + } else { + let line = match self.0.line_starts.binary_search(&index) { + Ok(x) => x, + Err(x) => x - 1, + }; + + let line_start = self.0.line_starts[line]; + let line_str = self.line(line).unwrap(); + + let column_index = index - line_start; + let column = line_str.get(0..column_index)?.graphemes(true).count(); + + Some(Location { + line: NonZeroUsize::new(line + 1).unwrap(), + column: NonZeroUsize::new(column + 1).unwrap(), + }) + } + } + + /// Returns the line &[str] from a line number. + /// + /// The line number can be calculated via [`location`]. + /// + /// [`location`]: Self::location + #[must_use] + pub fn line(&self, line: usize) -> Option<&str> { + if let Some(&line_start) = self.0.line_starts.get(line) { + let line_end = self + .0 + .line_starts + .get(line + 1) + .copied() + .unwrap_or(self.0.source.len()); + Some(&self.0.source[line_start..line_end]) + } else { + None + } + } +} + +/// A human-readable location in a [`Source`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Location { + /// The line number. + pub line: NonZeroUsize, + /// The column number. + pub column: NonZeroUsize, +} + +impl fmt::Display for Location { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.line, self.column) + } +} + +#[derive(Debug, Clone, Eq)] +struct SourceInner { + name: String, + source: String, + line_starts: Vec, +} + +impl PartialEq for SourceInner { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.name == other.name && self.source == other.source + } +} + +#[cfg(test)] +mod test { + use super::*; + use test_case::case; + + #[case("" => vec![0] ; "empty")] + #[case("hello\n" => vec![0, 6] ; "one")] + #[case("hello\nthere\r\nworld\n" => vec![0, 6, 13, 19] ; "multiple")] + fn line_starts(source: &str) -> Vec { + Source::new("", source).0.line_starts.to_vec() + } + + #[case("", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "empty 0")] + #[case("", 1 => None ; "empty 1")] + #[case("hello", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single 0")] + #[case("hello", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "single 1")] + #[case("hello", 2 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "single 2")] + #[case("hello", 3 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "single 3")] + #[case("hello", 4 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "single 4")] + #[case("hello", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "single 5")] + #[case("hello", 6 => None ; "single 6")] + #[case("h💣llo", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single with bomb 0")] + #[case("h💣llo", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "single with bomb 1")] + #[case("h💣llo", 2 => None ; "single with bomb 2")] + #[case("h💣llo", 3 => None ; "single with bomb 3")] + #[case("h💣llo", 4 => None ; "single with bomb 4")] + #[case("h💣llo", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "single with bomb 5")] + #[case("h💣llo", 6 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "single with bomb 6")] + #[case("h💣llo", 7 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "single with bomb 7")] + #[case("h💣llo", 8 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "single with bomb 8")] + #[case("h💣llo", 9 => None ; "single with bomb 9")] + #[case("hello\n", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single with newline 0")] + #[case("hello\n", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "single with newline 1")] + #[case("hello\n", 2 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "single with newline 2")] + #[case("hello\n", 3 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "single with newline 3")] + #[case("hello\n", 4 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "single with newline 4")] + #[case("hello\n", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "single with newline 5")] + #[case("hello\n", 6 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single with newline 6")] + #[case("hello\n", 7 => None ; "single with newline 7")] + #[case("h💣llo\n", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single with bomb with newline 0")] + #[case("h💣llo\n", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "single with bomb with newline 1")] + #[case("h💣llo\n", 2 => None ; "single with bomb with newline 2")] + #[case("h💣llo\n", 3 => None ; "single with bomb with newline 3")] + #[case("h💣llo\n", 4 => None ; "single with bomb with newline 4")] + #[case("h💣llo\n", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "single with bomb with newline 5")] + #[case("h💣llo\n", 6 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "single with bomb with newline 6")] + #[case("h💣llo\n", 7 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "single with bomb with newline 7")] + #[case("h💣llo\n", 8 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "single with bomb with newline 8")] + #[case("h💣llo\n", 9 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "single with bomb with newline 9")] + #[case("hello\nworld\n", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple 0")] + #[case("hello\nworld\n", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "multiple 1")] + #[case("hello\nworld\n", 2 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "multiple 2")] + #[case("hello\nworld\n", 3 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "multiple 3")] + #[case("hello\nworld\n", 4 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "multiple 4")] + #[case("hello\nworld\n", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "multiple 5")] + #[case("hello\nworld\n", 6 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple 6")] + #[case("hello\nworld\n", 7 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "multiple 7")] + #[case("hello\nworld\n", 8 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "multiple 8")] + #[case("hello\nworld\n", 9 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "multiple 9")] + #[case("hello\nworld\n", 10 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "multiple 10")] + #[case("hello\nworld\n", 11 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "multiple 11")] + #[case("hello\nworld\n", 12 => Some(Location { line: NonZeroUsize::new(3).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple 12")] + #[case("hello\nworld\n", 13 => None ; "multiple 13")] + #[case("h💣llo\nworld\n", 0 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple with bomb 0")] + #[case("h💣llo\nworld\n", 1 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "multiple with bomb 1")] + #[case("h💣llo\nworld\n", 2 => None ; "multiple with bomb 2")] + #[case("h💣llo\nworld\n", 3 => None ; "multiple with bomb 3")] + #[case("h💣llo\nworld\n", 4 => None ; "multiple with bomb 4")] + #[case("h💣llo\nworld\n", 5 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "multiple with bomb 5")] + #[case("h💣llo\nworld\n", 6 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "multiple with bomb 6")] + #[case("h💣llo\nworld\n", 7 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "multiple with bomb 7")] + #[case("h💣llo\nworld\n", 8 => Some(Location { line: NonZeroUsize::new(1).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "multiple with bomb 8")] + #[case("h💣llo\nworld\n", 9 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple with bomb 9")] + #[case("h💣llo\nworld\n", 10 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(2).unwrap() }) ; "multiple with bomb 10")] + #[case("h💣llo\nworld\n", 11 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(3).unwrap() }) ; "multiple with bomb 11")] + #[case("h💣llo\nworld\n", 12 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(4).unwrap() }) ; "multiple with bomb 12")] + #[case("h💣llo\nworld\n", 13 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(5).unwrap() }) ; "multiple with bomb 13")] + #[case("h💣llo\nworld\n", 14 => Some(Location { line: NonZeroUsize::new(2).unwrap(), column: NonZeroUsize::new(6).unwrap() }) ; "multiple with bomb 14")] + #[case("h💣llo\nworld\n", 15 => Some(Location { line: NonZeroUsize::new(3).unwrap(), column: NonZeroUsize::new(1).unwrap() }) ; "multiple with bomb 15")] + #[case("h💣llo\nworld\n", 16 => None ; "multiple with bomb 16")] + fn location(source: &str, index: usize) -> Option { + Source::new("", source).location(index) + } + + #[case("", 0 => Some("".into()) ; "empty 0")] + #[case("", 1 => None ; "empty 1")] + #[case("hello", 0 => Some("hello".into()) ; "single 0")] + #[case("hello", 1 => None ; "single 1")] + #[case("h💣llo", 0 => Some("h💣llo".into()) ; "single with bomb 0")] + #[case("h💣llo", 1 => None ; "single with bomb 1")] + #[case("hello\n", 0 => Some("hello\n".into()) ; "single with newline 0")] + #[case("hello\n", 1 => Some("".into()) ; "single with newline 1")] + #[case("hello\n", 2 => None ; "single with newline 2")] + #[case("h💣llo\n", 0 => Some("h💣llo\n".into()) ; "single with bomb with newline 0")] + #[case("h💣llo\n", 1 => Some("".into()) ; "single with bomb with newline 1")] + #[case("h💣llo\n", 2 => None ; "single with bomb with newline 2")] + #[case("hello\nworld", 0 => Some("hello\n".into()) ; "multiple 0")] + #[case("hello\nworld", 1 => Some("world".into()) ; "multiple 1")] + #[case("hello\nworld", 2 => None ; "multiple 2")] + #[case("h💣llo\nworld", 0 => Some("h💣llo\n".into()) ; "multiple with bomb 0")] + #[case("h💣llo\nworld", 1 => Some("world".into()) ; "multiple with bomb 1")] + #[case("h💣llo\nworld", 2 => None ; "multiple with bomb 2")] + fn line(source: &str, line: usize) -> Option { + Source::new("", source).line(line).map(Into::into) + } +} diff --git a/stack-core/src/symbol.rs b/stack-core/src/symbol.rs new file mode 100644 index 00000000..734bd216 --- /dev/null +++ b/stack-core/src/symbol.rs @@ -0,0 +1,38 @@ +use core::{borrow::Borrow, fmt, hash::Hash}; + +use compact_str::CompactString; +use internment::Intern; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct Symbol(Intern); + +impl Symbol { + /// Creates a [`Symbol`]. + #[inline] + pub fn new(value: CompactString) -> Self { + Self(Intern::new(value)) + } + + /// Creates a [`Symbol`] from a reference. + #[inline] + pub fn from_ref<'q, Q>(value: &'q Q) -> Self + where + Q: 'q + ?Sized + Eq + Hash, + CompactString: Borrow + From<&'q Q>, + { + Self(Intern::from_ref(value)) + } + + /// Returns the &[str] for this [`Symbol`]. + #[inline] + pub fn as_str(&self) -> &str { + self.0.as_str() + } +} + +impl fmt::Display for Symbol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.as_str()) + } +} diff --git a/stack-core/src/vec_one.rs b/stack-core/src/vec_one.rs new file mode 100644 index 00000000..3d55cc48 --- /dev/null +++ b/stack-core/src/vec_one.rs @@ -0,0 +1,199 @@ +#![allow(dead_code)] + +use core::slice::{Iter, IterMut, SliceIndex}; +use std::vec::IntoIter; + +/// A [`Vec`] with at least one element. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub(crate) struct VecOne { + vec: Vec, +} + +impl VecOne { + #[inline] + pub fn new(one: T) -> Self { + Self { vec: vec![one] } + } + + #[inline] + #[allow(clippy::len_without_is_empty)] + pub fn len(&self) -> usize { + self.vec.len() + } + + #[inline] + pub fn capacity(&self) -> usize { + self.vec.capacity() + } + + #[inline] + pub fn push(&mut self, value: T) { + self.vec.push(value) + } + + #[inline] + pub fn try_pop(&mut self) -> Option { + debug_assert_ne!(self.vec.len(), 0); + + if self.len() > 1 { + self.vec.pop() + } else { + None + } + } + + #[inline] + pub fn pop(mut self) -> (Option, T) { + debug_assert_ne!(self.vec.len(), 0); + + // SAFETY: This is upheld by the invariants of this type. + let value = unsafe { self.vec.pop().unwrap_unchecked() }; + + if self.vec.is_empty() { + (None, value) + } else { + (Some(self), value) + } + } + + #[inline] + #[must_use] + pub fn get(&self, index: I) -> Option<&I::Output> + where + I: SliceIndex<[T]>, + { + debug_assert_ne!(self.vec.len(), 0); + self.vec.get(index) + } + + #[inline] + #[must_use] + pub fn get_mut(&mut self, index: I) -> Option<&mut I::Output> + where + I: SliceIndex<[T]>, + { + debug_assert_ne!(self.vec.len(), 0); + self.vec.get_mut(index) + } + + #[inline] + #[must_use] + pub fn first(&self) -> &T { + debug_assert_ne!(self.vec.len(), 0); + // SAFETY: This is upheld by the invariants of this type. + unsafe { self.vec.first().unwrap_unchecked() } + } + + #[inline] + #[must_use] + pub fn first_mut(&mut self) -> &mut T { + debug_assert_ne!(self.vec.len(), 0); + // SAFETY: This is upheld by the invariants of this type. + unsafe { self.vec.first_mut().unwrap_unchecked() } + } + + #[inline] + #[must_use] + pub fn last(&self) -> &T { + debug_assert_ne!(self.vec.len(), 0); + // SAFETY: This is upheld by the invariants of this type. + unsafe { self.vec.last().unwrap_unchecked() } + } + + #[inline] + #[must_use] + pub fn last_mut(&mut self) -> &mut T { + debug_assert_ne!(self.vec.len(), 0); + // SAFETY: This is upheld by the invariants of this type. + unsafe { self.vec.last_mut().unwrap_unchecked() } + } + + #[inline] + pub fn iter(&self) -> Iter { + self.vec.iter() + } + + #[inline] + pub fn iter_mut(&mut self) -> IterMut { + self.vec.iter_mut() + } +} + +impl IntoIterator for VecOne { + type Item = T; + type IntoIter = IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.vec.into_iter() + } +} + +impl Extend for VecOne { + #[inline] + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + self.vec.extend(iter) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn vec_one() { + let mut vec = VecOne::new(123); + + assert_eq!(vec.len(), 1); + + assert_eq!(*vec.first(), 123); + assert_eq!(*vec.first_mut(), 123); + assert_eq!(*vec.last(), 123); + assert_eq!(*vec.last_mut(), 123); + + assert_eq!(vec.try_pop(), None); + + vec.push(456); + vec.push(789); + + assert_eq!(vec.len(), 3); + + assert_eq!(*vec.first(), 123); + assert_eq!(*vec.first_mut(), 123); + assert_eq!(*vec.last(), 789); + assert_eq!(*vec.last_mut(), 789); + + assert_eq!(vec.try_pop(), Some(789)); + assert_eq!(vec.try_pop(), Some(456)); + assert_eq!(vec.try_pop(), None); + + assert_eq!(vec.len(), 1); + + vec.extend([456, 789]); + + assert!(vec.iter().eq([123, 456, 789].iter())); + assert!(vec.iter_mut().eq([123, 456, 789].iter_mut())); + assert!(vec.into_iter().eq([123, 456, 789].into_iter())); + } + + #[test] + fn vec_one_pop() { + let mut vec = VecOne::new(123); + vec.extend([456, 789]); + + let (vec, val) = vec.pop(); + assert!(vec.is_some()); + assert_eq!(val, 789); + + let (vec, val) = vec.unwrap().pop(); + assert!(vec.is_some()); + assert_eq!(val, 456); + + let (vec, val) = vec.unwrap().pop(); + assert_eq!(vec, None); + assert_eq!(val, 123); + } +} diff --git a/stack-core/tests/_runner.rs b/stack-core/tests/_runner.rs new file mode 100644 index 00000000..f314dda8 --- /dev/null +++ b/stack-core/tests/_runner.rs @@ -0,0 +1,37 @@ +use core::str::FromStr; +use std::path::PathBuf; + +use stack_core::prelude::*; +use test_case::case; + +#[inline] +const fn e(kind: ExprKind) -> Expr { + Expr { kind, info: None } +} + +// TODO: Add tests for missing intrinsics. + +#[case("intrinsics/arithmetic.stack" => Ok(vec![e(ExprKind::Integer(3)), e(ExprKind::Integer(-1)), e(ExprKind::Integer(6)), e(ExprKind::Integer(2)), e(ExprKind::Integer(0))]) ; "arithmetic")] +#[case("intrinsics/compare.stack" => Ok(vec![e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false))]) ; "compare")] +#[case("intrinsics/logical.stack" => Ok(vec![e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(true)), e(ExprKind::Boolean(false)), e(ExprKind::Boolean(false))]) ; "logical")] +// TODO: Fix this. +// #[case("intrinsics/assert_fail.stack" => Err(RunError { reason: RunErrorReason::AssertionFailed, expr: e(ExprKind::Integer(123)) }) ; "assert fail")] +#[case("intrinsics/assert_okay.stack" => Ok(vec![]) ; "assert okay")] +#[case("intrinsics/stack.stack" => Ok(vec![e(ExprKind::Integer(1)), e(ExprKind::Integer(3)), e(ExprKind::Integer(3)), e(ExprKind::Integer(5)), e(ExprKind::Integer(4)), e(ExprKind::Integer(7)), e(ExprKind::Integer(8)), e(ExprKind::Integer(6))]) ; "stack")] +#[case("intrinsics/orelse.stack" => Ok(vec![e(ExprKind::Integer(1)), e(ExprKind::Integer(2)), e(ExprKind::Integer(1)), e(ExprKind::Nil)]) ; "orelse")] +#[case("intrinsics/push.stack" => Ok(vec![e(ExprKind::List(vec![e(ExprKind::Integer(1)), e(ExprKind::Integer(2)), e(ExprKind::Integer(3))])), e(ExprKind::String("he".into())), e(ExprKind::String("he".into()))]) ; "push")] +#[case("intrinsics/pop.stack" => Ok(vec![e(ExprKind::List(vec![e(ExprKind::Integer(1)), e(ExprKind::Integer(2))])), e(ExprKind::Integer(3)), e(ExprKind::String("h".into())), e(ExprKind::String("e".into()))]) ; "pop")] +fn integration(subpath: &str) -> Result, RunError> { + let mut path = PathBuf::from_str("tests").unwrap(); + path.push(subpath); + + let source = Source::from_path(path).unwrap(); + let mut lexer = Lexer::new(source); + let exprs = crate::parse(&mut lexer).unwrap(); + + let engine = Engine::new(); + let mut context = Context::new().with_stack_capacity(32); + context = engine.run(context, exprs)?; + + Ok(core::mem::take(context.stack_mut())) +} diff --git a/stack-core/tests/intrinsics/arithmetic.stack b/stack-core/tests/intrinsics/arithmetic.stack new file mode 100644 index 00000000..a884a6f6 --- /dev/null +++ b/stack-core/tests/intrinsics/arithmetic.stack @@ -0,0 +1,5 @@ +1 2 + +1 2 - +3 2 * +4 2 / +4 2 % diff --git a/stack-core/tests/intrinsics/assert_fail.stack b/stack-core/tests/intrinsics/assert_fail.stack new file mode 100644 index 00000000..9a994b51 --- /dev/null +++ b/stack-core/tests/intrinsics/assert_fail.stack @@ -0,0 +1 @@ +123 false assert diff --git a/stack-core/tests/intrinsics/assert_okay.stack b/stack-core/tests/intrinsics/assert_okay.stack new file mode 100644 index 00000000..cc5a69bf --- /dev/null +++ b/stack-core/tests/intrinsics/assert_okay.stack @@ -0,0 +1 @@ +123 true assert diff --git a/stack-core/tests/intrinsics/compare.stack b/stack-core/tests/intrinsics/compare.stack new file mode 100644 index 00000000..72a000a3 --- /dev/null +++ b/stack-core/tests/intrinsics/compare.stack @@ -0,0 +1,16 @@ +1 1 = +1 2 = +1 1 != +1 2 != +1 1 > +1 2 > +2 1 > +1 1 >= +1 2 >= +2 1 >= +1 1 < +1 2 < +2 1 < +1 1 <= +1 2 <= +2 1 <= diff --git a/stack-core/tests/intrinsics/logical.stack b/stack-core/tests/intrinsics/logical.stack new file mode 100644 index 00000000..e701bc88 --- /dev/null +++ b/stack-core/tests/intrinsics/logical.stack @@ -0,0 +1,13 @@ +false false or +false true or +true false or +true true or +0 0 or +1 1 or + +false false and +false true and +true false and +true true and +0 0 and +1 1 and diff --git a/stack-core/tests/intrinsics/orelse.stack b/stack-core/tests/intrinsics/orelse.stack new file mode 100644 index 00000000..413f60a2 --- /dev/null +++ b/stack-core/tests/intrinsics/orelse.stack @@ -0,0 +1,4 @@ +1 2 orelse +nil 2 orelse +1 nil orelse +nil nil orelse diff --git a/stack-core/tests/intrinsics/pop.stack b/stack-core/tests/intrinsics/pop.stack new file mode 100644 index 00000000..11dc2f13 --- /dev/null +++ b/stack-core/tests/intrinsics/pop.stack @@ -0,0 +1,2 @@ +'(1 2 3) pop +"he" pop diff --git a/stack-core/tests/intrinsics/push.stack b/stack-core/tests/intrinsics/push.stack new file mode 100644 index 00000000..fbcd147e --- /dev/null +++ b/stack-core/tests/intrinsics/push.stack @@ -0,0 +1,3 @@ +3 2 1 '() push push push +"e" "h" push +101 "h" push diff --git a/stack-core/tests/intrinsics/stack.stack b/stack-core/tests/intrinsics/stack.stack new file mode 100644 index 00000000..e0296681 --- /dev/null +++ b/stack-core/tests/intrinsics/stack.stack @@ -0,0 +1,4 @@ +1 2 drop +3 dupe +4 5 swap +6 7 8 rot diff --git a/stack-std/Cargo.toml b/stack-std/Cargo.toml new file mode 100644 index 00000000..3899f7c3 --- /dev/null +++ b/stack-std/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "stack-std" +version = "0.1.0" +edition = "2021" + +[dependencies] +stack-core = { path = "../stack-core" } +unicode-segmentation.workspace = true +compact_str.workspace = true diff --git a/stack-std/src/fs.rs b/stack-std/src/fs.rs new file mode 100644 index 00000000..f1e7db24 --- /dev/null +++ b/stack-std/src/fs.rs @@ -0,0 +1,41 @@ +use compact_str::ToCompactString; +use stack_core::prelude::*; + +pub fn module(sandbox: bool) -> Module { + let mut module = Module::new(Symbol::from_ref("fs")); + + if !sandbox { + module + .add_func(Symbol::from_ref("cwd"), |_, mut context, _| { + context.stack_push(Expr { + kind: std::env::current_dir() + .map(|x| { + ExprKind::String( + x.to_string_lossy().into_owned().to_compact_string(), + ) + }) + .unwrap_or(ExprKind::Nil), + info: None, + })?; + + Ok(context) + }) + .add_func(Symbol::from_ref("read-file"), |_, mut context, expr| { + let path = context.stack_pop(&expr)?; + + let kind = match path.kind { + ExprKind::String(ref x) => std::fs::read_to_string(x.as_str()) + .map(|x| x.to_compact_string()) + .map(ExprKind::String) + .unwrap_or_else(|e| ExprKind::Error(Error::new(e.to_string()))), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }); + } + + module +} diff --git a/stack-std/src/lib.rs b/stack-std/src/lib.rs new file mode 100644 index 00000000..4e0ac303 --- /dev/null +++ b/stack-std/src/lib.rs @@ -0,0 +1,3 @@ +pub mod fs; +pub mod scope; +pub mod str; diff --git a/stack-std/src/scope.rs b/stack-std/src/scope.rs new file mode 100644 index 00000000..b340db91 --- /dev/null +++ b/stack-std/src/scope.rs @@ -0,0 +1,88 @@ +use core::str::FromStr; + +use stack_core::prelude::*; + +pub fn module() -> Module { + let mut module = Module::new(Symbol::from_ref("scope")); + + module + .add_func(Symbol::from_ref("where"), |engine, mut context, expr| { + let symbol = context.stack_pop(&expr)?; + + match symbol.kind { + ExprKind::Symbol(ref x) => { + if Intrinsic::from_str(x.as_str()).is_ok() { + context.stack_push(Expr { + kind: ExprKind::String("intrinsic".into()), + info: None, + }) + } else if engine + .module(&Symbol::new( + x.as_str().split(':').next().unwrap_or_default().into(), + )) + .is_some() + { + context.stack_push(Expr { + kind: ExprKind::String("module".into()), + info: None, + }) + } else if context.let_get(*x).is_some() { + context.stack_push(Expr { + kind: ExprKind::String("let".into()), + info: None, + }) + } else if context.scope_item(*x).is_some() { + context.stack_push(Expr { + kind: ExprKind::String("scope".into()), + info: None, + }) + } else { + context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + }) + } + } + _ => context.stack_push(Expr { + kind: ExprKind::Nil, + info: None, + }), + } + .map(|_| context) + }) + .add_func(Symbol::from_ref("dump"), |_, mut context, _| { + let items: Vec = context + .scope_items() + .map(|(name, content)| { + let list: Vec = vec![ + Expr { + kind: ExprKind::Symbol(*name), + info: None, + }, + Expr { + kind: content + .borrow() + .val() + .map(|e| e.kind) + .unwrap_or(ExprKind::Nil), + info: None, + }, + ]; + + Expr { + kind: ExprKind::List(list), + info: None, + } + }) + .collect(); + + context + .stack_push(Expr { + kind: ExprKind::List(items), + info: None, + }) + .map(|_| context) + }); + + module +} diff --git a/stack-std/src/str.rs b/stack-std/src/str.rs new file mode 100644 index 00000000..b9a6cc69 --- /dev/null +++ b/stack-std/src/str.rs @@ -0,0 +1,257 @@ +use compact_str::{CompactString, ToCompactString}; +use stack_core::prelude::*; +use unicode_segmentation::UnicodeSegmentation; + +// TODO: Add str:escape and str:unescape. + +pub fn module() -> Module { + Module::new(Symbol::from_ref("str")) + .with_func(Symbol::from_ref("trim-start"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::String(x.trim_start().into()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("trim-end"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::String(x.trim_end().into()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("trim"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::String(x.trim().into()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("starts-with"), |_, mut context, expr| { + let patt = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + let kind = match (item.kind.clone(), patt.kind.clone()) { + (ExprKind::String(ref x), ExprKind::String(ref y)) => { + ExprKind::Boolean(x.starts_with(y.as_str())) + } + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("ends-with"), |_, mut context, expr| { + let patt = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + let kind = match (item.kind.clone(), patt.kind.clone()) { + (ExprKind::String(ref x), ExprKind::String(ref y)) => { + ExprKind::Boolean(x.ends_with(y.as_str())) + } + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("split-by"), |_, mut context, expr| { + let patt = context.stack_pop(&expr)?; + let item = context.stack_pop(&expr)?; + + let kind = match (item.kind.clone(), patt.kind.clone()) { + (ExprKind::String(ref x), ExprKind::String(ref y)) => ExprKind::List( + x.split(y.as_str()) + .map(|x| Expr { + kind: ExprKind::String(x.into()), + info: None, + }) + .collect(), + ), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func( + Symbol::from_ref("split-whitespace"), + |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::List( + x.split_whitespace() + .map(|x| Expr { + kind: ExprKind::String(x.into()), + info: None, + }) + .collect(), + ), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }, + ) + .with_func(Symbol::from_ref("to-lowercase"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::String(x.to_lowercase()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("to-uppercase"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::String(x.to_uppercase()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("is-ascii"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::Boolean(x.is_ascii()), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("is-char"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => { + ExprKind::Boolean(x.graphemes(true).count() == 1) + } + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("to-bytes"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::List( + x.as_bytes() + .iter() + .copied() + .map(|x| Expr { + kind: ExprKind::Integer(x as i64), + info: None, + }) + .collect(), + ), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("from-bytes"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::List(ref x) => x + .iter() + .try_fold(Vec::with_capacity(x.len()), |mut v, x| match x.kind { + ExprKind::Integer(i) if i >= 0 && i <= u8::MAX as i64 => { + v.push(i as u8); + Ok(v) + } + _ => Err(()), + }) + .map(|x| { + CompactString::from_utf8(x) + .map(ExprKind::String) + .unwrap_or(ExprKind::Nil) + }) + .unwrap_or(ExprKind::Nil), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("to-chars"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::String(ref x) => ExprKind::List( + x.as_str() + .graphemes(true) + .map(|x| Expr { + kind: ExprKind::String(x.into()), + info: None, + }) + .collect(), + ), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) + .with_func(Symbol::from_ref("from-chars"), |_, mut context, expr| { + let item = context.stack_pop(&expr)?; + + let kind = match item.kind { + ExprKind::List(ref x) => x + .iter() + .try_fold(String::with_capacity(x.len()), |mut v, x| match x.kind { + ExprKind::String(ref s) => { + v.push_str(s); + Ok(v) + } + _ => Err(()), + }) + .map(|x| x.to_compact_string()) + .map(ExprKind::String) + .unwrap_or(ExprKind::Nil), + _ => ExprKind::Nil, + }; + + context.stack_push(Expr { kind, info: None })?; + + Ok(context) + }) +} diff --git a/std/assert.stack b/std/assert.stack deleted file mode 100644 index 29c30021..00000000 --- a/std/assert.stack +++ /dev/null @@ -1,17 +0,0 @@ -'(fn - swap - '(drop) - '([tolist] ["assertion failed: " tolist] [swap] [concat "" join] panic) - '(true !=) - ifelse -) 'assert def - -'(fn - 'context def - 'right def - 'left def - - '('() ["assertion for [" push] [context push] ["] failed:\nLeft = " push] [left push] ["\nRight = " push] [right push] ["" join] panic) - '(left right !=) - if -) 'assert-eq def diff --git a/std/list.stack b/std/list.stack deleted file mode 100644 index bae5d22a..00000000 --- a/std/list.stack +++ /dev/null @@ -1,19 +0,0 @@ -"std/stack.stack" import - -'(fn - 0 'i def - ['list get len] ['length def] drop - - '(fn! i length <) 'i-in-bounds def - '(fn! i 1 + 'i set) 'increment-i def - '(fn! list i index swap drop) 'get-nth def - - '( - get-nth - - block - - increment-i - ) - '(i-in-bounds) while -) '(list block) 'list/for_each defn diff --git a/std/map.stack b/std/map.stack deleted file mode 100644 index d6dbb5e7..00000000 --- a/std/map.stack +++ /dev/null @@ -1,44 +0,0 @@ -'(fn - '() 'map/new def - - '(fn - '() - ['key get push] - ['value get push] - ) '(key value) 'map/pair defn - - '(fn 0 index swap drop) 'map/head def - '(fn 1 index swap drop) 'map/tail def - - '(fn - "std/list.stack" import - - false - - 'map get - '(fn - item map/head - key - = - - has-item - or - ) '(has-item item) let-fn - list/for_each - ) '(map key) 'map/has defn - - '(fn - 'map get - 'pair get - - push - ) '(map pair) 'map/insert defn - - '() - 'map/new export - 'map/pair export - 'map/insert export - 'map/has export - 'map/head export - 'map/tail export -) call diff --git a/std/math.stack b/std/math.stack deleted file mode 100644 index a81e4f37..00000000 --- a/std/math.stack +++ /dev/null @@ -1,70 +0,0 @@ -;; ( a b -- bool ) -'(fn! - ;; ( a b -- a b a b ) - swap dup rot swap dup rot - ;; ( a b a b -- bool ) - < rot = or -) '<= def - -;; ( a b -- bool ) -'(fn! - ;; ( a b -- a b a b ) - swap dup rot swap dup rot - ;; ( a b a b -- bool ) - > rot = or -) '>= def - -;; TODO: Figure out how to define u64 constants since they are to large to fit -;; in an i64. - -0 'u8-min def -255 'u8-max def -8 'u8-bits def - --128 'i8-min def -127 'i8-max def -8 'i8-bits def - -0 'u16-min def -65535 'u16-max def -16 'u16-bits def - --32768 'i16-min def -32767 'i16-max def -16 'i16-bits def - -0 'u32-min def -4294967295 'u32-max def -32 'u32-bits def - --2147483648 'i32-min def -2147483647 'i32-max def -32 'i32-bits def - --9223372036854775808 'i64-min def -9223372036854775807 'i64-max def -64 'i64-bits def - -;; TODO: Handle exponents in float lexical analysis to help define more -;; constants, as well as define nan and inf as known identifiers. - -1.0 0.0 / 'inf def --1.0 0.0 / 'neg-inf def -0.0 0.0 / 'nan def -2.71828182845904523536028747135266250 'euler def -3.14159265358979323846264338327950288 'pi def - -;; Returns `true` if the float is NaN. -'(fn! dup != and) 'is-nan def -;; Returns `true` if the float is positively or negatively infinite. -'(fn! dup inf = swap neg-inf = or and) 'is-inf def -;; Returns `true` if the float is positively or negatively finite. -'(fn! is-inf not) 'is-fin def - -;; TODO: Implement intrinsics for common float instructions, such as floor, -;; ceil, round, etc. - -;; Returns the reciprocal, inverse, of a float. -'(fn! 1.0 swap /) 'recip def -'(fn! 180.0 pi / *) 'to-degs def -'(fn! pi 180.0 / *) 'to-rads def diff --git a/std/stack.stack b/std/stack.stack deleted file mode 100644 index 89200715..00000000 --- a/std/stack.stack +++ /dev/null @@ -1,8 +0,0 @@ -'(fn! - swap - dup - rot - swap - dup - rot -) 'dup2 def diff --git a/std/type.stack b/std/type.stack deleted file mode 100644 index 246647a0..00000000 --- a/std/type.stack +++ /dev/null @@ -1,2 +0,0 @@ -'(fn typeof tolist 0 index swap drop "(" =) 'is-list def -'(fn typeof tolist dup len 2 > swap 1 index swap drop "f" = =) 'is-function def diff --git a/tests/_runner.rs b/tests/_runner.rs deleted file mode 100644 index bd1e4ad9..00000000 --- a/tests/_runner.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::fs; - -use stack::Program; - -#[test] -fn run_tests() { - let result = fs::read_dir("tests").unwrap(); - - for entry in result { - let entry = entry.unwrap(); - let path = entry.path(); - - if let Some(name) = path.file_name() { - if name.to_str().unwrap().ends_with(".stack") { - let contents = fs::read_to_string(&path).unwrap(); - - let mut program = Program::new() - .with_core() - // .unwrap() - // .with_module(map::module) - .unwrap(); - let result = program.eval_string(contents.as_str()); - - assert!( - result.is_ok(), - "{}: {}", - name.to_string_lossy(), - result.err().unwrap() - ); - } - } - } -} diff --git a/tests/assert.stack b/tests/assert.stack deleted file mode 100644 index 2920c384..00000000 --- a/tests/assert.stack +++ /dev/null @@ -1,9 +0,0 @@ -"std/assert.stack" import - -[1 1 =] ["assert: 1 = 1" assert] -[true] ["assert: true" assert] - -[2 2 + 4] ["assert-eq: 2 + 2 = 4" assert-eq] -[true true] ["assert-eq: true = true" assert-eq] -["hello" "hello"] ["assert-eq: hello = hello" assert-eq] -[2.0 2.0] ["assert-eq: 2.0 = 2.0" assert-eq] \ No newline at end of file diff --git a/tests/example_module.stack b/tests/example_module.stack deleted file mode 100644 index ad720a15..00000000 --- a/tests/example_module.stack +++ /dev/null @@ -1,22 +0,0 @@ -"std/assert.stack" import - -'(fn - 0 'a def - 1 'b def - - '() - 'a export - 'b export -) call - -[dup '((0 a) (1 b))] ["should export variables" assert-eq] - -'(fn - '(fn 2 +) 'add-two def - '(2 2 +) 'list def - - '() - 'add-two export - 'list export -) call -concat \ No newline at end of file diff --git a/tests/for_each.stack b/tests/for_each.stack deleted file mode 100644 index 182e9223..00000000 --- a/tests/for_each.stack +++ /dev/null @@ -1,13 +0,0 @@ -"std/list.stack" import -"std/assert.stack" import - -'() -("the" "words" "should" "be" "in" "order") - -'(fn push) -list/for_each - -[" " join] -["the words should be in order"] - -["for_each" assert-eq] diff --git a/tests/let.stack b/tests/let.stack deleted file mode 100644 index 7a57a0c5..00000000 --- a/tests/let.stack +++ /dev/null @@ -1,5 +0,0 @@ -"std/assert.stack" import - -'(fn a b -) '(a b) 'subtract defn - -[[10 2 subtract] 8] ["subtract" assert-eq] \ No newline at end of file diff --git a/tests/map.stack b/tests/map.stack deleted file mode 100644 index 0ffba9a6..00000000 --- a/tests/map.stack +++ /dev/null @@ -1,19 +0,0 @@ -;; "std/assert.stack" import - -;; map/new - -;; 'a map/get nil "new" assert-eq - -;; 1 'a map/insert -;; 'a map/get 1 "inserted a 1" assert-eq - -;; 2 'b map/insert -;; 'b map/get 2 "inserted b 2" assert-eq - -;; 'a map/remove -;; 'a map/get nil "removed a" assert-eq -;; 'b map/get 2 "exists b" assert-eq - -;; drop - -;; collect len 0 "stuff left over" assert-eq diff --git a/tests/module.stack b/tests/module.stack deleted file mode 100644 index ca1c5ebf..00000000 --- a/tests/module.stack +++ /dev/null @@ -1,21 +0,0 @@ -"std/assert.stack" import - -;; Test `use` -'(fn - ["tests/example_module.stack" import 'mod use] - - [mod/a 0] ["mod/a should be 0" assert-eq] - [mod/b 1] ["mod/b should be 1" assert-eq] - [0 mod/add-two 2] ["mod/add-two should be 2" assert-eq] - [mod/list '(2 2 +)] ["mod/list should '(2 2 +)" assert-eq] -) call - -;; Test `use-all` -'(fn - ["tests/example_module.stack" import use-all] - - [a 0] ["a should be 0" assert-eq] - [b 1] ["b should be 1" assert-eq] - [0 add-two 2] ["add-two should be 2" assert-eq] - [list '(2 2 +)] ["mod/list should '(2 2 +)" assert-eq] -) call \ No newline at end of file diff --git a/tests/objects.stack b/tests/objects.stack deleted file mode 100644 index 01c75258..00000000 --- a/tests/objects.stack +++ /dev/null @@ -1,24 +0,0 @@ -"std/assert.stack" import - -'(fn - 0 'i def - - '(fn - i 1 + 'i set - ) 'inc def - - '(fn i) 'value def - - '() - 'inc export - 'value export -) 'counter def - -counter 'my-counter use - -my-counter/inc -my-counter/inc -my-counter/inc -my-counter/value - -3 "closures and objects should be mutable" assert-eq \ No newline at end of file diff --git a/tests/reverse.stack b/tests/reverse.stack deleted file mode 100644 index b1853909..00000000 --- a/tests/reverse.stack +++ /dev/null @@ -1,7 +0,0 @@ -"std/assert.stack" import -"std/list.stack" import - -;; Add the string as a list -"Hello, World!" tolist - -[[reverse "" join] "!dlroW ,olleH"] ["reverse string" assert-eq] diff --git a/tests/scope.stack b/tests/scope.stack deleted file mode 100644 index b57971e6..00000000 --- a/tests/scope.stack +++ /dev/null @@ -1,63 +0,0 @@ -"std/assert.stack" import - -0 'var def -var 0 "can define variables" assert-eq - -1 'var def -var 1 "can redefine variables" assert-eq - -'(fn! 2 'var def) call -var 2 "can redefine variables with functions!" assert-eq - -0 'a def - -'(fn - a 1 + - 'a def -) 'add-one-to-a def - -add-one-to-a - -a 0 "functions create a new scope" assert-eq - -'(fn! - 3 'a set -) 'add-one-to-a! def - -add-one-to-a! - -a 3 "functions! use the current scope" assert-eq - -0 'i def -'(fn - 0 'i def - - '(fn i) - - '(fn - i 1 + - 'i set - ) -) - -;; "Create" a counter -call - -;; Call the first returned function to increment the counter -call - -;; Call the second returned function to get the current value -call - -1 "functions can use values from dead scopes (closures)" assert-eq -i 0 "closures are isolated" assert-eq - -'(fn - 0 'b def - - '(fn! 1 'b def) call - - b -) call - -1 "functions! can use the current scope" assert-eq diff --git a/tests/type.stack b/tests/type.stack deleted file mode 100644 index c3310191..00000000 --- a/tests/type.stack +++ /dev/null @@ -1,8 +0,0 @@ -"std/type.stack" import -"std/assert.stack" import - -['(fn) is-function] ["functions are functions" assert] -['(fn!) is-function] ["unscoped functions are functions" assert] - -['() is-function] ["lazy lists are lists" assert] -[() is-function] ["lists are lists" assert] \ No newline at end of file