add features

This commit is contained in:
trimill 2024-03-30 12:21:09 -04:00
parent 296ff666cb
commit 4e61ca90f0
Signed by: trimill
GPG key ID: 4F77A16E17E10BCB
32 changed files with 2861 additions and 355 deletions

278
Cargo.lock generated
View file

@ -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"

165
LICENSE Normal file
View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
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.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# talc
## installation
```sh
cargo install --profile release-lto --path talc-bin
```

View file

@ -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"

View file

@ -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::<Vec<String>>();
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);
},

View file

@ -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

View file

@ -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"]}

View file

@ -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<Expr<'s>>),
BinaryOp(BinaryOp, Box<Expr<'s>>, Box<Expr<'s>>),
Assign(Option<BinaryOp>, Box<LValue<'s>>, Box<Expr<'s>>),
AssignVar(&'s str, Box<Expr<'s>>),
AssignGlobal(&'s str, Box<Expr<'s>>),
AssignVar(&'s LStr, Box<Expr<'s>>),
AssignGlobal(&'s LStr, Box<Expr<'s>>),
Index(Box<Expr<'s>>, Box<Expr<'s>>),
FnCall(Box<Expr<'s>>, Vec<Expr<'s>>),
AssocFnCall(Box<Expr<'s>>, Symbol, Vec<Expr<'s>>),
Pipe(Box<Expr<'s>>, Box<Expr<'s>>),
Block(Vec<Expr<'s>>),
@ -38,20 +40,20 @@ pub enum Expr<'s> {
Or(Box<Expr<'s>>, Box<Expr<'s>>),
If(Box<Expr<'s>>, Box<Expr<'s>>, Option<Box<Expr<'s>>>),
While(Box<Expr<'s>>, Box<Expr<'s>>),
For(&'s str, Box<Expr<'s>>, Box<Expr<'s>>),
Lambda(Vec<&'s str>, Box<Expr<'s>>),
For(&'s LStr, Box<Expr<'s>>, Box<Expr<'s>>),
Lambda(Vec<&'s LStr>, Box<Expr<'s>>),
Try(Box<Expr<'s>>, Vec<CatchBlock<'s>>),
}
#[derive(Debug)]
pub struct CatchBlock<'s> {
pub name: Option<&'s str>,
pub name: Option<&'s LStr>,
pub types: Option<Vec<Symbol>>,
pub body: Expr<'s>,
}
#[derive(Debug)]
pub enum LValue<'s> {
Ident(&'s str),
Ident(&'s LStr),
Index(Box<Expr<'s>>, Box<Expr<'s>>),
}

View file

@ -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<str>,
name: Rc<LStr>,
scope: usize,
}
@ -42,7 +44,7 @@ pub fn compile_repl(expr: &Expr, globals: &[Local]) -> (Function, Vec<Local>) {
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<usize> {
fn resolve_local(&mut self, name: &LStr) -> Option<usize> {
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<usize> {
fn resolve_global(&mut self, name: &LStr) -> Option<usize> {
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);

View file

@ -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<T> = std::result::Result<T, Exception>;
#[derive(Clone, Debug)]
pub struct Exception {
pub ty: Symbol,
pub msg: Option<Rc<str>>,
pub msg: Option<Rc<LStr>>,
pub data: Option<Value>,
}
@ -15,7 +15,7 @@ impl Exception {
Self { ty, msg: None, data: None }
}
pub fn new_with_msg(ty: Symbol, msg: Rc<str>) -> Self {
pub fn new_with_msg(ty: Symbol, msg: Rc<LStr>) -> 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<str>, data: Value) -> Self {
pub fn new_with_msg_data(ty: Symbol, msg: Rc<LStr>, 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) => {

View file

@ -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;

824
talc-lang/src/lstring.rs Normal file
View file

@ -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<char> {
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<char, u8>)> {
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<char, u8>)> {
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<u8>,
}
#[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<LStr> for LString {
#[inline]
fn as_ref(&self) -> &LStr {
<&LStr as From<&[u8]>>::from(self.inner.as_ref())
}
}
impl AsMut<LStr> for LString {
#[inline]
fn as_mut(&mut self) -> &mut LStr {
<&mut LStr as From<&mut [u8]>>::from(self.inner.as_mut())
}
}
impl Borrow<LStr> for LString {
#[inline]
fn borrow(&self) -> &LStr {
<&LStr as From<&[u8]>>::from(self.inner.as_ref())
}
}
impl BorrowMut<LStr> for LString {
#[inline]
fn borrow_mut(&mut self) -> &mut LStr {
<&mut LStr as From<&mut [u8]>>::from(self.inner.as_mut())
}
}
//
// conversions
//
impl From<LString> for Vec<u8> {
#[inline]
fn from(value: LString) -> Self { value.inner }
}
impl From<Vec<u8>> for LString {
#[inline]
fn from(value: Vec<u8>) -> Self { Self { inner: value } }
}
impl From<String> 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<Cow<'_, LStr>> for LString {
#[inline]
fn from(value: Cow<'_, LStr>) -> Self {
match value {
Cow::Borrowed(b) => b.to_owned(),
Cow::Owned(o) => o
}
}
}
impl From<Cow<'_, str>> for LString {
#[inline]
fn from(value: Cow<'_, str>) -> Self {
value.into_owned().into()
}
}
impl From<Cow<'_, [u8]>> 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<LStr> {
#[inline]
fn from(v: &LStr) -> Rc<LStr> {
let arc = Rc::<[u8]>::from(v.as_bytes());
unsafe { Rc::from_raw(Rc::into_raw(arc) as *const LStr) }
}
}
impl From<LString> for Rc<LStr> {
#[inline]
fn from(v: LString) -> Rc<LStr> {
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<LString> for String {
type Error = FromUtf8Error;
#[inline]
fn try_from(value: LString) -> Result<Self, Self::Error> {
String::from_utf8(value.into())
}
}
impl<'a> TryFrom<&'a LStr> for &'a str {
type Error = Utf8Error;
#[inline]
fn try_from(value: &'a LStr) -> Result<Self, Self::Error> {
std::str::from_utf8(&value.inner)
}
}
impl From<char> for LString {
#[inline]
fn from(value: char) -> Self {
value.to_string().into()
}
}
impl From<u8> for LString {
#[inline]
fn from(value: u8) -> Self {
vec![value].into()
}
}
impl FromIterator<char> for LString {
#[inline]
fn from_iter<I: IntoIterator<Item=char>>(iter: I) -> Self {
String::from_iter(iter).into()
}
}
impl FromIterator<u8> for LString {
#[inline]
fn from_iter<T: IntoIterator<Item=u8>>(iter: T) -> Self {
Vec::from_iter(iter).into()
}
}
impl Extend<u8> for LString {
#[inline]
fn extend<I: IntoIterator<Item=u8>>(&mut self, iter: I) {
self.inner.extend(iter);
}
}
impl<'a> Extend<&'a u8> for LString {
#[inline]
fn extend<I: IntoIterator<Item=&'a u8>>(&mut self, iter: I) {
self.inner.extend(iter);
}
}
impl Extend<char> for LString {
#[inline]
fn extend<I: IntoIterator<Item=char>>(&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<I: IntoIterator<Item=&'a char>>(&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<usize> {
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, FromUtf8Error> {
String::from_utf8(self.inner)
}
}
#[derive(Clone)]
pub struct Bytes<'a>(Copied<slice::Iter<'a, u8>>);
impl<'a> Iterator for Bytes<'a> {
type Item = u8;
#[inline]
fn next(&mut self) -> Option<Self::Item> { self.0.next() }
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) { self.0.size_hint() }
#[inline]
fn count(self) -> usize { self.0.count() }
#[inline]
fn last(self) -> Option<Self::Item> { self.0.last() }
#[inline]
fn nth(&mut self, n: usize) -> Option<Self::Item> { self.0.nth(n) }
#[inline]
fn all<F: FnMut(Self::Item) -> bool>(&mut self, f: F) -> bool {
self.0.all(f)
}
#[inline]
fn any<F: FnMut(Self::Item) -> bool>(&mut self, f: F) -> bool {
self.0.any(f)
}
#[inline]
fn find<P: FnMut(&Self::Item) -> bool>(&mut self, predicate: P) -> Option<Self::Item> {
self.0.find(predicate)
}
#[inline]
fn position<P: FnMut(Self::Item) -> bool>(&mut self, predicate: P) -> Option<usize> {
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<char, u8>;
#[inline]
fn next(&mut self) -> Option<Self::Item> {
let (new_bytes, res) = next_codepoint(self.0)?;
self.0 = new_bytes;
Some(res)
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.0.len();
((len + 3)/4, Some(len))
}
}
impl<'a> DoubleEndedIterator for LosslessChars<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
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<Self::Item> {
loop {
if let Ok(c) = self.0.next()? {
return Some(c)
}
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
}
impl<'a> DoubleEndedIterator for Chars<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
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<OsStr> {
#[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<usize>);
impl_index!(std::ops::RangeFrom<usize>);
impl_index!(std::ops::RangeFull);
impl_index!(std::ops::RangeInclusive<usize>);
impl_index!(std::ops::RangeTo<usize>);
impl_index!(std::ops::RangeToInclusive<usize>);
impl_index!((std::ops::Bound<usize>, std::ops::Bound<usize>));

View file

@ -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<Expr<'input>> = {
LValue: Box<LValue<'input>> = {
<Identifier> => Box::new(LValue::Ident(<>)),
<l:BinaryIndex> "!" <r:FunctionCall> => Box::new(LValue::Index(l, r)),
<l:FieldAccess> "." <r:Identifier> => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
<l:BinaryIndex> "!" <r:CallOrAccess> => Box::new(LValue::Index(l, r)),
<l:CallOrAccess> "." <r:Identifier> => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
}
AssignOp: Option<BinaryOp> = {
@ -153,8 +155,8 @@ Pipe: Box<Expr<'input>> = {
Lambda: Box<Expr<'input>> = {
"\\" <xs:IdentList> "->" <e:BinaryCompare> => Box::new(Expr::Lambda(xs, e)),
":" <e:BinaryCompare> => Box::new(Expr::Lambda(vec!["$"], e)),
"::" <e:BinaryCompare> => Box::new(Expr::Lambda(vec!["$", "$$"], e)),
":" <e:BinaryCompare> => Box::new(Expr::Lambda(vec![lstr!("$")], e)),
"::" <e:BinaryCompare> => Box::new(Expr::Lambda(vec![lstr!("$"), lstr!("$$")], e)),
BinaryCompare,
}
@ -194,16 +196,43 @@ BinaryAppend: Box<Expr<'input>> = {
// .. ..= ..*
BinaryRange: Box<Expr<'input>> = {
<l:BinaryAdd> <o:RangeOp> <r:BinaryAdd> => Box::new(Expr::BinaryOp(o, l, r)),
<l:BinaryAdd> "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)),
BinaryAdd,
<l:BinaryBitOr> <o:RangeOp> <r:BinaryBitOr> => Box::new(Expr::BinaryOp(o, l, r)),
<l:BinaryBitOr> "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)),
BinaryBitOr,
}
RangeOp: BinaryOp = {
".." => BinaryOp::Range,
"..=" => BinaryOp::RangeIncl,
}
// #|
BinaryBitOr: Box<Expr<'input>> = {
<l:BinaryBitOr> "#|" <r:BinaryBitXor> => Box::new(Expr::BinaryOp(BinaryOp::BitOr, l, r)),
BinaryBitXor,
}
// #^
BinaryBitXor: Box<Expr<'input>> = {
<l:BinaryBitXor> "#^" <r:BinaryBitAnd> => Box::new(Expr::BinaryOp(BinaryOp::BitXor, l, r)),
BinaryBitAnd,
}
// #&
BinaryBitAnd: Box<Expr<'input>> = {
<l:BinaryBitAnd> "#&" <r:BinaryShift> => Box::new(Expr::BinaryOp(BinaryOp::BitAnd, l, r)),
BinaryShift,
}
// >> <<
BinaryShift: Box<Expr<'input>> = {
<l:BinaryShift> <o:ShiftOp> <r:BinaryAdd> => Box::new(Expr::BinaryOp(o, l, r)),
BinaryAdd,
}
ShiftOp: BinaryOp = {
">>" => BinaryOp::Shr,
"<<" => BinaryOp::Shl,
}
// + -
BinaryAdd: Box<Expr<'input>> = {
@ -248,13 +277,13 @@ BinaryPow: Box<Expr<'input>> = {
// index ( ! )
BinaryIndex: Box<Expr<'input>> = {
<l:BinaryIndex> "!" <r:UnaryMinus2> => Box::new(Expr::Index(l, r)),
FunctionCall,
CallOrAccess,
}
// unary-
UnaryMinus2: Box<Expr<'input>> = {
"-" <r:UnaryMinus2> => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)),
FunctionCall,
CallOrAccess,
}
@ -262,21 +291,14 @@ UnaryMinus2: Box<Expr<'input>> = {
// things
//
// function call
FunctionCall: Box<Expr<'input>> = {
<l:FunctionCall> "(" <r:ExprList> ")" => Box::new(Expr::FnCall(l, r)),
FieldAccess,
}
// field access
FieldAccess: Box<Expr<'input>> = {
<l:FieldAccess> "." <r:Identifier> => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
CallOrAccess: Box<Expr<'input>> = {
<l:CallOrAccess> "(" <args:ExprList> ")" => Box::new(Expr::FnCall(l, args)),
<l:CallOrAccess> "->" <r:Identifier> "(" <args:ExprList> ")" => Box::new(Expr::AssocFnCall(l, Symbol::get(r), args)),
<l:CallOrAccess> "." <r:Identifier> => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))),
Term,
}
//
// base
//
@ -291,8 +313,8 @@ TermNotIdent: Box<Expr<'input>> = {
"(" <Expr> ")" => <>,
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
"$" => Box::new(Expr::Ident("$")),
"$$" => Box::new(Expr::Ident("$$")),
"$" => Box::new(Expr::Ident(lstr!("$"))),
"$$" => Box::new(Expr::Ident(lstr!("$$"))),
"do" <Block> "end" => <>,
"if" <IfStmtChain> => <>,
@ -358,12 +380,12 @@ TableKey: Expr<'input> = {
TermNotIdent => *<>,
}
IdentList: Vec<&'input str> = {
IdentList: Vec<&'input LStr> = {
<mut xs:(<Identifier> ",")*> <x:Identifier?>
=> { 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<str> = {
TokStringSingle => <>[1..<>.len()-1].into(),
StringLiteral: Rc<LStr> = {
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()),

View file

@ -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<String, StrEscapeError> {
let mut s = String::with_capacity(src.len());
pub fn parse_str_escapes(src: &str) -> Result<LString, StrEscapeError> {
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<String, StrEscapeError> {
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<String, StrEscapeError> {
Ok(s)
}
pub fn parse_float(f: &str) -> Result<f64, ParseFloatError> {
pub fn parse_float<'a, S: Into<&'a LStr>>(f: S) -> Result<f64, ParseFloatError> {
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<f64, ParseFloatError> {
s.parse()
}
pub fn parse_int(f: &str, radix: u32) -> Result<i64, ParseIntError> {
pub fn parse_int<'a, S: Into<&'a LStr>>(f: S, radix: u32) -> Result<i64, ParseIntError> {
let mut s = String::new();
for c in f.chars() {
for c in f.into().chars() {
if c != '_' {
s.push(c);
}

View file

@ -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<SymbolTable> {
impl Symbol {
/// # Panics
/// If the mutex storing the symbol table is poisoned
pub fn try_get(name: &str) -> Option<Self> {
pub fn try_get<'a, S: Into<&'a LStr>>(name: S) -> Option<Self> {
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};

View file

@ -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 {

View file

@ -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, _| {

View file

@ -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<RefCell<Value>>),
String(Rc<str>),
String(Rc<LStr>),
List(RcList),
Table(RcTable),
Function(Rc<Function>),
NativeFunc(Rc<NativeFunc>),
Native(Rc<dyn NativeValue>),
}
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"<native value>");
Ok(())
}
fn partial_eq(&self, _other: &Value) -> bool {
false
}
fn copy_value(&self) -> Result<Option<Value>, 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, "<function {:?}>", Rc::as_ptr(g)),
Self::NativeFunc(g)
=> write!(f, "<native function {:?}>", 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<Value>)) -> Self {
let mut list = Vec::new();
f(&mut list);
list.into()
}
pub fn new_table(f: impl FnOnce(&mut HashMap<HashValue, Value>)) -> 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, "<function {:?}>", Rc::as_ptr(g)),
Self::NativeFunc(g)
=> write!(w, "<native function {:?}>", 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<T: NativeValue>(&self) -> Option<&T> {
match self {
Value::Native(n) => n.as_any().downcast_ref(),
_ => None
}
}
}
#[derive(Clone, Debug, PartialEq)]
@ -134,6 +243,7 @@ impl TryFrom<Value> 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<HashValue, Value>, Table, rcref);
impl_from!(Vec<Value>, List, rcref);
impl_from!(Function, Function, rc);
impl_from!(NativeFunc, NativeFunc, rc);
impl_from!(Rc<str>, String);
impl_from!(String, String, into);
impl_from!(&str, String, into);
impl_from!(Box<str>, String, into);
impl_from!(Rc<LStr>, String);
impl_from!(LString, String, into);
impl_from!(&LStr, String, into);
impl_from!(Box<LStr>, String, into);
impl_from!(Cow<'_, LStr>, String, into);
impl_from!(RefCell<Value>, Cell, rc);
impl From<Rc<dyn NativeValue>> for Value {
fn from(value: Rc<dyn NativeValue>) -> Self {
Self::Native(value)
}
}
impl<T: NativeValue + 'static> From<T> for Value {
fn from(value: T) -> Self {
Self::Native(Rc::new(value))
}
}

View file

@ -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<Value> for Value {
type Output = Result<Value>;
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<Value> for Value {
type Output = Result<Value>;
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<Value> for Value {
type Output = Result<Value>;
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<Value> for Value {
type Output = Result<Value>;
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<Value> for Value {
type Output = Result<Value>;
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))
}

View file

@ -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<Value> {
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> {

View file

@ -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"

View file

@ -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]

View file

@ -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;

View file

@ -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};

692
talc-std/src/file.rs Normal file
View file

@ -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<Symbol, OpenOptFn> = {
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<File>, w: BufWriter<File> },
Unbuffered { f: File },
}
impl BufFile {
fn new(file: File, buffered: bool) -> std::io::Result<Self> {
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> {
Self::new(unsafe { File::from_raw_fd(fd) }, buffered)
}
fn try_clone(&self) -> std::io::Result<BufFile> {
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<RawFd, IntoInnerError<BufWriter<File>>> {
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<usize> {
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<usize> {
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<BufFile>);
impl From<BufFile> 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("<file>");
Ok(())
}
}
#[derive(Debug)]
pub struct ValueProcess(RefCell<Child>);
impl From<Child> 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, "<process {id}>")
}
}
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<LString> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
Ok(F_STDIN.with(Clone::clone))
}
#[native_func(0)]
pub fn stdout(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(F_STDOUT.with(Clone::clone))
}
#[native_func(0)]
pub fn stderr(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
Ok(F_STDERR.with(Clone::clone))
}
//
// network
//
#[native_func(1)]
pub fn tcp_connect(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>| {
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<Value>) -> Result<Value> {
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<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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())
}

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
#[native_func(1)]
pub fn println(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
#[native_func(0)]
pub fn readln(_: &mut Vm, _: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
Ok(time.as_secs_f64().into())
}
#[native_func(1)]
pub fn env(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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());
}

View file

@ -360,7 +360,7 @@ pub fn zip(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
pub fn len(vm: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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)?;

View file

@ -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);
}

View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
}
}
#[native_func(1)]
pub fn float_to_bits(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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

235
talc-std/src/regex.rs Normal file
View file

@ -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<Regex> for ValueRegex {
fn from(value: Regex) -> Self { Self(value) }
}
impl From<ValueRegex> 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<Option<Value>> {
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::<Vec<Value>>()
.into()
}
fn regex_from<'a>(v: &'a Value, name: &str) -> Result<Cow<'a, Regex>> {
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::<ValueRegex>()
.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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Vec<Value>>().into())
}
#[native_func(2)]
pub fn captures_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Vec<Value>>().into())
}
#[native_func(3)]
pub fn replace_once(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value> = re.split(s)
.map(|s| LString::from(s).into())
.collect();
Ok(parts.into())
}

205
talc-std/src/string.rs Normal file
View file

@ -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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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<Value>) -> Result<Value> {
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::<Vec<Value>>()
.into())
}
#[native_func(1)]
pub fn str_of_bytes(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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<u8> = 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::<Result<Vec<u8>>>()?;
Ok(LString::from(bytes).into())
}
#[native_func(2)]
pub fn _format(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
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())
}

View file

@ -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<Value>) -> Result<Value> {
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<Value> {
.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<Value>) -> Result<Value> {
#[native_func(1)]
pub fn str_(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
Ok(val.to_string().into())
Ok(lformat!("{val}").into())
}
#[native_func(1)]
pub fn repr(_: &mut Vm, args: Vec<Value>) -> Result<Value> {
let [_, val] = unpack_args!(args);
Ok(format!("{val:#}").into())
Ok(lformat!("{val:#}").into())
}
//