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