add features
This commit is contained in:
parent
296ff666cb
commit
4e61ca90f0
32 changed files with 2861 additions and 355 deletions
278
Cargo.lock
generated
278
Cargo.lock
generated
|
@ -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
165
LICENSE
Normal 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
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# talc
|
||||
|
||||
## installation
|
||||
|
||||
```sh
|
||||
cargo install --profile release-lto --path talc-bin
|
||||
```
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"]}
|
||||
|
|
|
@ -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>>),
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
824
talc-lang/src/lstring.rs
Normal 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>));
|
||||
|
|
@ -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()),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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, _| {
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
692
talc-std/src/file.rs
Normal 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())
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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)?;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
235
talc-std/src/regex.rs
Normal 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
205
talc-std/src/string.rs
Normal 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())
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
|
||||
//
|
||||
|
|
Loading…
Reference in a new issue