initial commit
This commit is contained in:
commit
7aca8b423c
19 changed files with 2941 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
695
Cargo.lock
generated
Normal file
695
Cargo.lock
generated
Normal file
|
@ -0,0 +1,695 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.80"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ascii-canvas"
|
||||||
|
version = "3.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
|
||||||
|
dependencies = [
|
||||||
|
"term",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-set"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
|
||||||
|
dependencies = [
|
||||||
|
"bit-vec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bit-vec"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crunchy"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||||
|
|
||||||
|
[[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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"dirs-sys-next",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dirs-sys-next"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_users",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ena"
|
||||||
|
version = "0.14.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
|
||||||
|
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"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lalrpop"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da4081d44f4611b66c6dd725e6de3169f9f63905421e8626fcb86b6a898998b8"
|
||||||
|
dependencies = [
|
||||||
|
"ascii-canvas",
|
||||||
|
"bit-set",
|
||||||
|
"diff",
|
||||||
|
"ena",
|
||||||
|
"is-terminal",
|
||||||
|
"itertools",
|
||||||
|
"lalrpop-util",
|
||||||
|
"petgraph",
|
||||||
|
"regex",
|
||||||
|
"regex-syntax 0.7.5",
|
||||||
|
"string_cache",
|
||||||
|
"term",
|
||||||
|
"tiny-keccak",
|
||||||
|
"unicode-xid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lalrpop-util"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f35c735096c0293d313e8f2a641627472b83d01b937177fe76e5e2708d31e0d"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.153"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libredox"
|
||||||
|
version = "0.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.2",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_debug_unreachable"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-complex"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-rational"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"num-integer",
|
||||||
|
"num-traits",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "precomputed-hash"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.78"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.35"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_users"
|
||||||
|
version = "0.4.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"libredox",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax 0.8.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.7.5"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string_cache"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
|
||||||
|
dependencies = [
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"once_cell",
|
||||||
|
"parking_lot",
|
||||||
|
"phf_shared",
|
||||||
|
"precomputed-hash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "talc-bin"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"talc-lang",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "talc-lang"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"lalrpop",
|
||||||
|
"lalrpop-util",
|
||||||
|
"num-complex",
|
||||||
|
"num-rational",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "talc-std"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "term"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
|
||||||
|
dependencies = [
|
||||||
|
"dirs-next",
|
||||||
|
"rustversion",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.57"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tiny-keccak"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||||
|
dependencies = [
|
||||||
|
"crunchy",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-xid"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.48.5",
|
||||||
|
"windows_aarch64_msvc 0.48.5",
|
||||||
|
"windows_i686_gnu 0.48.5",
|
||||||
|
"windows_i686_msvc 0.48.5",
|
||||||
|
"windows_x86_64_gnu 0.48.5",
|
||||||
|
"windows_x86_64_gnullvm 0.48.5",
|
||||||
|
"windows_x86_64_msvc 0.48.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.52.0",
|
||||||
|
"windows_aarch64_msvc 0.52.0",
|
||||||
|
"windows_i686_gnu 0.52.0",
|
||||||
|
"windows_i686_msvc 0.52.0",
|
||||||
|
"windows_x86_64_gnu 0.52.0",
|
||||||
|
"windows_x86_64_gnullvm 0.52.0",
|
||||||
|
"windows_x86_64_msvc 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"talc-lang",
|
||||||
|
"talc-bin",
|
||||||
|
"talc-std",
|
||||||
|
]
|
||||||
|
resolver = "2"
|
8
talc-bin/Cargo.toml
Normal file
8
talc-bin/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "talc-bin"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
talc-lang = { path = "../talc-lang" }
|
||||||
|
anyhow = "1.0"
|
64
talc-bin/src/main.rs
Normal file
64
talc-bin/src/main.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
use talc_lang::{Parser, Vm, Symbol, value::{Value, NativeFunc}, compiler::repl};
|
||||||
|
use std::{io::Write, rc::Rc, cell::RefCell};
|
||||||
|
|
||||||
|
fn do_thing() -> impl Fn(Value, Vec<Value>) -> anyhow::Result<Value> {
|
||||||
|
let x = RefCell::new(0);
|
||||||
|
move |_, _| {
|
||||||
|
let v = *x.borrow();
|
||||||
|
if v > 5 {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
} else {
|
||||||
|
*x.borrow_mut() += 1;
|
||||||
|
Ok(Value::Int(v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let parser = Parser::new();
|
||||||
|
let mut lines = std::io::stdin().lines().map_while(Result::ok);
|
||||||
|
let mut vm = Vm::new(256);
|
||||||
|
let mut globals = Vec::new();
|
||||||
|
|
||||||
|
let prev1_sym = Symbol::get("_");
|
||||||
|
let prev2_sym = Symbol::get("__");
|
||||||
|
let prev3_sym = Symbol::get("___");
|
||||||
|
|
||||||
|
vm.set_global(prev1_sym, Value::Nil);
|
||||||
|
vm.set_global(prev2_sym, Value::Nil);
|
||||||
|
vm.set_global(prev3_sym, Value::Nil);
|
||||||
|
|
||||||
|
vm.set_global(Symbol::get("test"), Value::NativeFunc(NativeFunc {
|
||||||
|
arity: 0,
|
||||||
|
f: Box::new(do_thing()),
|
||||||
|
}.into()));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
print!(">> ");
|
||||||
|
std::io::stdout().flush().expect("could not flush stdout");
|
||||||
|
let line = lines.next().expect("could not get next line");
|
||||||
|
|
||||||
|
let ex = match parser.parse(&line) {
|
||||||
|
Ok(ex) => ex,
|
||||||
|
Err(e) => { println!("Error: {e}"); continue },
|
||||||
|
};
|
||||||
|
|
||||||
|
let func = match repl(&ex, &globals) {
|
||||||
|
Ok((f, g)) => { globals = g; f },
|
||||||
|
Err(e) => { println!("Error: {e}"); continue },
|
||||||
|
};
|
||||||
|
|
||||||
|
match vm.run(Rc::new(func)) {
|
||||||
|
Ok(v) => {
|
||||||
|
vm.set_global(prev3_sym, vm.get_global(prev2_sym).unwrap().clone());
|
||||||
|
vm.set_global(prev2_sym, vm.get_global(prev1_sym).unwrap().clone());
|
||||||
|
vm.set_global(prev1_sym, v.clone());
|
||||||
|
if v != Value::Nil {
|
||||||
|
println!("{v}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => println!("Error: {e}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
talc-lang/Cargo.toml
Normal file
14
talc-lang/Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
[package]
|
||||||
|
name = "talc-lang"
|
||||||
|
version = "0.1.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 = [] }
|
||||||
|
anyhow = "1.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
lalrpop = { version = "0.20", default_features = false, features = ["lexer", "unicode"]}
|
3
talc-lang/build.rs
Normal file
3
talc-lang/build.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
lalrpop::process_root()
|
||||||
|
}
|
48
talc-lang/src/ast.rs
Normal file
48
talc-lang/src/ast.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
use crate::value::Value;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum BinaryOp {
|
||||||
|
Add, Sub, Mul, Div, Mod, Pow, IntDiv,
|
||||||
|
Eq, Ne, Gt, Lt, Ge, Le,
|
||||||
|
Concat, Append,
|
||||||
|
Range, RangeIncl,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub enum UnaryOp {
|
||||||
|
Neg, Not, RangeEndless,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Expr<'s> {
|
||||||
|
Literal(Value),
|
||||||
|
Ident(&'s str),
|
||||||
|
|
||||||
|
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>>),
|
||||||
|
|
||||||
|
Index(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||||
|
FnCall(Box<Expr<'s>>, Vec<Expr<'s>>),
|
||||||
|
Pipe(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||||
|
|
||||||
|
Block(Vec<Expr<'s>>),
|
||||||
|
List(Vec<Expr<'s>>),
|
||||||
|
Table(Vec<(Expr<'s>, Expr<'s>)>),
|
||||||
|
|
||||||
|
And(Box<Expr<'s>>, Box<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>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum LValue<'s> {
|
||||||
|
Ident(&'s str),
|
||||||
|
Index(Box<Expr<'s>>, Box<Expr<'s>>),
|
||||||
|
}
|
206
talc-lang/src/chunk.rs
Normal file
206
talc-lang/src/chunk.rs
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
use crate::{value::Value, ast::{UnaryOp, BinaryOp}, symbol::Symbol};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub struct Arg24([u8; 3]);
|
||||||
|
|
||||||
|
impl Arg24 {
|
||||||
|
#[inline]
|
||||||
|
pub fn from_u32(n: u32) -> Self {
|
||||||
|
assert!(n <= 0xff_ffff, "value out of range for argument");
|
||||||
|
// can't panic: size of slice guaranteed
|
||||||
|
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_i32(n: i32) -> Self {
|
||||||
|
assert!((-0x80_0000..=0x7f_ffff).contains(&n), "value out of range for argument");
|
||||||
|
// can't panic: size of slice guaranteed
|
||||||
|
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_i64(n: i64) -> Self {
|
||||||
|
assert!((-0x80_0000..=0x7f_ffff).contains(&n), "value out of range for argument");
|
||||||
|
// can't panic: size of slice guaranteed
|
||||||
|
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_usize(n: usize) -> Self {
|
||||||
|
assert!(n <= 0xff_ffff, "value out of range for argument");
|
||||||
|
// can't panic: size of slice guaranteed
|
||||||
|
Self(n.to_le_bytes()[0..3].try_into().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn from_symbol(s: Symbol) -> Self {
|
||||||
|
Self::from_u32(s.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Safe if argument is a valid symbol ID
|
||||||
|
#[inline]
|
||||||
|
pub unsafe fn to_symbol_unchecked(self) -> Symbol {
|
||||||
|
Symbol::from_id_unchecked(u32::from(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arg24> for u32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Arg24) -> Self {
|
||||||
|
u32::from_le_bytes([v.0[0], v.0[1], v.0[2], 0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arg24> for usize {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Arg24) -> Self {
|
||||||
|
u32::from(v) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arg24> for i32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Arg24) -> Self {
|
||||||
|
let mut n = u32::from(v);
|
||||||
|
// sign-extend
|
||||||
|
if n & 0x00_800000 != 0 { n |= 0xff_000000; }
|
||||||
|
n as i32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Arg24> for i64 {
|
||||||
|
#[inline]
|
||||||
|
fn from(v: Arg24) -> Self {
|
||||||
|
i32::from(v) as i64
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<Arg24> for Symbol {
|
||||||
|
type Error = ();
|
||||||
|
#[inline]
|
||||||
|
fn try_from(value: Arg24) -> Result<Self, Self::Error> {
|
||||||
|
Symbol::from_id(u32::from(value)).ok_or(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(u8, C, align(4))]
|
||||||
|
#[derive(Clone, Copy, Debug, Default)]
|
||||||
|
pub enum Instruction {
|
||||||
|
#[default]
|
||||||
|
Nop,
|
||||||
|
|
||||||
|
LoadLocal(Arg24),
|
||||||
|
StoreLocal(Arg24),
|
||||||
|
NewLocal,
|
||||||
|
DropLocal(Arg24),
|
||||||
|
|
||||||
|
LoadGlobal(Arg24), StoreGlobal(Arg24),
|
||||||
|
|
||||||
|
Const(Arg24),
|
||||||
|
Int(Arg24),
|
||||||
|
Symbol(Arg24),
|
||||||
|
Bool(bool),
|
||||||
|
Nil,
|
||||||
|
|
||||||
|
Dup, DupTwo, Drop(Arg24), Swap,
|
||||||
|
|
||||||
|
UnaryOp(UnaryOp),
|
||||||
|
BinaryOp(BinaryOp),
|
||||||
|
|
||||||
|
NewList(u8), GrowList(u8),
|
||||||
|
NewTable(u8), GrowTable(u8),
|
||||||
|
|
||||||
|
Index, StoreIndex,
|
||||||
|
|
||||||
|
Jump(Arg24),
|
||||||
|
JumpTrue(Arg24),
|
||||||
|
JumpFalse(Arg24),
|
||||||
|
|
||||||
|
IterBegin, IterTest(Arg24),
|
||||||
|
|
||||||
|
Call(u8),
|
||||||
|
Return,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Instruction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
use Instruction as I;
|
||||||
|
match *self {
|
||||||
|
I::Nop => write!(f, "nop"),
|
||||||
|
I::LoadLocal(a) => write!(f, "load {}", usize::from(a)),
|
||||||
|
I::StoreLocal(a) => write!(f, "store {}", usize::from(a)),
|
||||||
|
I::NewLocal => write!(f, "newlocal"),
|
||||||
|
I::DropLocal(n) => write!(f, "discardlocal {}", usize::from(n)),
|
||||||
|
I::LoadGlobal(s) => write!(f, "loadglobal {}",
|
||||||
|
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||||
|
I::StoreGlobal(s) => write!(f, "storeglobal {}",
|
||||||
|
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||||
|
I::Const(c) => write!(f, "const {}", usize::from(c)),
|
||||||
|
I::Int(i) => write!(f, "int {}", i64::from(i)),
|
||||||
|
I::Symbol(s) => write!(f, "symbol {}",
|
||||||
|
Symbol::try_from(s).expect("symbol does not exist").name()),
|
||||||
|
I::Bool(b) => write!(f, "bool {b}"),
|
||||||
|
I::Nil => write!(f, "nil"),
|
||||||
|
I::Dup => write!(f, "dup"),
|
||||||
|
I::DupTwo => write!(f, "duptwo"),
|
||||||
|
I::Drop(n) => write!(f, "discard {}", usize::from(n)),
|
||||||
|
I::Swap => write!(f, "swap"),
|
||||||
|
I::UnaryOp(o) => write!(f, "unary {o:?}"),
|
||||||
|
I::BinaryOp(o) => write!(f, "binary {o:?}"),
|
||||||
|
I::NewList(n) => write!(f, "newlist {n}"),
|
||||||
|
I::GrowList(n) => write!(f, "growlist {n}"),
|
||||||
|
I::NewTable(n) => write!(f, "newtable {n}"),
|
||||||
|
I::GrowTable(n) => write!(f, "growtable {n}"),
|
||||||
|
I::Index => write!(f, "index"),
|
||||||
|
I::StoreIndex => write!(f, "storeindex"),
|
||||||
|
I::Jump(a) => write!(f, "jump {}", usize::from(a)),
|
||||||
|
I::JumpTrue(a) => write!(f, "jumptrue {}", usize::from(a)),
|
||||||
|
I::JumpFalse(a) => write!(f, "jumpfalse {}", usize::from(a)),
|
||||||
|
I::IterBegin => write!(f, "iterbegin"),
|
||||||
|
I::IterTest(a) => write!(f, "itertest {}", usize::from(a)),
|
||||||
|
I::Call(n) => write!(f, "call {n}"),
|
||||||
|
I::Return => write!(f, "return"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct Chunk {
|
||||||
|
pub consts: Vec<Value>,
|
||||||
|
pub instrs: Vec<Instruction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Chunk {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_const(&mut self, v: Value) -> usize {
|
||||||
|
assert!(self.consts.len() < 0xff_ffff, "too many constants in a chunk");
|
||||||
|
self.consts.push(v);
|
||||||
|
self.consts.len() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_instr(&mut self, i: Instruction) -> usize {
|
||||||
|
assert!(self.instrs.len() < 0xff_ffff, "too many instructions in a chunk");
|
||||||
|
self.instrs.push(i);
|
||||||
|
self.instrs.len() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Chunk {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
writeln!(f, "constants")?;
|
||||||
|
for (i, c) in self.consts.iter().enumerate() {
|
||||||
|
writeln!(f, " {i:04}: {c}")?;
|
||||||
|
}
|
||||||
|
writeln!(f, "instructions")?;
|
||||||
|
for (i, n) in self.instrs.iter().enumerate() {
|
||||||
|
writeln!(f, " {i:04}: {n}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
485
talc-lang/src/compiler.rs
Normal file
485
talc-lang/src/compiler.rs
Normal file
|
@ -0,0 +1,485 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::ast::{BinaryOp, Expr, LValue};
|
||||||
|
use crate::chunk::{Instruction as I, Chunk, Arg24};
|
||||||
|
use crate::symbol::Symbol;
|
||||||
|
use crate::value::Function;
|
||||||
|
use crate::value::Value;
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Local {
|
||||||
|
name: Rc<str>,
|
||||||
|
scope: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum CompilerMode {
|
||||||
|
Function, Repl, // Module,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Compiler<'a> {
|
||||||
|
parent: Option<&'a Compiler<'a>>,
|
||||||
|
mode: CompilerMode,
|
||||||
|
func: Function,
|
||||||
|
scope: usize,
|
||||||
|
locals: Vec<Local>,
|
||||||
|
globals: Vec<Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec<Local>)> {
|
||||||
|
let locals = vec![Local {
|
||||||
|
name: "self".into(),
|
||||||
|
scope: 0,
|
||||||
|
}];
|
||||||
|
let mut comp = Compiler {
|
||||||
|
parent: None,
|
||||||
|
mode: CompilerMode::Repl,
|
||||||
|
func: Function { arity: 0, chunk: Chunk::new() },
|
||||||
|
scope: 0,
|
||||||
|
locals,
|
||||||
|
globals: globals.to_vec(),
|
||||||
|
};
|
||||||
|
comp.expr(expr)?;
|
||||||
|
comp.emit(I::Return);
|
||||||
|
Ok((comp.func, comp.globals))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Compiler<'a> {
|
||||||
|
fn new_function(&'a self, func: Function, args: &[&str]) -> Self {
|
||||||
|
let mut new = Self {
|
||||||
|
parent: Some(self),
|
||||||
|
mode: CompilerMode::Function,
|
||||||
|
func,
|
||||||
|
scope: 0,
|
||||||
|
locals: Vec::new(),
|
||||||
|
globals: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
new.locals.push(Local {
|
||||||
|
name: "self".into(),
|
||||||
|
scope: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
for arg in args {
|
||||||
|
new.locals.push(Local {
|
||||||
|
name: (*arg).into(),
|
||||||
|
scope: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
new
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(mut self) -> Function {
|
||||||
|
self.emit(I::Return);
|
||||||
|
self.func
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Utility
|
||||||
|
//
|
||||||
|
|
||||||
|
fn add_const(&mut self, val: Value) -> usize {
|
||||||
|
self.func.chunk.add_const(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit(&mut self, instr: I) -> usize {
|
||||||
|
self.func.chunk.add_instr(instr)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn emit_discard(&mut self, mut n: usize) {
|
||||||
|
while n > 0 {
|
||||||
|
let instrs = &mut self.func.chunk.instrs;
|
||||||
|
|
||||||
|
// dup followed by store: remove the dup
|
||||||
|
if instrs.len() >= 2
|
||||||
|
&& matches!(instrs.get(instrs.len() - 2), Some(I::Dup))
|
||||||
|
&& matches!(instrs.last(), Some(
|
||||||
|
I::NewLocal | I::StoreLocal(_) | I::StoreGlobal(_)
|
||||||
|
))
|
||||||
|
{
|
||||||
|
// can't panic: checked that instrs.len() >= 2
|
||||||
|
let i = self.func.chunk.instrs.pop().unwrap();
|
||||||
|
self.func.chunk.instrs.pop().unwrap();
|
||||||
|
self.func.chunk.instrs.push(i);
|
||||||
|
n -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// final side-effectless instruction
|
||||||
|
let poppable = matches!(
|
||||||
|
instrs.last(),
|
||||||
|
Some(
|
||||||
|
I::Dup | I::Const(_) | I::Int(_)
|
||||||
|
| I::Nil | I::Bool(_) | I::Symbol(_)
|
||||||
|
| I::LoadLocal(_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if poppable {
|
||||||
|
// can't panic: checked that instrs.last() was Some
|
||||||
|
instrs.pop().unwrap();
|
||||||
|
n -= 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no more optimizations possible
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
self.emit(I::Drop(Arg24::from_usize(n)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ip(&self) -> usize {
|
||||||
|
self.func.chunk.instrs.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_instr(&mut self, n: usize, new: I) {
|
||||||
|
self.func.chunk.instrs[n] = new;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_scope(&mut self) {
|
||||||
|
self.scope += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_scope(&mut self) {
|
||||||
|
self.scope -= 1;
|
||||||
|
|
||||||
|
// no need to clean up at bottom scope
|
||||||
|
if self.scope == 0 { return; }
|
||||||
|
|
||||||
|
for i in (0..self.globals.len()).rev() {
|
||||||
|
if self.globals[i].scope <= self.scope {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.globals.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut count = 0;
|
||||||
|
for i in (0..self.locals.len()).rev() {
|
||||||
|
if self.locals[i].scope <= self.scope {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
self.locals.pop();
|
||||||
|
count += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if count > 0 && self.scope > 0 {
|
||||||
|
self.emit(I::DropLocal(Arg24::from_usize(count)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// variables
|
||||||
|
//
|
||||||
|
|
||||||
|
fn resolve_local(&mut self, name: &str) -> 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> {
|
||||||
|
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) {
|
||||||
|
match (self.resolve_local(name), self.resolve_global(name)) {
|
||||||
|
(Some(n), None) => {
|
||||||
|
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||||
|
},
|
||||||
|
(Some(n), Some(m)) if n >= m => {
|
||||||
|
self.emit(I::LoadLocal(Arg24::from_usize(n)));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
let sym = Symbol::get(name);
|
||||||
|
self.emit(I::LoadGlobal(Arg24::from_symbol(sym)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn declare_local(&mut self, name: &str) -> usize {
|
||||||
|
if let Some(i) = self.resolve_local(name) {
|
||||||
|
if self.locals[i].scope == self.scope {
|
||||||
|
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.locals.push(Local {
|
||||||
|
name: name.into(),
|
||||||
|
scope: self.scope,
|
||||||
|
});
|
||||||
|
|
||||||
|
let i = self.locals.len() - 1;
|
||||||
|
self.emit(I::NewLocal);
|
||||||
|
i
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_local(&mut self, i: usize) {
|
||||||
|
self.emit(I::StoreLocal(Arg24::from_usize(i)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_global(&mut self, name: &str) {
|
||||||
|
let sym = Symbol::get(name);
|
||||||
|
self.emit(I::StoreGlobal(Arg24::from_symbol(sym)));
|
||||||
|
if let Some(i) = self.resolve_global(name) {
|
||||||
|
if self.globals[i].scope == self.scope {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.globals.push(Local {
|
||||||
|
name: name.into(),
|
||||||
|
scope: self.scope,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn store_default(&mut self, name: &str) {
|
||||||
|
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),
|
||||||
|
(_, Some(_)) => self.store_global(name),
|
||||||
|
(None, None) => {
|
||||||
|
if self.mode == CompilerMode::Repl && self.scope == 1 {
|
||||||
|
self.store_global(name);
|
||||||
|
} else {
|
||||||
|
self.declare_local(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Expressions
|
||||||
|
//
|
||||||
|
|
||||||
|
fn expr(&mut self, e: &Expr) -> Result<()> {
|
||||||
|
match e {
|
||||||
|
Expr::Block(xs) if xs.is_empty() => { self.emit(I::Nil); },
|
||||||
|
Expr::Block(xs) => {
|
||||||
|
self.begin_scope();
|
||||||
|
for x in &xs[0..xs.len()-1] {
|
||||||
|
self.expr(x)?;
|
||||||
|
self.emit_discard(1);
|
||||||
|
}
|
||||||
|
self.expr(&xs[xs.len()-1])?;
|
||||||
|
self.end_scope();
|
||||||
|
},
|
||||||
|
Expr::Literal(v) => self.expr_literal(v),
|
||||||
|
Expr::Ident(ident) => self.load_var(ident),
|
||||||
|
Expr::UnaryOp(o, a) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::UnaryOp(*o));
|
||||||
|
},
|
||||||
|
Expr::BinaryOp(o, a, b) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.expr(b)?;
|
||||||
|
self.emit(I::BinaryOp(*o));
|
||||||
|
},
|
||||||
|
Expr::Assign(o, lv, a) => self.expr_assign(*o, lv, a)?,
|
||||||
|
Expr::AssignVar(name, a) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::Dup);
|
||||||
|
self.declare_local(name);
|
||||||
|
},
|
||||||
|
Expr::AssignGlobal(name, a) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::Dup);
|
||||||
|
self.store_global(name);
|
||||||
|
},
|
||||||
|
Expr::List(xs) => {
|
||||||
|
let mut first = true;
|
||||||
|
for chunk in xs.chunks(16) {
|
||||||
|
for e in chunk {
|
||||||
|
self.expr(e)?;
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
self.emit(I::NewList(chunk.len() as u8));
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
self.emit(I::GrowList(chunk.len() as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expr::Table(xs) => {
|
||||||
|
let mut first = true;
|
||||||
|
for chunk in xs.chunks(8) {
|
||||||
|
for (k, v) in chunk {
|
||||||
|
self.expr(k)?;
|
||||||
|
self.expr(v)?;
|
||||||
|
}
|
||||||
|
if first {
|
||||||
|
self.emit(I::NewTable(chunk.len() as u8));
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
self.emit(I::GrowTable(chunk.len() as u8));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Expr::Index(ct, idx) => {
|
||||||
|
self.expr(ct)?;
|
||||||
|
self.expr(idx)?;
|
||||||
|
self.emit(I::Index);
|
||||||
|
},
|
||||||
|
Expr::FnCall(f, args) => {
|
||||||
|
self.expr(f)?;
|
||||||
|
for a in args {
|
||||||
|
self.expr(a)?;
|
||||||
|
}
|
||||||
|
self.emit(I::Call(args.len() as u8));
|
||||||
|
},
|
||||||
|
Expr::Pipe(a, f) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.expr(f)?;
|
||||||
|
self.emit(I::Swap);
|
||||||
|
self.emit(I::Call(1));
|
||||||
|
},
|
||||||
|
Expr::And(a, b) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::Dup);
|
||||||
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||||
|
self.emit_discard(1);
|
||||||
|
self.expr(b)?;
|
||||||
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||||
|
},
|
||||||
|
Expr::Or(a, b) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::Dup);
|
||||||
|
let j1 = self.emit(I::JumpTrue(Arg24::from_usize(0)));
|
||||||
|
self.emit_discard(1);
|
||||||
|
self.expr(b)?;
|
||||||
|
self.update_instr(j1, I::JumpTrue(Arg24::from_usize(self.ip())));
|
||||||
|
},
|
||||||
|
Expr::If(cond, b1, b2) => {
|
||||||
|
self.expr(cond)?;
|
||||||
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||||
|
self.expr(b1)?;
|
||||||
|
let j2 = self.emit(I::Jump(Arg24::from_usize(0)));
|
||||||
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||||
|
if let Some(b2) = b2 {
|
||||||
|
self.expr(b2)?;
|
||||||
|
} else {
|
||||||
|
self.emit(I::Nil);
|
||||||
|
}
|
||||||
|
self.update_instr(j2,
|
||||||
|
I::Jump(Arg24::from_usize(self.ip()))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Expr::While(cond, body) => {
|
||||||
|
let start = self.ip();
|
||||||
|
self.expr(cond)?;
|
||||||
|
|
||||||
|
let j1 = self.emit(I::JumpFalse(Arg24::from_usize(0)));
|
||||||
|
self.expr(body)?;
|
||||||
|
self.emit_discard(1);
|
||||||
|
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||||
|
self.update_instr(j1, I::JumpFalse(Arg24::from_usize(self.ip())));
|
||||||
|
|
||||||
|
self.emit(I::Nil);
|
||||||
|
},
|
||||||
|
Expr::For(name, iter, body) => {
|
||||||
|
// load iterable and convert to iterator
|
||||||
|
self.expr(iter)?;
|
||||||
|
self.emit(I::IterBegin);
|
||||||
|
|
||||||
|
// declare loop variable
|
||||||
|
self.begin_scope();
|
||||||
|
self.emit(I::Nil);
|
||||||
|
let local = self.declare_local(name);
|
||||||
|
|
||||||
|
// begin loop
|
||||||
|
let start = self.ip();
|
||||||
|
|
||||||
|
// call iterator and jump if nil, otherwise store
|
||||||
|
self.emit(I::Dup);
|
||||||
|
self.emit(I::Call(0));
|
||||||
|
let j1 = self.emit(I::IterTest(Arg24::from_usize(0)));
|
||||||
|
self.store_local(local);
|
||||||
|
|
||||||
|
// body
|
||||||
|
self.expr(body)?;
|
||||||
|
self.emit_discard(1);
|
||||||
|
|
||||||
|
// end loop
|
||||||
|
self.emit(I::Jump(Arg24::from_usize(start)));
|
||||||
|
|
||||||
|
self.update_instr(j1, I::IterTest(Arg24::from_usize(self.ip())));
|
||||||
|
self.end_scope();
|
||||||
|
self.emit(I::Nil);
|
||||||
|
},
|
||||||
|
Expr::Lambda(args, body) => self.expr_lambda(args, body)?,
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_lambda(&mut self, args: &[&str], body: &Expr) -> Result<()> {
|
||||||
|
let func = Function {
|
||||||
|
arity: args.len(),
|
||||||
|
chunk: Chunk::new()
|
||||||
|
};
|
||||||
|
let mut inner = self.new_function(func, args);
|
||||||
|
inner.parent = Some(self);
|
||||||
|
inner.expr(body)?;
|
||||||
|
let func = inner.finish();
|
||||||
|
let n = self.add_const(Value::Function(Rc::new(func)));
|
||||||
|
self.emit(I::Const(Arg24::from_usize(n)));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_literal(&mut self, val: &Value) {
|
||||||
|
match val {
|
||||||
|
Value::Nil
|
||||||
|
=> { self.emit(I::Nil); },
|
||||||
|
Value::Bool(b)
|
||||||
|
=> { self.emit(I::Bool(*b)); },
|
||||||
|
Value::Int(i) if (-0x80_0000..=0x7f_ffff).contains(i)
|
||||||
|
=> { self.emit(I::Int(Arg24::from_i64(*i))); },
|
||||||
|
Value::Symbol(s)
|
||||||
|
=> { self.emit(I::Symbol(Arg24::from_symbol(*s))); },
|
||||||
|
_ => {
|
||||||
|
let n = self.add_const(val.clone());
|
||||||
|
self.emit(I::Const(Arg24::from_usize(n)));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expr_assign(&mut self, o: Option<BinaryOp>, lv: &LValue, a: &Expr) -> Result<()> {
|
||||||
|
match (lv, o) {
|
||||||
|
(LValue::Ident(i), None) => {
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::Dup);
|
||||||
|
self.store_default(i);
|
||||||
|
},
|
||||||
|
(LValue::Ident(i), Some(o)) => {
|
||||||
|
self.load_var(i);
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::BinaryOp(o));
|
||||||
|
self.emit(I::Dup);
|
||||||
|
self.store_default(i);
|
||||||
|
},
|
||||||
|
(LValue::Index(ct, i), None) => {
|
||||||
|
self.expr(ct)?;
|
||||||
|
self.expr(i)?;
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::StoreIndex);
|
||||||
|
},
|
||||||
|
(LValue::Index(ct, i), Some(o)) => {
|
||||||
|
self.expr(ct)?;
|
||||||
|
self.expr(i)?;
|
||||||
|
self.emit(I::DupTwo);
|
||||||
|
self.emit(I::Index);
|
||||||
|
self.expr(a)?;
|
||||||
|
self.emit(I::BinaryOp(o));
|
||||||
|
self.emit(I::StoreIndex);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
0
talc-lang/src/gc.rs
Normal file
0
talc-lang/src/gc.rs
Normal file
23
talc-lang/src/lib.rs
Normal file
23
talc-lang/src/lib.rs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
|
||||||
|
#[rustfmt::skip]
|
||||||
|
#[allow(clippy::extra_unused_lifetimes)]
|
||||||
|
#[allow(clippy::needless_lifetimes)]
|
||||||
|
#[allow(clippy::let_unit_value)]
|
||||||
|
#[allow(clippy::just_underscores_and_digits)]
|
||||||
|
#[allow(clippy::pedantic)]
|
||||||
|
mod parser {
|
||||||
|
include!(concat!(env!("OUT_DIR"),"/parser.rs"));
|
||||||
|
}
|
||||||
|
mod parser_util;
|
||||||
|
mod vm;
|
||||||
|
mod symbol;
|
||||||
|
|
||||||
|
pub mod ast;
|
||||||
|
pub mod value;
|
||||||
|
pub mod gc;
|
||||||
|
pub mod chunk;
|
||||||
|
pub mod compiler;
|
||||||
|
|
||||||
|
pub use parser::BlockParser as Parser;
|
||||||
|
pub use vm::Vm;
|
||||||
|
pub use symbol::Symbol;
|
371
talc-lang/src/parser.lalrpop
Normal file
371
talc-lang/src/parser.lalrpop
Normal file
|
@ -0,0 +1,371 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use crate::ast::*;
|
||||||
|
use crate::value::*;
|
||||||
|
use crate::symbol::Symbol;
|
||||||
|
use crate::parser_util::*;
|
||||||
|
use num_complex::Complex64;
|
||||||
|
|
||||||
|
grammar;
|
||||||
|
|
||||||
|
extern {
|
||||||
|
type Error = ParseError;
|
||||||
|
}
|
||||||
|
|
||||||
|
match {
|
||||||
|
// line separator (use \ to escape newlines)
|
||||||
|
r";|\n" => LineSeparator,
|
||||||
|
|
||||||
|
// whitespace
|
||||||
|
r"[ \t\r]*" => {},
|
||||||
|
r"\\\r?\n" => {},
|
||||||
|
r"--[^\n]*" => {},
|
||||||
|
|
||||||
|
// kw literals
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"nil",
|
||||||
|
// kw variables
|
||||||
|
"const",
|
||||||
|
"global",
|
||||||
|
"var",
|
||||||
|
// kw logic
|
||||||
|
"and",
|
||||||
|
"or",
|
||||||
|
"not",
|
||||||
|
// kw control flow
|
||||||
|
"begin",
|
||||||
|
"end",
|
||||||
|
"if",
|
||||||
|
"then",
|
||||||
|
"elif",
|
||||||
|
"else",
|
||||||
|
"while",
|
||||||
|
"for",
|
||||||
|
"do",
|
||||||
|
"in",
|
||||||
|
"continue",
|
||||||
|
"break",
|
||||||
|
} else {
|
||||||
|
// identifiers
|
||||||
|
r"[a-zA-Z_][a-zA-Z0-9_]*" => TokIdentifier,
|
||||||
|
|
||||||
|
// literals
|
||||||
|
r"0x[0-9A-Fa-f][0-9A-Fa-f_]*" => TokHexInteger,
|
||||||
|
r"[0-9][0-9_]*" => TokDecInteger,
|
||||||
|
r"0o[0-7][0-7]*" => TokOctInteger,
|
||||||
|
r"0s[0-5][0-5]*" => TokSexInteger,
|
||||||
|
r"0b[01][01_]*" => TokBinInteger,
|
||||||
|
|
||||||
|
r"\d[0-9_]*([eE][-+]?[0-9_]*\d[0-9_]*i?|i)|(\d[0-9_]*)?\.[0-9_]+([eE]_*[-+]?[0-9_]*\d[0-9_]*)?i?" => TokFloat,
|
||||||
|
|
||||||
|
r#""([^\\"]|\\.)*""# => TokStringDouble,
|
||||||
|
r#"'[^']*'"# => TokStringSingle,
|
||||||
|
|
||||||
|
r#":[a-zA-Z_][a-zA-Z0-9_]*"# => TokSymbolNone,
|
||||||
|
r#":'[^']*'"# => TokSymbolSingle,
|
||||||
|
r#":"([^\\"]|\\.)*""# => TokSymbolDouble,
|
||||||
|
} else {
|
||||||
|
// everything else
|
||||||
|
_
|
||||||
|
}
|
||||||
|
|
||||||
|
pub Block: Box<Expr<'input>> = {
|
||||||
|
LineSeparator* <xs:(<Expr> LineSeparator+)*> <x:Expr> LineSeparator* => {
|
||||||
|
let v = xs.into_iter().chain(std::iter::once(x)).map(|x| *x).collect();
|
||||||
|
return Box::new(Expr::Block(v));
|
||||||
|
},
|
||||||
|
LineSeparator* => Box::new(Expr::Block(Vec::new())),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Expr: Box<Expr<'input>> = Assign;
|
||||||
|
|
||||||
|
//
|
||||||
|
// assignment
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
Assign: Box<Expr<'input>> = {
|
||||||
|
<l:LValue> <o:AssignOp> <r:Assign> => Box::new(Expr::Assign(o, l, r)),
|
||||||
|
"var" <i:Identifier> "=" <r:Assign> => Box::new(Expr::AssignVar(i, r)),
|
||||||
|
"global" <i:Identifier> "=" <r:Assign> => Box::new(Expr::AssignGlobal(i, r)),
|
||||||
|
Or,
|
||||||
|
}
|
||||||
|
|
||||||
|
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)))))),
|
||||||
|
}
|
||||||
|
|
||||||
|
AssignOp: Option<BinaryOp> = {
|
||||||
|
"=" => None,
|
||||||
|
"+=" => Some(BinaryOp::Add),
|
||||||
|
"-=" => Some(BinaryOp::Sub),
|
||||||
|
"*=" => Some(BinaryOp::Mul),
|
||||||
|
"/=" => Some(BinaryOp::Div),
|
||||||
|
"%=" => Some(BinaryOp::Mod),
|
||||||
|
"^=" => Some(BinaryOp::Pow),
|
||||||
|
"++=" => Some(BinaryOp::Concat),
|
||||||
|
"&=" => Some(BinaryOp::Append),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// operations
|
||||||
|
//
|
||||||
|
|
||||||
|
// or
|
||||||
|
Or: Box<Expr<'input>> = {
|
||||||
|
<l:Or> "or" <r:And> => Box::new(Expr::Or(l, r)),
|
||||||
|
And,
|
||||||
|
}
|
||||||
|
|
||||||
|
// and
|
||||||
|
And: Box<Expr<'input>> = {
|
||||||
|
<l:And> "and" <r:UnaryNot> => Box::new(Expr::And(l, r)),
|
||||||
|
UnaryNot,
|
||||||
|
}
|
||||||
|
|
||||||
|
// not
|
||||||
|
UnaryNot: Box<Expr<'input>> = {
|
||||||
|
"not" <r:Pipe> => Box::new(Expr::UnaryOp(UnaryOp::Not, r)),
|
||||||
|
Pipe,
|
||||||
|
}
|
||||||
|
|
||||||
|
// |
|
||||||
|
Pipe: Box<Expr<'input>> = {
|
||||||
|
<l:Pipe> "|" <r:BinaryCompare> => Box::new(Expr::Pipe(l, r)),
|
||||||
|
BinaryCompare,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// == != > < >= <=
|
||||||
|
BinaryCompare: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryConcat> <o:CompareOp> <r:BinaryConcat> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||||
|
BinaryConcat,
|
||||||
|
}
|
||||||
|
|
||||||
|
CompareOp: BinaryOp = {
|
||||||
|
"==" => BinaryOp::Eq,
|
||||||
|
"!=" => BinaryOp::Ne,
|
||||||
|
">" => BinaryOp::Gt,
|
||||||
|
"<" => BinaryOp::Lt,
|
||||||
|
">=" => BinaryOp::Ge,
|
||||||
|
"<=" => BinaryOp::Le,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// concat (++)
|
||||||
|
BinaryConcat: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryConcat> "++" <r:BinaryAppend> => Box::new(Expr::BinaryOp(BinaryOp::Concat, l, r)),
|
||||||
|
BinaryAppend,
|
||||||
|
}
|
||||||
|
|
||||||
|
// append ( & )
|
||||||
|
BinaryAppend: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryAppend> "&" <r:BinaryRange> => Box::new(Expr::BinaryOp(BinaryOp::Append, l, r)),
|
||||||
|
BinaryRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// .. ..= ..*
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeOp: BinaryOp = {
|
||||||
|
".." => BinaryOp::Range,
|
||||||
|
"..=" => BinaryOp::RangeIncl,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// + -
|
||||||
|
BinaryAdd: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryAdd> <o:AddOp> <r:BinaryMul> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||||
|
BinaryMul,
|
||||||
|
}
|
||||||
|
|
||||||
|
AddOp: BinaryOp = {
|
||||||
|
"+" => BinaryOp::Add,
|
||||||
|
"-" => BinaryOp::Sub,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// * / %
|
||||||
|
BinaryMul: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryMul> <o:MulOp> <r:UnaryMinus> => Box::new(Expr::BinaryOp(o, l, r)),
|
||||||
|
UnaryMinus,
|
||||||
|
}
|
||||||
|
|
||||||
|
MulOp: BinaryOp = {
|
||||||
|
"*" => BinaryOp::Mul,
|
||||||
|
"/" => BinaryOp::Div,
|
||||||
|
"//" => BinaryOp::IntDiv,
|
||||||
|
"%" => BinaryOp::Mod,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// unary-
|
||||||
|
UnaryMinus: Box<Expr<'input>> = {
|
||||||
|
"-" <r:BinaryPow> => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)),
|
||||||
|
BinaryPow,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// power ( ^ )
|
||||||
|
BinaryPow: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryIndex> "^" <r:UnaryMinus> => Box::new(Expr::BinaryOp(BinaryOp::Pow, l, r)),
|
||||||
|
BinaryIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// index ( ! )
|
||||||
|
BinaryIndex: Box<Expr<'input>> = {
|
||||||
|
<l:BinaryIndex> "!" <r:UnaryMinus2> => Box::new(Expr::Index(l, r)),
|
||||||
|
FunctionCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
// unary-
|
||||||
|
UnaryMinus2: Box<Expr<'input>> = {
|
||||||
|
"-" <r:UnaryMinus2> => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)),
|
||||||
|
FunctionCall,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// things
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
// function call
|
||||||
|
FunctionCall: Box<Expr<'input>> = {
|
||||||
|
<l:FieldAccess> "(" <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)))))),
|
||||||
|
Term,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// base
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
Term: Box<Expr<'input>> = {
|
||||||
|
Identifier => Box::new(Expr::Ident(<>)),
|
||||||
|
TermNotIdent,
|
||||||
|
}
|
||||||
|
|
||||||
|
TermNotIdent: Box<Expr<'input>> = {
|
||||||
|
"(" <Expr> ")" => <>,
|
||||||
|
"[" <ExprList> "]" => Box::new(Expr::List(<>)),
|
||||||
|
"{" <TableItems> "}" => Box::new(Expr::Table(<>)),
|
||||||
|
"$" => Box::new(Expr::Ident("$")),
|
||||||
|
":" "(" <e:Block> ")" => Box::new(Expr::Lambda(vec!["$"], e)),
|
||||||
|
"\\" <xs:IdentList> "->" <e:Term> => Box::new(Expr::Lambda(xs, e)),
|
||||||
|
"do" <Block> "end" => <>,
|
||||||
|
"if" <IfStmtChain> => <>,
|
||||||
|
"while" <a:Expr> "do" <b:Block> "end" => Box::new(Expr::While(a, b)),
|
||||||
|
"for" <v:Identifier> "in" <a:Expr> "do" <b:Block> "end" => Box::new(Expr::For(v, a, b)),
|
||||||
|
Literal => Box::new(Expr::Literal(<>)),
|
||||||
|
}
|
||||||
|
|
||||||
|
IfStmtChain: Box<Expr<'input>> = {
|
||||||
|
<a:Expr> "then" <b:Block> "end" => Box::new(Expr::If(a, b, None)),
|
||||||
|
<a:Expr> "then" <b:Block> "else" <c:Block> "end" => Box::new(Expr::If(a, b, Some(c))),
|
||||||
|
<a:Expr> "then" <b:Block> "elif" <c:IfStmtChain> => Box::new(Expr::If(a, b, Some(c))),
|
||||||
|
}
|
||||||
|
|
||||||
|
ExprList: Vec<Expr<'input>> = {
|
||||||
|
<xs:(<Expr> ",")*> <x:Expr?> => {
|
||||||
|
let mut xs: Vec<_> = xs.into_iter().map(|x| *x).collect();
|
||||||
|
if let Some(x) = x { xs.push(*x) };
|
||||||
|
xs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableItems: Vec<(Expr<'input>, Expr<'input>)> = {
|
||||||
|
<mut xs:(<TableItem> ",")*> <x:TableItem?> => {
|
||||||
|
if let Some(x) = x { xs.push(x) };
|
||||||
|
xs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TableItem: (Expr<'input>, Expr<'input>) = {
|
||||||
|
<k:TableKey> "=" <v:Expr> => (k, *v),
|
||||||
|
}
|
||||||
|
|
||||||
|
TableKey: Expr<'input> = {
|
||||||
|
Identifier => Expr::Literal(Value::Symbol(Symbol::get(<>))),
|
||||||
|
TermNotIdent => *<>,
|
||||||
|
}
|
||||||
|
|
||||||
|
IdentList: Vec<&'input str> = {
|
||||||
|
<mut xs:(<Identifier> ",")*> <x:Identifier?>
|
||||||
|
=> { if let Some(x) = x { xs.push(x) }; xs }
|
||||||
|
}
|
||||||
|
|
||||||
|
Identifier: &'input str = TokIdentifier;
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// literals
|
||||||
|
//
|
||||||
|
|
||||||
|
Literal: Value = {
|
||||||
|
TokDecInteger =>? parse_int(<>, 10)
|
||||||
|
.map(Value::Int)
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
TokHexInteger =>? parse_int(&<>[2..], 16)
|
||||||
|
.map(Value::Int)
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
TokOctInteger =>? parse_int(&<>[2..], 8)
|
||||||
|
.map(Value::Int)
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
TokSexInteger =>? parse_int(&<>[2..], 6)
|
||||||
|
.map(Value::Int)
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
TokBinInteger =>? parse_int(&<>[2..], 2)
|
||||||
|
.map(Value::Int)
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
<f:TokFloat> =>? {
|
||||||
|
if let Some(f) = f.strip_suffix('i') {
|
||||||
|
parse_float(f)
|
||||||
|
.map(|im| Value::Complex(Complex64::new(0.0, im)))
|
||||||
|
.map_err(|e| ParseError::from(e).into())
|
||||||
|
} else {
|
||||||
|
parse_float(f)
|
||||||
|
.map(Value::Float)
|
||||||
|
.map_err(|e| ParseError::from(e).into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StringLiteral => Value::String(<>),
|
||||||
|
SymbolLiteral => Value::Symbol(<>),
|
||||||
|
"true" => Value::Bool(true),
|
||||||
|
"false" => Value::Bool(false),
|
||||||
|
"nil" => Value::Nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
StringLiteral: Rc<str> = {
|
||||||
|
TokStringSingle => <>[1..<>.len()-1].into(),
|
||||||
|
TokStringDouble =>? parse_str_escapes(&<>[1..<>.len()-1])
|
||||||
|
.map(|s| s.into())
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
}
|
||||||
|
|
||||||
|
SymbolLiteral: Symbol = {
|
||||||
|
TokSymbolNone => Symbol::get(&<>[1..]),
|
||||||
|
TokSymbolSingle => Symbol::get(&<>[2..<>.len()-1]),
|
||||||
|
TokSymbolDouble =>? parse_str_escapes(&<>[2..<>.len()-1])
|
||||||
|
.map(|s| Symbol::get(&s))
|
||||||
|
.map_err(|e| ParseError::from(e).into()),
|
||||||
|
}
|
105
talc-lang/src/parser_util.rs
Normal file
105
talc-lang/src/parser_util.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::num::{ParseIntError, ParseFloatError};
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Error)]
|
||||||
|
pub enum ParseError {
|
||||||
|
#[error("{0}")]
|
||||||
|
StrEscape(#[from] StrEscapeError),
|
||||||
|
#[error("{0}")]
|
||||||
|
Integer(#[from] ParseIntError),
|
||||||
|
#[error("{0}")]
|
||||||
|
Float(#[from] ParseFloatError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Error)]
|
||||||
|
pub enum StrEscapeError {
|
||||||
|
#[error("EOF in string escape")]
|
||||||
|
Eof,
|
||||||
|
#[error("EOF in string escape \\x")]
|
||||||
|
HexEof,
|
||||||
|
#[error("EOF in string escape \\u")]
|
||||||
|
UnicodeEof,
|
||||||
|
#[error("Invalid string escape \\{0}")]
|
||||||
|
Invalid(char),
|
||||||
|
#[error("Invalid hex digit '{0}' in string escape")]
|
||||||
|
InvalidHex(char),
|
||||||
|
#[error("Missing brace after string escape \\u")]
|
||||||
|
MissingBrace,
|
||||||
|
#[error("Invalid codepoint in string escape: {0:x}")]
|
||||||
|
InvalidCodepoint(u32),
|
||||||
|
#[error("Codepoint in string escape too large")]
|
||||||
|
CodepointTooLarge,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_str_escapes(src: &str) -> Result<String, StrEscapeError> {
|
||||||
|
let mut s = String::with_capacity(src.len());
|
||||||
|
let mut chars = src.chars();
|
||||||
|
|
||||||
|
while let Some(c) = chars.next() {
|
||||||
|
if c != '\\' { s.push(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'),
|
||||||
|
'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());
|
||||||
|
},
|
||||||
|
'u' => {
|
||||||
|
let Some('{') = chars.next() else {
|
||||||
|
return Err(StrEscapeError::MissingBrace)
|
||||||
|
};
|
||||||
|
let mut n = 0u32;
|
||||||
|
loop {
|
||||||
|
let Some(c) = chars.next() else {
|
||||||
|
return Err(StrEscapeError::UnicodeEof)
|
||||||
|
};
|
||||||
|
if c == '}' { break }
|
||||||
|
if n >= 0x1000_0000u32 {
|
||||||
|
return Err(StrEscapeError::CodepointTooLarge)
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
|
||||||
|
},
|
||||||
|
c => return Err(StrEscapeError::Invalid(c)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_float(f: &str) -> Result<f64, ParseFloatError> {
|
||||||
|
let mut s = String::new();
|
||||||
|
for c in f.chars() {
|
||||||
|
if c != '_' {
|
||||||
|
s.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.parse()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_int(f: &str, radix: u32) -> Result<i64, ParseIntError> {
|
||||||
|
let mut s = String::new();
|
||||||
|
for c in f.chars() {
|
||||||
|
if c != '_' {
|
||||||
|
s.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i64::from_str_radix(&s, radix)
|
||||||
|
}
|
76
talc-lang/src/symbol.rs
Normal file
76
talc-lang/src/symbol.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use std::{sync::{OnceLock, Arc, Mutex}, collections::HashMap};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct SymbolTable {
|
||||||
|
names: Vec<Arc<str>>,
|
||||||
|
values: HashMap<Arc<str>, Symbol>
|
||||||
|
}
|
||||||
|
|
||||||
|
static SYM_TABLE: OnceLock<Mutex<SymbolTable>> = OnceLock::new();
|
||||||
|
|
||||||
|
const MAX_SYMBOL: usize = 0xff_ffff;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, Hash)]
|
||||||
|
pub struct Symbol(u32);
|
||||||
|
|
||||||
|
fn get_table() -> &'static Mutex<SymbolTable> {
|
||||||
|
SYM_TABLE.get_or_init(|| Mutex::new(SymbolTable::default()))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Symbol {
|
||||||
|
/// # Panics
|
||||||
|
/// If the mutex storing the symbol table is poisoned
|
||||||
|
pub fn try_get(name: &str) -> Option<Self> {
|
||||||
|
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 {
|
||||||
|
let mut table = get_table().lock().expect("couldn't lock symbol table");
|
||||||
|
|
||||||
|
if let Some(sym) = table.values.get(name) {
|
||||||
|
return *sym
|
||||||
|
}
|
||||||
|
|
||||||
|
let symno = table.names.len();
|
||||||
|
assert!(symno <= MAX_SYMBOL, "too many symbols");
|
||||||
|
|
||||||
|
let sym = Symbol(symno as u32);
|
||||||
|
let name: Arc<str> = name.into();
|
||||||
|
|
||||||
|
table.names.push(name.clone());
|
||||||
|
table.values.insert(name, sym);
|
||||||
|
|
||||||
|
sym
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// If the mutex storing the symbol table is poisoned
|
||||||
|
pub fn from_id(id: u32) -> Option<Self> {
|
||||||
|
let table = get_table().lock().expect("couldn't lock symbol table");
|
||||||
|
if id as usize <= table.names.len() {
|
||||||
|
Some(Self(id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// Safe if argument is a valid symbol ID
|
||||||
|
pub unsafe fn from_id_unchecked(id: u32) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn id(self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Panics
|
||||||
|
/// If the mutex storing the symbol table is poisoned
|
||||||
|
pub fn name(self) -> Arc<str> {
|
||||||
|
let table = get_table().lock().expect("couldn't lock symbol table");
|
||||||
|
table.names.get(self.0 as usize).cloned().expect("symbol does not exist")
|
||||||
|
}
|
||||||
|
}
|
572
talc-lang/src/value.rs
Normal file
572
talc-lang/src/value.rs
Normal file
|
@ -0,0 +1,572 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ops::{Add, Sub, Mul, Div, Neg};
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use anyhow::bail;
|
||||||
|
use num_rational::Rational64;
|
||||||
|
use num_complex::Complex64;
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
|
use crate::chunk::Chunk;
|
||||||
|
use crate::symbol::Symbol;
|
||||||
|
|
||||||
|
type RcList = Rc<RefCell<Vec<Value>>>;
|
||||||
|
type RcTable = Rc<RefCell<HashMap<HashValue, Value>>>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Function {
|
||||||
|
pub arity: usize,
|
||||||
|
pub chunk: Chunk,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct NativeFunc {
|
||||||
|
pub arity: usize,
|
||||||
|
pub f: Box<dyn Fn(Value, Vec<Value>) -> Result<Value>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Debug for NativeFunc {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("NativeFunc")
|
||||||
|
.field("arity", &self.arity)
|
||||||
|
.finish_non_exhaustive()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct HashValue(Value);
|
||||||
|
|
||||||
|
impl Eq for HashValue {}
|
||||||
|
|
||||||
|
impl TryFrom<Value> for HashValue {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
fn try_from(value: Value) -> std::result::Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
Value::Nil
|
||||||
|
| Value::Bool(_)
|
||||||
|
| Value::Symbol(_)
|
||||||
|
| Value::Int(_)
|
||||||
|
| Value::Ratio(_)
|
||||||
|
| Value::String(_) => Ok(Self(value)),
|
||||||
|
_ => bail!("value {} cannot be hashed", value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HashValue {
|
||||||
|
pub fn into_inner(self) -> Value { self.0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hash for HashValue {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
std::mem::discriminant(&self.0).hash(state);
|
||||||
|
match &self.0 {
|
||||||
|
Value::Nil => (),
|
||||||
|
Value::Bool(b) => b.hash(state),
|
||||||
|
Value::Symbol(s) => s.hash(state),
|
||||||
|
Value::Int(n) => n.hash(state),
|
||||||
|
Value::Ratio(r) => r.hash(state),
|
||||||
|
Value::String(s) => s.hash(state),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum RangeType {
|
||||||
|
Open, Closed, Endless,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct Range {
|
||||||
|
start: i64,
|
||||||
|
stop: i64,
|
||||||
|
ty: RangeType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Range {
|
||||||
|
pub fn len(&self) -> Option<usize> {
|
||||||
|
match self.ty {
|
||||||
|
RangeType::Open => Some((self.stop - self.start).max(0) as usize),
|
||||||
|
RangeType::Closed => Some((self.stop - self.start + 1).max(0) as usize),
|
||||||
|
RangeType::Endless => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self.ty {
|
||||||
|
RangeType::Open => self.stop - self.start <= 0,
|
||||||
|
RangeType::Closed => self.stop - self.start < 0,
|
||||||
|
RangeType::Endless => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for Range {
|
||||||
|
type Item = i64;
|
||||||
|
type IntoIter = Box<dyn Iterator<Item=i64>>;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
match self.ty {
|
||||||
|
RangeType::Open => Box::new(self.start..self.stop),
|
||||||
|
RangeType::Closed => Box::new(self.start..=self.stop),
|
||||||
|
RangeType::Endless => Box::new(self.start..),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Value {
|
||||||
|
Nil,
|
||||||
|
Bool(bool),
|
||||||
|
Symbol(Symbol),
|
||||||
|
Range(Range),
|
||||||
|
|
||||||
|
Int(i64),
|
||||||
|
Float(f64),
|
||||||
|
Ratio(Rational64),
|
||||||
|
Complex(Complex64),
|
||||||
|
|
||||||
|
String(Rc<str>),
|
||||||
|
List(RcList),
|
||||||
|
Table(RcTable),
|
||||||
|
Function(Rc<Function>),
|
||||||
|
NativeFunc(Rc<NativeFunc>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Value {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Value::Nil => write!(f, "nil"),
|
||||||
|
Value::Bool(b) => write!(f, "{b}"),
|
||||||
|
Value::Symbol(s) => write!(f, ":{}", s.name()),
|
||||||
|
Value::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),
|
||||||
|
},
|
||||||
|
Value::Int(n) => write!(f, "{n}"),
|
||||||
|
Value::Float(x) => write!(f, "{x:?}"),
|
||||||
|
Value::Ratio(r) => write!(f, "{}/{}", r.numer(), r.denom()),
|
||||||
|
Value::Complex(z) => write!(f, "{z}"),
|
||||||
|
|
||||||
|
Value::String(s) => write!(f, "{s}"),
|
||||||
|
Value::List(l) => {
|
||||||
|
write!(f, "[")?;
|
||||||
|
for (i, item) in l.borrow().iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
write!(f, ", ")?;
|
||||||
|
}
|
||||||
|
write!(f, "{item}")?;
|
||||||
|
}
|
||||||
|
write!(f, "]")
|
||||||
|
},
|
||||||
|
Value::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, " }}")
|
||||||
|
},
|
||||||
|
Value::Function(_) => write!(f, "<function>"),
|
||||||
|
Value::NativeFunc(_) => write!(f, "<native function>"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn truthy(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Value::Nil => false,
|
||||||
|
Value::Bool(b) => *b,
|
||||||
|
Value::Range(r) => matches!(r.len(), None | Some(1..)),
|
||||||
|
Value::Int(n) => *n != 0,
|
||||||
|
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::List(l) => l.borrow().len() > 0,
|
||||||
|
|
||||||
|
Value::Symbol(_) | Value::Table(_)
|
||||||
|
| Value::Function(_) | Value::NativeFunc(_) => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
fn ratio_to_f64(r: Rational64) -> f64 {
|
||||||
|
*r.numer() as f64 / *r.denom() as f64
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
fn promote(a: Value, b: Value) -> (Value, Value) {
|
||||||
|
use Value as V;
|
||||||
|
match (&a, &b) {
|
||||||
|
(V::Int(x), V::Ratio(..)) => (V::Ratio((*x).into()), b),
|
||||||
|
(V::Int(x), V::Float(..)) => (V::Float(*x as f64), b),
|
||||||
|
(V::Int(x), V::Complex(..)) => (V::Complex((*x as f64).into()), b),
|
||||||
|
(V::Ratio(x), V::Float(..)) => (V::Float(ratio_to_f64(*x)), b),
|
||||||
|
(V::Ratio(x), V::Complex(..)) => (V::Complex(ratio_to_f64(*x).into()), b),
|
||||||
|
(V::Float(x), V::Complex(..)) => (V::Complex((*x).into()), b),
|
||||||
|
(V::Ratio(..), V::Int(y)) => (a, V::Ratio((*y).into())),
|
||||||
|
(V::Float(..), V::Int(y)) => (a, V::Float(*y as f64)),
|
||||||
|
(V::Complex(..), V::Int(y)) => (a, V::Complex((*y as f64).into())),
|
||||||
|
(V::Float(..), V::Ratio(y)) => (a, V::Float(ratio_to_f64(*y))),
|
||||||
|
(V::Complex(..), V::Ratio(y)) => (a, V::Complex(ratio_to_f64(*y).into())),
|
||||||
|
(V::Complex(..), V::Float(y)) => (a, V::Complex((*y).into())),
|
||||||
|
_ => (a, b),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl Neg for Value {
|
||||||
|
type Output = Result<Self>;
|
||||||
|
fn neg(self) -> Self::Output {
|
||||||
|
use Value as V;
|
||||||
|
match self {
|
||||||
|
V::Int(x) => Ok(V::Int(-x)),
|
||||||
|
V::Ratio(x) => Ok(V::Ratio(-x)),
|
||||||
|
V::Float(x) => Ok(V::Float(-x)),
|
||||||
|
V::Complex(x) => Ok(V::Complex(-x)),
|
||||||
|
a => Err(anyhow!("cannot negate {a}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<Value> for Value {
|
||||||
|
type Output = Result<Self>;
|
||||||
|
fn add(self, rhs: Value) -> Self::Output {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Int(x + y)),
|
||||||
|
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x + y)),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x + y)),
|
||||||
|
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x + y)),
|
||||||
|
(l, r) => Err(anyhow!("cannot add {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sub<Value> for Value {
|
||||||
|
type Output = Result<Self>;
|
||||||
|
fn sub(self, rhs: Value) -> Self::Output {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Int(x - y)),
|
||||||
|
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x - y)),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x - y)),
|
||||||
|
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x - y)),
|
||||||
|
(l, r) => Err(anyhow!("cannot subtract {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mul<Value> for Value {
|
||||||
|
type Output = Result<Self>;
|
||||||
|
fn mul(self, rhs: Value) -> Self::Output {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Int(x * y)),
|
||||||
|
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x * y)),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x * y)),
|
||||||
|
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x * y)),
|
||||||
|
(l, r) => Err(anyhow!("cannot multiply {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Div<Value> for Value {
|
||||||
|
type Output = Result<Self>;
|
||||||
|
fn div(self, rhs: Value) -> Self::Output {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Ratio(Rational64::new(x, y))),
|
||||||
|
(V::Ratio(x), V::Ratio(y)) => Ok(V::Ratio(x / y)),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x / y)),
|
||||||
|
(V::Complex(x), V::Complex(y)) => Ok(V::Complex(x / y)),
|
||||||
|
(l, r) => Err(anyhow!("cannot divide {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
const fn ipow(n: i64, p: u64) -> i64 {
|
||||||
|
match (n, p) {
|
||||||
|
(0, 0) => panic!("power 0^0"),
|
||||||
|
(0, _) => 0,
|
||||||
|
(_, 0) => 1,
|
||||||
|
(n, p) if p > u32::MAX as u64 => {
|
||||||
|
let (lo, hi) = (p as u32, (p >> 32) as u32);
|
||||||
|
let (a, b) = (n.pow(lo), n.pow(hi));
|
||||||
|
a * b.pow(0x1_0000).pow(0x1_0000)
|
||||||
|
}
|
||||||
|
(n, p) => n.pow(p as u32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::cast_sign_loss)]
|
||||||
|
const fn rpow(n: i64, d: i64, p: i64) -> (i64, i64) {
|
||||||
|
match p {
|
||||||
|
0.. => (ipow(n, p as u64), ipow(d, p as u64)),
|
||||||
|
_ => (ipow(d, (-p) as u64), ipow(n, (-p) as u64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn modulo(self, rhs: Value) -> Result<Self> {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Int(x.rem_euclid(y))),
|
||||||
|
(V::Ratio(_x), V::Ratio(_y)) => todo!("ratio modulo"),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x.rem_euclid(y))),
|
||||||
|
(V::Complex(_x), V::Complex(_y)) => todo!("complex modulo"),
|
||||||
|
(l, r) => Err(anyhow!("cannot modulo {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn int_div(self, rhs: Value) -> Result<Self> {
|
||||||
|
use Value as V;
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y)) => Ok(V::Int(x.div_euclid(y))),
|
||||||
|
(V::Ratio(_x), V::Ratio(_y)) => todo!("ratio integer division"),
|
||||||
|
(V::Float(x), V::Float(y)) => Ok(V::Float(x.div_euclid(y))),
|
||||||
|
(V::Complex(_x), V::Complex(_y)) => todo!("complex integer division"),
|
||||||
|
(l, r) => Err(anyhow!("cannot integer divide {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pow(self, rhs: Value) -> Result<Self> {
|
||||||
|
use Value as V;
|
||||||
|
if let (V::Ratio(x), V::Int(y)) = (&self, &rhs) {
|
||||||
|
return Ok(V::Ratio(rpow(*(*x).numer(), *(*x).denom(), *y).into()));
|
||||||
|
}
|
||||||
|
match promote(self, rhs) {
|
||||||
|
(V::Int(x), V::Int(y))
|
||||||
|
=> Ok(V::Ratio(rpow(x, 1, y).into())),
|
||||||
|
(V::Float(x), V::Float(y))
|
||||||
|
=> Ok(V::Float(x.powf(y))),
|
||||||
|
(V::Ratio(x), V::Ratio(y))
|
||||||
|
=> Ok(V::Float(ratio_to_f64(x).powf(ratio_to_f64(y)))),
|
||||||
|
(V::Complex(x), V::Complex(y))
|
||||||
|
=> Ok(V::Complex(x.powc(y))),
|
||||||
|
(l, r) => Err(anyhow!("cannot exponentiate {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for Value {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
use Value as V;
|
||||||
|
use RangeType as Rty;
|
||||||
|
match (self, other) {
|
||||||
|
(V::Nil, V::Nil) => true,
|
||||||
|
(V::Bool(a), V::Bool(b)) => a == b,
|
||||||
|
(V::Int(a), V::Int(b)) => *a == *b,
|
||||||
|
(V::Ratio(a), V::Ratio(b)) => *a == *b,
|
||||||
|
(V::Float(a), V::Float(b)) => *a == *b,
|
||||||
|
(V::Complex(a), V::Complex(b)) => *a == *b,
|
||||||
|
(V::Int(a), V::Ratio(b)) => Rational64::from(*a) == *b,
|
||||||
|
(V::Ratio(a), V::Int(b)) => *a == Rational64::from(*b),
|
||||||
|
(V::Int(a), V::Float(b)) => *a as f64 == *b,
|
||||||
|
(V::Float(a), V::Int(b)) => *a == *b as f64,
|
||||||
|
(V::Int(a), V::Complex(b)) => Complex64::from(*a as f64) == *b,
|
||||||
|
(V::Complex(a), V::Int(b)) => *a == Complex64::from(*b as f64),
|
||||||
|
(V::Ratio(a), V::Float(b)) => ratio_to_f64(*a) == *b,
|
||||||
|
(V::Float(a), V::Ratio(b)) => *a == ratio_to_f64(*b),
|
||||||
|
(V::Ratio(a), V::Complex(b)) => Complex64::from(ratio_to_f64(*a)) == *b,
|
||||||
|
(V::Complex(a), V::Ratio(b)) => *a == Complex64::from(ratio_to_f64(*b)),
|
||||||
|
(V::Float(a), V::Complex(b)) => Complex64::from(*a) == *b,
|
||||||
|
(V::Complex(a), V::Float(b)) => *a == Complex64::from(*b),
|
||||||
|
(V::String(a), V::String(b)) => *a == *b,
|
||||||
|
(V::List(a), V::List(b)) => *a.borrow() == *b.borrow(),
|
||||||
|
(V::Symbol(a), V::Symbol(b)) => a == b,
|
||||||
|
(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::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,
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for Value {
|
||||||
|
#[allow(clippy::cast_precision_loss)]
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
use Value as V;
|
||||||
|
match (self, other) {
|
||||||
|
(V::Nil, V::Nil) => Some(Ordering::Equal),
|
||||||
|
(V::Bool(a), V::Bool(b)) => a.partial_cmp(b),
|
||||||
|
(V::Int(a), V::Int(b)) => a.partial_cmp(b),
|
||||||
|
(V::Ratio(a), V::Ratio(b)) => a.partial_cmp(b),
|
||||||
|
(V::Float(a), V::Float(b)) => a.partial_cmp(b),
|
||||||
|
(V::Int(a), V::Ratio(b)) => Rational64::from(*a).partial_cmp(b),
|
||||||
|
(V::Ratio(a), V::Int(b)) => a.partial_cmp(&Rational64::from(*b)),
|
||||||
|
(V::Int(a), V::Float(b)) => (*a as f64).partial_cmp(b),
|
||||||
|
(V::Float(a), V::Int(b)) => a.partial_cmp(&(*b as f64)),
|
||||||
|
(V::Ratio(a), V::Float(b)) => ratio_to_f64(*a).partial_cmp(b),
|
||||||
|
(V::Float(a), V::Ratio(b)) => a.partial_cmp(&ratio_to_f64(*b)),
|
||||||
|
(V::String(a), V::String(b)) => a.partial_cmp(b),
|
||||||
|
(V::List(a), V::List(b)) => a.borrow().partial_cmp(&*b.borrow()),
|
||||||
|
(V::Symbol(a), V::Symbol(b)) => a.partial_cmp(b),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Value {
|
||||||
|
pub fn val_cmp(&self, other: &Self) -> anyhow::Result<Ordering> {
|
||||||
|
match self.partial_cmp(other) {
|
||||||
|
Some(o) => Ok(o),
|
||||||
|
None => Err(anyhow!("cannot compare {self:?} and {other:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn index(&self, idx: Self) -> Result<Self> {
|
||||||
|
use Value as V;
|
||||||
|
match (self, idx) {
|
||||||
|
(_lhs, V::List(_l)) => todo!("assign index with list"),
|
||||||
|
(_lhs, V::Range(_r)) => todo!("assign index with range"),
|
||||||
|
(V::List(l), V::Int(i)) => {
|
||||||
|
let l = l.borrow();
|
||||||
|
if i >= 0 && (i as usize) < l.len() {
|
||||||
|
Ok(l[i as usize].clone())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("index {i} out of bounds for list of length {}", l.len()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(V::Range(r), V::Int(i)) => {
|
||||||
|
if i >= 0 && (
|
||||||
|
r.ty == RangeType::Endless
|
||||||
|
|| i < r.stop
|
||||||
|
|| (r.ty == RangeType::Closed && i == r.stop)
|
||||||
|
) {
|
||||||
|
Ok(Value::Int(r.start + i))
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("index {i} out of bounds for range {}", self))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(V::Table(t), i) => {
|
||||||
|
let t = t.borrow();
|
||||||
|
let i = i.try_into()?;
|
||||||
|
Ok(t.get(&i).cloned().unwrap_or(Value::Nil))
|
||||||
|
},
|
||||||
|
(lhs, rhs) => Err(anyhow!("cannot assign to index {lhs} with {rhs}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_index(&self, idx: Self, val: Self) -> Result<()> {
|
||||||
|
use Value as V;
|
||||||
|
match (self, idx) {
|
||||||
|
(V::List(l), V::Int(i)) => {
|
||||||
|
let mut l = l.borrow_mut();
|
||||||
|
if i >= 0 && (i as usize) < l.len() {
|
||||||
|
l[i as usize] = val;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("index {i} out of bounds for list of length {}", l.len()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(V::Table(t), i) => {
|
||||||
|
let mut t = t.borrow_mut();
|
||||||
|
let i = i.try_into()?;
|
||||||
|
t.insert(i, val);
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
(l, r) => Err(anyhow!("cannot index {l:?} with {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn concat(&self, other: &Self) -> Result<Self> {
|
||||||
|
use Value as V;
|
||||||
|
match (self, other) {
|
||||||
|
(V::List(l1), V::List(l2)) => {
|
||||||
|
let mut l = l1.borrow().clone();
|
||||||
|
l.extend_from_slice(&l2.borrow());
|
||||||
|
Ok(V::List(Rc::new(RefCell::new(l))))
|
||||||
|
},
|
||||||
|
(V::String(s1), V::String(s2)) => {
|
||||||
|
let mut s = s1.as_ref().to_owned();
|
||||||
|
s.push_str(s2);
|
||||||
|
Ok(V::String(s.into()))
|
||||||
|
}
|
||||||
|
(l, r) => Err(anyhow!("cannot concatenate {l:?} and {r:?}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range(&self, other: &Self, closed: bool) -> Result<Self> {
|
||||||
|
if let (Value::Int(start), Value::Int(stop)) = (self, other) {
|
||||||
|
let ty = if closed { RangeType::Closed } else { RangeType::Open };
|
||||||
|
Ok(Value::Range(Range { start: *start, stop: *stop, ty }))
|
||||||
|
} else {
|
||||||
|
bail!("cannot create range between {self} and {other}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn range_endless(&self) -> Result<Self> {
|
||||||
|
if let Value::Int(start) = self {
|
||||||
|
Ok(Value::Range(Range { start: *start, stop: 0, ty: RangeType::Endless }))
|
||||||
|
} else {
|
||||||
|
bail!("cannot create endless range from {self}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_iter_function(self) -> Result<Self> {
|
||||||
|
match self {
|
||||||
|
Self::Function(_) | Self::NativeFunc(_) => Ok(self),
|
||||||
|
Self::Range(range) => {
|
||||||
|
let range_iter = RefCell::new(range.into_iter());
|
||||||
|
let f = move |_, _| {
|
||||||
|
if let Some(v) = range_iter.borrow_mut().next() {
|
||||||
|
Ok(Value::Int(v))
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Value::NativeFunc(Rc::new(NativeFunc {
|
||||||
|
arity: 0,
|
||||||
|
f: Box::new(f),
|
||||||
|
})))
|
||||||
|
},
|
||||||
|
Self::List(list) => {
|
||||||
|
let idx = RefCell::new(0);
|
||||||
|
let f = move |_, _| {
|
||||||
|
let i = *idx.borrow();
|
||||||
|
if let Some(v) = list.borrow().get(i) {
|
||||||
|
*idx.borrow_mut() += 1;
|
||||||
|
Ok(v.clone())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Value::NativeFunc(Rc::new(NativeFunc {
|
||||||
|
arity: 0,
|
||||||
|
f: Box::new(f),
|
||||||
|
})))
|
||||||
|
},
|
||||||
|
Self::Table(table) => {
|
||||||
|
let keys: Vec<HashValue> = table.borrow().keys().cloned().collect();
|
||||||
|
let keys = RefCell::new(keys.into_iter());
|
||||||
|
let f = move |_, _| {
|
||||||
|
if let Some(v) = keys.borrow_mut().next() {
|
||||||
|
Ok(v.into_inner())
|
||||||
|
} else {
|
||||||
|
Ok(Value::Nil)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(Value::NativeFunc(Rc::new(NativeFunc {
|
||||||
|
arity: 0,
|
||||||
|
f: Box::new(f),
|
||||||
|
})))
|
||||||
|
},
|
||||||
|
_ => bail!("cannot iterate {self}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
257
talc-lang/src/vm.rs
Normal file
257
talc-lang/src/vm.rs
Normal file
|
@ -0,0 +1,257 @@
|
||||||
|
use std::{rc::Rc, cmp::Ordering, cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
|
use crate::{chunk::Instruction, value::{Value, Function}, ast::{BinaryOp, UnaryOp}, symbol::Symbol};
|
||||||
|
|
||||||
|
use anyhow::{Result, anyhow, bail};
|
||||||
|
|
||||||
|
struct CallFrame {
|
||||||
|
locals: Vec<Value>,
|
||||||
|
func: Rc<Function>,
|
||||||
|
ip: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Vm {
|
||||||
|
stack: Vec<Value>,
|
||||||
|
call_stack: Vec<CallFrame>,
|
||||||
|
stack_max: usize,
|
||||||
|
globals: HashMap<Symbol, Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result<Value> {
|
||||||
|
match o {
|
||||||
|
BinaryOp::Add => a + b,
|
||||||
|
BinaryOp::Sub => a - b,
|
||||||
|
BinaryOp::Mul => a * b,
|
||||||
|
BinaryOp::Div => a / b,
|
||||||
|
BinaryOp::Mod => a.modulo(b),
|
||||||
|
BinaryOp::IntDiv => a.int_div(b),
|
||||||
|
BinaryOp::Pow => a.pow(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)),
|
||||||
|
BinaryOp::Ge => a.val_cmp(&b).map(|o| Value::Bool(o != Ordering::Less)),
|
||||||
|
BinaryOp::Lt => a.val_cmp(&b).map(|o| Value::Bool(o == Ordering::Less)),
|
||||||
|
BinaryOp::Le => a.val_cmp(&b).map(|o| Value::Bool(o != Ordering::Greater)),
|
||||||
|
BinaryOp::Range => a.range(&b, false),
|
||||||
|
BinaryOp::RangeIncl => a.range(&b, true),
|
||||||
|
BinaryOp::Concat => a.concat(&b),
|
||||||
|
BinaryOp::Append => todo!("append"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unary_op(o: UnaryOp, a: Value) -> Result<Value> {
|
||||||
|
match o {
|
||||||
|
UnaryOp::Neg => -a,
|
||||||
|
UnaryOp::Not => Ok(Value::Bool(!a.truthy())),
|
||||||
|
UnaryOp::RangeEndless => a.range_endless(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vm {
|
||||||
|
pub fn new(stack_max: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
stack: Vec::with_capacity(16),
|
||||||
|
call_stack: Vec::with_capacity(16),
|
||||||
|
globals: HashMap::with_capacity(16),
|
||||||
|
stack_max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_global(&mut self, name: Symbol, val: Value) {
|
||||||
|
self.globals.insert(name, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_global(&self, name: Symbol) -> Option<&Value> {
|
||||||
|
self.globals.get(&name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn push(&mut self, v: Value) {
|
||||||
|
self.stack.push(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pop(&mut self) -> Value {
|
||||||
|
self.stack.pop().expect("temporary stack underflow")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn pop_n(&mut self, n: usize) -> Vec<Value> {
|
||||||
|
let res = self.stack.split_off(self.stack.len() - n);
|
||||||
|
assert!(res.len() == n, "temporary stack underflow");
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn debug_instr(&self, frame: &CallFrame, instr: Instruction) {
|
||||||
|
let framecode = (Rc::as_ptr(&frame.func) as usize >> 4) & 0xffff;
|
||||||
|
println!("({:04x}) {:04}: {instr}", framecode, frame.ip);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, func: Rc<Function>) -> Result<Value> {
|
||||||
|
use Instruction as I;
|
||||||
|
|
||||||
|
assert!(func.arity == 0, "root function must not take arguments");
|
||||||
|
let mut frame = CallFrame {
|
||||||
|
func: func.clone(),
|
||||||
|
locals: Vec::with_capacity(16),
|
||||||
|
ip: 0,
|
||||||
|
};
|
||||||
|
frame.locals.push(Value::Function(func));
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let instr = frame.func.chunk.instrs[frame.ip];
|
||||||
|
self.debug_instr(&frame, instr);
|
||||||
|
frame.ip += 1;
|
||||||
|
match instr {
|
||||||
|
I::Nop => (),
|
||||||
|
I::LoadLocal(n)
|
||||||
|
=> self.push(frame.locals[usize::from(n)].clone()),
|
||||||
|
I::StoreLocal(n)
|
||||||
|
=> frame.locals[usize::from(n)] = self.pop(),
|
||||||
|
I::NewLocal
|
||||||
|
=> frame.locals.push(self.pop()),
|
||||||
|
I::DropLocal(n)
|
||||||
|
=> frame.locals.truncate(frame.locals.len() - usize::from(n)),
|
||||||
|
I::LoadGlobal(s) => {
|
||||||
|
let sym = unsafe { s.to_symbol_unchecked() };
|
||||||
|
let v = self.globals.get(&sym)
|
||||||
|
.ok_or_else(|| anyhow!("global not defined"))?.clone();
|
||||||
|
self.push(v);
|
||||||
|
},
|
||||||
|
I::StoreGlobal(s) => {
|
||||||
|
let sym = unsafe { s.to_symbol_unchecked() };
|
||||||
|
let v = self.pop();
|
||||||
|
self.globals.insert(sym, v);
|
||||||
|
},
|
||||||
|
I::Const(n)
|
||||||
|
=> self.push(frame.func.chunk.consts[usize::from(n)].clone()),
|
||||||
|
I::Nil => self.push(Value::Nil),
|
||||||
|
I::Bool(b) => self.push(Value::Bool(b)),
|
||||||
|
I::Symbol(n) => {
|
||||||
|
let sym = unsafe { Symbol::from_id_unchecked(u32::from(n)) };
|
||||||
|
self.push(Value::Symbol(sym));
|
||||||
|
},
|
||||||
|
I::Int(n) => self.push(Value::Int(i64::from(n))),
|
||||||
|
I::Dup => self.push(self.stack[self.stack.len() - 1].clone()),
|
||||||
|
I::DupTwo => {
|
||||||
|
self.push(self.stack[self.stack.len() - 2].clone());
|
||||||
|
self.push(self.stack[self.stack.len() - 2].clone());
|
||||||
|
},
|
||||||
|
I::Drop(n) => for _ in 0..u32::from(n) { self.pop(); },
|
||||||
|
I::Swap => {
|
||||||
|
let len = self.stack.len();
|
||||||
|
self.stack.swap(len - 1, len - 2);
|
||||||
|
},
|
||||||
|
I::BinaryOp(op) => {
|
||||||
|
let b = self.pop();
|
||||||
|
let a = self.pop();
|
||||||
|
self.push(binary_op(op, a, b)?);
|
||||||
|
},
|
||||||
|
I::UnaryOp(op) => {
|
||||||
|
let a = self.pop();
|
||||||
|
self.push(unary_op(op, a)?);
|
||||||
|
},
|
||||||
|
I::NewList(n) => {
|
||||||
|
let list = self.pop_n(n as usize);
|
||||||
|
self.push(Value::List(Rc::new(RefCell::new(list))));
|
||||||
|
},
|
||||||
|
I::GrowList(n) => {
|
||||||
|
let ext = self.pop_n(n as usize);
|
||||||
|
let list = self.pop();
|
||||||
|
let Value::List(list) = list else { panic!("not a list") };
|
||||||
|
list.borrow_mut().extend(ext);
|
||||||
|
self.push(Value::List(list));
|
||||||
|
},
|
||||||
|
I::NewTable(n) => {
|
||||||
|
let mut table = HashMap::new();
|
||||||
|
for _ in 0..n {
|
||||||
|
let v = self.pop();
|
||||||
|
let k = self.pop().try_into()?;
|
||||||
|
table.insert(k, v);
|
||||||
|
}
|
||||||
|
self.push(Value::Table(Rc::new(RefCell::new(table))));
|
||||||
|
},
|
||||||
|
I::GrowTable(n) => {
|
||||||
|
let mut ext = self.pop_n(2 * n as usize);
|
||||||
|
let table = self.pop();
|
||||||
|
let Value::Table(table) = table else { panic!("not a table") };
|
||||||
|
for _ in 0..n {
|
||||||
|
// can't panic:
|
||||||
|
let v = ext.pop().unwrap();
|
||||||
|
let k = ext.pop().unwrap().try_into()?;
|
||||||
|
table.borrow_mut().insert(k, v);
|
||||||
|
}
|
||||||
|
self.push(Value::Table(table));
|
||||||
|
},
|
||||||
|
I::Index => {
|
||||||
|
let idx = self.pop();
|
||||||
|
let ct = self.pop();
|
||||||
|
self.push(ct.index(idx)?);
|
||||||
|
},
|
||||||
|
I::StoreIndex => {
|
||||||
|
let v = self.pop();
|
||||||
|
let idx = self.pop();
|
||||||
|
let ct = self.pop();
|
||||||
|
ct.store_index(idx, v.clone())?;
|
||||||
|
self.push(v);
|
||||||
|
},
|
||||||
|
I::Jump(n) => frame.ip = usize::from(n),
|
||||||
|
I::JumpTrue(n) => if self.pop().truthy() { frame.ip = usize::from(n) },
|
||||||
|
I::JumpFalse(n) => if !self.pop().truthy() { frame.ip = usize::from(n) },
|
||||||
|
I::IterBegin => {
|
||||||
|
let v = self.pop().to_iter_function()?;
|
||||||
|
self.push(v);
|
||||||
|
},
|
||||||
|
I::IterTest(n) => {
|
||||||
|
let v = &self.stack[self.stack.len() - 1];
|
||||||
|
if v == &Value::Nil {
|
||||||
|
self.pop();
|
||||||
|
self.pop();
|
||||||
|
frame.ip = usize::from(n);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
I::Call(n) => {
|
||||||
|
let n = usize::from(n);
|
||||||
|
let func = &self.stack[self.stack.len() - n - 1];
|
||||||
|
if let Value::NativeFunc(nf) = func {
|
||||||
|
let nf = nf.clone();
|
||||||
|
let args = self.pop_n(n);
|
||||||
|
let func = self.pop();
|
||||||
|
if nf.arity != n {
|
||||||
|
bail!("function call with wrong argument count");
|
||||||
|
}
|
||||||
|
let res = (nf.f)(func, args)?;
|
||||||
|
self.stack.push(res);
|
||||||
|
} else if let Value::Function(func) = func {
|
||||||
|
if func.arity != n {
|
||||||
|
bail!("function call with wrong argument count");
|
||||||
|
}
|
||||||
|
if self.call_stack.len() + 1 >= self.stack_max {
|
||||||
|
bail!("call stack overflow")
|
||||||
|
}
|
||||||
|
self.call_stack.push(frame);
|
||||||
|
let func = func.clone();
|
||||||
|
let args = self.pop_n(n + 1);
|
||||||
|
frame = CallFrame {
|
||||||
|
func,
|
||||||
|
ip: 0,
|
||||||
|
locals: args,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
bail!("attempt to call non-function {}", func);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
I::Return if self.call_stack.is_empty() => {
|
||||||
|
let v = self.pop();
|
||||||
|
self.stack.clear();
|
||||||
|
return Ok(v);
|
||||||
|
},
|
||||||
|
I::Return => {
|
||||||
|
// can't panic: checked that call_stack wasn't empty
|
||||||
|
frame = self.call_stack.pop().unwrap();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
talc-std/Cargo.toml
Normal file
6
talc-std/Cargo.toml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
[package]
|
||||||
|
name = "talc-std"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
0
talc-std/src/lib.rs
Normal file
0
talc-std/src/lib.rs
Normal file
Loading…
Reference in a new issue