initial commit

This commit is contained in:
trimill 2024-02-21 11:04:18 -05:00
commit 7aca8b423c
Signed by: trimill
GPG key ID: 4F77A16E17E10BCB
19 changed files with 2941 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

695
Cargo.lock generated Normal file
View 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
View file

@ -0,0 +1,7 @@
[workspace]
members = [
"talc-lang",
"talc-bin",
"talc-std",
]
resolver = "2"

8
talc-bin/Cargo.toml Normal file
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
lalrpop::process_root()
}

48
talc-lang/src/ast.rs Normal file
View 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
View 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
View 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
View file

23
talc-lang/src/lib.rs Normal file
View 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;

View 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()),
}

View 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
View 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
View 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
View 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
View file

@ -0,0 +1,6 @@
[package]
name = "talc-std"
version = "0.1.0"
edition = "2021"
[dependencies]

0
talc-std/src/lib.rs Normal file
View file