commit 7aca8b423c8d5f6f14ac8ec2007dc2ed522d317c Author: trimill Date: Wed Feb 21 11:04:18 2024 -0500 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..28234b7 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1809e70 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = [ + "talc-lang", + "talc-bin", + "talc-std", +] +resolver = "2" diff --git a/talc-bin/Cargo.toml b/talc-bin/Cargo.toml new file mode 100644 index 0000000..d395aea --- /dev/null +++ b/talc-bin/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "talc-bin" +version = "0.1.0" +edition = "2021" + +[dependencies] +talc-lang = { path = "../talc-lang" } +anyhow = "1.0" diff --git a/talc-bin/src/main.rs b/talc-bin/src/main.rs new file mode 100644 index 0000000..50fa494 --- /dev/null +++ b/talc-bin/src/main.rs @@ -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) -> anyhow::Result { + 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}"), + } + } +} diff --git a/talc-lang/Cargo.toml b/talc-lang/Cargo.toml new file mode 100644 index 0000000..c12de10 --- /dev/null +++ b/talc-lang/Cargo.toml @@ -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"]} diff --git a/talc-lang/build.rs b/talc-lang/build.rs new file mode 100644 index 0000000..143899f --- /dev/null +++ b/talc-lang/build.rs @@ -0,0 +1,3 @@ +fn main() -> Result<(), Box> { + lalrpop::process_root() +} diff --git a/talc-lang/src/ast.rs b/talc-lang/src/ast.rs new file mode 100644 index 0000000..6bfdb5b --- /dev/null +++ b/talc-lang/src/ast.rs @@ -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>), + BinaryOp(BinaryOp, Box>, Box>), + + Assign(Option, Box>, Box>), + AssignVar(&'s str, Box>), + AssignGlobal(&'s str, Box>), + + Index(Box>, Box>), + FnCall(Box>, Vec>), + Pipe(Box>, Box>), + + Block(Vec>), + List(Vec>), + Table(Vec<(Expr<'s>, Expr<'s>)>), + + And(Box>, Box>), + Or(Box>, Box>), + If(Box>, Box>, Option>>), + While(Box>, Box>), + For(&'s str, Box>, Box>), + Lambda(Vec<&'s str>, Box>), +} + +#[derive(Debug)] +pub enum LValue<'s> { + Ident(&'s str), + Index(Box>, Box>), +} diff --git a/talc-lang/src/chunk.rs b/talc-lang/src/chunk.rs new file mode 100644 index 0000000..3f370fe --- /dev/null +++ b/talc-lang/src/chunk.rs @@ -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 for u32 { + #[inline] + fn from(v: Arg24) -> Self { + u32::from_le_bytes([v.0[0], v.0[1], v.0[2], 0]) + } +} + +impl From for usize { + #[inline] + fn from(v: Arg24) -> Self { + u32::from(v) as usize + } +} + +impl From 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 for i64 { + #[inline] + fn from(v: Arg24) -> Self { + i32::from(v) as i64 + } +} + +impl TryFrom for Symbol { + type Error = (); + #[inline] + fn try_from(value: Arg24) -> Result { + 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, + pub instrs: Vec, +} + +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(()) + } +} + + diff --git a/talc-lang/src/compiler.rs b/talc-lang/src/compiler.rs new file mode 100644 index 0000000..69b8c9a --- /dev/null +++ b/talc-lang/src/compiler.rs @@ -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, + 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, + globals: Vec, +} + +pub fn repl(expr: &Expr, globals: &[Local]) -> Result<(Function, Vec)> { + 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 { + 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 { + 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, 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(()) + } + +} diff --git a/talc-lang/src/gc.rs b/talc-lang/src/gc.rs new file mode 100644 index 0000000..e69de29 diff --git a/talc-lang/src/lib.rs b/talc-lang/src/lib.rs new file mode 100644 index 0000000..4528984 --- /dev/null +++ b/talc-lang/src/lib.rs @@ -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; diff --git a/talc-lang/src/parser.lalrpop b/talc-lang/src/parser.lalrpop new file mode 100644 index 0000000..7d4f102 --- /dev/null +++ b/talc-lang/src/parser.lalrpop @@ -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> = { + LineSeparator* LineSeparator+)*> 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> = Assign; + +// +// assignment +// + + +Assign: Box> = { + => Box::new(Expr::Assign(o, l, r)), + "var" "=" => Box::new(Expr::AssignVar(i, r)), + "global" "=" => Box::new(Expr::AssignGlobal(i, r)), + Or, +} + +LValue: Box> = { + => Box::new(LValue::Ident(<>)), + "!" => Box::new(LValue::Index(l, r)), + "." => Box::new(LValue::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), +} + +AssignOp: Option = { + "=" => 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> = { + "or" => Box::new(Expr::Or(l, r)), + And, +} + +// and +And: Box> = { + "and" => Box::new(Expr::And(l, r)), + UnaryNot, +} + +// not +UnaryNot: Box> = { + "not" => Box::new(Expr::UnaryOp(UnaryOp::Not, r)), + Pipe, +} + +// | +Pipe: Box> = { + "|" => Box::new(Expr::Pipe(l, r)), + BinaryCompare, +} + + +// == != > < >= <= +BinaryCompare: Box> = { + => 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> = { + "++" => Box::new(Expr::BinaryOp(BinaryOp::Concat, l, r)), + BinaryAppend, +} + +// append ( & ) +BinaryAppend: Box> = { + "&" => Box::new(Expr::BinaryOp(BinaryOp::Append, l, r)), + BinaryRange, +} + + + +// .. ..= ..* +BinaryRange: Box> = { + => Box::new(Expr::BinaryOp(o, l, r)), + "..*" => Box::new(Expr::UnaryOp(UnaryOp::RangeEndless, l)), + BinaryAdd, +} + +RangeOp: BinaryOp = { + ".." => BinaryOp::Range, + "..=" => BinaryOp::RangeIncl, +} + + +// + - +BinaryAdd: Box> = { + => Box::new(Expr::BinaryOp(o, l, r)), + BinaryMul, +} + +AddOp: BinaryOp = { + "+" => BinaryOp::Add, + "-" => BinaryOp::Sub, +} + + +// * / % +BinaryMul: Box> = { + => Box::new(Expr::BinaryOp(o, l, r)), + UnaryMinus, +} + +MulOp: BinaryOp = { + "*" => BinaryOp::Mul, + "/" => BinaryOp::Div, + "//" => BinaryOp::IntDiv, + "%" => BinaryOp::Mod, +} + + +// unary- +UnaryMinus: Box> = { + "-" => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)), + BinaryPow, +} + + +// power ( ^ ) +BinaryPow: Box> = { + "^" => Box::new(Expr::BinaryOp(BinaryOp::Pow, l, r)), + BinaryIndex, +} + + +// index ( ! ) +BinaryIndex: Box> = { + "!" => Box::new(Expr::Index(l, r)), + FunctionCall, +} + +// unary- +UnaryMinus2: Box> = { + "-" => Box::new(Expr::UnaryOp(UnaryOp::Neg, r)), + FunctionCall, +} + + +// +// things +// + + +// function call +FunctionCall: Box> = { + "(" ")" => Box::new(Expr::FnCall(l, r)), + FieldAccess, +} + + +// field access +FieldAccess: Box> = { + "." => Box::new(Expr::Index(l, Box::new(Expr::Literal(Value::Symbol(Symbol::get(r)))))), + Term, +} + + +// +// base +// + + +Term: Box> = { + Identifier => Box::new(Expr::Ident(<>)), + TermNotIdent, +} + +TermNotIdent: Box> = { + "(" ")" => <>, + "[" "]" => Box::new(Expr::List(<>)), + "{" "}" => Box::new(Expr::Table(<>)), + "$" => Box::new(Expr::Ident("$")), + ":" "(" ")" => Box::new(Expr::Lambda(vec!["$"], e)), + "\\" "->" => Box::new(Expr::Lambda(xs, e)), + "do" "end" => <>, + "if" => <>, + "while" "do" "end" => Box::new(Expr::While(a, b)), + "for" "in" "do" "end" => Box::new(Expr::For(v, a, b)), + Literal => Box::new(Expr::Literal(<>)), +} + +IfStmtChain: Box> = { + "then" "end" => Box::new(Expr::If(a, b, None)), + "then" "else" "end" => Box::new(Expr::If(a, b, Some(c))), + "then" "elif" => Box::new(Expr::If(a, b, Some(c))), +} + +ExprList: Vec> = { + ",")*> => { + 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>)> = { + ",")*> => { + if let Some(x) = x { xs.push(x) }; + xs + } +} + +TableItem: (Expr<'input>, Expr<'input>) = { + "=" => (k, *v), +} + +TableKey: Expr<'input> = { + Identifier => Expr::Literal(Value::Symbol(Symbol::get(<>))), + TermNotIdent => *<>, +} + +IdentList: Vec<&'input str> = { + ",")*> + => { 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()), + =>? { + 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 = { + 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()), +} diff --git a/talc-lang/src/parser_util.rs b/talc-lang/src/parser_util.rs new file mode 100644 index 0000000..d53def7 --- /dev/null +++ b/talc-lang/src/parser_util.rs @@ -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 { + 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 { + 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 { + let mut s = String::new(); + for c in f.chars() { + if c != '_' { + s.push(c); + } + } + i64::from_str_radix(&s, radix) +} diff --git a/talc-lang/src/symbol.rs b/talc-lang/src/symbol.rs new file mode 100644 index 0000000..ab1d377 --- /dev/null +++ b/talc-lang/src/symbol.rs @@ -0,0 +1,76 @@ +use std::{sync::{OnceLock, Arc, Mutex}, collections::HashMap}; + +#[derive(Default)] +struct SymbolTable { + names: Vec>, + values: HashMap, Symbol> +} + +static SYM_TABLE: OnceLock> = 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 { + 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 { + 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 = 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 { + 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 { + let table = get_table().lock().expect("couldn't lock symbol table"); + table.names.get(self.0 as usize).cloned().expect("symbol does not exist") + } +} diff --git a/talc-lang/src/value.rs b/talc-lang/src/value.rs new file mode 100644 index 0000000..0aa5540 --- /dev/null +++ b/talc-lang/src/value.rs @@ -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>>; +type RcTable = Rc>>; + +#[derive(Debug)] +pub struct Function { + pub arity: usize, + pub chunk: Chunk, +} + +pub struct NativeFunc { + pub arity: usize, + pub f: Box) -> Result>, +} + +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 for HashValue { + type Error = anyhow::Error; + fn try_from(value: Value) -> std::result::Result { + 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(&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 { + 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>; + 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), + List(RcList), + Table(RcTable), + Function(Rc), + NativeFunc(Rc), +} + +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, ""), + Value::NativeFunc(_) => write!(f, ""), + } + } +} + +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; + 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 for Value { + type Output = Result; + 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 for Value { + type Output = Result; + 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 for Value { + type Output = Result; + 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 for Value { + type Output = Result; + 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 { + 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 { + 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 { + 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 { + 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 { + match self.partial_cmp(other) { + Some(o) => Ok(o), + None => Err(anyhow!("cannot compare {self:?} and {other:?}")), + } + } + + pub fn index(&self, idx: Self) -> Result { + 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 { + 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 { + 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 { + 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 { + 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 = 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}"), + } + } +} diff --git a/talc-lang/src/vm.rs b/talc-lang/src/vm.rs new file mode 100644 index 0000000..ac0db27 --- /dev/null +++ b/talc-lang/src/vm.rs @@ -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, + func: Rc, + ip: usize, +} + +pub struct Vm { + stack: Vec, + call_stack: Vec, + stack_max: usize, + globals: HashMap, +} + +pub fn binary_op(o: BinaryOp, a: Value, b: Value) -> Result { + 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 { + 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 { + 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) -> Result { + 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(); + }, + } + } + } + +} diff --git a/talc-std/Cargo.toml b/talc-std/Cargo.toml new file mode 100644 index 0000000..d2ed7c0 --- /dev/null +++ b/talc-std/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "talc-std" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/talc-std/src/lib.rs b/talc-std/src/lib.rs new file mode 100644 index 0000000..e69de29