diff --git a/Cargo.lock b/Cargo.lock index 584889c..68e562d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anstream" -version = "0.6.12" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -70,9 +70,9 @@ dependencies = [ [[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 = "bit-set" @@ -97,9 +97,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "cfg-if" @@ -108,10 +108,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "clap" -version = "4.5.1" +name = "cfg_aliases" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + +[[package]] +name = "clap" +version = "4.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -119,9 +125,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.1" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", @@ -131,14 +137,14 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.51", + "syn", ] [[package]] @@ -149,9 +155,9 @@ checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "5.2.0" +version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f9a0700e0127ba15d1d52dd742097f821cd9c65939303a44d970465040a297" +checksum = "d517d4b86184dbb111d3556a10f1c8a04da7428d2987bf1081602bf11c3aa9ee" dependencies = [ "error-code", ] @@ -170,20 +176,14 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "ctrlc" -version = "3.4.2" +version = "3.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b467862cc8610ca6fc9a1532d7777cee0804e678ab45410897b9396495994a0b" +checksum = "672465ae37dc1bc6380a6547a8883d5dd397b0f1faaad4f265726cc7042a5345" dependencies = [ "nix", "windows-sys", ] -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "dirs-next" version = "2.0.0" @@ -244,9 +244,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.1.0" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a147e1a6641a55d994b3e4e9fa4d9b180c8d652c09b363af8c9bf1b8e04139" +checksum = "a0474425d51df81997e2f90a21591180b38eccf27292d755f3e30750225c175b" [[package]] name = "fd-lock" @@ -284,15 +284,9 @@ checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - -[[package]] -name = "hermit-abi" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "379dada1584ad501b383485dd706b8afb7a70fcbc7f4da7d780638a5a6124a60" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "home" @@ -305,63 +299,51 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] -[[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", -] - [[package]] name = "itertools" -version = "0.10.5" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[package]] name = "lalrpop" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8" +checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas", "bit-set", - "diff", "ena", - "is-terminal", "itertools", "lalrpop-util", "petgraph", "regex", - "regex-syntax 0.7.5", + "regex-syntax", "string_cache", "term", "tiny-keccak", "unicode-xid", + "walkdir", ] [[package]] name = "lalrpop-util" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d" +checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex", + "regex-automata", ] [[package]] @@ -382,7 +364,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", "redox_syscall", ] @@ -405,21 +387,21 @@ 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 = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "new_debug_unreachable" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nibble_vec" @@ -432,12 +414,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", + "cfg_aliases", "libc", ] @@ -541,9 +524,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -619,46 +602,40 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] name = "regex-automata" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.2", + "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" - -[[package]] -name = "regex-syntax" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rustix" -version = "0.38.31" +version = "0.38.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "errno", "libc", "linux-raw-sys", @@ -673,11 +650,11 @@ checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" [[package]] name = "rustyline" -version = "13.0.0" +version = "14.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" +checksum = "7803e8936da37efd9b6d4478277f4b2b9bb5cdb37a113e8d63222e58da647e63" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cfg-if", "clipboard-win", "fd-lock", @@ -690,7 +667,16 @@ dependencies = [ "unicode-segmentation", "unicode-width", "utf8parse", - "winapi", + "windows-sys", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", ] [[package]] @@ -707,9 +693,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" -version = "1.13.1" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "string_cache" @@ -732,20 +718,9 @@ checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ab617d94515e94ae53b8406c628598680aa0c9587474ecbe58188f7b345d66c" +checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" dependencies = [ "proc-macro2", "quote", @@ -754,7 +729,7 @@ dependencies = [ [[package]] name = "talc-bin" -version = "0.1.0" +version = "0.2.0" dependencies = [ "clap", "ctrlc", @@ -765,7 +740,7 @@ dependencies = [ [[package]] name = "talc-lang" -version = "0.1.0" +version = "0.2.0" dependencies = [ "lalrpop", "lalrpop-util", @@ -774,22 +749,24 @@ dependencies = [ "num-rational", "num-traits", "thiserror", + "unicode-ident", ] [[package]] name = "talc-macros" -version = "0.1.0" +version = "0.2.0" dependencies = [ "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "talc-std" -version = "0.1.0" +version = "0.2.0" dependencies = [ "lazy_static", "rand", + "regex", "talc-lang", "talc-macros", ] @@ -807,22 +784,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.57" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.51", + "syn", ] [[package]] @@ -864,6 +841,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -886,6 +873,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -898,7 +894,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.3", + "windows-targets 0.52.4", ] [[package]] @@ -918,17 +914,17 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" dependencies = [ - "windows_aarch64_gnullvm 0.52.3", - "windows_aarch64_msvc 0.52.3", - "windows_i686_gnu 0.52.3", - "windows_i686_msvc 0.52.3", - "windows_x86_64_gnu 0.52.3", - "windows_x86_64_gnullvm 0.52.3", - "windows_x86_64_msvc 0.52.3", + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", ] [[package]] @@ -939,9 +935,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" [[package]] name = "windows_aarch64_msvc" @@ -951,9 +947,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" [[package]] name = "windows_i686_gnu" @@ -963,9 +959,9 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" [[package]] name = "windows_i686_msvc" @@ -975,9 +971,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" [[package]] name = "windows_x86_64_gnu" @@ -987,9 +983,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" [[package]] name = "windows_x86_64_gnullvm" @@ -999,9 +995,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" [[package]] name = "windows_x86_64_msvc" @@ -1011,6 +1007,6 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.3" +version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ae5dd08 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# talc + +## installation + +```sh +cargo install --profile release-lto --path talc-bin +``` diff --git a/talc-bin/Cargo.toml b/talc-bin/Cargo.toml index 25d823b..908a13b 100644 --- a/talc-bin/Cargo.toml +++ b/talc-bin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "talc-bin" -version = "0.1.0" +version = "0.2.0" edition = "2021" [[bin]] @@ -10,6 +10,6 @@ path = "src/main.rs" [dependencies] talc-lang = { path = "../talc-lang" } talc-std = { path = "../talc-std" } -rustyline = "13.0" +rustyline = "14.0" clap = { version = "4.5", features = ["derive"] } ctrlc = "3.4" diff --git a/talc-bin/src/helper.rs b/talc-bin/src/helper.rs index 6c5103c..273de31 100644 --- a/talc-bin/src/helper.rs +++ b/talc-bin/src/helper.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, cell::RefCell, collections::HashMap, rc::Rc}; use rustyline::{completion::Completer, highlight::Highlighter, hint::Hinter, validate::{ValidationContext, ValidationResult, Validator}, Helper, Result}; -use talc_lang::{Lexer, Vm}; +use talc_lang::{lstring::LStr, Lexer, Vm}; #[derive(Clone, Copy)] enum TokenType { @@ -62,8 +62,8 @@ impl Completer for TalcHelper { let res: String = res.chars().rev().collect(); let mut keys = self.vm.borrow().globals().keys() .map(|sym| sym.name()) - .filter(|name| name.starts_with(&res)) - .map(|s| s.to_string()) + .filter(|name| name.starts_with(LStr::from_str(&res))) + .map(LStr::to_string) .collect::>(); keys.sort(); Ok((pos - res.as_bytes().len(), keys)) @@ -150,7 +150,7 @@ impl Validator for TalcHelper { v => { mismatch = Some((v, t)); break } } "do" => match delims.last().copied() { - Some("while") | Some("for") | Some("catch") => { + Some("while" | "for" | "catch") => { delims.pop(); delims.push(t); }, diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs index ea38fbb..3b1d1fa 100644 --- a/talc-bin/src/main.rs +++ b/talc-bin/src/main.rs @@ -28,12 +28,12 @@ struct Args { color: ColorChoice, } -fn exec(src: String, args: &Args) -> ExitCode { +fn exec(src: &str, args: &Args) -> ExitCode { let parser = talc_lang::Parser::new(); let mut vm = Vm::new(256); talc_std::load_all(&mut vm); - let ex = match parser.parse(&src) { + let ex = match parser.parse(src) { Ok(ex) => ex, Err(e) => { eprintln!("Error: {e}"); @@ -66,7 +66,7 @@ fn main() -> ExitCode { } match std::fs::read_to_string(args.file.as_ref().unwrap()) { - Ok(s) => exec(s, &args), + Ok(s) => exec(&s, &args), Err(e) => { eprintln!("Error: {e}"); ExitCode::FAILURE diff --git a/talc-lang/Cargo.toml b/talc-lang/Cargo.toml index 3e0a900..a7dd2e0 100644 --- a/talc-lang/Cargo.toml +++ b/talc-lang/Cargo.toml @@ -1,15 +1,16 @@ [package] name = "talc-lang" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] lalrpop-util = { version = "0.20", features = ["lexer", "unicode"] } num-complex = "0.4" num-rational = { version = "0.4", default-features = false, features = [] } -num-traits = "*" +num-traits = "0.2" thiserror = "1.0" lazy_static = "1.4" +unicode-ident = "1.0" [build-dependencies] lalrpop = { version = "0.20", default_features = false, features = ["lexer", "unicode"]} diff --git a/talc-lang/src/ast.rs b/talc-lang/src/ast.rs index 98e8039..cc5c306 100644 --- a/talc-lang/src/ast.rs +++ b/talc-lang/src/ast.rs @@ -1,8 +1,9 @@ -use crate::{value::Value, symbol::Symbol}; +use crate::{lstring::LStr, symbol::Symbol, value::Value}; #[derive(Clone, Copy, Debug)] pub enum BinaryOp { Add, Sub, Mul, Div, Mod, Pow, IntDiv, + Shr, Shl, BitAnd, BitXor, BitOr, Eq, Ne, Gt, Lt, Ge, Le, Concat, Append, Range, RangeIncl, @@ -16,17 +17,18 @@ pub enum UnaryOp { #[derive(Debug)] pub enum Expr<'s> { Literal(Value), - Ident(&'s str), + Ident(&'s LStr), UnaryOp(UnaryOp, Box>), BinaryOp(BinaryOp, Box>, Box>), Assign(Option, Box>, Box>), - AssignVar(&'s str, Box>), - AssignGlobal(&'s str, Box>), + AssignVar(&'s LStr, Box>), + AssignGlobal(&'s LStr, Box>), Index(Box>, Box>), FnCall(Box>, Vec>), + AssocFnCall(Box>, Symbol, Vec>), Pipe(Box>, Box>), Block(Vec>), @@ -38,20 +40,20 @@ pub enum Expr<'s> { Or(Box>, Box>), If(Box>, Box>, Option>>), While(Box>, Box>), - For(&'s str, Box>, Box>), - Lambda(Vec<&'s str>, Box>), + For(&'s LStr, Box>, Box>), + Lambda(Vec<&'s LStr>, Box>), Try(Box>, Vec>), } #[derive(Debug)] pub struct CatchBlock<'s> { - pub name: Option<&'s str>, + pub name: Option<&'s LStr>, pub types: Option>, pub body: Expr<'s>, } #[derive(Debug)] pub enum LValue<'s> { - Ident(&'s str), + Ident(&'s LStr), Index(Box>, Box>), } diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs index 6537333..13458c5 100644 --- a/talc-lang/src/compiler.rs +++ b/talc-lang/src/compiler.rs @@ -2,13 +2,15 @@ use std::rc::Rc; use crate::ast::{BinaryOp, Expr, LValue, CatchBlock}; use crate::chunk::{Instruction as I, Chunk, Arg24, Catch}; +use crate::lstr; +use crate::lstring::LStr; use crate::symbol::Symbol; use crate::value::function::{FuncAttrs, Function}; use crate::value::Value; #[derive(Debug, Clone)] pub struct Local { - name: Rc, + name: Rc, scope: usize, } @@ -42,7 +44,7 @@ pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec) { impl<'a> Default for Compiler<'a> { fn default() -> Self { let locals = vec![Local { - name: "self".into(), + name: lstr!("self").into(), scope: 0, }]; Self { @@ -74,7 +76,7 @@ impl<'a> Compiler<'a> { } } - fn new_function(&'a self, args: &[&str]) -> Self { + fn new_function(&'a self, args: &[&LStr]) -> Self { let mut new = Self { mode: CompilerMode::Function, parent: Some(self), @@ -204,20 +206,20 @@ impl<'a> Compiler<'a> { // variables // - fn resolve_local(&mut self, name: &str) -> Option { + fn resolve_local(&mut self, name: &LStr) -> Option { self.locals.iter().rev() .position(|v| v.name.as_ref() == name) .map(|x| self.locals.len() - x - 1) } - fn resolve_global(&mut self, name: &str) -> Option { + fn resolve_global(&mut self, name: &LStr) -> Option { self.globals.iter().rev() .position(|v| v.name.as_ref() == name) .map(|x| self.globals.len() - x - 1) } - fn load_var(&mut self, name: &str) { + fn load_var(&mut self, name: &LStr) { match (self.resolve_local(name), self.resolve_global(name)) { (Some(n), None) => { self.emit(I::LoadLocal(Arg24::from_usize(n))); @@ -232,7 +234,7 @@ impl<'a> Compiler<'a> { } } - fn declare_local(&mut self, name: &str) -> usize { + fn declare_local(&mut self, name: &LStr) -> usize { if let Some(i) = self.resolve_local(name) { if self.locals[i].scope == self.scope { self.emit(I::StoreLocal(Arg24::from_usize(i))); @@ -254,7 +256,7 @@ impl<'a> Compiler<'a> { self.emit(I::StoreLocal(Arg24::from_usize(i))); } - fn store_global(&mut self, name: &str) { + fn store_global(&mut self, name: &LStr) { let sym = Symbol::get(name); self.emit(I::StoreGlobal(Arg24::from_symbol(sym))); if let Some(i) = self.resolve_global(name) { @@ -268,7 +270,7 @@ impl<'a> Compiler<'a> { }); } - fn store_default(&mut self, name: &str) { + fn store_default(&mut self, name: &LStr) { match (self.resolve_local(name), self.resolve_global(name)) { (Some(n), None) => self.store_local(n), (Some(n), Some(m)) if n >= m => self.store_local(n), @@ -369,6 +371,17 @@ impl<'a> Compiler<'a> { } self.emit(I::Call(args.len() as u8)); }, + Expr::AssocFnCall(o, f, args) => { + self.expr(o); + self.emit(I::Dup); + self.emit(I::Symbol(Arg24::from_symbol(*f))); + self.emit(I::Index); + self.emit(I::Swap); + for a in args { + self.expr(a); + } + self.emit(I::Call((args.len() + 1) as u8)); + }, Expr::Return(e) => { self.expr(e); self.emit(I::Return); @@ -467,7 +480,7 @@ impl<'a> Compiler<'a> { self.chunk.finish_catch_table(idx, table); } - fn expr_for(&mut self, name: &str, iter: &Expr, body: &Expr) { + fn expr_for(&mut self, name: &LStr, iter: &Expr, body: &Expr) { // load iterable and convert to iterator self.expr(iter); self.emit(I::IterBegin); @@ -498,7 +511,7 @@ impl<'a> Compiler<'a> { self.emit(I::Nil); } - fn expr_lambda(&mut self, args: &[&str], body: &Expr) { + fn expr_lambda(&mut self, args: &[&LStr], body: &Expr) { let mut inner = self.new_function(args); inner.parent = Some(self); inner.expr(body); diff --git a/talc-lang/src/exception.rs b/talc-lang/src/exception.rs index 67174a0..79a5adf 100644 --- a/talc-lang/src/exception.rs +++ b/talc-lang/src/exception.rs @@ -1,12 +1,12 @@ use std::{rc::Rc, collections::HashMap, cell::RefCell, fmt::Display}; -use crate::{value::{HashValue, Value}, symbol::{SYM_DATA, SYM_MSG, SYM_TYPE}, symbol::Symbol}; +use crate::{lstring::LStr, symbol::{Symbol, SYM_DATA, SYM_MSG, SYM_TYPE}, value::{HashValue, Value}}; pub type Result = std::result::Result; #[derive(Clone, Debug)] pub struct Exception { pub ty: Symbol, - pub msg: Option>, + pub msg: Option>, pub data: Option, } @@ -15,7 +15,7 @@ impl Exception { Self { ty, msg: None, data: None } } - pub fn new_with_msg(ty: Symbol, msg: Rc) -> Self { + pub fn new_with_msg(ty: Symbol, msg: Rc) -> Self { Self { ty, msg: Some(msg), data: None } } @@ -23,7 +23,7 @@ impl Exception { Self { ty, msg: None, data: Some(data) } } - pub fn new_with_msg_data(ty: Symbol, msg: Rc, data: Value) -> Self { + pub fn new_with_msg_data(ty: Symbol, msg: Rc, data: Value) -> Self { Self { ty, msg: Some(msg), data: Some(data) } } @@ -79,7 +79,9 @@ macro_rules! exception { ($exc_ty:expr, $fstr:literal, $($arg:expr),*) => { $crate::exception::Exception::new_with_msg( $exc_ty, - format!($fstr, $($arg),*).into() + $crate::lstring::LString::from( + format!($fstr, $($arg),*) + ).into() ) }; ($exc_ty:expr, $fstr:literal) => { diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs index 6894f9b..8e2fcae 100644 --- a/talc-lang/src/lib.rs +++ b/talc-lang/src/lib.rs @@ -18,6 +18,7 @@ pub mod value; pub mod exception; pub mod chunk; pub mod compiler; +pub mod lstring; pub use parser::BlockParser as Parser; pub use vm::Vm; diff --git a/talc-lang/src/lstring.rs b/talc-lang/src/lstring.rs new file mode 100644 index 0000000..2ffafa8 --- /dev/null +++ b/talc-lang/src/lstring.rs @@ -0,0 +1,824 @@ +use std::{borrow::{Borrow, BorrowMut, Cow}, ffi::OsStr, fmt::{self, Write}, io, iter::{Copied, FusedIterator}, ops::{Add, AddAssign, Deref, DerefMut, Index, IndexMut}, rc::Rc, slice, str::Utf8Error, string::FromUtf8Error}; + +use unicode_ident::{is_xid_continue, is_xid_start}; + +#[macro_export] +macro_rules! lstr { + ($s:literal) => { + $crate::lstring::LStr::from_str($s) + }; +} + +#[macro_export] +macro_rules! lformat { + ($($t:tt)*) => { + $crate::lstring::LString::from(format!($($t)*)) + }; +} + +// +// utility +// + +#[inline] +fn is_continue(b: u8) -> bool { + b & 0xc0 == 0x80 +} + +#[inline] +fn calc_continue(mut ch: u32, bytes: &[u8]) -> Option { + for b in bytes { + if !is_continue(*b) { return None } + ch = (ch << 6) | (b & 0x3f) as u32; + } + char::from_u32(ch) +} + +#[inline] +fn next_codepoint(bytes: &[u8]) -> Option<(&[u8], Result)> { + let init = *bytes.first()?; + match init { + 0..=0x7f => return Some((&bytes[1..], Ok(init as char))), + 0xc0..=0xdf => 'case: { + if bytes.len() < 2 { break 'case } + let Some(ch) = calc_continue(init as u32 & 0x1f, &bytes[1..2]) else { + break 'case; + }; + return Some((&bytes[2..], Ok(ch))) + }, + 0xe0..=0xef => 'case: { + if bytes.len() < 3 { break 'case } + let Some(ch) = calc_continue(init as u32 & 0x0f, &bytes[1..3]) else { + break 'case; + }; + return Some((&bytes[3..], Ok(ch))) + }, + 0xf0..=0xf7 => 'case: { + if bytes.len() < 4 { break 'case } + let Some(ch) = calc_continue(init as u32 & 0x07, &bytes[1..4]) else { + break 'case; + }; + return Some((&bytes[4..], Ok(ch))) + } + _ => (), + }; + + Some((&bytes[1..], Err(init))) +} + +#[inline] +fn next_codepoint_back(bytes: &[u8]) -> Option<(&[u8], Result)> { + let len = bytes.len(); + if len < 1 { return None } + + let last = bytes[len-1]; + if (0..=0x7f).contains(&last) { + return Some((&bytes[..len-1], Ok(last as char))) + } + + 'case: { + if !is_continue(last) { break 'case } + + if len < 2 { break 'case } + let b1 = bytes[len-2]; + if 0xe0 & b1 == 0xc0 { + if let Some(ch) = calc_continue(b1 as u32 & 0x1f, &[last]) { + return Some((&bytes[..len-2], Ok(ch))) + }; + } else if !is_continue(b1) { + break 'case + } + + if len < 3 { break 'case } + let b2 = bytes[len-3]; + if 0xf0 & b2 == 0xe0 { + if let Some(ch) = calc_continue(b2 as u32 & 0x0f, &[b1, last]) { + return Some((&bytes[..len-3], Ok(ch))) + }; + } else if !is_continue(b2) { + break 'case + } + + if len < 4 { break 'case } + let b3 = bytes[len-4]; + if 0xf8 & b3 == 0xf0 { + if let Some(ch) = calc_continue(b3 as u32 & 0x07, &[b2, b1, last]) { + return Some((&bytes[..len-4], Ok(ch))) + }; + } + } + Some((&bytes[..len-1], Err(last))) +} + +#[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct LString { + inner: Vec, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] +pub struct LStr { + inner: [u8], +} + +impl fmt::Debug for LStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_char('"')?; + let mut bytes = &self.inner; + while let Some((new_bytes, res)) = next_codepoint(bytes) { + bytes = new_bytes; + match res { + Ok('"') => f.write_str("\\\"")?, + Ok('\\') => f.write_str("\\\\")?, + Ok('\x00') => f.write_str("\\0")?, + Ok('\x07') => f.write_str("\\a")?, + Ok('\x08') => f.write_str("\\b")?, + Ok('\x09') => f.write_str("\\t")?, + Ok('\x0a') => f.write_str("\\n")?, + Ok('\x0b') => f.write_str("\\v")?, + Ok('\x0c') => f.write_str("\\f")?, + Ok('\x0d') => f.write_str("\\r")?, + Ok('\x1b') => f.write_str("\\e")?, + Ok(c) if c.is_control() => write!(f, "\\u{{{:x}}}", c as u32)?, + Ok(c) => f.write_char(c)?, + Err(b) => write!(f, "\\x{b:02x}")?, + } + } + f.write_char('"') + } +} + +impl fmt::Display for LStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut bytes = &self.inner; + while let Some((new_bytes, res)) = next_codepoint(bytes) { + bytes = new_bytes; + if let Ok(c) = res { + f.write_char(c)?; + } + } + Ok(()) + } +} + +impl fmt::Debug for LString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Debug::fmt(self.as_ref(), f) + } +} + +impl fmt::Display for LString { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self.as_ref(), f) + } +} + +// +// deref, asref, borrow +// + +impl Deref for LString { + type Target = LStr; + + #[inline] + fn deref(&self) -> &Self::Target { + <&LStr as From<&[u8]>>::from(self.inner.as_ref()) + } +} + +impl DerefMut for LString { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + <&mut LStr as From<&mut [u8]>>::from(self.inner.as_mut()) + } +} + +impl AsRef for LString { + #[inline] + fn as_ref(&self) -> &LStr { + <&LStr as From<&[u8]>>::from(self.inner.as_ref()) + } +} + +impl AsMut for LString { + #[inline] + fn as_mut(&mut self) -> &mut LStr { + <&mut LStr as From<&mut [u8]>>::from(self.inner.as_mut()) + } +} + +impl Borrow for LString { + #[inline] + fn borrow(&self) -> &LStr { + <&LStr as From<&[u8]>>::from(self.inner.as_ref()) + } +} + +impl BorrowMut for LString { + #[inline] + fn borrow_mut(&mut self) -> &mut LStr { + <&mut LStr as From<&mut [u8]>>::from(self.inner.as_mut()) + } +} + +// +// conversions +// + +impl From for Vec { + #[inline] + fn from(value: LString) -> Self { value.inner } +} + +impl From> for LString { + #[inline] + fn from(value: Vec) -> Self { Self { inner: value } } +} + +impl From for LString { + #[inline] + fn from(value: String) -> Self { Self { inner: value.into_bytes() } } +} + +impl From<&LStr> for LString { + #[inline] + fn from(value: &LStr) -> Self { value.to_owned() } +} + +impl From<&str> for LString { + #[inline] + fn from(value: &str) -> Self { value.to_owned().into() } +} + +impl From<&[u8]> for LString { + #[inline] + fn from(value: &[u8]) -> Self { value.to_owned().into() } +} + +impl From> for LString { + #[inline] + fn from(value: Cow<'_, LStr>) -> Self { + match value { + Cow::Borrowed(b) => b.to_owned(), + Cow::Owned(o) => o + } + } +} + +impl From> for LString { + #[inline] + fn from(value: Cow<'_, str>) -> Self { + value.into_owned().into() + } +} + +impl From> for LString { + #[inline] + fn from(value: Cow<'_, [u8]>) -> Self { + value.into_owned().into() + } +} + +impl<'a> From<&'a LStr> for &'a [u8] { + #[inline] + fn from(value: &'a LStr) -> Self { &value.inner } +} + +impl<'a> From<&'a [u8]> for &'a LStr { + #[inline] + fn from(value: &'a [u8]) -> Self { + LStr::from_bytes(value) + } +} + +impl<'a> From<&'a str> for &'a LStr { + #[inline] + fn from(value: &'a str) -> Self { + LStr::from_str(value) + } +} + +impl<'a> From<&'a mut LStr> for &'a mut [u8] { + #[inline] + fn from(value: &'a mut LStr) -> Self { &mut value.inner } +} + +impl<'a> From<&'a mut [u8]> for &'a mut LStr { + #[inline] + fn from(value: &'a mut [u8]) -> Self { + LStr::from_bytes_mut(value) + } +} + +impl<'a> From<&'a LString> for &'a LStr { + #[inline] + fn from(value: &'a LString) -> Self { value } +} + +impl From<&LStr> for Rc { + #[inline] + fn from(v: &LStr) -> Rc { + let arc = Rc::<[u8]>::from(v.as_bytes()); + unsafe { Rc::from_raw(Rc::into_raw(arc) as *const LStr) } + } +} + +impl From for Rc { + #[inline] + fn from(v: LString) -> Rc { + Rc::from(&v[..]) + } +} + +impl ToOwned for LStr { + type Owned = LString; + + #[inline] + fn to_owned(&self) -> Self::Owned { + self.as_bytes().to_owned().into() + } +} + +impl TryFrom for String { + type Error = FromUtf8Error; + + #[inline] + fn try_from(value: LString) -> Result { + String::from_utf8(value.into()) + } +} + +impl<'a> TryFrom<&'a LStr> for &'a str { + type Error = Utf8Error; + + #[inline] + fn try_from(value: &'a LStr) -> Result { + std::str::from_utf8(&value.inner) + } +} + +impl From for LString { + #[inline] + fn from(value: char) -> Self { + value.to_string().into() + } +} + +impl From for LString { + #[inline] + fn from(value: u8) -> Self { + vec![value].into() + } +} + +impl FromIterator for LString { + #[inline] + fn from_iter>(iter: I) -> Self { + String::from_iter(iter).into() + } +} + +impl FromIterator for LString { + #[inline] + fn from_iter>(iter: T) -> Self { + Vec::from_iter(iter).into() + } +} + +impl Extend for LString { + #[inline] + fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } +} + +impl<'a> Extend<&'a u8> for LString { + #[inline] + fn extend>(&mut self, iter: I) { + self.inner.extend(iter); + } +} + +impl Extend for LString { + #[inline] + fn extend>(&mut self, iter: I) { + let iter = iter.into_iter(); + let (lo, _) = iter.size_hint(); + self.reserve(lo); + iter.for_each(move |ch| self.push_char(ch)); + } +} + +impl<'a> Extend<&'a char> for LString { + #[inline] + fn extend>(&mut self, iter: I) { + let iter = iter.into_iter(); + let (lo, _) = iter.size_hint(); + self.reserve(lo); + iter.for_each(move |ch| self.push_char(*ch)); + } +} + +// +// write +// + +impl io::Write for LString { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { Ok(()) } +} + +//impl fmt::Write for LString { +// fn write_str(&mut self, s: &str) -> fmt::Result { +// self.extend(s.as_bytes()); +// Ok(()) +// } +//} + +// +// methods +// + +impl LString { + #[inline] + pub const fn new() -> Self { + Self { inner: Vec::new() } + } + + #[inline] + pub fn with_capacity(capacity: usize) -> Self { + Vec::with_capacity(capacity).into() + } + + #[inline] + pub fn reserve(&mut self, additional: usize) { + self.inner.reserve(additional); + } + + #[inline] + pub fn push_byte(&mut self, byte: u8) { + self.inner.push(byte); + } + + #[inline] + pub fn push_bytes(&mut self, bytes: &[u8]) { + self.inner.extend_from_slice(bytes); + } + + #[inline] + pub fn push_char(&mut self, ch: char) { + let mut buf = [0; 5]; + self.push_bytes(ch.encode_utf8(&mut buf).as_bytes()); + } + + #[inline] + pub fn push_lstr(&mut self, lstring: &LStr) { + self.push_bytes(lstring.as_bytes()); + } + + #[inline] + pub fn push_str(&mut self, string: &str) { + self.push_bytes(string.as_bytes()); + } + + #[inline] + pub fn clear(&mut self) { + self.inner.clear(); + } + + #[inline] + pub fn leak<'a>(self) -> &'a mut LStr { + self.inner.leak().into() + } + + #[inline] + pub fn into_string(self) -> Result { + String::from_utf8(self.inner) + } +} + +#[derive(Clone)] +pub struct Bytes<'a>(Copied>); + +impl<'a> Iterator for Bytes<'a> { + type Item = u8; + + #[inline] + fn next(&mut self) -> Option { self.0.next() } + #[inline] + fn size_hint(&self) -> (usize, Option) { self.0.size_hint() } + #[inline] + fn count(self) -> usize { self.0.count() } + #[inline] + fn last(self) -> Option { self.0.last() } + #[inline] + fn nth(&mut self, n: usize) -> Option { self.0.nth(n) } + #[inline] + fn all bool>(&mut self, f: F) -> bool { + self.0.all(f) + } + #[inline] + fn any bool>(&mut self, f: F) -> bool { + self.0.any(f) + } + #[inline] + fn find bool>(&mut self, predicate: P) -> Option { + self.0.find(predicate) + } + #[inline] + fn position bool>(&mut self, predicate: P) -> Option { + self.0.position(predicate) + } +} + +impl<'a> ExactSizeIterator for Bytes<'a> { + #[inline] + fn len(&self) -> usize { self.0.len() } +} + +impl<'a> FusedIterator for Bytes<'a> {} + +#[derive(Clone)] +pub struct LosslessChars<'a>(&'a [u8]); + +impl<'a> Iterator for LosslessChars<'a> { + type Item = Result; + + #[inline] + fn next(&mut self) -> Option { + let (new_bytes, res) = next_codepoint(self.0)?; + self.0 = new_bytes; + Some(res) + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + let len = self.0.len(); + ((len + 3)/4, Some(len)) + } +} + +impl<'a> DoubleEndedIterator for LosslessChars<'a> { + fn next_back(&mut self) -> Option { + let (new_bytes, res) = next_codepoint_back(self.0)?; + self.0 = new_bytes; + Some(res) + } +} + + +#[derive(Clone)] +pub struct Chars<'a>(LosslessChars<'a>); + +impl<'a> Iterator for Chars<'a> { + type Item = char; + + #[inline] + fn next(&mut self) -> Option { + loop { + if let Ok(c) = self.0.next()? { + return Some(c) + } + } + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl<'a> DoubleEndedIterator for Chars<'a> { + fn next_back(&mut self) -> Option { + loop { + if let Ok(c) = self.0.next_back()? { + return Some(c) + } + } + } +} + + +impl LStr { + #[inline] + pub const fn from_str(string: &str) -> &Self { + Self::from_bytes(string.as_bytes()) + } + + #[inline] + pub const fn from_bytes(bytes: &[u8]) -> &Self { + unsafe { &*(bytes as *const [u8] as *const LStr) } + } + + #[inline] + pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self { + unsafe { &mut *(bytes as *mut [u8] as *mut LStr) } + } + + #[inline] + pub const fn as_bytes(&self) -> &[u8] { &self.inner } + + #[inline] + pub fn as_bytes_mut(&mut self) -> &mut [u8] { &mut self.inner } + + #[inline] + pub fn bytes(&self) -> Bytes { + Bytes(self.as_bytes().iter().copied()) + } + #[inline] + pub fn chars(&self) -> Chars { + Chars(self.chars_lossless()) + } + #[inline] + pub fn chars_lossless(&self) -> LosslessChars { + LosslessChars(self.as_bytes()) + } + + #[inline] + pub const fn to_str(&self) -> Result<&str, Utf8Error> { + std::str::from_utf8(&self.inner) + } + + #[inline] + pub const fn len(&self) -> usize { + self.inner.len() + } + + #[inline] + pub const fn is_empty(&self) -> bool { + self.inner.is_empty() + } + + #[inline] + pub const fn is_utf8(&self) -> bool { + self.to_str().is_ok() + } + + #[inline] + pub fn to_utf8_lossy(&self) -> Cow<'_, LStr> { + match String::from_utf8_lossy(self.as_bytes()) { + Cow::Borrowed(b) => Cow::Borrowed(b.into()), + Cow::Owned(o) => Cow::Owned(o.into()), + } + } + + #[inline] + pub fn to_os_str(&self) -> Cow { + #[cfg(unix)] { + use std::os::unix::ffi::OsStrExt; + Cow::Borrowed(OsStr::from_bytes(self.as_bytes())) + } + #[cfg(not(unix))] { + Cow::Owned(self.to_string().into()) + } + } + + fn convert_while_ascii(&self, f: impl Fn(&u8) -> u8) -> LString { + let mut out = LString::new(); + for b in self.bytes() { + if !(0..=0x7f).contains(&b) { + break + } + out.push_byte(f(&b)); + } + out + } + + pub fn to_uppercase(&self) -> LString { + let mut out = self.convert_while_ascii(u8::to_ascii_uppercase); + for ch in self[out.len()..].chars_lossless() { + match ch { + Ok(c) => out.extend(c.to_uppercase()), + Err(b) => out.push_byte(b), + } + } + out + } + + pub fn to_lowercase(&self) -> LString { + let mut out = self.convert_while_ascii(u8::to_ascii_lowercase); + for ch in self[out.len()..].chars_lossless() { + match ch { + Ok(c) => out.extend(c.to_lowercase()), + Err(b) => out.push_byte(b), + } + } + out + } + + pub fn trim(&self) -> &LStr { + self.trim_by(char::is_whitespace) + } + + pub fn trim_by(&self, pattern: impl Fn(char) -> bool) -> &LStr { + let mut start = 0; + for ch in self.chars_lossless() { + if !ch.is_ok_and(&pattern) { + break + } + start += ch.map_or_else(|_| 1, char::len_utf8); + } + if start == self.len() { + return &self[0..0] + } + let mut end = self.len(); + for ch in self.chars_lossless().rev() { + if !ch.is_ok_and(&pattern) { + break + } + end -= ch.map_or_else(|_| 1, char::len_utf8); + } + &self[start..end] + } + + pub fn starts_with(&self, s: &LStr) -> bool { + self.as_bytes().starts_with(s.as_bytes()) + } + + pub fn ends_with(&self, s: &LStr) -> bool { + self.as_bytes().ends_with(s.as_bytes()) + } + + pub fn is_identifier(&self) -> bool { + let mut chars = self.chars_lossless(); + let first = chars.next() + .is_some_and(|ch| ch.is_ok_and(is_xid_start)); + if !first { + return false + } + chars.all(|ch| ch.is_ok_and(is_xid_continue)) + } +} + +impl Add<&LStr> for LString { + type Output = Self; + + #[inline] + fn add(mut self, rhs: &LStr) -> Self::Output { + self.push_lstr(rhs); + self + } +} + +impl AddAssign<&LStr> for LString { + #[inline] + fn add_assign(&mut self, rhs: &LStr) { + self.push_lstr(rhs); + } +} + +impl Default for &LStr { + fn default() -> Self { [].as_ref().into() } +} + +impl Default for &mut LStr { + fn default() -> Self { [].as_mut().into() } +} + +macro_rules! impl_index { + ($ty:ty) => { + impl Index<$ty> for LStr { + type Output = LStr; + + fn index(&self, index: $ty) -> &Self::Output { + self.inner.index(index).into() + } + } + + impl IndexMut<$ty> for LStr { + fn index_mut(&mut self, index: $ty) -> &mut LStr { + self.inner.index_mut(index).into() + } + } + + impl Index<$ty> for LString { + type Output = LStr; + + fn index(&self, index: $ty) -> &Self::Output { + self.inner.index(index).into() + } + } + + impl IndexMut<$ty> for LString { + fn index_mut(&mut self, index: $ty) -> &mut LStr { + self.inner.index_mut(index).into() + } + } + }; +} + +impl_index!(std::ops::Range); +impl_index!(std::ops::RangeFrom); +impl_index!(std::ops::RangeFull); +impl_index!(std::ops::RangeInclusive); +impl_index!(std::ops::RangeTo); +impl_index!(std::ops::RangeToInclusive); +impl_index!((std::ops::Bound, std::ops::Bound)); + diff --git a/talc-lang/src/parser.lalrpop b/talc-lang/src/parser.lalrpop index d59c7c0..985b238 100644 --- a/talc-lang/src/parser.lalrpop +++ b/talc-lang/src/parser.lalrpop @@ -3,6 +3,8 @@ use crate::ast::*; use crate::value::Value; use crate::symbol::Symbol; use crate::parser_util::*; +use crate::lstring::LStr; +use crate::lstr; use num_complex::Complex64; grammar; @@ -100,8 +102,8 @@ Assign: Box> = { LValue: Box> = { => Box::new(LValue::Ident(<>)), - "!" => Box::new(LValue::Index(l, r)), - "." => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), + "!" => Box::new(LValue::Index(l, r)), + "." => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), } AssignOp: Option = { @@ -153,8 +155,8 @@ Pipe: Box> = { Lambda: Box> = { "\\" "->" => Box::new(Expr::Lambda(xs, e)), - ":" => Box::new(Expr::Lambda(vec!["$"], e)), - "::" => Box::new(Expr::Lambda(vec!["$", "$$"], e)), + ":" => Box::new(Expr::Lambda(vec![lstr!("$")], e)), + "::" => Box::new(Expr::Lambda(vec![lstr!("$"), lstr!("$$")], e)), BinaryCompare, } @@ -194,16 +196,43 @@ BinaryAppend: Box> = { // .. ..= ..* BinaryRange: Box> = { - => Box::new(Expr::BinaryOp(o, l, r)), - "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)), - BinaryAdd, + => Box::new(Expr::BinaryOp(o, l, r)), + "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)), + BinaryBitOr, } RangeOp: BinaryOp = { ".." => BinaryOp::Range, "..=" => BinaryOp::RangeIncl, } +// #| +BinaryBitOr: Box> = { + "#|" => Box::new(Expr::BinaryOp(BinaryOp::BitOr, l, r)), + BinaryBitXor, +} +// #^ +BinaryBitXor: Box> = { + "#^" => Box::new(Expr::BinaryOp(BinaryOp::BitXor, l, r)), + BinaryBitAnd, +} + +// #& +BinaryBitAnd: Box> = { + "#&" => Box::new(Expr::BinaryOp(BinaryOp::BitAnd, l, r)), + BinaryShift, +} + +// >> << +BinaryShift: Box> = { + => Box::new(Expr::BinaryOp(o, l, r)), + BinaryAdd, +} + +ShiftOp: BinaryOp = { + ">>" => BinaryOp::Shr, + "<<" => BinaryOp::Shl, +} // + - BinaryAdd: Box> = { @@ -248,13 +277,13 @@ BinaryPow: Box> = { // index ( ! ) BinaryIndex: Box> = { "!" => Box::new(Expr::Index(l, r)), - FunctionCall, + CallOrAccess, } // unary- UnaryMinus2: Box> = { "-" => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)), - FunctionCall, + CallOrAccess, } @@ -262,21 +291,14 @@ UnaryMinus2: Box> = { // things // - // function call -FunctionCall: Box> = { - "(" ")" => Box::new(Expr::FnCall(l, r)), - FieldAccess, -} - - -// field access -FieldAccess: Box> = { - "." => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), +CallOrAccess: Box> = { + "(" ")" => Box::new(Expr::FnCall(l, args)), + "->" "(" ")" => Box::new(Expr::AssocFnCall(l, Symbol::get(r), args)), + "." => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), Term, } - // // base // @@ -291,8 +313,8 @@ TermNotIdent: Box> = { "(" ")" => <>, "[" "]" => Box::new(Expr::List(<>)), "{" "}" => Box::new(Expr::Table(<>)), - "$" => Box::new(Expr::Ident("$")), - "$$" => Box::new(Expr::Ident("$$")), + "$" => Box::new(Expr::Ident(lstr!("$"))), + "$$" => Box::new(Expr::Ident(lstr!("$$"))), "do" "end" => <>, "if" => <>, @@ -358,12 +380,12 @@ TableKey: Expr<'input> = { TermNotIdent => *<>, } -IdentList: Vec<&'input str> = { +IdentList: Vec<&'input LStr> = { ",")*> => { if let Some(x) = x { xs.push(x) }; xs } } -Identifier: &'input str = TokIdentifier; +Identifier: &'input LStr = TokIdentifier => <>.into(); // @@ -404,8 +426,8 @@ Literal: Value = { "nil" => Value::Nil, } -StringLiteral: Rc = { - TokStringSingle => <>[1..<>.len()-1].into(), +StringLiteral: Rc = { + TokStringSingle => LStr::from_str(&<>[1..<>.len()-1]).into(), TokStringDouble =>? parse_str_escapes(&<>[1..<>.len()-1]) .map(|s| s.into()) .map_err(|e| ParseError::from(e).into()), diff --git a/talc-lang/src/parser_util.rs b/talc-lang/src/parser_util.rs index d53def7..8193db8 100644 --- a/talc-lang/src/parser_util.rs +++ b/talc-lang/src/parser_util.rs @@ -2,6 +2,8 @@ use std::num::{ParseIntError, ParseFloatError}; use thiserror::Error; +use crate::lstring::{LStr, LString}; + #[derive(Clone, Debug, Error)] pub enum ParseError { #[error("{0}")] @@ -32,31 +34,30 @@ pub enum StrEscapeError { CodepointTooLarge, } -pub fn parse_str_escapes(src: &str) -> Result { - let mut s = String::with_capacity(src.len()); +pub fn parse_str_escapes(src: &str) -> Result { + let mut s = LString::with_capacity(src.len()); let mut chars = src.chars(); while let Some(c) = chars.next() { - if c != '\\' { s.push(c); continue } + if c != '\\' { s.push_char(c); continue } let c = chars.next().ok_or(StrEscapeError::Eof)?; match c { - '"' | '\'' | '\\' => s.push(c), - '0' => s.push('\0'), - 'a' => s.push('\x07'), - 'b' => s.push('\x08'), - 't' => s.push('\t'), - 'n' => s.push('\n'), - 'v' => s.push('\x0b'), - 'f' => s.push('\x0c'), - 'r' => s.push('\r'), - 'e' => s.push('\x1b'), + '"' | '\'' | '\\' => s.push_char(c), + '0' => s.push_char('\0'), + 'a' => s.push_char('\x07'), + 'b' => s.push_char('\x08'), + 't' => s.push_char('\t'), + 'n' => s.push_char('\n'), + 'v' => s.push_char('\x0b'), + 'f' => s.push_char('\x0c'), + 'r' => s.push_char('\r'), + 'e' => s.push_char('\x1b'), 'x' => { let c = chars.next().ok_or(StrEscapeError::HexEof)?; let n1 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?; let c = chars.next().ok_or(StrEscapeError::HexEof)?; let n2 = c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?; - // can't panic: all bytes 0..256 are valid codepoints - s.push(char::from_u32(n1 * 16 + n2).unwrap()); + s.push_byte((n1 * 16 + n2) as u8); }, 'u' => { let Some('{') = chars.next() else { @@ -74,7 +75,7 @@ pub fn parse_str_escapes(src: &str) -> Result { n = n * 16 + c.to_digit(16).ok_or(StrEscapeError::InvalidHex(c))?; } let ch = char::from_u32(n).ok_or(StrEscapeError::InvalidCodepoint(n))?; - s.push(ch); + s.push_char(ch); }, c => return Err(StrEscapeError::Invalid(c)), @@ -84,9 +85,9 @@ pub fn parse_str_escapes(src: &str) -> Result { Ok(s) } -pub fn parse_float(f: &str) -> Result { +pub fn parse_float<'a, S: Into<&'a LStr>>(f: S) -> Result { let mut s = String::new(); - for c in f.chars() { + for c in f.into().chars() { if c != '_' { s.push(c); } @@ -94,9 +95,9 @@ pub fn parse_float(f: &str) -> Result { s.parse() } -pub fn parse_int(f: &str, radix: u32) -> Result { +pub fn parse_int<'a, S: Into<&'a LStr>>(f: S, radix: u32) -> Result { let mut s = String::new(); - for c in f.chars() { + for c in f.into().chars() { if c != '_' { s.push(c); } diff --git a/talc-lang/src/symbol.rs b/talc-lang/src/symbol.rs index 8f8d22d..c96ec07 100644 --- a/talc-lang/src/symbol.rs +++ b/talc-lang/src/symbol.rs @@ -4,8 +4,8 @@ use lazy_static::lazy_static; #[derive(Default)] struct SymbolTable { - names: Vec<&'static str>, - values: HashMap<&'static str, Symbol> + names: Vec<&'static LStr>, + values: HashMap<&'static LStr, Symbol> } lazy_static! { @@ -53,14 +53,16 @@ fn get_table() -> &'static Mutex { impl Symbol { /// # Panics /// If the mutex storing the symbol table is poisoned - pub fn try_get(name: &str) -> Option { + pub fn try_get<'a, S: Into<&'a LStr>>(name: S) -> Option { + let name = name.into(); let table = get_table().lock().expect("couldn't lock symbol table"); table.values.get(name).copied() } /// # Panics /// If the mutex storing the symbol table is poisoned - pub fn get(name: &str) -> Self { + pub fn get<'a, S: Into<&'a LStr>>(name: S) -> Self { + let name = name.into(); let mut table = get_table().lock().expect("couldn't lock symbol table"); if let Some(sym) = table.values.get(&name) { @@ -71,7 +73,7 @@ impl Symbol { assert!(symno <= MAX_SYMBOL, "too many symbols"); let sym = Symbol(symno as u32); - let name = String::leak(name.to_owned()); + let name = LString::leak(name.to_owned()); table.names.push(name); table.values.insert(name, sym); @@ -102,9 +104,9 @@ impl Symbol { /// # Panics /// If the mutex storing the symbol table is poisoned - pub fn name(self) -> &'static str { + pub fn name(self) -> &'static LStr { let table = get_table().lock().expect("couldn't lock symbol table"); - table.names.get(self.0 as usize).cloned().expect("symbol does not exist") + table.names.get(self.0 as usize).copied().expect("symbol does not exist") } } @@ -119,3 +121,6 @@ macro_rules! symbol { } pub use symbol; + +use crate::lstring::{LStr, LString}; + diff --git a/talc-lang/src/value/function.rs b/talc-lang/src/value/function.rs index afdda20..ca38366 100644 --- a/talc-lang/src/value/function.rs +++ b/talc-lang/src/value/function.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::{chunk::Chunk, Vm, exception::Result}; -use super::{Value}; +use super::Value; #[derive(Clone, Copy, Debug, Default)] pub struct FuncAttrs { diff --git a/talc-lang/src/value/index.rs b/talc-lang/src/value/index.rs index 835eb48..4abc138 100644 --- a/talc-lang/src/value/index.rs +++ b/talc-lang/src/value/index.rs @@ -30,6 +30,40 @@ impl Value { let i = i.try_into()?; Ok(t.get(&i).cloned().unwrap_or(Value::Nil)) }, + (V::String(s), V::Range(r)) => { + let slen = s.len(); + match r.ty { + RangeType::Open => { + if r.start < 0 || r.start > slen as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for string of length {}", r.stop, slen) + } + if r.stop < 0 || r.stop > slen as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for string of length {}", r.stop, slen) + } + Ok(s[r.start as usize..r.stop as usize].into()) + }, + RangeType::Closed => { + if r.start < 0 || r.start > slen as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for string of length {}", r.stop, slen) + } + if r.stop < 0 || r.stop >= slen as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for string of length {}", r.stop, slen) + } + Ok(s[r.start as usize..=r.stop as usize].into()) + }, + RangeType::Endless => { + if r.start < 0 || r.start > slen as i64 { + throw!(*SYM_INDEX_ERROR, + "index {} out of bounds for string of length {}", r.stop, slen) + } + Ok(s[r.start as usize..].into()) + }, + } + }, (col, idx) => if let Ok(ii) = idx.clone().to_iter_function() { let col = col.clone(); let func = move |vm: &mut Vm, _| { diff --git a/talc-lang/src/value/mod.rs b/talc-lang/src/value/mod.rs index cee77e5..fe2020e 100644 --- a/talc-lang/src/value/mod.rs +++ b/talc-lang/src/value/mod.rs @@ -1,8 +1,13 @@ +use std::any::Any; +use std::borrow::Cow; +use std::io; use std::{cell::RefCell, collections::HashMap, fmt::Display, hash::Hash, rc::Rc}; pub use num_complex::Complex64; +use num_complex::ComplexFloat; pub use num_rational::Rational64; +use crate::lstring::{LStr, LString}; use crate::symbol::{Symbol, SYM_HASH_ERROR}; use crate::exception::{Exception, throw}; @@ -30,64 +35,160 @@ pub enum Value { Complex(Complex64), Cell(Rc>), - String(Rc), + String(Rc), List(RcList), Table(RcTable), Function(Rc), NativeFunc(Rc), + + Native(Rc), +} + +pub trait NativeValue: std::fmt::Debug + Any { + fn get_type(&self) -> Symbol; + fn as_any(&self) -> &dyn Any; + + fn to_lstring(&self, w: &mut LString, _repr: bool) -> io::Result<()> { + w.extend(b""); + Ok(()) + } + fn partial_eq(&self, _other: &Value) -> bool { + false + } + fn copy_value(&self) -> Result, Exception> { + Ok(None) + } } impl Display for Value { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Nil => write!(f, "nil"), - Self::Bool(b) => write!(f, "{b}"), - Self::Symbol(s) => write!(f, ":{}", s.name()), - Self::Range(r) => match r.ty { - RangeType::Open => write!(f, "{}..{}", r.start, r.stop), - RangeType::Closed => write!(f, "{}..={}", r.start, r.stop), - RangeType::Endless => write!(f, "{}..*", r.start), - }, - Self::Int(n) => write!(f, "{n}"), - Self::Float(x) => write!(f, "{x:?}"), - Self::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()), - Self::Complex(z) => write!(f, "{z}"), - - Self::Cell(v) if f.alternate() => write!(f, "cell({:#})", v.borrow()), - Self::Cell(v) => write!(f, "{})", v.borrow()), - - Self::String(s) if f.alternate() => write!(f, "{s:?}"), - Self::String(s) => write!(f, "{s}"), - - Self::List(l) => { - write!(f, "[")?; - for (i, item) in l.borrow().iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "{item:#}")?; - } - write!(f, "]") - }, - Self::Table(t) => { - write!(f, "{{ ")?; - for (i, (k, v)) in t.borrow().iter().enumerate() { - if i != 0 { - write!(f, ", ")?; - } - write!(f, "({:#}) = {v:#}", k.0)?; - } - write!(f, " }}") - }, - Self::Function(g) - => write!(f, "", Rc::as_ptr(g)), - Self::NativeFunc(g) - => write!(f, "", Rc::as_ptr(g)), - } + let s = if f.alternate() { Cow::Owned(self.repr()) } else { self.str() }; + write!(f, "{s}") } } impl Value { + pub fn new_list(f: impl FnOnce(&mut Vec)) -> Self { + let mut list = Vec::new(); + f(&mut list); + list.into() + } + + pub fn new_table(f: impl FnOnce(&mut HashMap)) -> Self { + let mut table = HashMap::new(); + f(&mut table); + table.into() + } + + pub fn write_to_lstring(&self, w: &mut LString, repr: bool) -> io::Result<()> { + use std::io::Write; + match self { + Self::Nil => write!(w, "nil"), + Self::Bool(b) => write!(w, "{b}"), + Self::Symbol(s) => { + let name = s.name(); + w.push_byte(b':'); + if name.is_identifier() { + w.extend(name.as_bytes()); + } else { + write!(w, "{name:?}")?; + } + Ok(()) + }, + Self::Range(r) => match r.ty { + RangeType::Open => write!(w, "{}..{}", r.start, r.stop), + RangeType::Closed => write!(w, "{}..={}", r.start, r.stop), + RangeType::Endless => write!(w, "{}..*", r.start), + }, + Self::Int(i) => write!(w, "{i}"), + Self::Float(x) => write!(w, "{x:?}"), + Self::Ratio(r) => write!(w, "{}/{}", r.numer(), r.denom()), + Self::Complex(z) => { + write!(w, "{:?}", z.re())?; + if z.im() >= 0.0 || z.im.is_nan() { + w.push_byte(b'+'); + } + write!(w, "{:?}i", z.im()) + }, + Self::Cell(v) if repr => { + w.write_all(b"cell(")?; + v.borrow().write_to_lstring(w, repr)?; + w.write_all(b")") + }, + Self::Cell(v) => v.borrow().write_to_lstring(w, false), + + Self::String(s) if repr => write!(w, "{s:?}"), + Self::String(s) => w.write_all(s.as_bytes()), + + Self::List(l) => { + w.write_all(b"[")?; + for (i, item) in l.borrow().iter().enumerate() { + if i != 0 { + w.write_all(b", ")?; + } + item.write_to_lstring(w, true)?; + } + w.write_all(b"]") + }, + Self::Table(t) => { + w.write_all(b"{ ")?; + for (i, (k, v)) in t.borrow().iter().enumerate() { + if i != 0 { + w.write_all(b", ")?; + } + k.0.write_table_key_repr(w)?; + w.write_all(b" = ")?; + v.write_to_lstring(w, true)?; + } + w.write_all(b" }") + }, + Self::Function(g) + => write!(w, "", Rc::as_ptr(g)), + Self::NativeFunc(g) + => write!(w, "", Rc::as_ptr(g)), + Self::Native(n) => n.to_lstring(w, repr), + } + } + + fn write_table_key_repr(&self, w: &mut LString) -> std::io::Result<()> { + match self { + Self::Nil | Self::Bool(_) + | Self::Int(_) | Self::String(_) + => self.write_to_lstring(w, true), + Self::Symbol(s) => { + let name = s.name(); + if name.is_identifier() { + w.push_lstr(name); + Ok(()) + } else { + self.write_to_lstring(w, true) + } + }, + _ => { + w.push_byte(b'('); + self.write_to_lstring(w, true)?; + w.push_byte(b')'); + Ok(()) + } + } + } + + pub fn str(&self) -> Cow<'_, LStr> { + if let Value::String(s) = self { + Cow::Borrowed(s) + } else { + let mut s = LString::new(); + self.write_to_lstring(&mut s, false).expect("write_to_lstring failed"); + Cow::Owned(s) + } + } + + pub fn repr(&self) -> LString { + let mut s = LString::new(); + self.write_to_lstring(&mut s, true).expect("write_to_lstring failed"); + s + } + pub fn get_type(&self) -> Symbol { use crate::symbol::*; match self { @@ -105,6 +206,7 @@ impl Value { Value::Table(_) => *SYM_TABLE, Value::Function(_) => *SYM_FUNCTION, Value::NativeFunc(_) => *SYM_NATIVE_FUNC, + Value::Native(n) => n.get_type(), } } @@ -115,6 +217,13 @@ impl Value { | Value::Int(_) | Value::Ratio(_) | Value::String(_) ) } + + pub fn downcast_native(&self) -> Option<&T> { + match self { + Value::Native(n) => n.as_any().downcast_ref(), + _ => None + } + } } #[derive(Clone, Debug, PartialEq)] @@ -134,6 +243,7 @@ impl TryFrom for HashValue { impl HashValue { pub fn into_inner(self) -> Value { self.0 } + pub fn inner(&self) -> &Value { &self.0 } } impl Hash for HashValue { @@ -202,8 +312,21 @@ impl_from!(HashMap, Table, rcref); impl_from!(Vec, List, rcref); impl_from!(Function, Function, rc); impl_from!(NativeFunc, NativeFunc, rc); -impl_from!(Rc, String); -impl_from!(String, String, into); -impl_from!(&str, String, into); -impl_from!(Box, String, into); +impl_from!(Rc, String); +impl_from!(LString, String, into); +impl_from!(&LStr, String, into); +impl_from!(Box, String, into); +impl_from!(Cow<'_, LStr>, String, into); impl_from!(RefCell, Cell, rc); + +impl From> for Value { + fn from(value: Rc) -> Self { + Self::Native(value) + } +} + +impl From for Value { + fn from(value: T) -> Self { + Self::Native(Rc::new(value)) + } +} diff --git a/talc-lang/src/value/ops.rs b/talc-lang/src/value/ops.rs index 27b285a..4830cf0 100644 --- a/talc-lang/src/value/ops.rs +++ b/talc-lang/src/value/ops.rs @@ -1,9 +1,9 @@ -use std::{cell::RefCell, cmp::Ordering, ops::{Add, Div, Mul, Neg, Sub}, rc::Rc}; +use std::{cell::RefCell, cmp::Ordering, ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Neg, Shl, Shr, Sub}, rc::Rc}; use num_complex::Complex64; use num_rational::Rational64; -use crate::{exception::{throw, Result}, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR}, value::range::RangeType, Vm}; +use crate::{exception::{throw, Result}, lstring::LString, symbol::{SYM_END_ITERATION, SYM_TYPE_ERROR}, value::range::RangeType, Vm}; use super::{function::{FuncAttrs, NativeFunc}, range::Range, HashValue, Value}; @@ -27,12 +27,13 @@ impl Value { Value::Float(x) => *x != 0.0 && !x.is_nan(), Value::Ratio(r) => !(*r.numer() == 0 && *r.denom() != 0), Value::Complex(c) => !(c.re == 0.0 && c.im == 0.0 || c.is_nan()), - Value::String(s) => s.len() > 0, + Value::String(s) => !s.is_empty(), Value::List(l) => l.borrow().len() > 0, Value::Cell(v) => v.borrow().truthy(), Value::Symbol(_) | Value::Table(_) - | Value::Function(_) | Value::NativeFunc(_) => true, + | Value::Function(_) | Value::NativeFunc(_) + | Value::Native(_) => true, } } } @@ -196,6 +197,66 @@ impl Value { } } +impl Shl for Value { + type Output = Result; + + fn shl(self, rhs: Value) -> Self::Output { + use Value as V; + match (self, rhs) { + (V::Int(a), V::Int(b)) => Ok(Value::Int(((a as u64) << b as u64) as i64)), + (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} left by {r:#}") + } + } +} + +impl Shr for Value { + type Output = Result; + + fn shr(self, rhs: Value) -> Self::Output { + use Value as V; + match (self, rhs) { + (V::Int(a), V::Int(b)) => Ok(Value::Int((a as u64 >> b as u64) as i64)), + (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}") + } + } +} + +impl BitAnd for Value { + type Output = Result; + + fn bitand(self, rhs: Value) -> Self::Output { + use Value as V; + match (self, rhs) { + (V::Int(a), V::Int(b)) => Ok(Value::Int(a & b)), + (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}") + } + } +} + +impl BitXor for Value { + type Output = Result; + + fn bitxor(self, rhs: Value) -> Self::Output { + use Value as V; + match (self, rhs) { + (V::Int(a), V::Int(b)) => Ok(Value::Int(a ^ b)), + (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}") + } + } +} + +impl BitOr for Value { + type Output = Result; + + fn bitor(self, rhs: Value) -> Self::Output { + use Value as V; + match (self, rhs) { + (V::Int(a), V::Int(b)) => Ok(Value::Int(a | b)), + (l, r) => throw!(*SYM_TYPE_ERROR, "cannot shift {l:#} right by {r:#}") + } + } +} + impl PartialEq for Value { #[allow(clippy::cast_precision_loss)] fn eq(&self, other: &Self) -> bool { @@ -225,13 +286,15 @@ impl PartialEq for Value { (V::Symbol(a), V::Symbol(b)) => a == b, (V::Cell(a), V::Cell(b)) => *a.borrow() == *b.borrow(), (V::Range(a), V::Range(b)) => match (a.ty, b.ty) { - (Rty::Open, Rty::Open) => a.start == b.start && a.stop == b.stop, - (Rty::Closed, Rty::Closed) => a.start == b.start && a.stop == b.stop, + (Rty::Open, Rty::Open) + | (Rty::Closed, Rty::Closed) => a.start == b.start && a.stop == b.stop, (Rty::Open, Rty::Closed) => a.start == b.start && a.stop == b.stop - 1, (Rty::Closed, Rty::Open) => a.start == b.start && a.stop - 1 == b.stop, (Rty::Endless, Rty::Endless) => a.start == b.start, _ => false, - } + }, + (V::Native(a), b) => a.partial_eq(b), + (a, V::Native(b)) => b.partial_eq(a), _ => false, } } @@ -278,8 +341,8 @@ impl Value { Ok(l.into()) }, (V::String(s1), V::String(s2)) => { - let mut s = s1.as_ref().to_owned(); - s.push_str(s2); + let mut s: LString = s1.as_ref().to_owned(); + s.push_lstr(s2); Ok(V::String(s.into())) } (V::Table(t1), V::Table(t2)) => { @@ -355,7 +418,7 @@ impl Value { let pos = *byte_pos.borrow(); if let Some(v) = &s[pos..].chars().next() { *byte_pos.borrow_mut() += v.len_utf8(); - Ok(Value::from(v.to_string())) + Ok(Value::from(LString::from(*v))) } else { Ok(Value::from(*SYM_END_ITERATION)) } diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs index a34bcf9..72ff537 100644 --- a/talc-lang/src/vm.rs +++ b/talc-lang/src/vm.rs @@ -1,6 +1,6 @@ use std::{cmp::Ordering, collections::HashMap, rc::Rc, sync::{atomic::AtomicBool, Arc}}; -use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{function::{FuncAttrs, Function, NativeFunc}, Value}, exception::{throw, Exception, Result}}; +use crate::{ast::{BinaryOp, UnaryOp}, chunk::Instruction, exception::{throw, Exception, Result}, lstring::LStr, symbol::{Symbol, SYM_CALL_STACK_OVERFLOW, SYM_INTERRUPTED, SYM_NAME_ERROR, SYM_TYPE_ERROR}, value::{function::{FuncAttrs, Function, NativeFunc}, Value}}; struct TryFrame { idx: usize, stack_len: usize } @@ -41,6 +41,11 @@ pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result { BinaryOp::Mod => a.modulo(b), BinaryOp::IntDiv => a.int_div(b), BinaryOp::Pow => a.pow(b), + BinaryOp::Shl => a << b, + BinaryOp::Shr => a >> b, + BinaryOp::BitAnd => a & b, + BinaryOp::BitXor => a ^ b, + BinaryOp::BitOr => a | b, BinaryOp::Eq => Ok(Value::Bool(a == b)), BinaryOp::Ne => Ok(Value::Bool(a != b)), BinaryOp::Gt => a.val_cmp(&b).map(|o| Value::Bool(o == Ordering::Greater)), @@ -125,8 +130,9 @@ impl Vm { self.globals.insert(name, val); } - pub fn set_global_name(&mut self, name: &str, val: Value) { - self.globals.insert(Symbol::get(name), val); + pub fn set_global_name<'a, S>(&mut self, name: S, val: Value) + where S: Into<&'a LStr> { + self.globals.insert(Symbol::get(name.into()), val); } pub fn get_global(&self, name: Symbol) -> Option<&Value> { diff --git a/talc-macros/Cargo.toml b/talc-macros/Cargo.toml index a7d44c4..82ba62c 100644 --- a/talc-macros/Cargo.toml +++ b/talc-macros/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "talc-macros" -version = "0.1.0" +version = "0.2.0" edition = "2021" [lib] proc-macro = true [dependencies] -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0", features = ["full"] } quote = "1.0" diff --git a/talc-std/Cargo.toml b/talc-std/Cargo.toml index b1914fe..b557bcd 100644 --- a/talc-std/Cargo.toml +++ b/talc-std/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "talc-std" -version = "0.1.0" +version = "0.2.0" edition = "2021" [dependencies] talc-lang = { path = "../talc-lang" } talc-macros = { path = "../talc-macros" } lazy_static = "1.4" +regex = "1.10" rand = { version = "0.8", optional = true } [features] diff --git a/talc-std/src/collection.rs b/talc-std/src/collection.rs index 2d862c3..034d404 100644 --- a/talc-std/src/collection.rs +++ b/talc-std/src/collection.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use talc_lang::{exception, exception::Result, symbol::SYM_TYPE_ERROR, throw, value::{function::NativeFunc, Value}, vmcall, Vm}; +use talc_lang::{exception::{exception, Result}, symbol::SYM_TYPE_ERROR, throw, value::{function::NativeFunc, Value}, vmcall, Vm}; use talc_macros::native_func; use crate::unpack_args; diff --git a/talc-std/src/exception.rs b/talc-std/src/exception.rs index ebf159b..edec195 100644 --- a/talc-std/src/exception.rs +++ b/talc-std/src/exception.rs @@ -1,4 +1,4 @@ -use talc_lang::{symbol::SYM_TYPE_ERROR, exception::{throw, Exception, Result}, value::Value, Vm}; +use talc_lang::{exception::{throw, Exception, Result}, symbol::SYM_TYPE_ERROR, value::Value, Vm}; use talc_macros::native_func; use crate::{unpack_args, unpack_varargs}; diff --git a/talc-std/src/file.rs b/talc-std/src/file.rs new file mode 100644 index 0000000..19208c7 --- /dev/null +++ b/talc-std/src/file.rs @@ -0,0 +1,692 @@ +use std::{cell::RefCell, collections::HashMap, fs::{File, OpenOptions}, io::{BufRead, BufReader, BufWriter, ErrorKind, IntoInnerError, Read, Write}, net::{TcpListener, TcpStream, ToSocketAddrs}, os::fd::{AsRawFd, FromRawFd, IntoRawFd, RawFd}, process::{Child, Command, Stdio}, time::Duration}; + +use talc_lang::{exception::Result, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{function::NativeFunc, HashValue, NativeValue, Value}, Vm}; +use talc_macros::native_func; +use lazy_static::lazy_static; + +use crate::{unpack_args, SYM_IO_ERROR}; + +type OpenOptFn = for<'a> fn(&'a mut OpenOptions, bool) -> &'a mut OpenOptions; +lazy_static! { + static ref SYM_STD_FILE: Symbol = Symbol::get("std.file"); + static ref SYM_STD_PROCESS: Symbol = Symbol::get("std.process"); + + static ref SYM_R: Symbol = Symbol::get("r"); + static ref SYM_W: Symbol = Symbol::get("w"); + static ref SYM_A: Symbol = Symbol::get("a"); + static ref SYM_T: Symbol = Symbol::get("t"); + static ref SYM_C: Symbol = Symbol::get("c"); + static ref SYM_N: Symbol = Symbol::get("n"); + static ref SYM_U: Symbol = Symbol::get("u"); + + static ref OPEN_OPT_MAP: HashMap = { + let mut map = HashMap::new(); + map.insert(*SYM_R, OpenOptions::read as OpenOptFn); + map.insert(*SYM_W, OpenOptions::write); + map.insert(*SYM_A, OpenOptions::append); + map.insert(*SYM_T, OpenOptions::truncate); + map.insert(*SYM_C, OpenOptions::create); + map.insert(*SYM_N, OpenOptions::create_new); + map + }; + + static ref SYM_STDIN: Symbol = Symbol::get("stdin"); + static ref SYM_STDOUT: Symbol = Symbol::get("stdout"); + static ref SYM_STDERR: Symbol = Symbol::get("stderr"); + static ref SYM_CD: Symbol = Symbol::get("cd"); + static ref SYM_DELENV: Symbol = Symbol::get("delenv"); + static ref SYM_ENV: Symbol = Symbol::get("env"); + + static ref SYM_PROCESS: Symbol = Symbol::get("process"); + + static ref SYM_INHERIT: Symbol = Symbol::get("inherit"); + static ref SYM_PIPED: Symbol = Symbol::get("piped"); + static ref SYM_NULL: Symbol = Symbol::get("null"); + static ref SYM_ALL: Symbol = Symbol::get("all"); + +} + +thread_local! { + static F_STDIN: Value = { + let fd = std::io::stdin().lock().as_raw_fd(); + let file = unsafe { File::from_raw_fd(fd) }; + let bf = BufFile::new(file, true).expect("failed to create stdin buffered file"); + ValueFile::from(bf).into() + }; + static F_STDOUT: Value = { + let fd = std::io::stdout().lock().as_raw_fd(); + let file = unsafe { File::from_raw_fd(fd) }; + let bf = BufFile::new(file, true).expect("failed to create stdout buffered file"); + ValueFile::from(bf).into() + }; + static F_STDERR: Value = { + let fd = std::io::stderr().lock().as_raw_fd(); + let file = unsafe { File::from_raw_fd(fd) }; + let bf = BufFile::new(file, true).expect("failed to create stderr buffered file"); + ValueFile::from(bf).into() + }; +} + +#[derive(Debug)] +enum BufFile { + Buffered { r: BufReader, w: BufWriter }, + Unbuffered { f: File }, +} + +impl BufFile { + fn new(file: File, buffered: bool) -> std::io::Result { + if buffered { + let file2 = file.try_clone()?; + Ok(Self::Buffered { r: BufReader::new(file), w: BufWriter::new(file2) }) + } else { + Ok(Self::Unbuffered { f: file }) + } + } + + fn from_raw_fd(fd: RawFd, buffered: bool) -> std::io::Result { + Self::new(unsafe { File::from_raw_fd(fd) }, buffered) + } + + fn try_clone(&self) -> std::io::Result { + match self { + Self::Buffered { r, .. } => { + let file = r.get_ref().try_clone()?; + Self::new(file, true) + }, + Self::Unbuffered { f } => Ok(Self::Unbuffered { f: f.try_clone()? }) + } + } + + fn try_into_raw_fd(self) -> std::result::Result>> { + match self { + BufFile::Buffered { r, w } => { + w.into_inner()?.into_raw_fd(); + Ok(r.into_inner().into_raw_fd()) + } + BufFile::Unbuffered { f } => Ok(f.into_raw_fd()), + } + } +} + +impl AsRawFd for BufFile { + fn as_raw_fd(&self) -> RawFd { + match self { + BufFile::Buffered { r, .. } => r.get_ref().as_raw_fd(), + BufFile::Unbuffered { f } => f.as_raw_fd(), + } + } +} + +impl Read for BufFile { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + match self { + BufFile::Buffered { r, .. } => r.read(buf), + BufFile::Unbuffered { f } => f.read(buf), + } + } +} + +impl Write for BufFile { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + match self { + BufFile::Buffered { w, .. } => w.write(buf), + BufFile::Unbuffered { f } => f.write(buf), + } + } + + fn flush(&mut self) -> std::io::Result<()> { + match self { + BufFile::Buffered { w, .. } => w.flush(), + BufFile::Unbuffered { f } => f.flush(), + } + } +} + +#[derive(Debug)] +pub struct ValueFile(RefCell); + +impl From for ValueFile { + fn from(value: BufFile) -> Self { + Self(RefCell::new(value)) + } +} + +impl NativeValue for ValueFile { + fn get_type(&self) -> Symbol { *SYM_STD_FILE } + fn as_any(&self) -> &dyn std::any::Any { self } + fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> { + w.push_str(""); + Ok(()) + } +} + +#[derive(Debug)] +pub struct ValueProcess(RefCell); + +impl From for ValueProcess { + fn from(value: Child) -> Self { + Self(RefCell::new(value)) + } +} + +impl NativeValue for ValueProcess { + fn get_type(&self) -> Symbol { *SYM_STD_PROCESS } + fn as_any(&self) -> &dyn std::any::Any { self } + fn to_lstring(&self, w: &mut LString, _repr: bool) -> std::io::Result<()> { + let id = self.0.borrow().id(); + write!(w, "") + } +} + +pub fn load(vm: &mut Vm) { + vm.set_global_name("open", open().into()); + vm.set_global_name("read", read().into()); + vm.set_global_name("read_all", read_all().into()); + vm.set_global_name("read_until", read_until().into()); + vm.set_global_name("read_line", read_line().into()); + vm.set_global_name("write", write().into()); + vm.set_global_name("flush", flush().into()); + + vm.set_global_name("stdin", stdin().into()); + vm.set_global_name("stdout", stdout().into()); + vm.set_global_name("stderr", stderr().into()); + + vm.set_global_name("tcp_connect", tcp_connect().into()); + vm.set_global_name("tcp_connect_timeout", tcp_connect_timeout().into()); + vm.set_global_name("tcp_listen", tcp_listen().into()); + + vm.set_global_name("spawn", spawn().into()); + vm.set_global_name("system", system().into()); + vm.set_global_name("exit_code", exit_code().into()); + vm.set_global_name("process_id", process_id().into()); +} + + +#[native_func(2)] +pub fn open(_: &mut Vm, args: Vec) -> Result { + let [_, path, opts] = unpack_args!(args); + let Value::String(path) = path else { + throw!(*SYM_TYPE_ERROR, "path to open must be a string") + }; + let mut oo = std::fs::OpenOptions::new(); + let mut buffered = true; + match opts { + Value::Symbol(s) => { + if s == *SYM_U { + oo.read(true).write(true); + buffered = false; + } else { + match OPEN_OPT_MAP.get(&s) { + Some(f) => f(&mut oo, true), + None => throw!(*SYM_TYPE_ERROR, "invalid option for open") + }; + } + }, + Value::List(l) => for s in l.borrow().iter() { + let Value::Symbol(s) = s else { + throw!(*SYM_TYPE_ERROR, "invalid option for open") + }; + if *s == *SYM_U { + buffered = false; + } else { + match OPEN_OPT_MAP.get(s) { + Some(f) => f(&mut oo, true), + None => throw!(*SYM_TYPE_ERROR, "invalid option for open") + }; + } + }, + Value::Nil => { + oo.read(true).write(true); + }, + _ => throw!(*SYM_TYPE_ERROR, "invalid option for open") + } + match oo.open(path.to_os_str()) { + Ok(f) => match BufFile::new(f, buffered) { + Ok(bf) => Ok(ValueFile::from(bf).into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } +} + +#[native_func(2)] +pub fn read(_: &mut Vm, args: Vec) -> Result { + let [_, file, nbytes] = unpack_args!(args); + let Value::Int(nbytes) = nbytes else { + throw!(*SYM_TYPE_ERROR, "read expected integer, got {nbytes:#}") + }; + let Ok(nbytes) = usize::try_from(nbytes) else { + throw!(*SYM_TYPE_ERROR, "number of bytes to read must be nonnegative") + }; + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "read expected file, got {file:#}") + }; + let mut file = file.0.borrow_mut(); + let mut buf = vec![0; nbytes]; + if let Err(e) = file.read_exact(&mut buf) { + if e.kind() == ErrorKind::UnexpectedEof { + Ok(Value::Nil) + } else { + throw!(*SYM_IO_ERROR, "{e}") + } + } else { + Ok(LString::from(buf).into()) + } +} + +#[native_func(1)] +pub fn read_all(_: &mut Vm, args: Vec) -> Result { + let [_, file] = unpack_args!(args); + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "read_all expected file, got {file:#}") + }; + let mut file = file.0.borrow_mut(); + let mut buf = Vec::new(); + if let Err(e) = file.read_to_end(&mut buf) { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(LString::from(buf).into()) +} + +fn read_until_impl(r: &mut impl BufRead, end: &[u8]) -> std::io::Result { + let last = *end.last().unwrap(); + let mut buf = Vec::new(); + loop { + let n = r.read_until(last, &mut buf)?; + if n == 0 || buf.ends_with(end) { + break + } + } + Ok(buf.into()) +} + +#[native_func(2)] +pub fn read_until(_: &mut Vm, args: Vec) -> Result { + let [_, file, end] = unpack_args!(args); + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "read_until expected file, got {file:#}") + }; + let Value::String(end) = end else { + throw!(*SYM_TYPE_ERROR, "read_until expected string, got {end:#}") + }; + if end.is_empty() { + throw!(*SYM_TYPE_ERROR, "read_until: end string must not be empty") + } + let mut file = file.0.borrow_mut(); + if let BufFile::Buffered { r, .. } = &mut *file { + match read_until_impl(r, end.as_bytes()) { + Ok(s) if s.is_empty() => Ok(Value::Nil), + Ok(s) => Ok(s.into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}") + } + } else { + throw!(*SYM_TYPE_ERROR, "read_until: file must be buffered") + } +} + +#[native_func(1)] +pub fn read_line(_: &mut Vm, args: Vec) -> Result { + let [_, file] = unpack_args!(args); + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "read_line expected file, got {file:#}") + }; + let mut file = file.0.borrow_mut(); + if let BufFile::Buffered { r, .. } = &mut *file { + let mut buf = Vec::new(); + match r.read_until(b'\n', &mut buf) { + Ok(0) => Ok(Value::Nil), + Ok(_) => Ok(LString::from(buf).into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}") + } + } else { + throw!(*SYM_TYPE_ERROR, "read_line: file must be buffered") + } +} + +#[native_func(2)] +pub fn write(_: &mut Vm, args: Vec) -> Result { + let [_, file, data] = unpack_args!(args); + let Value::String(data) = data else { + throw!(*SYM_TYPE_ERROR, "write expected string, got {data:#}") + }; + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "write expected file, got {file:#}") + }; + let mut file = file.0.borrow_mut(); + if let Err(e) = file.write_all(data.as_bytes()) { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) +} + +#[native_func(1)] +pub fn flush(_: &mut Vm, args: Vec) -> Result { + let [_, file] = unpack_args!(args); + let Some(file): Option<&ValueFile> = file.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "flush expected file, got {file:#}") + }; + let mut file = file.0.borrow_mut(); + if let Err(e) = file.flush() { + throw!(*SYM_IO_ERROR, "{e}") + } + Ok(Value::Nil) +} + +#[native_func(0)] +pub fn stdin(_: &mut Vm, _: Vec) -> Result { + Ok(F_STDIN.with(Clone::clone)) +} + +#[native_func(0)] +pub fn stdout(_: &mut Vm, _: Vec) -> Result { + Ok(F_STDOUT.with(Clone::clone)) +} + +#[native_func(0)] +pub fn stderr(_: &mut Vm, _: Vec) -> Result { + Ok(F_STDERR.with(Clone::clone)) +} + +// +// network +// + +#[native_func(1)] +pub fn tcp_connect(_: &mut Vm, args: Vec) -> Result { + let [_, addr] = unpack_args!(args); + let Value::String(addr) = addr else { + throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}") + }; + let Ok(addr) = addr.to_str() else { + throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + }; + match TcpStream::connect(addr) { + Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) { + Ok(bf) => Ok(ValueFile::from(bf).into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } +} + +#[native_func(1)] +pub fn tcp_connect_timeout(_: &mut Vm, args: Vec) -> Result { + let [_, addr, timeout] = unpack_args!(args); + let Value::String(addr) = addr else { + throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected string, got {addr:#}") + }; + let Ok(addr) = addr.to_str() else { + throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + }; + let timeout = match timeout { + Value::Int(n) if n >= 0 => Duration::from_secs(n as u64), + Value::Float(n) if n >= 0.0 && n <= Duration::MAX.as_secs_f64() => Duration::from_secs_f64(n), + Value::Int(_) | Value::Float(_) => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout: invalid timeout"), + _ => throw!(*SYM_TYPE_ERROR, "tcp_connect_timeout expected int or float, got {timeout:#}") + }; + let mut addrs = match addr.to_socket_addrs() { + Ok(addrs) => addrs, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + }; + let Some(addr) = addrs.next() else { + throw!(*SYM_IO_ERROR, "invalid address"); + }; + match TcpStream::connect_timeout(&addr, timeout) { + Ok(stream) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) { + Ok(bf) => Ok(ValueFile::from(bf).into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } +} + +fn tcp_listen_inner(listener: TcpListener) -> Value { + let listener = RefCell::new(listener); + let func = move |_: &mut Vm, _: Vec| { + match listener.borrow_mut().accept() { + Ok((stream, addr)) => match BufFile::from_raw_fd(stream.into_raw_fd(), true) { + Ok(bf) => Ok(vec![ + ValueFile::from(bf).into(), + LString::from(addr.to_string()).into() + ].into()), + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } + }; + NativeFunc::new(Box::new(func), 0).into() +} + +#[native_func(1)] +pub fn tcp_listen(_: &mut Vm, args: Vec) -> Result { + let [_, addr] = unpack_args!(args); + let Value::String(addr) = addr else { + throw!(*SYM_TYPE_ERROR, "tcp_connect expected string, got {addr:#}") + }; + let Ok(addr) = addr.to_str() else { + throw!(*SYM_TYPE_ERROR, "address must be valid UTF-8") + }; + match TcpListener::bind(addr) { + Ok(listener) => { + let addr = listener.local_addr() + .map(|a| LString::from(a.to_string()).into()) + .unwrap_or(Value::Nil); + Ok(vec![ + tcp_listen_inner(listener), + addr, + ].into()) + }, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } +} + +// +// processes +// + +fn spawn_opt_stdio( + fname: &str, + proc: &mut Command, + func: fn(&mut Command, Stdio) -> &mut Command, + opt: &Value, +) -> Result<()> { + match opt { + Value::Nil => func(proc, Stdio::inherit()), + Value::Symbol(s) if *s == *SYM_INHERIT => func(proc, Stdio::inherit()), + Value::Symbol(s) if *s == *SYM_PIPED => func(proc, Stdio::piped()), + Value::Symbol(s) if *s == *SYM_NULL => func(proc, Stdio::null()), + Value::Native(n) if n.get_type() == *SYM_STD_FILE => { + let f: &ValueFile = opt.downcast_native().unwrap(); + let bf = match f.0.borrow().try_clone() { + Ok(bf) => bf, + Err(e) => throw!(*SYM_IO_ERROR, "{e}") + }; + let fd = match bf.try_into_raw_fd() { + Ok(fd) => fd, + Err(e) => throw!(*SYM_IO_ERROR, "{e}") + }; + let stdio = unsafe { Stdio::from_raw_fd(fd) }; + func(proc, stdio) + }, + _ => throw!(*SYM_TYPE_ERROR, "{fname} stdio option expected :inherit, :piped, :null, or file, got {opt:#}") + }; + Ok(()) +} + +fn spawn_opt_cd(fname: &str, proc: &mut Command, cd: &Value) -> Result<()> { + if let Value::Nil = cd { return Ok(()) } + let Value::String(cd) = cd else { + throw!(*SYM_TYPE_ERROR, "{fname} cd option expected string, got {cd:#}") + }; + proc.current_dir(cd.to_os_str()); + Ok(()) +} + +fn spawn_opt_delenv(fname: &str, proc: &mut Command, delenv: &Value) -> Result<()> { + match delenv { + Value::Nil => (), + Value::Symbol(s) if *s == *SYM_ALL => { + proc.env_clear(); + }, + Value::List(l) => for e in l.borrow().iter() { + let Value::String(e) = e else { + throw!(*SYM_TYPE_ERROR, + "{fname} delenv option expected :all or list of strings, got {delenv:#}") + }; + proc.env_remove(e.to_os_str()); + }, + _ => throw!(*SYM_TYPE_ERROR, + "{fname} delenv option expected :all or list of strings, got {delenv:#}") + } + Ok(()) +} + +fn spawn_opt_env(fname: &str, proc: &mut Command, env: &Value) -> Result<()> { + match env { + Value::Nil => (), + Value::Table(t) => for (k, v) in t.borrow().iter() { + let Value::String(k) = k.inner() else { + throw!(*SYM_TYPE_ERROR, + "{fname} efromnv option expected table with string keys, got {env:#}") + }; + proc.env(k.to_os_str(), v.str().to_os_str()); + }, + _ => throw!(*SYM_TYPE_ERROR, + "{fname} env option expected table with string keys, got {env:#}") + } + Ok(()) +} + +fn spawn_inner(fname: &str, proc: &mut Command, opts: Value) -> Result { + let (i, o, e) = match opts { + Value::Native(ref n) if n.get_type() == *SYM_STD_FILE => { + throw!(*SYM_TYPE_ERROR, "{fname} options expected :inherit, :piped, :null, or table, got {opts:#}") + } + Value::Table(t) => { + let t = t.borrow(); + + if let Some(cd) = t.get(&HashValue::from(*SYM_CD)) { + spawn_opt_cd(fname, proc, cd)?; + } + + if let Some(delenv) = t.get(&HashValue::from(*SYM_DELENV)) { + spawn_opt_delenv(fname, proc, delenv)?; + } + + if let Some(env) = t.get(&HashValue::from(*SYM_ENV)) { + spawn_opt_env(fname, proc, env)?; + } + + let i = t.get(&HashValue::from(*SYM_STDIN)) + .cloned() + .unwrap_or_else(|| Value::from(*SYM_INHERIT)); + let o = t.get(&HashValue::from(*SYM_STDOUT)) + .cloned() + .unwrap_or_else(|| Value::from(*SYM_INHERIT)); + let e = t.get(&HashValue::from(*SYM_STDERR)) + .cloned() + .unwrap_or_else(|| Value::from(*SYM_INHERIT)); + (i, o, e) + }, + v => (v.clone(), v.clone(), v), + }; + + spawn_opt_stdio(fname, proc, Command::stdin, &i)?; + spawn_opt_stdio(fname, proc, Command::stdout, &o)?; + spawn_opt_stdio(fname, proc, Command::stderr, &e)?; + + let mut child = match proc.spawn() { + Ok(c) => c, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + }; + let mut table = HashMap::new(); + if let Some(stdin) = child.stdin.take() { + let bf = match BufFile::from_raw_fd(stdin.into_raw_fd(), true) { + Ok(bf) => bf, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + }; + table.insert(HashValue::from(*SYM_STDIN), ValueFile::from(bf).into()); + } + if let Some(stdout) = child.stdout.take() { + let bf = match BufFile::from_raw_fd(stdout.into_raw_fd(), true) { + Ok(bf) => bf, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + }; + table.insert(HashValue::from(*SYM_STDOUT), ValueFile::from(bf).into()); + } + if let Some(stderr) = child.stderr.take() { + let bf = match BufFile::from_raw_fd(stderr.into_raw_fd(), true) { + Ok(bf) => bf, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + }; + table.insert(HashValue::from(*SYM_STDERR), ValueFile::from(bf).into()); + } + table.insert(HashValue::from(*SYM_PROCESS), ValueProcess::from(child).into()); + Ok(table.into()) +} + +#[native_func(3)] +pub fn spawn(_: &mut Vm, args: Vec) -> Result { + let [_, cmd, args, opts] = unpack_args!(args); + let Value::String(cmd) = cmd else { + throw!(*SYM_TYPE_ERROR, "spawn expected string, got {cmd:#}") + }; + let Value::List(args) = args else { + throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got {args:#}") + }; + let mut proc = Command::new(cmd.to_os_str()); + for arg in args.borrow().iter() { + let Value::String(arg) = arg else { + throw!(*SYM_TYPE_ERROR, "spawn expected list of strings, got list containing {arg:#}") + }; + proc.arg(arg.to_os_str()); + } + spawn_inner("spawn", &mut proc, opts) +} + +#[native_func(2)] +pub fn system(_: &mut Vm, args: Vec) -> Result { + let [_, cmd, opts] = unpack_args!(args); + let Value::String(cmd) = cmd else { + throw!(*SYM_TYPE_ERROR, "spawn expected string, got {cmd:#}") + }; + let mut proc; + if cfg!(target_os = "windows") { + proc = Command::new("cmd"); + proc.arg("/C") + .arg(cmd.to_os_str()) + } else { + proc = Command::new("sh"); + proc.arg("-c") + .arg(cmd.to_os_str()) + }; + spawn_inner("system", &mut proc, opts) +} + +#[native_func(1)] +pub fn exit_code(_: &mut Vm, args: Vec) -> Result { + let [_, proc] = unpack_args!(args); + let Some(proc): Option<&ValueProcess> = proc.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "exit_code expected process, got {proc:#}") + }; + let mut proc = proc.0.borrow_mut(); + match proc.wait() { + Ok(code) => { + Ok(code.code() + .map(|c| Value::Int(c as i64)) + .unwrap_or_default()) + }, + Err(e) => throw!(*SYM_IO_ERROR, "{e}"), + } +} + +#[native_func(1)] +pub fn process_id(_: &mut Vm, args: Vec) -> Result { + let [_, proc] = unpack_args!(args); + let Some(proc): Option<&ValueProcess> = proc.downcast_native() else { + throw!(*SYM_TYPE_ERROR, "exit_code expected process, got {proc:#}") + }; + let proc = proc.0.borrow(); + Ok((proc.id() as i64).into()) +} + diff --git a/talc-std/src/io.rs b/talc-std/src/io.rs index 3baafc0..2d89ead 100644 --- a/talc-std/src/io.rs +++ b/talc-std/src/io.rs @@ -1,13 +1,17 @@ -use std::{io::Write, time::{SystemTime, UNIX_EPOCH}}; +use std::{io::{BufRead, Write}, os::unix::ffi::OsStrExt, time::{SystemTime, UNIX_EPOCH}}; -use talc_lang::{value::Value, vmcall, Vm, exception::{throw, Result}}; +use talc_lang::{exception::{throw, Result}, lstring::LString, symbol::SYM_TYPE_ERROR, value::Value, vmcall, Vm}; use talc_macros::native_func; use crate::{unpack_args, SYM_IO_ERROR}; #[native_func(1)] pub fn print(_: &mut Vm, args: Vec) -> Result { - if let Err(e) = write!(std::io::stdout(), "{}", args[1]) { + let res = match &args[1] { + Value::String(s) => std::io::stdout().write_all(s.as_bytes()), + v => write!(std::io::stdout(), "{v}"), + }; + if let Err(e) = res { throw!(*SYM_IO_ERROR, "{e}") } Ok(Value::Nil) @@ -15,7 +19,14 @@ pub fn print(_: &mut Vm, args: Vec) -> Result { #[native_func(1)] pub fn println(_: &mut Vm, args: Vec) -> Result { - if let Err(e) = writeln!(std::io::stdout(), "{}", args[1]) { + let res = match &args[1] { + Value::String(s) => std::io::stdout().write_all(s.as_bytes()), + v => write!(std::io::stdout(), "{v}"), + }; + if let Err(e) = res { + throw!(*SYM_IO_ERROR, "{e}") + } + if let Err(e) = std::io::stdout().write_all(b"\n") { throw!(*SYM_IO_ERROR, "{e}") } Ok(Value::Nil) @@ -23,11 +34,11 @@ pub fn println(_: &mut Vm, args: Vec) -> Result { #[native_func(0)] pub fn readln(_: &mut Vm, _: Vec) -> Result { - let mut buf = String::new(); - if let Err(e) = std::io::stdin().read_line(&mut buf) { + let mut buf = Vec::new(); + if let Err(e) = std::io::stdin().lock().read_until(b'\n', &mut buf) { throw!(*SYM_IO_ERROR, "{e}") } - Ok(buf.into()) + Ok(LString::from(buf).into()) } #[native_func(0)] @@ -52,6 +63,53 @@ pub fn time_fn(vm: &mut Vm, args: Vec) -> Result { Ok(time.as_secs_f64().into()) } +#[native_func(1)] +pub fn env(_: &mut Vm, args: Vec) -> Result { + let [_, key] = unpack_args!(args); + let Value::String(key) = key else { + throw!(*SYM_TYPE_ERROR, "env expected string, got {key:#}") + }; + if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { + throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + } + let val = std::env::var_os(key.to_os_str()); + match val { + Some(val) => Ok(LString::from(val.as_bytes()).into()), + None => Ok(Value::Nil), + } +} + +#[native_func(2)] +pub fn setenv(_: &mut Vm, args: Vec) -> Result { + let [_, key, val] = unpack_args!(args); + let Value::String(key) = key else { + throw!(*SYM_TYPE_ERROR, "setenv expected string, got {key:#}") + }; + if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { + throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + } + let val = val.str(); + if val.as_bytes().contains(&0) { + throw!(*SYM_TYPE_ERROR, "environment variable value must not contain a null byte") + } + std::env::set_var(key.to_os_str(), val.to_os_str()); + Ok(Value::Nil) +} + +#[native_func(1)] +pub fn delenv(_: &mut Vm, args: Vec) -> Result { + let [_, key] = unpack_args!(args); + let Value::String(key) = key else { + throw!(*SYM_TYPE_ERROR, "delenv expected string, got {key:#}") + }; + if key.is_empty() || key.as_bytes().contains(&b'=') || key.as_bytes().contains(&0) { + throw!(*SYM_TYPE_ERROR, "environment variable must not be empty or contain an equals sign or null byte") + } + std::env::remove_var(key.to_os_str()); + Ok(Value::Nil) +} + + pub fn load(vm: &mut Vm) { vm.set_global_name("print", print().into()); vm.set_global_name("println", println().into()); @@ -60,4 +118,8 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("time", time().into()); vm.set_global_name("time_ns", time_ns().into()); vm.set_global_name("time_fn", time_fn().into()); + + vm.set_global_name("env", env().into()); + vm.set_global_name("setenv", setenv().into()); + vm.set_global_name("delenv", delenv().into()); } diff --git a/talc-std/src/iter.rs b/talc-std/src/iter.rs index 11ba8b9..e90b330 100644 --- a/talc-std/src/iter.rs +++ b/talc-std/src/iter.rs @@ -360,7 +360,7 @@ pub fn zip(_: &mut Vm, args: Vec) -> Result { let f = move |vm: &mut Vm, _| { let mut res = Vec::with_capacity(iters.len()); - for i in iters.iter() { + for i in &iters { match vmcalliter!(vm; i.clone())? { Some(v) => res.push(v), None => return Ok(Value::iter_pack(None)), @@ -497,7 +497,7 @@ pub fn cartprod(_: &mut Vm, args: Vec) -> Result { let v = vmcalliter!(vm; b.clone())?; let mut s = state.borrow_mut(); if let Some(x) = v { - s.b_data.push(x) + s.b_data.push(x); } else { s.b_done = true; if s.b_idx == 0 { @@ -513,7 +513,7 @@ pub fn cartprod(_: &mut Vm, args: Vec) -> Result { if state.borrow().b_idx == 0 { if let Some(v) = vmcalliter!(vm; a.clone())? { - state.borrow_mut().a_val = v + state.borrow_mut().a_val = v; } else { state.borrow_mut().done = true; return Ok(Value::iter_pack(None)) @@ -571,7 +571,7 @@ pub fn table(vm: &mut Vm, args: Vec) -> Result { pub fn len(vm: &mut Vm, args: Vec) -> Result { let [_, value] = unpack_args!(args); match value { - Value::String(s) => return Ok((s.len() as i64).into()), + Value::String(s) => return Ok((s.chars().count() as i64).into()), Value::List(l) => return Ok((l.borrow().len() as i64).into()), Value::Table(t) => return Ok((t.borrow().len() as i64).into()), Value::Range(r) if r.ty != RangeType::Endless @@ -591,9 +591,8 @@ pub fn fold(vm: &mut Vm, args: Vec) -> Result { let [_, func, iter] = unpack_args!(args); let iter: Value = iter.to_iter_function()?; - let mut result = match vmcalliter!(vm; iter.clone())? { - Some(v) => v, - None => return Ok(Value::iter_pack(None)), + let Some(mut result) = vmcalliter!(vm; iter.clone())? else { + return Ok(Value::iter_pack(None)) }; while let Some(value) = vmcalliter!(vm; iter.clone())? { result = vmcall!(vm; func.clone(), result, value)?; diff --git a/talc-std/src/lib.rs b/talc-std/src/lib.rs index 3b5e2cd..03af428 100644 --- a/talc-std/src/lib.rs +++ b/talc-std/src/lib.rs @@ -6,6 +6,9 @@ pub mod iter; pub mod exception; pub mod num; pub mod collection; +pub mod string; +pub mod file; +pub mod regex; #[cfg(feature="random")] pub mod random; @@ -16,6 +19,9 @@ pub fn load_all(vm: &mut Vm) { collection::load(vm); num::load(vm); io::load(vm); + string::load(vm); + regex::load(vm); + file::load(vm); #[cfg(feature="random")] random::load(vm); } diff --git a/talc-std/src/num.rs b/talc-std/src/num.rs index ab6fc1a..2ac96d9 100644 --- a/talc-std/src/num.rs +++ b/talc-std/src/num.rs @@ -1,7 +1,7 @@ use std::cmp::Ordering; use lazy_static::lazy_static; -use talc_lang::{parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{Complex64, Value}, Vm, exception::Result}; +use talc_lang::{exception::Result, lstring::LString, parse_int, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{ops::RatioExt, Complex64, Value}, Vm}; use talc_macros::native_func; use crate::{unpack_args, unpack_varargs}; @@ -18,7 +18,7 @@ lazy_static! { fn to_floaty(v: Value) -> Value { match v { Value::Int(v) => Value::Float(v as f64), - Value::Ratio(v) => Value::Float(*v.numer() as f64 / *v.denom() as f64), + Value::Ratio(v) => Value::Float(v.to_f64()), Value::Float(v) => Value::Float(v), Value::Complex(v) => Value::Complex(v), v => v, @@ -29,7 +29,7 @@ fn to_floaty(v: Value) -> Value { fn to_complex(v: Value) -> Value { match v { Value::Int(v) => Value::Complex((v as f64).into()), - Value::Ratio(v) => Value::Complex((*v.numer() as f64 / *v.denom() as f64).into()), + Value::Ratio(v) => Value::Complex(v.to_f64().into()), Value::Float(v) => Value::Complex(v.into()), Value::Complex(v) => Value::Complex(v), v => v, @@ -44,8 +44,8 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("e", std::f64::consts::E.into()); vm.set_global_name("tau", std::f64::consts::TAU.into()); vm.set_global_name("pi", std::f64::consts::PI.into()); - vm.set_global_name("egamma", 0.5772156649015329.into()); - vm.set_global_name("phi", 1.618033988749895.into()); + vm.set_global_name("egamma", 0.577_215_664_901_532_9.into()); + vm.set_global_name("phi", 1.618_033_988_749_895.into()); vm.set_global_name("inf", (f64::INFINITY).into()); vm.set_global_name("NaN", (f64::NAN).into()); @@ -57,6 +57,7 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("oct", oct().into()); vm.set_global_name("hex", hex().into()); vm.set_global_name("to_radix", to_radix().into()); + vm.set_global_name("to_radix_upper", to_radix_upper().into()); vm.set_global_name("from_radix", from_radix().into()); vm.set_global_name("gcd", gcd().into()); @@ -79,6 +80,8 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("isnan", isnan().into()); vm.set_global_name("isfinite", isfinite().into()); vm.set_global_name("isinfinite", isinfinite().into()); + vm.set_global_name("float_to_bits", float_to_bits().into()); + vm.set_global_name("float_of_bits", float_of_bits().into()); vm.set_global_name("numer", numer().into()); vm.set_global_name("denom", denom().into()); @@ -91,10 +94,10 @@ pub fn load(vm: &mut Vm) { vm.set_global_name("sqrt", sqrt().into()); vm.set_global_name("cbrt", cbrt().into()); - vm.set_global_name("ln", cbrt().into()); - vm.set_global_name("log2", cbrt().into()); - vm.set_global_name("exp", cbrt().into()); - vm.set_global_name("exp2", cbrt().into()); + vm.set_global_name("ln", ln().into()); + vm.set_global_name("log2", log2().into()); + vm.set_global_name("exp", exp().into()); + vm.set_global_name("exp2", exp2().into()); vm.set_global_name("sin", sin().into()); vm.set_global_name("cos", cos().into()); @@ -116,7 +119,7 @@ pub fn load(vm: &mut Vm) { // base conversions // -fn to_radix_inner(n: i64, radix: u32) -> String { +fn to_radix_inner(n: i64, radix: u32, upper: bool) -> LString { let mut result = vec![]; let mut begin = 0; @@ -133,13 +136,15 @@ fn to_radix_inner(n: i64, radix: u32) -> String { let m = x % (radix as u64); x /= radix as u64; - result.push(char::from_digit(m as u32, radix).unwrap() as u8); + let mut c = char::from_digit(m as u32, radix).unwrap(); + if upper { c.make_ascii_uppercase(); } + result.push(c as u8); if x == 0 { break; } } result[begin..].reverse(); - String::from_utf8(result).expect("string was not valid utf8") + LString::from(result) } @@ -149,7 +154,7 @@ pub fn bin(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "bin expected integer argument, got {x:#}") }; - Ok(to_radix_inner(x, 2).into()) + Ok(to_radix_inner(x, 2, false).into()) } #[native_func(1)] @@ -158,7 +163,7 @@ pub fn sex(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "sex expected integer argument, got {x:#}") }; - Ok(to_radix_inner(x, 6).into()) + Ok(to_radix_inner(x, 6, false).into()) } #[native_func(1)] @@ -167,7 +172,7 @@ pub fn oct(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "oct expected integer argument, got {x:#}") }; - Ok(to_radix_inner(x, 8).into()) + Ok(to_radix_inner(x, 8, false).into()) } #[native_func(1)] @@ -176,7 +181,7 @@ pub fn hex(_: &mut Vm, args: Vec) -> Result { let Value::Int(x) = x else { throw!(*SYM_TYPE_ERROR, "hex expected integer argument, got {x:#}") }; - Ok(to_radix_inner(x, 16).into()) + Ok(to_radix_inner(x, 16, false).into()) } #[native_func(2)] @@ -188,7 +193,19 @@ pub fn to_radix(_: &mut Vm, args: Vec) -> Result { if *radix < 2 || *radix > 36 { throw!(*SYM_TYPE_ERROR, "to_radix expected radix in range 0..=36") } - Ok(to_radix_inner(*x, *radix as u32).into()) + Ok(to_radix_inner(*x, *radix as u32, false).into()) +} + +#[native_func(2)] +pub fn to_radix_upper(_: &mut Vm, args: Vec) -> Result { + let [_, x, radix] = unpack_args!(args); + let (Value::Int(x), Value::Int(radix)) = (&x, &radix) else { + throw!(*SYM_TYPE_ERROR, "to_radix_upper expected integer arguments, got {x:#} and {radix:#}") + }; + if *radix < 2 || *radix > 36 { + throw!(*SYM_TYPE_ERROR, "to_radix_upper expected radix in range 0..=36") + } + Ok(to_radix_inner(*x, *radix as u32, true).into()) } #[native_func(2)] @@ -200,7 +217,7 @@ pub fn from_radix(_: &mut Vm, args: Vec) -> Result { if *radix < 2 || *radix > 36 { throw!(*SYM_TYPE_ERROR, "from_radix expected radix in range 0..=36") } - match parse_int(s, *radix as u32) { + match parse_int(s.as_ref(), *radix as u32) { Ok(v) => Ok(v.into()), Err(_) => throw!(*SYM_TYPE_ERROR, "string was not a valid integer in given radix"), } @@ -539,6 +556,25 @@ pub fn isinfinite(_: &mut Vm, args: Vec) -> Result { } } +#[native_func(1)] +pub fn float_to_bits(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + let Value::Float(f) = val else { + throw!(*SYM_TYPE_ERROR, "float_to_bits expected float argument, got {val:#}") + }; + Ok(Value::Int(f.to_bits() as i64)) +} + +#[native_func(1)] +pub fn float_of_bits(_: &mut Vm, args: Vec) -> Result { + let [_, val] = unpack_args!(args); + let Value::Int(i) = val else { + throw!(*SYM_TYPE_ERROR, "float_of_bits expected integer argument, got {val:#}") + }; + Ok(Value::Float(f64::from_bits(i as u64))) +} + + // // rational operations diff --git a/talc-std/src/regex.rs b/talc-std/src/regex.rs new file mode 100644 index 0000000..58af8a6 --- /dev/null +++ b/talc-std/src/regex.rs @@ -0,0 +1,235 @@ +use std::borrow::Cow; + +use talc_lang::{exception::{exception, Result}, lstring::LString, symbol::{Symbol, SYM_TYPE_ERROR}, throw, value::{NativeValue, Value}, Vm}; +use talc_macros::native_func; +use regex::{Captures, Match, Regex}; +use lazy_static::lazy_static; + +use crate::unpack_args; + +lazy_static! { + static ref SYM_STD_REGEX: Symbol = Symbol::get("std.regex"); + static ref SYM_START: Symbol = Symbol::get("start"); + static ref SYM_END: Symbol = Symbol::get("end"); + static ref SYM_STR: Symbol = Symbol::get("str"); +} + +#[derive(Clone, Debug)] +pub struct ValueRegex(Regex); + +impl From for ValueRegex { + fn from(value: Regex) -> Self { Self(value) } +} + +impl From for Regex { + fn from(value: ValueRegex) -> Self { value.0 } +} + +impl NativeValue for ValueRegex { + fn get_type(&self) -> Symbol { *SYM_STD_REGEX } + fn as_any(&self) -> &dyn std::any::Any { self } + fn to_lstring(&self, w: &mut LString, repr: bool) -> std::io::Result<()> { + use std::io::Write; + if repr { + write!(w, "/{}/", self.0) + } else { + write!(w, "{}", self.0) + } + } + fn copy_value(&self) -> Result> { + Ok(Some(self.clone().into())) + } +} + +pub fn load(vm: &mut Vm) { + vm.set_global_name("regex", _regex().into()); + vm.set_global_name("matches", matches().into()); + vm.set_global_name("match", _match().into()); + vm.set_global_name("match_once", match_once().into()); + vm.set_global_name("captures", captures().into()); + vm.set_global_name("captures_once", captures_once().into()); + vm.set_global_name("replace", replace().into()); + vm.set_global_name("replace_once", replace_once().into()); + vm.set_global_name("split", split().into()); + vm.set_global_name("split_once", split_once().into()); +} + +fn match_to_value(m: Match) -> Value { + Value::new_table(|t| { + t.insert((*SYM_START).into(), (m.start() as i64).into()); + t.insert((*SYM_END).into(), (m.end() as i64).into()); + t.insert((*SYM_STR).into(), LString::from(m.as_str().to_string()).into()); + }) +} + +fn captures_to_value(cs: Captures) -> Value { + cs.iter() + .map(|c| c.map_or(Value::Nil, match_to_value)) + .collect::>() + .into() +} + +fn regex_from<'a>(v: &'a Value, name: &str) -> Result> { + match v { + Value::String(s) => { + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "regex must be valid UTF-8") + }; + Regex::new(s) + .map(Cow::Owned) + .map_err(|e| exception!(*SYM_TYPE_ERROR, "invalid regex: {e}")) + }, + Value::Native(n) if n.get_type() == *SYM_STD_REGEX => { + n.as_any().downcast_ref::() + .map(|vr| Cow::Borrowed(&vr.0)) + .ok_or_else(|| exception!( + *SYM_TYPE_ERROR, "BEES {name} expected string or regex, got {v:#}")) + }, + _ => throw!(*SYM_TYPE_ERROR, "{name} expected string or regex, got {v:#}") + } +} + +#[native_func(1)] +pub fn _regex(_: &mut Vm, args: Vec) -> Result { + let [_, re] = unpack_args!(args); + regex_from(&re, "regex") + .map(|re| ValueRegex(re.into_owned()).into()) +} + +#[native_func(2)] +pub fn matches(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "matches expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let re = regex_from(&re, "matches")?; + Ok(re.is_match(s).into()) +} + +#[native_func(2)] +pub fn match_once(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "match_once expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let re = regex_from(&re, "match_once")?; + Ok(re.find(s).map_or(Value::Nil, match_to_value)) +} + +#[native_func(2)] +pub fn _match(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "match expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let re = regex_from(&re, "match")?; + Ok(re.find_iter(s).map(match_to_value).collect::>().into()) +} + +#[native_func(2)] +pub fn captures_once(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "captures_once expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let re = regex_from(&re, "captures_once")?; + Ok(re.captures(s).map_or(Value::Nil, captures_to_value)) +} + +#[native_func(2)] +pub fn captures(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "captures expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let re = regex_from(&re, "captures")?; + Ok(re.captures_iter(s).map(captures_to_value).collect::>().into()) +} + +#[native_func(3)] +pub fn replace_once(_: &mut Vm, args: Vec) -> Result { + let [_, re, rep, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "replace_once expected string, got {s:#}") + }; + let Value::String(rep) = rep else { + throw!(*SYM_TYPE_ERROR, "replace_once expected string or function, got {rep:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let Ok(rep) = rep.to_str() else { + throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8") + }; + let re = regex_from(&re, "replace_once")?; + Ok(LString::from(re.replace(s, rep)).into()) +} + +#[native_func(3)] +pub fn replace(_: &mut Vm, args: Vec) -> Result { + let [_, re, rep, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "replace expected string, got {s:#}") + }; + let Value::String(rep) = rep else { + throw!(*SYM_TYPE_ERROR, "replace expected string or function, got {rep:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "search string must be valid UTF-8") + }; + let Ok(rep) = rep.to_str() else { + throw!(*SYM_TYPE_ERROR, "replacement string must be valid UTF-8") + }; + let re = regex_from(&re, "replace")?; + Ok(LString::from(re.replace_all(s, rep)).into()) +} + +#[native_func(2)] +pub fn split_once(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "split_once expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8") + }; + let re = regex_from(&re, "split_once")?; + let mut parts = re.splitn(s, 2); + let (part1, part2) = ( + LString::from(parts.next().unwrap_or_default()).into(), + LString::from(parts.next().unwrap_or_default()).into() + ); + Ok(vec![part1, part2].into()) +} + +#[native_func(2)] +pub fn split(_: &mut Vm, args: Vec) -> Result { + let [_, re, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "split expected string, got {s:#}") + }; + let Ok(s) = s.to_str() else { + throw!(*SYM_TYPE_ERROR, "string to split must be valid UTF-8") + }; + let re = regex_from(&re, "split")?; + let parts: Vec = re.split(s) + .map(|s| LString::from(s).into()) + .collect(); + Ok(parts.into()) +} + diff --git a/talc-std/src/string.rs b/talc-std/src/string.rs new file mode 100644 index 0000000..5e056c8 --- /dev/null +++ b/talc-std/src/string.rs @@ -0,0 +1,205 @@ +use talc_lang::{exception::Result, lformat, lstring::LString, symbol::SYM_TYPE_ERROR, throw, value::Value, Vm}; +use talc_macros::native_func; + +use crate::unpack_args; + +pub fn load(vm: &mut Vm) { + vm.set_global_name("ord", ord().into()); + vm.set_global_name("chr", chr().into()); + vm.set_global_name("len_bytes", len_bytes().into()); + vm.set_global_name("trim", trim().into()); + vm.set_global_name("upper", upper().into()); + vm.set_global_name("lower", lower().into()); + vm.set_global_name("starts_with", starts_with().into()); + vm.set_global_name("ends_with", ends_with().into()); + vm.set_global_name("is_utf8", is_utf8().into()); + vm.set_global_name("to_utf8", to_utf8().into()); + vm.set_global_name("to_utf8_lossy", to_utf8_lossy().into()); + vm.set_global_name("str_to_bytes", str_to_bytes().into()); + vm.set_global_name("str_of_bytes", str_of_bytes().into()); + vm.set_global_name("format", _format().into()); +} + +#[native_func(1)] +pub fn ord(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "ord expected string argument, got {s:#}") + }; + let mut chars = s.chars(); + let Some(c) = chars.next() else { + throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1") + }; + if chars.next().is_some() { + throw!(*SYM_TYPE_ERROR, "argument to ord must have length 1") + }; + Ok(Value::Int(c as u32 as i64)) +} + +#[native_func(1)] +pub fn chr(_: &mut Vm, args: Vec) -> Result { + let [_, i] = unpack_args!(args); + let Value::Int(i) = i else { + throw!(*SYM_TYPE_ERROR, "chr expected integer argument, got {i:#}") + }; + let Ok(i) = u32::try_from(i) else { + throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint") + }; + let Some(c) = char::from_u32(i) else { + throw!(*SYM_TYPE_ERROR, "argument to chr is not a valid codepoint") + }; + Ok(Value::String(LString::from(c).into())) +} + +#[native_func(1)] +pub fn len_bytes(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "len_bytes expected string argument, got {s:#}") + }; + Ok(Value::Int(s.len() as i64)) +} + +#[native_func(1)] +pub fn trim(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "trim expected string argument, got {s:#}") + }; + Ok(s.trim().into()) +} + +#[native_func(1)] +pub fn upper(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "upper expected string argument, got {s:#}") + }; + Ok(s.to_uppercase().into()) +} + +#[native_func(1)] +pub fn lower(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "lower expected string argument, got {s:#}") + }; + Ok(s.to_lowercase().into()) +} + +#[native_func(2)] +pub fn starts_with(_: &mut Vm, args: Vec) -> Result { + let [_, d, pre] = unpack_args!(args); + let res = match (d, pre) { + (Value::List(d), Value::List(pre)) => d.borrow().starts_with(&pre.borrow()), + (Value::String(d), Value::String(pre)) => d.starts_with(&pre), + (d, pre) => throw!(*SYM_TYPE_ERROR, + "starts_with expected two lists or strings, got {d:#} and {pre:#}") + }; + Ok(res.into()) +} + +#[native_func(2)] +pub fn ends_with(_: &mut Vm, args: Vec) -> Result { + let [_, d, suf] = unpack_args!(args); + let res = match (d, suf) { + (Value::List(d), Value::List(suf)) => d.borrow().ends_with(&suf.borrow()), + (Value::String(d), Value::String(suf)) => d.ends_with(&suf), + (d, suf) => throw!(*SYM_TYPE_ERROR, + "ends_with expected two lists or strings, got {d:#} and {suf:#}") + }; + Ok(res.into()) +} + +#[native_func(1)] +pub fn is_utf8(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}") + }; + Ok(s.is_utf8().into()) +} + +#[native_func(1)] +pub fn to_utf8(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}") + }; + if s.is_utf8() { + Ok(s.into()) + } else { + Ok(Value::Nil) + } +} + +#[native_func(1)] +pub fn to_utf8_lossy(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "is_utf8 expected string argument, got {s:#}") + }; + Ok(s.to_utf8_lossy().into()) +} + +#[native_func(1)] +pub fn str_to_bytes(_: &mut Vm, args: Vec) -> Result { + let [_, s] = unpack_args!(args); + let Value::String(s) = s else { + throw!(*SYM_TYPE_ERROR, "str_to_bytes expected string argument, got {s:#}") + }; + Ok(s.as_bytes() + .iter() + .map(|v| (*v as i64).into()) + .collect::>() + .into()) +} + +#[native_func(1)] +pub fn str_of_bytes(_: &mut Vm, args: Vec) -> Result { + let [_, b] = unpack_args!(args); + let Value::List(b) = b else { + throw!(*SYM_TYPE_ERROR, "str_of_bytes expected list argument, got {b:#}") + }; + let bytes: Vec = b.borrow().iter() + .map(|v| match v { + Value::Int(i) if (0..=255).contains(i) => Ok(*i as u8), + _ => throw!(*SYM_TYPE_ERROR, "str_of_bytes expected list of integers in 0..=255"), + }).collect::>>()?; + Ok(LString::from(bytes).into()) +} + +#[native_func(2)] +pub fn _format(_: &mut Vm, args: Vec) -> Result { + let [_, fstr, fargs] = unpack_args!(args); + let (Value::String(fstr), Value::List(fargs)) = (&fstr, &fargs) else { + throw!(*SYM_TYPE_ERROR, "format expected string and list, got {fstr:#} and {fargs:#}") + }; + let mut res = LString::new(); + let mut bytes = fstr.bytes(); + let mut faidx = 0; + while let Some(b) = bytes.next() { + if b == b'%' { + match bytes.next() { + Some(b'%') => res.push_byte(b'%'), + Some(b @ (b'#' | b'?')) => { + let fargs = fargs.borrow(); + let Some(a) = fargs.get(faidx) else { + throw!(*SYM_TYPE_ERROR, "not enough args for format string") + }; + faidx += 1; + if b == b'?' { + res += &lformat!("{a:#}"); + } else { + res += &lformat!("{a}"); + } + + }, + _ => throw!(*SYM_TYPE_ERROR, "invalid format code") + } + } else { + res.push_byte(b); + } + } + Ok(res.into()) +} diff --git a/talc-std/src/value.rs b/talc-std/src/value.rs index 83b1636..b96e424 100644 --- a/talc-std/src/value.rs +++ b/talc-std/src/value.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, collections::HashMap, rc::Rc}; -use talc_lang::{exception::Result, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, exception, value::{ops::RatioExt, HashValue, Rational64, Value}, Vm}; +use talc_lang::{exception::{exception, Result}, lformat, parse_float, parse_int, symbol::SYM_TYPE_ERROR, throw, value::{ops::RatioExt, HashValue, Rational64, Value}, Vm}; use talc_macros::native_func; use crate::unpack_args; @@ -49,39 +49,39 @@ pub fn as_(_: &mut Vm, args: Vec) -> Result { if val.get_type() == ty { return Ok(val) } - match (val, ty.name()) { - (_, "nil") => Ok(Value::Nil), - (v, "string") => Ok(Value::String(v.to_string().into())), - (v, "bool") => Ok(Value::Bool(v.truthy())), + match (val, ty.name().as_bytes()) { + (_, b"nil") => Ok(Value::Nil), + (v, b"string") => Ok(Value::String(lformat!("{v}").into())), + (v, b"bool") => Ok(Value::Bool(v.truthy())), - (Value::Symbol(s), "int") => Ok(Value::Int(s.id() as i64)), + (Value::Symbol(s), b"int") => Ok(Value::Int(s.id() as i64)), - (Value::Int(x), "ratio") => Ok(Value::Ratio(x.into())), - (Value::Int(x), "float") => Ok(Value::Float(x as f64)), - (Value::Int(x), "complex") => Ok(Value::Complex((x as f64).into())), - (Value::Ratio(x), "int") => Ok(Value::Int(x.to_integer())), - (Value::Ratio(x), "float") => Ok(Value::Float(x.to_f64())), - (Value::Ratio(x), "complex") => Ok(Value::Complex(x.to_f64().into())), - (Value::Float(x), "int") => Ok(Value::Int(x as i64)), - (Value::Float(x), "ratio") => { + (Value::Int(x), b"ratio") => Ok(Value::Ratio(x.into())), + (Value::Int(x), b"float") => Ok(Value::Float(x as f64)), + (Value::Int(x), b"complex") => Ok(Value::Complex((x as f64).into())), + (Value::Ratio(x), b"int") => Ok(Value::Int(x.to_integer())), + (Value::Ratio(x), b"float") => Ok(Value::Float(x.to_f64())), + (Value::Ratio(x), b"complex") => Ok(Value::Complex(x.to_f64().into())), + (Value::Float(x), b"int") => Ok(Value::Int(x as i64)), + (Value::Float(x), b"ratio") => { let r = Rational64::approximate_float(x) .ok_or_else(|| exception!(*SYM_TYPE_ERROR, "float {x:?} could not be converted to ratio"))?; Ok(Value::Ratio(r)) } - (Value::Float(x), "complex") => Ok(Value::Complex(x.into())), + (Value::Float(x), b"complex") => Ok(Value::Complex(x.into())), - (Value::String(s), "int") - => parse_int(&s, 10) - .map(|v| v.into()) + (Value::String(s), b"int") + => parse_int(s.as_ref(), 10) + .map(i64::into) .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as integer")), - (Value::String(s), "float") - => parse_float(&s) - .map(|v| v.into()) + (Value::String(s), b"float") + => parse_float(s.as_ref()) + .map(f64::into) .map_err(|_| exception!(*SYM_TYPE_ERROR, "could not parse {s:#} as float")), - (v, t) => throw!(*SYM_TYPE_ERROR, - "cannot convert value of type {} to type {t}", v.get_type().name()) + (v, _) => throw!(*SYM_TYPE_ERROR, + "cannot convert value of type {} to type {}", v.get_type().name(), ty.name()) } } @@ -110,6 +110,11 @@ pub fn copy_inner(value: Value) -> Result { .collect(); Ok(v?.into()) }, + Value::Native(ref n) => match n.copy_value()? { + Some(x) => Ok(x), + None => throw!(*SYM_TYPE_ERROR, + "cannot copy value of type {}", value.get_type().name()) + } _ => throw!(*SYM_TYPE_ERROR, "cannot copy value of type {}", value.get_type().name()) } @@ -129,13 +134,13 @@ pub fn copy(_: &mut Vm, args: Vec) -> Result { #[native_func(1)] pub fn str_(_: &mut Vm, args: Vec) -> Result { let [_, val] = unpack_args!(args); - Ok(val.to_string().into()) + Ok(lformat!("{val}").into()) } #[native_func(1)] pub fn repr(_: &mut Vm, args: Vec) -> Result { let [_, val] = unpack_args!(args); - Ok(format!("{val:#}").into()) + Ok(lformat!("{val:#}").into()) } //