Almost Alpha

Massive improvements across the board.

One day I'll adopt incremental commits.
This commit is contained in:
lbfalvy committed 2024-02-24 00:31:00 +00:00
1 parent c279301583
commit ed0d64d52e
147 files changed
+4121 -4203

No files matched your search

Generated
+159 -257
View File
@@ -4,20 +4,21 @@ version = 3
[[package]]
name = "ahash"
version = "0.8.3"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "0.7.20"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
@@ -30,9 +31,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anstream"
version = "0.6.4"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
@@ -44,33 +45,33 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.0"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.0"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.0"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.1"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys",
@@ -99,12 +100,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -115,21 +110,21 @@ dependencies = [
]
[[package]]
name = "bstr"
version = "1.5.0"
name = "bound"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5"
checksum = "6021ae095f16f54aaae093f4c723700430e71eab731d3b0a07fc8fe258fd5110"
[[package]]
name = "bstr"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -138,9 +133,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.4.7"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
@@ -148,9 +143,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.4.7"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
@@ -160,21 +155,21 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "4.4.7"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.13",
"syn 2.0.50",
]
[[package]]
name = "clap_lex"
version = "0.6.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
@@ -182,15 +177,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]]
name = "concurrent-queue"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "const_format"
version = "0.2.32"
@@ -213,45 +199,37 @@ dependencies = [
[[package]]
name = "cpufeatures"
version = "0.2.7"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.3"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.15"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.16"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294"
dependencies = [
"cfg-if",
]
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
@@ -275,42 +253,15 @@ dependencies = [
[[package]]
name = "dyn-clone"
version = "1.0.11"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30"
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
[[package]]
name = "either"
version = "1.8.1"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "generic-array"
@@ -324,22 +275,22 @@ dependencies = [
[[package]]
name = "globset"
version = "0.4.10"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.14.2"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
@@ -362,19 +313,20 @@ dependencies = [
[[package]]
name = "intern-all"
version = "0.2.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d79d55e732e243f6762e0fc7b245bfd9fa0e0246356ed6cfdba62d9c707e36c1"
checksum = "20c9bf7d7b0572f7b4398fddc93ac1a200a92d1ba319a27dac04649b2223c0f6"
dependencies = [
"hashbrown",
"lazy_static",
"trait-set",
]
[[package]]
name = "itertools"
version = "0.12.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
dependencies = [
"either",
]
@@ -397,48 +349,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.148"
version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
[[package]]
name = "linux-raw-sys"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.17"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.5.0"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c"
dependencies = [
"autocfg",
]
[[package]]
name = "memorize"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b505dbd3a88b64417e29469500c32af2b538ba5f703100761f657540a1c442d"
dependencies = [
"hashbrown",
]
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "never"
@@ -448,9 +373,9 @@ checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
[[package]]
name = "num-traits"
version = "0.2.15"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
@@ -469,20 +394,19 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "orchidlang"
version = "0.2.2"
version = "0.3.0"
dependencies = [
"bound",
"clap",
"const_format",
"dyn-clone",
"hashbrown",
"intern-all",
"itertools",
"memorize",
"never",
"once_cell",
"ordered-float",
"paste",
"polling",
"rayon",
"rust-embed",
"substack",
@@ -494,62 +418,42 @@ dependencies = [
[[package]]
name = "ordered-float"
version = "4.1.0"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5"
checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
dependencies = [
"num-traits",
]
[[package]]
name = "paste"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pin-project-lite"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "polling"
version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531"
dependencies = [
"cfg-if",
"concurrent-queue",
"pin-project-lite",
"rustix",
"tracing",
"windows-sys",
]
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "proc-macro2"
version = "1.0.56"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.26"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.8.0"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
@@ -557,9 +461,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
@@ -571,7 +475,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
"bitflags",
]
[[package]]
@@ -581,10 +485,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "regex"
version = "1.7.3"
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
@@ -593,15 +497,15 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.29"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rust-embed"
version = "8.0.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -610,41 +514,28 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.0.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.13",
"syn 2.0.50",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.0.0"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
dependencies = [
"globset",
"sha2",
"walkdir",
]
[[package]]
name = "rustix"
version = "0.38.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "same-file"
version = "1.0.6"
@@ -655,22 +546,30 @@ dependencies = [
]
[[package]]
name = "scopeguard"
version = "1.2.0"
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde"
version = "1.0.160"
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
]
[[package]]
name = "sha2"
version = "0.10.6"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
@@ -679,9 +578,9 @@ dependencies = [
[[package]]
name = "strsim"
version = "0.10.0"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[package]]
name = "substack"
@@ -702,9 +601,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.13"
version = "2.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
dependencies = [
"proc-macro2",
"quote",
@@ -742,23 +641,6 @@ dependencies = [
"winapi 0.2.8",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
[[package]]
name = "trait-set"
version = "0.3.0"
@@ -772,21 +654,21 @@ dependencies = [
[[package]]
name = "typenum"
version = "1.16.0"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.8"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-xid"
@@ -808,9 +690,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.3.3"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
@@ -846,9 +728,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi 0.3.9",
]
@@ -861,18 +743,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.48.0"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
@@ -885,42 +767,62 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
]
+17 -19
View File
@@ -1,15 +1,13 @@
[package]
name = "orchidlang"
version = "0.2.2"
version = "0.3.0"
edition = "2021"
license = "GPL-3.0-or-later"
license = "GPL-3.0"
repository = "https://github.com/lbfalvy/orchid"
description = """
An embeddable pure functional scripting language
"""
authors = [
"Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"
]
authors = ["Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"]
[lib]
path = "src/lib.rs"
@@ -23,21 +21,21 @@ doc = false
[dependencies]
hashbrown = "0.14"
ordered-float = "4.1"
ordered-float = "4.2"
itertools = "0.12"
dyn-clone = "1.0"
clap = { version = "4.4", features = ["derive"] }
trait-set = "0.3"
paste = "1.0"
rust-embed = { version = "8.0", features = ["include-exclude"] }
take_mut = "0.2.2"
unicode-segmentation = "1.10.1"
polling = "3.3.0"
never = "0.1.0"
memorize = "2.0.0"
substack = "1.1.0"
rayon = "1.8.0"
intern-all = "0.2.0"
once_cell = "1.19.0"
const_format = "0.2.32"
termsize = "0.1.6"
rust-embed = { version = "8.2", features = ["include-exclude"] }
take_mut = "0.2"
unicode-segmentation = "1.11"
never = "0.1"
substack = "1.1"
intern-all = "0.4.1"
once_cell = "1.19"
const_format = "0.2"
bound = "0.5"
# Dependencies of orcx
clap = { version = "4.5", features = ["derive"] }
rayon = "1.8"
termsize = "0.1"
+12 -7
View File
@@ -2,15 +2,15 @@ This document is a wishlist, its items aren't ordered in any way other than inli
# Language
## Typeclasses
Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js
None! Thanks to very aggressive modularization, changes to the core language are almost never needed to achieve specific goals
# Rules
## Placeholder constraints
Simultaneously match a pattern to a subexpression and give it a name to copy it over
- Copy unique 1->1 names over by default to preserve their location info
## Role annotations
Some way for the rule repository to record the roles certain tokens took in patterns, and some way for the macros to attach semantic information to these roles, so that dev tooling can understand the purpose of each token
# STL
@@ -20,11 +20,8 @@ Functions for each command type which destructure it and pass it to an Orchid ca
## Runtime error handling
result? multipath cps utils? Not sure yet.
## Pattern matching
This was the main trick in Orchid, still want to do it, still need to polish the language first
## Macro error handling
Error tokens with rules to lift them out. Kinda depends on preservation of location info in rules to be really useful
Error tokens with rules to lift them out.
# Systems
@@ -36,3 +33,11 @@ Event-driven I/O with single-fire events and resubscription to relay backpressur
## New: Marshall
Serialization of Orchid data, including code, given customizable sets of serializable foreign items. Alternatively, code reflection so that all this can go in the STL
# Miscellaneous
## Language server
A very rudimentary language server to visually indicate what the macros do to your code
## Type checker
In the distant hopeful future, I'd like to support a type system
+1
View File
@@ -1 +1,2 @@
type-complexity-threshold = 300
avoid-breaking-exported-api = false
+8 -10
View File
@@ -1,14 +1,12 @@
import std::(to_float, to_string, panic, string::char_at)
import std::(panic, string::char_at)
import std::conv::(to_float, to_string)
export const main := do{
cps print "left operand: ";
cps data = readln;
cps data = prompt "left operand: ";
let a = to_float data;
cps print "operator: ";
cps op = readln;
cps println ("you selected \"" ++ op ++ "\"");
cps print "right operand: ";
cps data = readln;
cps op = prompt "operator: ";
cps println "you selected \"${op}\"";
cps data = prompt "right operand: ";
let b = to_float data;
let result = (
if op == "+" then a + b
@@ -17,6 +15,6 @@ export const main := do{
else if op == "/" then a / b
else (panic "Unsupported operation")
);
cps println ("Result: " ++ to_string result);
0
cps println "Result: ${result}";
exit_status::success
}
+17 -28
View File
@@ -1,46 +1,35 @@
import system::(io, fs, async)
import std::(to_string, to_uint, inspect)
import std::(conv::(to_string, to_uint), inspect)
--[
const folder_view_old := \path. do{
cps println $ "Contents of " ++ fs::os_print path;
const folder_view := \path. do cps {
cps println $ "Contents of ${path}";
cps entries = async::block_on $ fs::read_dir path;
cps list::enumerate entries
|> list::map ((t[id, t[name, is_dir]]) =>
println $ to_string id ++ ": " ++ fs::os_print name ++ if is_dir then "/" else ""
println $ "${id}: ${name}" ++ if is_dir then "/" else ""
)
|> list::chain;
cps print "select an entry, or .. to move up: ";
cps choice = readln;
if (choice == "..") then do {
let parent_path = fs::pop_path path
|> option::assume
|> tuple::pick 0 2;
next parent_path
} else do {
cps choice = prompt "select an entry, or .. to move up: ";
cps new_path = if choice == ".." then do cps {
let t[parent_path, _] = fs::pop_path path
|> option::assume;
cps pass parent_path;
} else do cps {
let t[subname, is_dir] = to_uint choice
|> (list::get entries)
|> option::assume;
let subpath = fs::join_paths path subname;
if is_dir then next subpath
else do {
cps if is_dir then pass subpath else do cps {
cps file = async::block_on $ fs::read_file subpath;
cps contents = async::block_on $ io::read_string file;
cps println contents;
next path
}
}
}
]--
const folder_view := \path. do cps {
cps println $ "Contents of " ++ fs::os_print path;
cps entries = async::block_on $ fs::read_dir path;
let t[name, is_dir] = option::assume $ list::get entries 0;
cps println $ to_string name ++ " " ++ fs::os_print is_dir
cps _ = prompt "Hit Enter to return to the parent directory: ";
cps pass path;
};
};
cps pass new_path;
}
const main := loop_over (path = fs::cwd) {
cps folder_view path;
cps path = folder_view path;
}
+1 -10
View File
@@ -1,10 +1 @@
import std::exit_status
import std::conv
import std::number
import std::tuple
import std::list
const main := match t["set", "foo", 1] {
t[= "set", key, val] =>
$"Setting ${ key ++ $"${1 + 1}" } to ${val}"
}
const main := println "Hello World!" exit_status::success
+1 -1
View File
@@ -9,5 +9,5 @@ export const main := do{
|> list::reduce ((a, b) => a + b)
|> option::assume;
cps println $ to_string sum;
0
exit_status::success
}
+2 -2
View File
@@ -1,4 +1,4 @@
import std::to_string
import std::conv::to_string
export const main := do{
let foo = map::new[
@@ -10,5 +10,5 @@ export const main := do{
let num = map::get foo "bar"
|> option::assume;
cps println $ to_string num;
0
exit_status::success
}
+2 -2
View File
@@ -15,9 +15,9 @@ const bar := map::new[
]
const test2 := match bar {
map::having ["is_alive" = true, "greeting" = foo] => foo
map::having ["is_alive" = true, "greeting" = hello, "name" = name] => hello
}
const tests := test2 ++ ", " ++ test1
const tests := "${test2}, ${test1}"
const main := conv::to_string bar
+23
View File
@@ -0,0 +1,23 @@
protocol animal (
const noise := vcall "noise"
)
type dog (
const new := \name. wrap name
impl super::animal := map::new [
"noise" = \dog. "${dog}: Woof!"
]
)
type cat (
const new := wrap 0
impl super::animal := map::new [
"noise" = \_. "a cat: Mew!"
]
)
const main := do {
list::new [dog::new "Pavlov", cat::new]
|> list::map (\a. println $ animal::noise a)
|> list::chain exit_status::success
}
+9
View File
@@ -0,0 +1,9 @@
Content-Length: 5860
Content-Length: 5860
Content-Length: 5860
Content-Length: 5860
{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":85579,"clientInfo":{"name":"Visual Studio Code","version":"1.86.0"},"locale":"en","rootPath":"/home/lbfalvy/Code/orchid","rootUri":"file:///home/lbfalvy/Code/orchid","capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional","normalizesLineEndings":true,"changeAnnotationSupport":{"groupsOnLabel":true}},"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]},"resolveSupport":{"properties":["location.range"]}},"codeLens":{"refreshSupport":true},"executeCommand":{"dynamicRegistration":true},"didChangeConfiguration":{"dynamicRegistration":true},"workspaceFolders":true,"foldingRange":{"refreshSupport":true},"semanticTokens":{"refreshSupport":true},"fileOperations":{"dynamicRegistration":true,"didCreate":true,"didRename":true,"didDelete":true,"willCreate":true,"willRename":true,"willDelete":true},"inlineValue":{"refreshSupport":true},"inlayHint":{"refreshSupport":true},"diagnostics":{"refreshSupport":true}},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"codeDescriptionSupport":true,"dataSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"insertTextModeSupport":{"valueSet":[1,2]},"labelDetailsSupport":true},"insertTextMode":2,"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]},"completionList":{"itemDefaults":["commitCharacters","editRange","insertTextFormat","insertTextMode","data"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true},"activeParameterSupport":true},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]},"labelSupport":true},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"disabledSupport":true,"dataSupport":true,"resolveSupport":{"properties":["edit"]},"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"honorsChangeAnnotations":true},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true,"rangesSupport":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true,"prepareSupportDefaultBehavior":1,"honorsChangeAnnotations":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true,"foldingRangeKind":{"valueSet":["comment","imports","region"]},"foldingRange":{"collapsedText":false}},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"callHierarchy":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["namespace","type","class","enum","interface","struct","typeParameter","parameter","variable","property","enumMember","event","function","method","macro","keyword","modifier","comment","string","number","regexp","operator","decorator"],"tokenModifiers":["declaration","definition","readonly","static","deprecated","abstract","async","modification","documentation","defaultLibrary"],"formats":["relative"],"requests":{"range":true,"full":{"delta":true}},"multilineTokenSupport":false,"overlappingTokenSupport":false,"serverCancelSupport":true,"augmentsSyntaxTokens":true},"linkedEditingRange":{"dynamicRegistration":true},"typeHierarchy":{"dynamicRegistration":true},"inlineValue":{"dynamicRegistration":true},"inlayHint":{"dynamicRegistLine truncated
+33 -21
View File
@@ -1,49 +1,61 @@
use itertools::Itertools;
use orchidlang::error::Reporter;
use orchidlang::facade::macro_runner::MacroRunner;
use orchidlang::libs::std::exit_status::ExitStatus;
use orchidlang::libs::std::exit_status::OrcExitStatus;
use orchidlang::location::{CodeGenInfo, CodeLocation};
use orchidlang::name::Sym;
use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectTree};
use orchidlang::sym;
use crate::cli::cmd_prompt;
/// A little utility to step through the resolution of a macro set
pub fn main(macro_runner: MacroRunner, sym: Sym) -> ExitStatus {
let outname = sym.iter().join("::");
let (mut code, location) = match macro_runner.consts.get(&sym) {
Some(rep) => (rep.value.clone(), rep.range.clone()),
None => {
let valid = macro_runner.consts.keys();
let valid_str = valid.map(|t| t.iter().join("::")).join("\n\t");
eprintln!("Symbol {outname} not found\nvalid symbols: \n\t{valid_str}\n");
return ExitStatus::Failure;
/// A little utility to step through the reproject of a macro set
pub fn main(tree: ProjectTree, symbol: Sym) -> OrcExitStatus {
print!("Macro debugger starting on {symbol}");
let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::macro_runner)));
let expr_ent = match tree.0.walk1_ref(&[], &symbol[..], |_| true) {
Ok((e, _)) => e.clone(),
Err(e) => {
eprintln!("{}", e.at(&location.origin()));
return OrcExitStatus::Failure;
},
};
print!("Debugging macros in {outname} defined at {location}");
println!("\nInitial state: {code}");
let mut expr = match expr_ent.item() {
Some(ProjItem { kind: ItemKind::Const(c) }) => c.clone(),
_ => {
eprintln!("macro-debug argument must be a constant");
return OrcExitStatus::Failure;
},
};
let reporter = Reporter::new();
let macro_runner = MacroRunner::new(&tree, None, &reporter);
reporter.assert_exit();
println!("\nInitial state: {expr}");
// print_for_debug(&code);
let mut steps = macro_runner.step(sym).enumerate();
let mut steps = macro_runner.step(expr.clone()).enumerate();
loop {
let (cmd, _) = cmd_prompt("\ncmd> ").unwrap();
match cmd.trim() {
"" | "n" | "next" => match steps.next() {
None => print!("Halted"),
Some((idx, c)) => {
code = c;
print!("Step {idx}: {code}");
expr = c;
print!("Step {idx}: {expr}");
},
},
"p" | "print" => {
let glossary = code.value.collect_names();
let gl_str = glossary.iter().map(|t| t.iter().join("::")).join(", ");
print!("code: {code}\nglossary: {gl_str}")
let glossary = expr.value.collect_names();
let gl_str = glossary.iter().join(", ");
print!("code: {expr}\nglossary: {gl_str}")
},
"d" | "dump" => print!("Rules: {}", macro_runner.repo),
"q" | "quit" => return ExitStatus::Success,
"q" | "quit" => return OrcExitStatus::Success,
"complete" => {
match steps.last() {
Some((idx, c)) => print!("Step {idx}: {c}"),
None => print!("Already halted"),
}
return ExitStatus::Success;
return OrcExitStatus::Success;
},
"h" | "help" => print!(
"Available commands:
+2
View File
@@ -1,2 +1,4 @@
pub mod macro_debug;
pub mod print_project;
pub mod shared;
pub mod tests;
+1 -5
View File
@@ -10,11 +10,7 @@ pub struct ProjPrintOpts {
fn indent(amount: u16) -> String { " ".repeat(amount.into()) }
pub fn print_proj_mod(
module: &ProjectMod,
lvl: u16,
opts: ProjPrintOpts,
) -> String {
pub fn print_proj_mod(module: &ProjectMod, lvl: u16, opts: ProjPrintOpts) -> String {
let mut acc = String::new();
let tab = indent(lvl);
for (key, ModEntry { member, x }) in &module.entries {
+64
View File
@@ -0,0 +1,64 @@
use std::io::BufReader;
use std::thread;
use orchidlang::facade::loader::Loader;
use orchidlang::libs::asynch::system::AsynchSystem;
use orchidlang::libs::directfs::DirectFS;
use orchidlang::libs::io::{IOService, Sink, Source, Stream};
use orchidlang::libs::scheduler::system::SeqScheduler;
use orchidlang::libs::std::std_system::StdConfig;
pub fn stdin_source() -> Source { BufReader::new(Box::new(std::io::stdin())) }
pub fn stdout_sink() -> Sink { Box::new(std::io::stdout()) }
pub fn stderr_sink() -> Sink { Box::new(std::io::stderr()) }
pub fn with_std_env<T>(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T {
with_env(stdin_source(), stdout_sink(), stderr_sink(), cb)
}
pub fn with_env<T>(
stdin: Source,
stdout: Sink,
stderr: Sink,
cb: impl for<'a> FnOnce(Loader<'a>) -> T,
) -> T {
let mut asynch = AsynchSystem::new();
let scheduler = SeqScheduler::new(&mut asynch);
let std_streams = [
("stdin", Stream::Source(stdin)),
("stdout", Stream::Sink(stdout)),
("stderr", Stream::Sink(stderr)),
];
let env = Loader::new()
.add_system(StdConfig { impure: true })
.add_system(asynch)
.add_system(scheduler.clone())
.add_system(IOService::new(scheduler.clone(), std_streams))
.add_system(DirectFS::new(scheduler));
cb(env)
}
pub fn worker_cnt() -> usize { thread::available_parallelism().map(usize::from).unwrap_or(1) }
macro_rules! unwrap_exit {
($param:expr) => {
match $param {
Ok(v) => v,
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
},
}
};
($param:expr; $error:expr) => {
match $param {
Ok(v) => v,
Err(e) => {
eprintln!("{e}");
return $error;
},
}
};
}
pub(crate) use unwrap_exit;
+111
View File
@@ -0,0 +1,111 @@
use std::fmt;
use std::io::BufReader;
use std::path::Path;
use hashbrown::HashMap;
use itertools::Itertools;
use orchidlang::error::{ProjectError, ProjectResult, Reporter};
use orchidlang::facade::loader::Loader;
use orchidlang::facade::macro_runner::MacroRunner;
use orchidlang::facade::merge_trees::NortConst;
use orchidlang::facade::process::Process;
use orchidlang::foreign::error::{RTError, RTErrorObj, RTResult};
use orchidlang::foreign::inert::Inert;
use orchidlang::interpreter::error::RunError;
use orchidlang::interpreter::nort;
use orchidlang::libs::io::{Sink, Source};
use orchidlang::libs::std::exit_status::OrcExitStatus;
use orchidlang::name::Sym;
use rayon::iter::ParallelIterator;
use rayon::slice::ParallelSlice;
use super::shared::{with_env, worker_cnt};
pub fn mock_source() -> Source { BufReader::new(Box::new(&[][..])) }
pub fn mock_sink() -> Sink { Box::<Vec<u8>>::default() }
pub fn with_mock_env<T>(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T {
with_env(mock_source(), mock_sink(), mock_sink(), cb)
}
#[derive(Clone)]
pub struct TestDidNotHalt(Sym);
impl RTError for TestDidNotHalt {}
impl fmt::Display for TestDidNotHalt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Test {} did not halt", self.0)
}
}
#[derive(Clone)]
pub struct TestDidNotSucceed(Sym, nort::Expr);
impl RTError for TestDidNotSucceed {}
impl fmt::Display for TestDidNotSucceed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Test {} settled on {}", self.0, self.1)
}
}
pub fn run_test(proc: &mut Process, name: Sym, data: NortConst) -> RTResult<()> {
let res = proc.run(data.value, Some(10_000)).map_err(|e| match e {
RunError::Extern(e) => e,
RunError::Interrupted(_) => TestDidNotHalt(name.clone()).pack(),
})?;
match res.clone().downcast()? {
Inert(OrcExitStatus::Success) => Ok(()),
_ => Err(TestDidNotSucceed(name, res).pack()),
}
}
pub fn run_tests(
dir: &Path,
macro_limit: usize,
threads: Option<usize>,
tests: &[(Sym, NortConst)],
) -> ProjectResult<()> {
with_mock_env(|env| {
let reporter = Reporter::new();
env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter);
reporter.bind()
})?;
let threads = threads.unwrap_or_else(worker_cnt);
rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap();
let batch_size = tests.len().div_ceil(threads);
let errors = (tests.par_chunks(batch_size))
.map(|tests| {
with_mock_env(|env| {
let reporter = Reporter::new();
let mut proc = env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter);
reporter.assert(); // checked above
(tests.iter())
.filter_map(|(test, constant)| {
Some((test.clone(), run_test(&mut proc, test.clone(), constant.clone()).err()?))
})
.collect_vec()
})
})
.collect::<Vec<_>>()
.into_iter()
.flatten()
.collect::<HashMap<_, _>>();
if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) }
}
pub struct TestsFailed(HashMap<Sym, RTErrorObj>);
impl ProjectError for TestsFailed {
const DESCRIPTION: &'static str = "Various tests failed";
fn message(&self) -> String {
([format!("{} tests failed. Errors:", self.0.len())].into_iter())
.chain(self.0.iter().map(|(k, e)| format!("In {k}, {e}")))
.join("\n")
}
}
pub fn get_tree_tests(dir: &Path, reporter: &Reporter) -> ProjectResult<Vec<(Sym, NortConst)>> {
with_mock_env(|env| {
let tree = env.load_dir(dir.to_owned(), reporter);
let tree = MacroRunner::new(&tree, Some(10_000), reporter).run_macros(tree, reporter);
(tree.all_consts().into_iter())
.filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test"))
.map(|(k, v)| Ok((k.clone(), NortConst::convert_from(v, reporter))))
.collect::<ProjectResult<Vec<_>>>()
})
}
+122 -165
View File
@@ -2,37 +2,37 @@ mod cli;
mod features;
use std::fs::File;
use std::io::BufReader;
use std::num::NonZeroUsize;
use std::path::{Path, PathBuf};
use std::io::{stdin, stdout, Write};
use std::path::PathBuf;
use std::process::ExitCode;
use std::thread::available_parallelism;
use clap::{Parser, Subcommand};
use hashbrown::{HashMap, HashSet};
use hashbrown::HashSet;
use itertools::Itertools;
use orchidlang::error::{ProjectError, ProjectErrorObj, ProjectResult};
use orchidlang::facade::loader::Loader;
use never::Never;
use orchidlang::error::Reporter;
use orchidlang::facade::macro_runner::MacroRunner;
use orchidlang::facade::merge_trees::merge_trees;
use orchidlang::facade::merge_trees::{merge_trees, NortConst};
use orchidlang::facade::process::Process;
use orchidlang::foreign::inert::Inert;
use orchidlang::interpreter::context::Halt;
use orchidlang::interpreter::nort;
use orchidlang::libs::asynch::system::AsynchSystem;
use orchidlang::libs::directfs::DirectFS;
use orchidlang::libs::io::{IOService, Stream};
use orchidlang::libs::scheduler::system::SeqScheduler;
use orchidlang::libs::std::exit_status::ExitStatus;
use orchidlang::libs::std::std_system::StdConfig;
use orchidlang::location::{CodeGenInfo, CodeLocation};
use orchidlang::gen::tpl;
use orchidlang::gen::traits::Gen;
use orchidlang::interpreter::gen_nort::nort_gen;
use orchidlang::interpreter::nort::{self};
use orchidlang::libs::std::exit_status::OrcExitStatus;
use orchidlang::libs::std::string::OrcString;
use orchidlang::location::{CodeGenInfo, CodeLocation, SourceRange};
use orchidlang::name::Sym;
use orchidlang::parse::context::FlatLocContext;
use orchidlang::parse::lexer::{lex, Lexeme};
use orchidlang::sym;
use orchidlang::tree::{ModMemberRef, TreeTransforms};
use rayon::prelude::ParallelIterator;
use rayon::slice::ParallelSlice;
use orchidlang::virt_fs::{decl_file, DeclTree};
use crate::features::macro_debug;
use crate::features::print_project::{print_proj_mod, ProjPrintOpts};
use crate::features::shared::{stderr_sink, stdout_sink, unwrap_exit, with_env, with_std_env};
use crate::features::tests::{get_tree_tests, mock_source, run_test, run_tests, with_mock_env};
#[derive(Subcommand, Debug)]
enum Command {
@@ -58,6 +58,7 @@ enum Command {
#[arg(long)]
width: Option<u16>,
},
Repl,
}
/// Orchid interpreter
#[derive(Parser, Debug)]
@@ -111,132 +112,35 @@ impl Args {
pub fn chk_proj(&self) -> Result<(), String> { self.chk_dir_main() }
}
macro_rules! unwrap_exit {
($param:expr) => {
match $param {
Ok(v) => v,
Err(e) => {
eprintln!("{e}");
return ExitCode::FAILURE;
},
}
};
}
pub fn with_std_proc<T>(
dir: &Path,
macro_limit: usize,
f: impl for<'a> FnOnce(Process<'a>) -> ProjectResult<T>,
) -> ProjectResult<T> {
with_std_env(|env| {
let mr = MacroRunner::new(&env.load_dir(dir.to_owned())?)?;
let source_syms = mr.run_macros(Some(macro_limit))?;
let consts = merge_trees(source_syms, env.systems())?;
let proc = Process::new(consts, env.handlers());
f(proc)
})
}
// TODO
pub fn run_test(proc: &mut Process, name: Sym) -> ProjectResult<()> { Ok(()) }
pub fn run_tests(
dir: &Path,
macro_limit: usize,
threads: Option<usize>,
tests: &[Sym],
) -> ProjectResult<()> {
with_std_proc(dir, macro_limit, |proc| proc.validate_refs())?;
let threads = threads
.or_else(|| available_parallelism().ok().map(NonZeroUsize::into))
.unwrap_or(1);
rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap();
let batch_size = tests.len().div_ceil(threads);
let errors = tests
.par_chunks(batch_size)
.map(|tests| {
let res = with_std_proc(dir, macro_limit, |mut proc| {
let mut errors = HashMap::new();
for test in tests {
if let Err(e) = run_test(&mut proc, test.clone()) {
errors.insert(test.clone(), e);
}
}
Ok(errors)
});
res.expect("Tested earlier")
})
.reduce(HashMap::new, |l, r| l.into_iter().chain(r).collect());
if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) }
}
pub struct TestsFailed(HashMap<Sym, ProjectErrorObj>);
impl ProjectError for TestsFailed {
const DESCRIPTION: &'static str = "Various tests failed";
fn message(&self) -> String {
format!(
"{} tests failed. Errors:\n{}",
self.0.len(),
self.0.iter().map(|(k, e)| format!("In {k}, {e}")).join("\n")
)
}
}
fn get_tree_tests(dir: &Path) -> ProjectResult<Vec<Sym>> {
with_std_env(|env| {
env.load_dir(dir.to_owned()).map(|tree| {
(tree.all_consts().into_iter())
.filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test"))
.map(|(k, _)| k.clone())
.collect::<Vec<_>>()
})
})
}
pub fn with_std_env<T>(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T {
let mut asynch = AsynchSystem::new();
let scheduler = SeqScheduler::new(&mut asynch);
let std_streams = [
("stdin", Stream::Source(BufReader::new(Box::new(std::io::stdin())))),
("stdout", Stream::Sink(Box::new(std::io::stdout()))),
("stderr", Stream::Sink(Box::new(std::io::stderr()))),
];
let env = Loader::new()
.add_system(StdConfig { impure: true })
.add_system(asynch)
.add_system(scheduler.clone())
.add_system(IOService::new(scheduler.clone(), std_streams))
.add_system(DirectFS::new(scheduler));
cb(env)
}
pub fn main() -> ExitCode {
let args = Args::parse();
unwrap_exit!(args.chk_proj());
let dir = PathBuf::from(args.dir);
let main = args.main.map_or_else(
|| Sym::literal("tree::main::main"),
|main| Sym::parse(&main).expect("--main cannot be empty"),
);
let main_s = args.main.as_ref().map_or("tree::main::main", |s| s);
let main = Sym::parse(main_s).expect("--main cannot be empty");
let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::entrypoint)));
let reporter = Reporter::new();
// subcommands
#[allow(clippy::blocks_in_conditions)]
match args.command {
Some(Command::ListMacros) => with_std_env(|env| {
let tree = unwrap_exit!(env.load_main(dir, main));
let mr = unwrap_exit!(MacroRunner::new(&tree));
Some(Command::ListMacros) => with_mock_env(|env| {
let tree = env.load_main(dir, [main], &reporter);
let mr = MacroRunner::new(&tree, None, &reporter);
println!("Parsed rules: {}", mr.repo);
ExitCode::SUCCESS
}),
Some(Command::ProjectTree { hide_locations, width }) => {
let tree = unwrap_exit!(with_std_env(|env| env.load_main(dir, main)));
let tree = with_mock_env(|env| env.load_main(dir, [main], &reporter));
let w = width.or_else(|| termsize::get().map(|s| s.cols)).unwrap_or(74);
let print_opts = ProjPrintOpts { width: w, hide_locations };
println!("Project tree: {}", print_proj_mod(&tree.0, 0, print_opts));
ExitCode::SUCCESS
},
Some(Command::MacroDebug { symbol }) => with_std_env(|env| {
let tree = unwrap_exit!(env.load_main(dir, main));
Some(Command::MacroDebug { symbol }) => with_mock_env(|env| {
let tree = env.load_main(dir, [main], &reporter);
let symbol = Sym::parse(&symbol).expect("macro-debug needs an argument");
macro_debug::main(unwrap_exit!(MacroRunner::new(&tree)), symbol).code()
macro_debug::main(tree, symbol).code()
}),
Some(Command::Test { only: Some(_), threads: Some(_), .. }) => {
eprintln!(
@@ -253,25 +157,33 @@ pub fn main() -> ExitCode {
ExitCode::FAILURE
},
Some(Command::Test { only: None, threads, system: None }) => {
let tree_tests = unwrap_exit!(get_tree_tests(&dir));
let tree_tests = reporter.unwrap_exit(get_tree_tests(&dir, &reporter));
unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tree_tests));
ExitCode::SUCCESS
},
Some(Command::Test { only: Some(symbol), threads: None, system: None }) => {
let symbol = Sym::parse(&symbol).expect("Test needs an argument");
unwrap_exit!(run_tests(&dir, args.macro_limit, Some(1), &[symbol]));
ExitCode::SUCCESS
with_env(mock_source(), stdout_sink(), stderr_sink(), |env| {
// iife in lieu of try blocks
let tree = env.load_main(dir.clone(), [symbol.clone()], &reporter);
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
let consts = mr.run_macros(tree, &reporter).all_consts();
let test = consts.get(&symbol).expect("Test not found");
let nc = NortConst::convert_from(test.clone(), &reporter);
let mut proc = Process::new(merge_trees(consts, env.systems(), &reporter), env.handlers());
unwrap_exit!(run_test(&mut proc, symbol.clone(), nc.clone()));
ExitCode::SUCCESS
})
},
Some(Command::Test { only: None, threads, system: Some(system) }) => {
let subtrees = unwrap_exit!(with_std_env(|env| {
let subtrees = unwrap_exit!(with_mock_env(|env| {
match env.systems().find(|s| s.name == system) {
None => Err(format!("System {system} not found")),
Some(sys) => {
let mut paths = HashSet::new();
sys.code.search_all((), |path, node, ()| {
if matches!(node, ModMemberRef::Item(_)) {
let name = Sym::new(path.unreverse())
.expect("Empty path means global file");
let name = Sym::new(path.unreverse()).expect("Empty path means global file");
paths.insert(name);
}
});
@@ -279,48 +191,93 @@ pub fn main() -> ExitCode {
},
}
}));
let in_subtrees =
|sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..]));
let tests = unwrap_exit!(with_std_env(|env| -> ProjectResult<_> {
let tree = env.load_main(dir.clone(), main.clone())?;
let mr = MacroRunner::new(&tree)?;
let src_consts = mr.run_macros(Some(args.macro_limit))?;
let consts = merge_trees(src_consts, env.systems())?;
let test_names = (consts.into_iter())
.filter(|(k, v)| {
in_subtrees(k.clone())
&& v.comments.iter().any(|c| c.trim() == "test")
})
.map(|p| p.0)
.collect_vec();
Ok(test_names)
}));
let in_subtrees = |sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..]));
let tests = with_mock_env(|env| {
let tree = env.load_main(dir.clone(), [main.clone()], &reporter);
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
let src_consts = mr.run_macros(tree, &reporter).all_consts();
let consts = merge_trees(src_consts, env.systems(), &reporter);
(consts.into_iter())
.filter(|(k, v)| in_subtrees(k.clone()) && v.comments.iter().any(|c| c.trim() == "test"))
.collect_vec()
});
eprintln!("Running {} tests", tests.len());
unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tests));
eprintln!("All tests pass");
ExitCode::SUCCESS
},
None => with_std_env(|env| {
let tree = unwrap_exit!(env.load_main(dir, main.clone()));
let mr = unwrap_exit!(MacroRunner::new(&tree));
let src_consts = unwrap_exit!(mr.run_macros(Some(args.macro_limit)));
let consts = unwrap_exit!(merge_trees(src_consts, env.systems()));
let mut proc = Process::new(consts, env.handlers());
unwrap_exit!(proc.validate_refs());
let main = nort::Clause::Constant(main.clone())
.to_expr(CodeLocation::Gen(CodeGenInfo::no_details("entrypoint")));
let ret = unwrap_exit!(proc.run(main, None));
let Halt { state, inert, .. } = ret;
let proc = env.proc_main(dir, [main.clone()], true, Some(args.macro_limit), &reporter);
reporter.assert_exit();
let ret = unwrap_exit!(proc.run(nort::Clause::Constant(main).into_expr(location), None));
drop(proc);
assert!(inert, "Gas is not used, only inert data should be yielded");
match state.clone().downcast() {
Ok(Inert(ExitStatus::Success)) => ExitCode::SUCCESS,
Ok(Inert(ExitStatus::Failure)) => ExitCode::FAILURE,
match ret.clone().downcast() {
Ok(Inert(OrcExitStatus::Success)) => ExitCode::SUCCESS,
Ok(Inert(OrcExitStatus::Failure)) => ExitCode::FAILURE,
Err(_) => {
println!("{}", state.clause);
println!("{}", ret.clause);
ExitCode::SUCCESS
},
}
}),
Some(Command::Repl) => with_std_env(|env| {
let sctx = env.project_ctx(&reporter);
loop {
let reporter = Reporter::new();
print!("orc");
let mut src = String::new();
let mut paren_tally = 0;
loop {
print!("> ");
stdout().flush().unwrap();
let mut buf = String::new();
stdin().read_line(&mut buf).unwrap();
src += &buf;
let range = SourceRange::mock();
let spctx = sctx.parsing(range.code());
let pctx = FlatLocContext::new(&spctx, &range);
let res =
lex(Vec::new(), &buf, &pctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {});
res.tokens.iter().for_each(|e| match &e.lexeme {
Lexeme::LP(_) => paren_tally += 1,
Lexeme::RP(_) => paren_tally -= 1,
_ => (),
});
if 0 == paren_tally {
break;
}
}
let tree = env.load_project_main(
[sym!(tree::main::__repl_input__)],
DeclTree::ns("tree::main", [decl_file(&format!("const __repl_input__ := {src}"))]),
&reporter,
);
let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter);
let proj_consts = mr.run_macros(tree, &reporter).all_consts();
let consts = merge_trees(proj_consts, env.systems(), &reporter);
let ctx = nort_gen(location.clone());
let to_string_tpl = tpl::A(tpl::C("std::string::convert"), tpl::Slot);
if let Err(err) = reporter.bind() {
eprintln!("{err}");
continue;
}
let proc = Process::new(consts, env.handlers());
let prompt = tpl::C("tree::main::__repl_input__").template(ctx.clone(), []);
let out = match proc.run(prompt, Some(1000)) {
Ok(out) => out,
Err(e) => {
eprintln!("{e}");
continue;
},
};
if let Ok(out) = proc.run(to_string_tpl.template(ctx, [out.clone()]), Some(1000)) {
if let Ok(s) = out.clone().downcast::<Inert<OrcString>>() {
println!("{}", s.0.as_str());
continue;
}
}
println!("{out}")
}
}),
}
}
+160 -60
View File
@@ -1,28 +1,30 @@
//! Abstractions for handling various code-related errors under a common trait
//! object.
use core::fmt;
use std::any::Any;
use std::fmt::{Debug, Display};
use std::cell::RefCell;
use std::sync::Arc;
use std::{fmt, process};
use dyn_clone::{clone_box, DynClone};
use itertools::Itertools;
use crate::location::CodeLocation;
use crate::location::CodeOrigin;
use crate::utils::boxed_iter::{box_once, BoxedIter};
#[allow(unused)] // for doc
use crate::virt_fs::CodeNotFound;
/// A point of interest in resolving the error, such as the point where
/// processing got stuck, a command that is likely to be incorrect
#[derive(Clone)]
pub struct ErrorPosition {
/// The suspected location
pub location: CodeLocation,
/// Any information about the role of this location
/// The suspected origin
pub origin: CodeOrigin,
/// Any information about the role of this origin
pub message: Option<String>,
}
impl From<CodeLocation> for ErrorPosition {
fn from(location: CodeLocation) -> Self { Self { location, message: None } }
impl From<CodeOrigin> for ErrorPosition {
fn from(origin: CodeOrigin) -> Self { Self { origin, message: None } }
}
/// Errors addressed to the developer which are to be resolved with
@@ -36,13 +38,13 @@ pub trait ProjectError: Sized + Send + Sync + 'static {
/// Code positions relevant to this error. If you don't implement this, you
/// must implement [ProjectError::one_position]
#[must_use]
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> {
box_once(ErrorPosition { location: self.one_position(), message: None })
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> + '_ {
box_once(ErrorPosition { origin: self.one_position(), message: None })
}
/// Short way to provide a single location. If you don't implement this, you
/// Short way to provide a single origin. If you don't implement this, you
/// must implement [ProjectError::positions]
#[must_use]
fn one_position(&self) -> CodeLocation { unimplemented!() }
fn one_position(&self) -> CodeOrigin { unimplemented!() }
/// Convert the error into an `Arc<dyn DynProjectError>` to be able to
/// handle various errors together
#[must_use]
@@ -53,7 +55,11 @@ pub trait ProjectError: Sized + Send + Sync + 'static {
pub trait DynProjectError: Send + Sync {
/// Access type information about this error
#[must_use]
fn as_any(&self) -> &dyn Any;
fn as_any_ref(&self) -> &dyn Any;
/// Pack the error into a trait object, or leave it as-is if it's already a
/// trait object
#[must_use]
fn into_packed(self: Arc<Self>) -> ProjectErrorObj;
/// A general description of this type of error
#[must_use]
fn description(&self) -> &str;
@@ -62,13 +68,14 @@ pub trait DynProjectError: Send + Sync {
fn message(&self) -> String { self.description().to_string() }
/// Code positions relevant to this error.
#[must_use]
fn positions(&self) -> BoxedIter<ErrorPosition>;
fn positions(&self) -> BoxedIter<'_, ErrorPosition>;
}
impl<T> DynProjectError for T
where T: ProjectError
{
fn as_any(&self) -> &dyn Any { self }
fn as_any_ref(&self) -> &dyn Any { self }
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
fn description(&self) -> &str { T::DESCRIPTION }
fn message(&self) -> String { ProjectError::message(self) }
fn positions(&self) -> BoxedIter<ErrorPosition> {
@@ -76,19 +83,27 @@ where T: ProjectError
}
}
impl Display for dyn DynProjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl DynProjectError for ProjectErrorObj {
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
fn description(&self) -> &str { (**self).description() }
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { (*self).clone() }
fn message(&self) -> String { (**self).message() }
fn positions(&self) -> BoxedIter<'_, ErrorPosition> { (**self).positions() }
}
impl fmt::Display for dyn DynProjectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let description = self.description();
let message = self.message();
let positions = self.positions().collect::<Vec<_>>();
writeln!(f, "Project error: {description}\n{message}")?;
if positions.is_empty() {
writeln!(f, "No locations specified")?;
writeln!(f, "No origins specified")?;
} else {
for ErrorPosition { location, message } in positions {
for ErrorPosition { origin, message } in positions {
match message {
None => writeln!(f, "@{location}"),
Some(msg) => writeln!(f, "@{location}: {msg}"),
None => writeln!(f, "@{origin}"),
Some(msg) => writeln!(f, "@{origin}: {msg}"),
}?
}
}
@@ -96,22 +111,20 @@ impl Display for dyn DynProjectError {
}
}
impl Debug for dyn DynProjectError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self}")
}
impl fmt::Debug for dyn DynProjectError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
}
/// Type-erased [ProjectError] implementor through the [DynProjectError]
/// object-trait
pub type ProjectErrorObj = Arc<dyn DynProjectError>;
/// Alias for a result with an error of [Rc] of [ProjectError] trait object.
/// This is the type of result most commonly returned by pre-run operations.
/// Alias for a result with an error of [ProjectErrorObj]. This is the type of
/// result most commonly returned by pre-runtime operations.
pub type ProjectResult<T> = Result<T, ProjectErrorObj>;
/// A trait for error types that are only missing a location. Do not depend on
/// this trait, refer to [DynErrorSansLocation] instead.
pub trait ErrorSansLocation: Clone + Sized + Send + Sync + 'static {
/// A trait for error types that are only missing an origin. Do not depend on
/// this trait, refer to [DynErrorSansOrigin] instead.
pub trait ErrorSansOrigin: Clone + Sized + Send + Sync + 'static {
/// General description of the error condition
const DESCRIPTION: &'static str;
/// Specific description of the error including code fragments or concrete
@@ -119,67 +132,154 @@ pub trait ErrorSansLocation: Clone + Sized + Send + Sync + 'static {
fn message(&self) -> String { Self::DESCRIPTION.to_string() }
/// Convert the error to a type-erased structure for handling on shared
/// channels
fn pack(self) -> ErrorSansLocationObj { Box::new(self) }
fn pack(self) -> ErrorSansOriginObj { Box::new(self) }
/// A shortcut to streamline switching code between [ErrorSansOriginObj] and
/// concrete types
fn bundle(self, origin: &CodeOrigin) -> ProjectErrorObj { self.pack().bundle(origin) }
}
/// Object-safe equivalent to [ErrorSansLocation]. Implement that one instead of
/// this. Typically found as [ErrorSansLocationObj]
pub trait DynErrorSansLocation: Any + Send + Sync + DynClone {
/// Object-safe equivalent to [ErrorSansOrigin]. Implement that one instead of
/// this. Typically found as [ErrorSansOriginObj]
pub trait DynErrorSansOrigin: Any + Send + Sync + DynClone {
/// Allow to downcast the base object to distinguish between various errors.
/// The main intended purpose is to trigger a fallback when [CodeNotFound] is
/// encountered, but the possibilities are not limited to that.
fn as_any_ref(&self) -> &dyn Any;
/// Regularize the type
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj;
/// Generic description of the error condition
fn description(&self) -> &str;
/// Specific description of this particular error
fn message(&self) -> String;
/// Add an origin
fn bundle(self: Box<Self>, origin: &CodeOrigin) -> ProjectErrorObj;
}
/// Type-erased [ErrorSansLocation] implementor through the object-trait
/// [DynErrorSansLocation]. This can be turned into a [ProjectErrorObj] with
/// [bundle_location].
pub type ErrorSansLocationObj = Box<dyn DynErrorSansLocation>;
/// A generic project result without location
pub type ResultSansLocation<T> = Result<T, ErrorSansLocationObj>;
/// Type-erased [ErrorSansOrigin] implementor through the object-trait
/// [DynErrorSansOrigin]. This can be turned into a [ProjectErrorObj] with
/// [ErrorSansOriginObj::bundle].
pub type ErrorSansOriginObj = Box<dyn DynErrorSansOrigin>;
/// A generic project result without origin
pub type ResultSansOrigin<T> = Result<T, ErrorSansOriginObj>;
impl<T: ErrorSansLocation + 'static> DynErrorSansLocation for T {
impl<T: ErrorSansOrigin + 'static> DynErrorSansOrigin for T {
fn description(&self) -> &str { Self::DESCRIPTION }
fn message(&self) -> String { self.message() }
fn message(&self) -> String { (*self).message() }
fn as_any_ref(&self) -> &dyn Any { self }
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { (*self).pack() }
fn bundle(self: Box<Self>, origin: &CodeOrigin) -> ProjectErrorObj {
Arc::new(OriginBundle(origin.clone(), *self))
}
}
impl Clone for ErrorSansLocationObj {
impl Clone for ErrorSansOriginObj {
fn clone(&self) -> Self { clone_box(&**self) }
}
impl DynErrorSansLocation for ErrorSansLocationObj {
impl DynErrorSansOrigin for ErrorSansOriginObj {
fn description(&self) -> &str { (**self).description() }
fn message(&self) -> String { (**self).message() }
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
fn into_packed(self: Box<Self>) -> ErrorSansOriginObj { *self }
fn bundle(self: Box<Self>, origin: &CodeOrigin) -> ProjectErrorObj { (*self).bundle(origin) }
}
impl Display for ErrorSansLocationObj {
impl fmt::Display for ErrorSansOriginObj {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "{}\nLocation missing from error", self.message())
writeln!(f, "{}\nOrigin missing from error", self.message())
}
}
impl Debug for ErrorSansLocationObj {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{self}")
}
impl fmt::Debug for ErrorSansOriginObj {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") }
}
struct LocationBundle(CodeLocation, Box<dyn DynErrorSansLocation>);
impl DynProjectError for LocationBundle {
fn as_any(&self) -> &dyn Any { self.1.as_any_ref() }
struct OriginBundle<T: ErrorSansOrigin>(CodeOrigin, T);
impl<T: ErrorSansOrigin> DynProjectError for OriginBundle<T> {
fn as_any_ref(&self) -> &dyn Any { self.1.as_any_ref() }
fn into_packed(self: Arc<Self>) -> ProjectErrorObj { self }
fn description(&self) -> &str { self.1.description() }
fn message(&self) -> String { self.1.message() }
fn positions(&self) -> BoxedIter<ErrorPosition> {
box_once(ErrorPosition { location: self.0.clone(), message: None })
box_once(ErrorPosition { origin: self.0.clone(), message: None })
}
}
/// Add a location to an [ErrorSansLocation]
pub fn bundle_location(
location: &CodeLocation,
details: &dyn DynErrorSansLocation,
) -> ProjectErrorObj {
Arc::new(LocationBundle(location.clone(), clone_box(details)))
/// A collection for tracking fatal errors without halting. Participating
/// functions return [ProjectResult] even if they only ever construct [Ok]. When
/// they call other participating functions, instead of directly forwarding
/// errors with `?` they should prefer constructing a fallback value with
/// [Reporter::fallback]. If any error is added to a [Reporter] in a function,
/// the return value is valid but its meaning need not be related in any way to
/// the inputs.
///
/// Returning [Err] from a function that accepts `&mut Reporter` indicates not
/// that there was a fatal error but that it wasn't possible to construct a
/// fallback, so if it can, the caller should construct one.
pub struct Reporter(RefCell<Vec<ProjectErrorObj>>);
impl Reporter {
/// Create a new error reporter
pub fn new() -> Self { Self(RefCell::new(Vec::new())) }
/// Returns true if any errors were regorded. If this ever returns true, it
/// will always return true in the future.
pub fn failing(&self) -> bool { !self.0.borrow().is_empty() }
/// Report a fatal error
pub fn report(&self, error: ProjectErrorObj) { self.0.borrow_mut().push(error) }
/// Catch a fatal error, report it, and substitute the value
pub fn fallback<T>(&self, res: ProjectResult<T>, cb: impl FnOnce(ProjectErrorObj) -> T) -> T {
res.inspect_err(|e| self.report(e.clone())).unwrap_or_else(cb)
}
/// Panic if there were errors
pub fn assert(&self) { self.unwrap(Ok(())) }
/// Exit with code -1 if there were errors
pub fn assert_exit(&self) { self.unwrap_exit(Ok(())) }
/// Panic with descriptive messages if there were errors. If there were no
/// errors, unwrap the result
pub fn unwrap<T>(&self, res: ProjectResult<T>) -> T {
if self.failing() {
panic!("Errors were encountered: \n{}", self.0.borrow().iter().join("\n"));
}
res.unwrap()
}
/// Print errors and exit if any occurred. If there were no errors, unwrap
/// the result
pub fn unwrap_exit<T>(&self, res: ProjectResult<T>) -> T {
if self.failing() {
eprintln!("Errors were encountered: \n{}", self.0.borrow().iter().join("\n"));
process::exit(-1)
}
res.unwrap_or_else(|e| {
eprintln!("{e}");
process::exit(-1)
})
}
/// Take the errors out of the reporter
#[must_use]
pub fn into_errors(self) -> Option<Vec<ProjectErrorObj>> {
let v = self.0.into_inner();
if v.is_empty() { None } else { Some(v) }
}
/// Raise an error if the reporter contains any errors
pub fn bind(self) -> ProjectResult<()> {
match self.into_errors() {
None => Ok(()),
Some(v) if v.len() == 1 => Err(v.into_iter().exactly_one().unwrap()),
Some(v) => Err(MultiError(v).pack()),
}
}
}
impl Default for Reporter {
fn default() -> Self { Self::new() }
}
struct MultiError(Vec<ProjectErrorObj>);
impl ProjectError for MultiError {
const DESCRIPTION: &'static str = "Multiple errors occurred";
fn message(&self) -> String { format!("{} errors occurred", self.0.len()) }
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> + '_ {
self.0.iter().flat_map(|e| {
e.positions().map(|pos| {
let emsg = e.message();
let msg = if let Some(pmsg) = pos.message { format!("{emsg}: {pmsg}") } else { emsg };
ErrorPosition { origin: pos.origin, message: Some(msg) }
})
})
}
}
+124 -64
View File
@@ -1,21 +1,28 @@
use std::path::{Path, PathBuf};
use std::{fs, iter};
//! The main structure of the façade, collects systems and exposes various
//! operations over the whole set.
use intern_all::{i, Tok};
use substack::Substack;
use std::borrow::Borrow;
use std::path::PathBuf;
use intern_all::i;
use super::macro_runner::MacroRunner;
use super::merge_trees::merge_trees;
use super::process::Process;
use super::system::{IntoSystem, System};
use crate::error::ProjectResult;
use super::unbound_ref::validate_refs;
use crate::error::Reporter;
use crate::gen::tree::ConstTree;
use crate::interpreter::context::RunEnv;
use crate::interpreter::handler::HandlerTable;
use crate::location::{CodeGenInfo, CodeLocation};
use crate::name::{Sym, VPath};
use crate::pipeline::load_solution::{load_solution, SolutionContext};
use crate::location::{CodeGenInfo, CodeOrigin};
use crate::name::{PathSlice, Sym, VPath};
use crate::pipeline::load_project::{load_project, ProjectContext};
use crate::pipeline::project::ProjectTree;
use crate::sym;
use crate::utils::combine::Combine;
use crate::utils::sequence::Sequence;
use crate::utils::unwrap_or::unwrap_or;
use crate::virt_fs::{DeclTree, DirNode, VirtFS};
use crate::virt_fs::{DeclTree, DirNode, Loaded, VirtFS};
/// A compiled environment ready to load user code. It stores the list of
/// systems and combines with usercode to produce a [Process]
@@ -28,9 +35,7 @@ impl<'a> Loader<'a> {
pub fn new() -> Self { Self { systems: Vec::new() } }
/// Retrieve the list of systems
pub fn systems(&self) -> impl Iterator<Item = &System<'a>> {
self.systems.iter()
}
pub fn systems(&self) -> impl Iterator<Item = &System<'a>> { self.systems.iter() }
/// Register a new system in the environment
#[must_use]
@@ -50,21 +55,22 @@ impl<'a> Loader<'a> {
/// Combine the `constants` fields of all systems
pub fn constants(&self) -> ConstTree {
(self.systems())
.try_fold(ConstTree::tree::<&str>([]), |acc, sys| {
acc.combine(sys.constants.clone())
})
.try_fold(ConstTree::tree::<&str>([]), |acc, sys| acc.combine(sys.constants.clone()))
.expect("Conflicting const trees")
}
pub fn handlers(self) -> HandlerTable<'a> {
(self.systems.into_iter())
.fold(HandlerTable::new(), |t, sys| t.combine(sys.handlers))
/// Extract the command handlers from the systems, consuming the loader in the
/// process. This has to consume the systems because handler tables aren't
/// Copy. It also establishes the practice that environments live on the
/// stack.
pub fn handlers(&self) -> HandlerTable<'_> {
(self.systems.iter()).fold(HandlerTable::new(), |t, sys| t.link(&sys.handlers))
}
/// Compile the environment from the set of systems and return it directly.
/// See [#load_dir]
pub fn solution_ctx(&self) -> ProjectResult<SolutionContext> {
Ok(SolutionContext {
pub fn project_ctx<'b>(&self, reporter: &'b Reporter) -> ProjectContext<'_, 'b> {
ProjectContext {
lexer_plugins: Sequence::new(|| {
self.systems().flat_map(|sys| &sys.lexer_plugins).map(|b| &**b)
}),
@@ -72,47 +78,113 @@ impl<'a> Loader<'a> {
self.systems().flat_map(|sys| &sys.line_parsers).map(|b| &**b)
}),
preludes: Sequence::new(|| self.systems().flat_map(|sys| &sys.prelude)),
})
reporter,
}
}
/// Combine source code from all systems with the specified directory into a
/// common [VirtFS]
pub fn make_dir_tree(&self, dir: PathBuf) -> DeclTree {
pub fn make_dir_fs(&self, dir: PathBuf) -> DeclTree {
let dir_node = DirNode::new(dir, ".orc").rc();
let base = DeclTree::tree([("tree", DeclTree::leaf(dir_node))]);
(self.systems().try_fold(base, |acc, sub| acc.combine(sub.code.clone())))
DeclTree::tree([("tree", DeclTree::leaf(dir_node))])
}
/// All system trees merged into one
pub fn system_fs(&self) -> DeclTree {
(self.systems().try_fold(DeclTree::empty(), |acc, sub| acc.combine(sub.code.clone())))
.expect("Conflicting system trees")
}
/// A wrapper around [load_project] that only takes the arguments that aren't
/// fully specified by systems
pub fn load_project_main(
&self,
entrypoints: impl IntoIterator<Item = Sym>,
root: DeclTree,
reporter: &Reporter,
) -> ProjectTree {
let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint)));
let constants = self.constants().unwrap_mod();
let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone()));
let root = self.system_fs().combine(root).expect("System trees conflict with root");
load_project(&self.project_ctx(reporter), targets, &constants, &root)
}
/// A wrapper around [load_project] that only takes the arguments that aren't
/// fully specified by systems
pub fn load_project(&self, root: DeclTree, reporter: &Reporter) -> ProjectTree {
let mut orc_files: Vec<VPath> = Vec::new();
find_all_orc_files([].borrow(), &mut orc_files, &root);
let entrypoints = (orc_files.into_iter()).map(|p| p.name_with_suffix(i!(str: "tree")).to_sym());
let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint)));
let constants = self.constants().unwrap_mod();
let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone()));
let root = self.system_fs().combine(root).expect("System trees conflict with root");
load_project(&self.project_ctx(reporter), targets, &constants, &root)
}
/// Load a directory from the local file system as an Orchid project.
/// File loading proceeds along import statements and ignores all files
/// not reachable from the specified file.
pub fn load_main(
&self,
dir: PathBuf,
target: Sym,
) -> ProjectResult<ProjectTree> {
let ctx = self.solution_ctx()?;
let tgt_loc =
CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint"));
let root = self.make_dir_tree(dir.clone());
let targets = iter::once((target, tgt_loc));
let constants = self.constants().unwrap_mod();
load_solution(ctx, targets, &constants, &root)
targets: impl IntoIterator<Item = Sym>,
reporter: &Reporter,
) -> ProjectTree {
self.load_project_main(targets, self.make_dir_fs(dir), reporter)
}
/// Load every orchid file in a directory
pub fn load_dir(&self, dir: PathBuf) -> ProjectResult<ProjectTree> {
let ctx = self.solution_ctx()?;
let tgt_loc =
CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint"));
let mut orc_files: Vec<VPath> = Vec::new();
find_all_orc_files(&dir, &mut orc_files, Substack::Bottom);
let root = self.make_dir_tree(dir.clone());
let constants = self.constants().unwrap_mod();
let targets = (orc_files.into_iter())
.map(|p| (p.as_suffix_of(i("tree")).to_sym(), tgt_loc.clone()));
load_solution(ctx, targets, &constants, &root)
pub fn load_dir(&self, dir: PathBuf, reporter: &Reporter) -> ProjectTree {
self.load_project(self.make_dir_fs(dir), reporter)
}
/// Build a process by calling other utilities in [crate::facade]. A sort of
/// facade over the facade. If you need a custom file system, consider
/// combining this with [Loader::load_project]. For usage with
/// [Loader::load_main] and [Loader::load_dir] we offer the shorthands
/// [Loader::proc_main] and [Loader::proc_dir].
pub fn proc(
&'a self,
tree: ProjectTree,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
let mr = MacroRunner::new(&tree, macro_limit, reporter);
let pm_tree = mr.run_macros(tree, reporter);
let consts = merge_trees(pm_tree.all_consts(), self.systems(), reporter);
if check_refs {
validate_refs(consts.keys().cloned().collect(), reporter, &mut |sym, location| {
(consts.get(&sym).map(|nc| nc.value.clone()))
.ok_or_else(|| RunEnv::sym_not_found(sym, location))
});
}
Process::new(consts, self.handlers())
}
/// Load a project and process everything
pub fn proc_dir(
&'a self,
dir: PathBuf,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
self.proc(self.load_dir(dir.to_owned(), reporter), check_refs, macro_limit, reporter)
}
/// Load a project and process everything to load specific symbols
pub fn proc_main(
&'a self,
dir: PathBuf,
targets: impl IntoIterator<Item = Sym>,
check_refs: bool,
macro_limit: Option<usize>,
reporter: &Reporter,
) -> Process<'a> {
self.proc(self.load_main(dir.to_owned(), targets, reporter), check_refs, macro_limit, reporter)
}
}
@@ -120,24 +192,12 @@ impl<'a> Default for Loader<'a> {
fn default() -> Self { Self::new() }
}
fn find_all_orc_files(
path: &Path,
paths: &mut Vec<VPath>,
stack: Substack<'_, Tok<String>>,
) {
assert!(path.exists(), "find_all_orc_files encountered missing path");
if path.is_symlink() {
let path = unwrap_or!(fs::read_link(path).ok(); return);
find_all_orc_files(&path, paths, stack)
} else if path.is_file() {
if path.extension().and_then(|t| t.to_str()) == Some("orc") {
paths.push(VPath(stack.unreverse()))
}
} else if path.is_dir() {
let entries = unwrap_or!(path.read_dir().ok(); return);
for entry in entries.filter_map(Result::ok) {
let name = unwrap_or!(entry.file_name().into_string().ok(); return);
find_all_orc_files(&entry.path(), paths, stack.push(i(&name)))
}
fn find_all_orc_files(path: &PathSlice, paths: &mut Vec<VPath>, vfs: &impl VirtFS) {
match vfs.read(path) {
Err(_) => (),
Ok(Loaded::Code(_)) => paths.push(path.to_vpath()),
Ok(Loaded::Collection(items)) => items
.iter()
.for_each(|suffix| find_all_orc_files(&path.to_vpath().suffix([suffix.clone()]), paths, vfs)),
}
}
+66 -48
View File
@@ -1,66 +1,69 @@
//! Encapsulates the macro runner's scaffolding. Relies on a [ProjectTree]
//! loaded by the [super::loader::Loader]
use std::iter;
use hashbrown::HashMap;
use crate::error::{ProjectError, ProjectResult};
use crate::location::CodeLocation;
use crate::name::Sym;
use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult, Reporter};
use crate::location::CodeOrigin;
use crate::parse::parsed;
use crate::pipeline::project::{
ConstReport, ProjectTree,
};
use crate::pipeline::project::{ItemKind, ProjItem, ProjectTree};
use crate::rule::repository::Repo;
use crate::tree::TreeTransforms;
/// Encapsulates the macro repository and the constant list, and allows querying
/// for macro execution results
pub struct MacroRunner {
/// Optimized catalog of substitution rules
pub repo: Repo,
/// Runtime code containing macro invocations
pub consts: HashMap<Sym, ConstReport>,
pub timeout: Option<usize>,
}
impl MacroRunner {
pub fn new(tree: &ProjectTree) -> ProjectResult<Self> {
/// Initialize a macro runner
pub fn new(tree: &ProjectTree, timeout: Option<usize>, reporter: &Reporter) -> Self {
let rules = tree.all_rules();
let repo = Repo::new(rules).map_err(|(rule, e)| e.to_project(&rule))?;
Ok(Self { repo, consts: tree.all_consts().into_iter().collect() })
let repo = Repo::new(rules, reporter);
Self { repo, timeout }
}
pub fn run_macros(
&self,
timeout: Option<usize>,
) -> ProjectResult<HashMap<Sym, ConstReport>> {
let mut symbols = HashMap::new();
for (name, report) in self.consts.iter() {
let value = match timeout {
None => (self.repo.pass(&report.value))
.unwrap_or_else(|| report.value.clone()),
Some(limit) => {
let (o, leftover_gas) = self.repo.long_step(&report.value, limit + 1);
match leftover_gas {
1.. => o,
_ => {
let err = MacroTimeout {
location: CodeLocation::Source(report.range.clone()),
symbol: name.clone(),
limit,
};
return Err(err.pack());
},
}
},
};
symbols.insert(name.clone(), ConstReport { value, ..report.clone() });
/// Process the macros in an expression.
pub fn process_expr(&self, expr: parsed::Expr) -> ProjectResult<parsed::Expr> {
match self.timeout {
None => Ok((self.repo.pass(&expr)).unwrap_or_else(|| expr.clone())),
Some(limit) => {
let (o, leftover_gas) = self.repo.long_step(&expr, limit + 1);
if 0 < leftover_gas {
return Ok(o);
}
Err(MacroTimeout { location: expr.range.origin(), limit }.pack())
},
}
Ok(symbols)
}
/// Run all macros in the project.
pub fn run_macros(&self, tree: ProjectTree, reporter: &Reporter) -> ProjectTree {
ProjectTree(tree.0.map_data(
|_, item| match &item.kind {
ItemKind::Const(c) => match self.process_expr(c.clone()) {
Ok(expr) => ProjItem { kind: ItemKind::Const(expr) },
Err(e) => {
reporter.report(e);
item
},
},
_ => item,
},
|_, x| x,
|_, x| x,
))
}
/// Obtain an iterator that steps through the preprocessing of a constant
/// for debugging macros
pub fn step(&self, sym: Sym) -> impl Iterator<Item = parsed::Expr> + '_ {
let mut target =
self.consts.get(&sym).expect("Target not found").value.clone();
pub fn step(&self, mut expr: parsed::Expr) -> impl Iterator<Item = parsed::Expr> + '_ {
iter::from_fn(move || {
target = self.repo.step(&target)?;
Some(target.clone())
expr = self.repo.step(&expr)?;
Some(expr.clone())
})
}
}
@@ -68,17 +71,32 @@ impl MacroRunner {
/// Error raised when a macro runs too long
#[derive(Debug)]
pub struct MacroTimeout {
location: CodeLocation,
symbol: Sym,
location: CodeOrigin,
limit: usize,
}
impl ProjectError for MacroTimeout {
const DESCRIPTION: &'static str = "Macro execution has not halted";
fn message(&self) -> String {
let Self { symbol, limit, .. } = self;
format!("Macro processing in {symbol} took more than {limit} steps")
let Self { limit, .. } = self;
format!("Macro processing took more than {limit} steps")
}
fn one_position(&self) -> CodeLocation { self.location.clone() }
fn one_position(&self) -> CodeOrigin { self.location.clone() }
}
struct MacroErrors(Vec<ProjectErrorObj>);
impl ProjectError for MacroErrors {
const DESCRIPTION: &'static str = "Errors occurred during macro execution";
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> + '_ {
self.0.iter().enumerate().flat_map(|(i, e)| {
e.positions().map(move |ep| ErrorPosition {
origin: ep.origin,
message: Some(match ep.message {
Some(msg) => format!("Error #{}: {}; {msg}", i + 1, e.message()),
None => format!("Error #{}: {}", i + 1, e.message()),
}),
})
})
}
}
+33 -17
View File
@@ -1,20 +1,27 @@
//! Combine constants from [super::macro_runner::MacroRunner::run_macros] with
//! systems from [super::loader::Loader::systems]
use std::sync::Arc;
use hashbrown::HashMap;
use super::system::System;
use crate::error::ProjectResult;
use crate::error::Reporter;
use crate::foreign::inert::Inert;
use crate::foreign::to_clause::ToClause;
use crate::intermediate::ast_to_ir::ast_to_ir;
use crate::intermediate::ir_to_nort::ir_to_nort;
use crate::interpreter::nort;
use crate::location::{CodeGenInfo, CodeLocation};
use crate::name::Sym;
use crate::name::{NameLike, Sym};
use crate::pipeline::project::ConstReport;
use crate::sym;
use crate::tree::{ModMemberRef, TreeTransforms};
use crate::utils::unwrap_or::unwrap_or;
/// Equivalent of [crate::pipeline::project::ConstReport] for the interpreter's
/// representation, [crate::interpreter::nort].
#[derive(Clone)]
pub struct NortConst {
/// Comments associated with the constant which may affect its interpretation
pub comments: Vec<Arc<String>>,
@@ -23,31 +30,40 @@ pub struct NortConst {
/// Value assigned to the constant
pub value: nort::Expr,
}
impl NortConst {
/// Convert into NORT constant from AST constant
pub fn convert_from(value: ConstReport, reporter: &Reporter) -> NortConst {
let module = Sym::new(value.name.split_last().1[..].iter())
.expect("Constant names from source are at least 2 long");
let location = CodeLocation::new_src(value.range.clone(), value.name);
let nort = match ast_to_ir(value.value, value.range, module.clone()) {
Ok(ir) => ir_to_nort(&ir),
Err(e) => {
reporter.report(e);
Inert(0).to_expr(location.clone())
},
};
Self { value: nort, location, comments: value.comments }
}
}
/// Combine a list of symbols loaded from source and the constant trees from
/// each system.
pub fn merge_trees<'a: 'b, 'b>(
source: impl IntoIterator<Item = (Sym, ConstReport)> + 'b,
source: impl IntoIterator<Item = (Sym, ConstReport)>,
systems: impl IntoIterator<Item = &'b System<'a>> + 'b,
) -> ProjectResult<impl IntoIterator<Item = (Sym, NortConst)> + 'static> {
reporter: &Reporter,
) -> HashMap<Sym, NortConst> {
let mut out = HashMap::new();
for (name, rep) in source {
let ir = ast_to_ir(rep.value, name.clone())?;
// if name == Sym::literal("tree::main::main") {
// panic!("{ir:?}");
// }
out.insert(name.clone(), NortConst {
value: ir_to_nort(&ir),
location: CodeLocation::Source(rep.range),
comments: rep.comments,
});
for (name, rep) in source.into_iter() {
out.insert(name.clone(), NortConst::convert_from(rep, reporter));
}
for system in systems {
let const_module = system.constants.unwrap_mod_ref();
const_module.search_all((), |stack, node, ()| {
let c = unwrap_or!(node => ModMemberRef::Item; return);
let location = CodeLocation::Gen(CodeGenInfo::details(
"constant from",
let location = CodeLocation::new_gen(CodeGenInfo::details(
sym!(facade::merge_tree),
format!("system.name={}", system.name),
));
let value = c.clone().gen_nort(stack.clone(), location.clone());
@@ -55,5 +71,5 @@ pub fn merge_trees<'a: 'b, 'b>(
out.insert(Sym::new(stack.unreverse()).expect("root item is forbidden"), crep);
});
}
Ok(out)
out
}
+2 -1
View File
@@ -3,6 +3,7 @@
pub mod loader;
pub mod macro_runner;
pub mod merge_trees;
pub mod process;
pub mod system;
pub mod merge_trees;
pub mod unbound_ref;
-111
View File
@@ -1,111 +0,0 @@
use std::iter;
use hashbrown::HashMap;
use never::Never;
use super::process::Process;
use super::system::System;
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
use crate::intermediate::ast_to_ir::ast_to_ir;
use crate::intermediate::ir_to_nort::ir_to_nort;
use crate::interpreter::handler::HandlerTable;
use crate::location::{CodeGenInfo, CodeLocation};
use crate::name::{Sym, VPath};
use crate::parse::parsed;
use crate::pipeline::project::{
collect_consts, collect_rules, ConstReport, ProjectTree,
};
use crate::rule::repository::Repo;
use crate::tree::ModMember;
/// Everything needed for macro execution, and constructing the process
pub struct PreMacro<'a> {
/// Optimized catalog of substitution rules
pub repo: Repo,
/// Runtime code containing macro invocations
pub consts: HashMap<Sym, (ConstReport, CodeLocation)>,
/// Libraries and plug-ins
pub systems: Vec<System<'a>>,
}
impl<'a> PreMacro<'a> {
/// Build a [PreMacro] from a source tree and system list
pub fn new(
tree: &ProjectTree,
systems: Vec<System<'a>>,
) -> ProjectResult<Self> {
Ok(Self {
repo,
consts: (consts.into_iter())
.map(|(name, expr)| {
let (ent, _) = (tree.0)
.walk1_ref(&[], &name.split_last().1[..], |_| true)
.expect("path sourced from symbol names");
let location = (ent.x.locations.first().cloned())
.unwrap_or_else(|| CodeLocation::Source(expr.value.range.clone()));
(name, (expr, location))
})
.collect(),
systems,
})
}
/// Run all macros to termination or the optional timeout. If a timeout does
/// not occur, returns a process which can execute Orchid code
pub fn run_macros(
self,
timeout: Option<usize>,
) -> ProjectResult<Process<'a>> {
let Self { systems, repo, consts } = self;
for sys in systems.iter() {
let const_module = sys.constants.unwrap_mod_ref();
let _ = const_module.search_all((), &mut |path, module, ()| {
for (key, ent) in &module.entries {
if let ModMember::Item(c) = &ent.member {
let path = VPath::new(path.unreverse()).as_prefix_of(key.clone());
let cginfo = CodeGenInfo::details(
"constant from",
format!("system.name={}", sys.name),
);
symbols
.insert(path.to_sym(), c.gen_nort(CodeLocation::Gen(cginfo)));
}
}
Ok::<(), Never>(())
});
}
Ok(Process {
symbols,
handlers: (systems.into_iter())
.fold(HandlerTable::new(), |tbl, sys| tbl.combine(sys.handlers)),
})
}
/// Obtain an iterator that steps through the preprocessing of a constant
/// for debugging macros
pub fn step(&self, sym: Sym) -> impl Iterator<Item = parsed::Expr> + '_ {
let mut target =
self.consts.get(&sym).expect("Target not found").0.value.clone();
iter::from_fn(move || {
target = self.repo.step(&target)?;
Some(target.clone())
})
}
}
/// Error raised when a macro runs too long
#[derive(Debug)]
pub struct MacroTimeout {
location: CodeLocation,
symbol: Sym,
limit: usize,
}
impl ProjectError for MacroTimeout {
const DESCRIPTION: &'static str = "Macro execution has not halted";
fn message(&self) -> String {
let Self { symbol, limit, .. } = self;
format!("Macro processing in {symbol} took more than {limit} steps")
}
fn one_position(&self) -> CodeLocation { self.location.clone() }
}
+16 -88
View File
@@ -1,29 +1,31 @@
//! Run Orchid commands in the context of the loaded environment. Either
//! returned by [super::loader::Loader::proc], or constructed manually from the
//! return value of [super::merge_trees::merge_trees] and
//! [super::loader::Loader::handlers].
use hashbrown::HashMap;
use itertools::Itertools;
use super::merge_trees::NortConst;
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
use crate::interpreter::context::{Halt, RunContext};
use crate::interpreter::context::{Halt, RunEnv, RunParams};
use crate::interpreter::error::RunError;
use crate::interpreter::handler::{run_handler, HandlerTable};
use crate::interpreter::nort::{Clause, Expr};
use crate::location::CodeLocation;
use crate::interpreter::handler::HandlerTable;
use crate::interpreter::nort::Expr;
use crate::interpreter::run::run;
use crate::name::Sym;
/// This struct ties the state of systems to loaded code, and allows to call
/// Orchid-defined functions
pub struct Process<'a> {
pub(crate) symbols: HashMap<Sym, Expr>,
pub(crate) handlers: HandlerTable<'a>,
}
pub struct Process<'a>(RunEnv<'a>);
impl<'a> Process<'a> {
/// Build a process from the return value of [crate::facade::merge_trees] and
pub fn new(
consts: impl IntoIterator<Item = (Sym, NortConst)>,
handlers: HandlerTable<'a>,
) -> Self {
let symbols = consts.into_iter().map(|(k, v)| (k, v.value)).collect();
Self { handlers, symbols }
let symbols: HashMap<_, _> = consts.into_iter().map(|(k, v)| (k, v.value)).collect();
Self(RunEnv::new(handlers, move |sym, location| {
symbols.get(&sym).cloned().ok_or_else(|| RunEnv::sym_not_found(sym, location))
}))
}
/// Execute the given command in this process. If gas is specified, at most as
@@ -31,81 +33,7 @@ impl<'a> Process<'a> {
///
/// This is useful to catch infinite loops or ensure that a tenant program
/// yields
pub fn run(
&mut self,
prompt: Expr,
gas: Option<usize>,
) -> Result<Halt, RunError> {
let ctx = RunContext { gas, symbols: &self.symbols, stack_size: 1000 };
run_handler(prompt, &mut self.handlers, ctx)
}
/// Find all unbound constant names in a symbol. This is often useful to
/// identify dynamic loading targets.
#[must_use]
pub fn unbound_refs(&self, key: Sym) -> Vec<(Sym, CodeLocation)> {
let mut errors = Vec::new();
let sym = self.symbols.get(&key).expect("symbol must exist");
sym.search_all(&mut |s: &Expr| {
if let Clause::Constant(sym) = &*s.cls() {
if !self.symbols.contains_key(sym) {
errors.push((sym.clone(), s.location()))
}
}
None::<()>
});
errors
}
/// Assert that the code contains no invalid constants. This ensures that,
/// unless [Clause::Constant]s are created procedurally,
/// a [crate::interpreter::error::RunError::MissingSymbol] cannot be produced
pub fn validate_refs(&self) -> ProjectResult<()> {
let mut errors = Vec::new();
for key in self.symbols.keys() {
errors.extend(self.unbound_refs(key.clone()).into_iter().map(
|(symbol, location)| MissingSymbol {
symbol,
location,
referrer: key.clone(),
},
));
}
match errors.is_empty() {
true => Ok(()),
false => Err(MissingSymbols { errors }.pack()),
}
}
}
#[derive(Debug, Clone)]
struct MissingSymbol {
referrer: Sym,
location: CodeLocation,
symbol: Sym,
}
#[derive(Debug)]
struct MissingSymbols {
errors: Vec<MissingSymbol>,
}
impl ProjectError for MissingSymbols {
const DESCRIPTION: &'static str = "A name not referring to a known symbol was found in the source after \
macro execution. This can either mean that a symbol name was mistyped, or \
that macro execution didn't correctly halt.";
fn message(&self) -> String {
format!(
"The following symbols do not exist:\n{}",
(self.errors.iter())
.map(|MissingSymbol { symbol, referrer, .. }| format!(
"{symbol} referenced in {referrer}"
))
.join("\n")
)
}
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> {
(self.errors.iter())
.map(|i| ErrorPosition { location: i.location.clone(), message: None })
pub fn run(&self, prompt: Expr, gas: Option<usize>) -> Result<Halt, RunError<'_>> {
run(prompt, &self.0, &mut RunParams { stack: 1000, gas })
}
}
+6 -5
View File
@@ -1,10 +1,13 @@
//! Unified extension struct instances of which are catalogued by
//! [super::loader::Loader]. Language extensions must implement [IntoSystem].
use crate::error::{ErrorPosition, ProjectError};
use crate::gen::tree::ConstTree;
use crate::interpreter::handler::HandlerTable;
use crate::name::VName;
use crate::parse::lex_plugin::LexerPlugin;
use crate::parse::parse_plugin::ParseLinePlugin;
use crate::pipeline::load_solution::Prelude;
use crate::pipeline::load_project::Prelude;
use crate::virt_fs::DeclTree;
/// A description of every point where an external library can hook into Orchid.
@@ -48,8 +51,7 @@ pub struct MissingSystemCode {
referrer: VName,
}
impl ProjectError for MissingSystemCode {
const DESCRIPTION: &'static str =
"A system tried to import a path that doesn't exist";
const DESCRIPTION: &'static str = "A system tried to import a path that doesn't exist";
fn message(&self) -> String {
format!(
"Path {} imported by {} is not defined by {} or any system before it",
@@ -61,8 +63,7 @@ impl ProjectError for MissingSystemCode {
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> { [] }
}
/// Trait for objects that can be converted into a [System] in the presence
/// of an [Interner].
/// Trait for objects that can be converted into a [System].
pub trait IntoSystem<'a> {
/// Convert this object into a system using an interner
fn into_system(self) -> System<'a>;
+94
View File
@@ -0,0 +1,94 @@
//! Referencing a constant that doesn't exist is a runtime error in Orchid, even
//! though this common error condition is usually caused by faulty macro
//! execution. This module constains functions to detect and raise these errors
//! eagerly.
use std::fmt;
use hashbrown::HashSet;
use trait_set::trait_set;
use crate::error::{ProjectError, Reporter};
use crate::interpreter::nort::{Clause, Expr};
use crate::location::{CodeGenInfo, CodeLocation};
use crate::name::Sym;
use crate::sym;
/// Start with a symbol
pub fn unbound_refs_sym<E: SubError>(
symbol: Sym,
location: CodeLocation,
visited: &mut HashSet<Sym>,
load: &mut impl FnMut(Sym, CodeLocation) -> Result<Expr, E>,
reporter: &Reporter,
) {
if visited.insert(symbol.clone()) {
match load(symbol.clone(), location.clone()) {
Err(error) => reporter.report(MissingSymbol { symbol, location, error }.pack()),
Ok(expr) => unbound_refs_expr(expr, visited, load, reporter),
}
}
}
/// Find all unbound constant names in a snippet. This is mostly useful to
/// detect macro errors.
pub fn unbound_refs_expr<E: SubError>(
expr: Expr,
visited: &mut HashSet<Sym>,
load: &mut impl FnMut(Sym, CodeLocation) -> Result<Expr, E>,
reporter: &Reporter,
) {
expr.search_all(&mut |s: &Expr| {
if let Clause::Constant(symbol) = &*s.cls_mut() {
unbound_refs_sym(symbol.clone(), s.location(), visited, load, reporter)
}
None::<()>
});
}
/// Assert that the code contains no invalid references that reference missing
/// symbols. [Clause::Constant]s can be created procedurally, so this isn't a
/// total guarantee, more of a convenience.
pub fn validate_refs<E: SubError>(
all_syms: HashSet<Sym>,
reporter: &Reporter,
load: &mut impl FnMut(Sym, CodeLocation) -> Result<Expr, E>,
) -> HashSet<Sym> {
let mut visited = HashSet::new();
for sym in all_syms {
let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orchidlang::validate_refs)));
unbound_refs_sym(sym, location, &mut visited, load, reporter);
}
visited
}
trait_set! {
/// Any error the reference walker can package into a [MissingSymbol]
pub trait SubError = fmt::Display + Clone + Send + Sync + 'static;
}
/// Information about a reproject failure
#[derive(Clone)]
pub struct MissingSymbol<E: SubError> {
/// The error returned by the loader function. This is usually a ismple "not
/// found", but with unusual setups it might provide some useful info.
pub error: E,
/// Location of the first reference to the missing symbol.
pub location: CodeLocation,
/// The symbol in question
pub symbol: Sym,
}
impl<E: SubError> ProjectError for MissingSymbol<E> {
const DESCRIPTION: &'static str = "A name not referring to a known symbol was found in the source after \
macro execution. This can either mean that a symbol name was mistyped, or \
that macro execution didn't correctly halt.";
fn message(&self) -> String { format!("{}: {}", self.symbol, self.error) }
fn one_position(&self) -> crate::location::CodeOrigin { self.location.origin() }
}
// struct MissingSymbols {
// errors: Vec<ErrorPosition>,
// }
// impl ProjectError for MissingSymbols {
// fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> {
// self.errors.iter().cloned() } }
+71 -68
View File
@@ -1,65 +1,70 @@
//! Adaptor trait to embed Rust values in Orchid expressions
use std::any::Any;
use std::fmt::{Debug, Display};
use std::fmt;
use std::sync::{Arc, Mutex};
use never::Never;
use super::error::{ExternError, ExternResult};
use crate::interpreter::context::RunContext;
use crate::interpreter::error::RunError;
use super::error::{RTError, RTResult};
use crate::interpreter::context::{RunEnv, RunParams};
use crate::interpreter::nort;
use crate::location::{CodeLocation, SourceRange};
use crate::name::NameLike;
use crate::parse::lexer::Lexeme;
use crate::parse::parsed;
use crate::utils::ddispatch::{request, Request, Responder};
/// Information returned by [Atomic::run].
pub enum AtomicReturn {
/// No work was done. If the atom takes an argument, it can be provided now
Inert(nort::Clause),
Inert(Atom),
/// Work was done, returns new clause and consumed gas. 1 gas is already
/// consumed by the virtual call, so nonzero values indicate expensive
/// operations.
Change(usize, nort::Clause),
}
impl AtomicReturn {
/// Report indicating that the value is inert
pub fn inert<T: Atomic, E>(this: T) -> Result<Self, E> {
Ok(Self::Inert(this.atom_cls()))
}
/// Report indicating that the value is inert. The result here is always [Ok],
/// it's meant to match the return type of [Atomic::run]
#[allow(clippy::unnecessary_wraps)]
pub fn inert<T: Atomic, E>(this: T) -> Result<Self, E> { Ok(Self::Inert(Atom::new(this))) }
}
/// Returned by [Atomic::run]
pub type AtomicResult = Result<AtomicReturn, RunError>;
pub type AtomicResult = RTResult<AtomicReturn>;
/// General error produced when a non-function [Atom] is applied to something as
/// a function.
#[derive(Clone)]
pub struct NotAFunction(pub nort::Expr);
impl ExternError for NotAFunction {}
impl Display for NotAFunction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl RTError for NotAFunction {}
impl fmt::Display for NotAFunction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?} is not a function", self.0)
}
}
/// Information about a function call presented to an external function
pub struct CallData<'a> {
pub struct CallData<'a, 'b> {
/// Location of the function expression
pub location: CodeLocation,
/// The argument the function was called on. Functions are curried
pub arg: nort::Expr,
/// Information relating to this interpreter run
pub ctx: RunContext<'a>,
/// Globally available information such as the list of all constants
pub env: &'a RunEnv<'b>,
/// Resource limits and other details specific to this interpreter run
pub params: &'a mut RunParams,
}
/// Information about a normalization run presented to an atom
#[derive(Clone)]
pub struct RunData<'a> {
pub struct RunData<'a, 'b> {
/// Location of the atom
pub location: CodeLocation,
/// Information about the execution
pub ctx: RunContext<'a>,
/// Globally available information such as the list of all constants
pub env: &'a RunEnv<'b>,
/// Resource limits and other details specific to this interpreter run
pub params: &'a mut RunParams,
}
/// Functionality the interpreter needs to handle a value
@@ -67,15 +72,15 @@ pub struct RunData<'a> {
/// # Lifecycle methods
///
/// Atomics expose the methods [Atomic::redirect], [Atomic::run],
/// [Atomic::apply] and [Atomic::apply_ref] to interact with the interpreter.
/// [Atomic::apply] and [Atomic::apply_mut] to interact with the interpreter.
/// The interpreter first tries to call `redirect` to find a subexpression to
/// normalize. If it returns `None` or the subexpression is inert, `run` is
/// called. `run` takes ownership of the value and returns a new one.
///
/// If `run` indicated in its return value that the result is inert and the atom
/// is in the position of a function, `apply` or `apply_ref` is called depending
/// is in the position of a function, `apply` or `apply_mut` is called depending
/// upon whether the atom is referenced elsewhere. `apply` falls back to
/// `apply_ref` so implementing it is considered an optimization to avoid
/// `apply_mut` so implementing it is considered an optimization to avoid
/// excessive copying.
///
/// Atoms don't generally have to be copyable because clauses are refcounted in
@@ -83,7 +88,7 @@ pub struct RunData<'a> {
/// and apply them as functions to multiple different arguments so atoms that
/// represent functions have to support application by-ref without consuming the
/// function itself.
pub trait Atomic: Any + Debug + Responder + Send
pub trait Atomic: Any + fmt::Debug + Responder + Send
where Self: 'static
{
/// Casts this value to [Any] so that its original value can be salvaged
@@ -99,6 +104,12 @@ where Self: 'static
/// See [Atomic::as_any], exactly the same but for references
#[must_use]
fn as_any_ref(&self) -> &dyn Any;
/// Print the atom's type name. Should only ever be implemented as
///
/// ```ignore
/// fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
/// ```
fn type_name(&self) -> &'static str;
/// Returns a reference to a possible expression held inside the atom which
/// can be reduced. For an overview of the lifecycle see [Atomic]
@@ -112,22 +123,20 @@ where Self: 'static
fn run(self: Box<Self>, run: RunData) -> AtomicResult;
/// Combine the function with an argument to produce a new clause. Falls back
/// to [Atomic::apply_ref] by default.
/// to [Atomic::apply_mut] by default.
///
/// For an overview of the lifecycle see [Atomic]
fn apply(self: Box<Self>, call: CallData) -> ExternResult<nort::Clause> {
self.apply_ref(call)
}
fn apply(mut self: Box<Self>, call: CallData) -> RTResult<nort::Clause> { self.apply_mut(call) }
/// Combine the function with an argument to produce a new clause
///
/// For an overview of the lifecycle see [Atomic]
fn apply_ref(&self, call: CallData) -> ExternResult<nort::Clause>;
fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause>;
/// Must return true for atoms parsed from identical source.
/// If the atom cannot be parsed from source, it can safely be ignored
#[allow(unused_variables)]
fn parser_eq(&self, other: &dyn Any) -> bool { false }
fn parser_eq(&self, other: &dyn Atomic) -> bool { false }
/// Wrap the atom in a clause to be placed in an [AtomicResult].
#[must_use]
@@ -139,29 +148,38 @@ where Self: 'static
/// Shorthand for `self.atom_cls().to_inst()`
fn atom_clsi(self) -> nort::ClauseInst
where Self: Sized {
self.atom_cls().to_inst()
self.atom_cls().into_inst()
}
/// Wrap the atom in a new expression instance to be placed in a tree
#[must_use]
fn atom_expr(self, location: CodeLocation) -> nort::Expr
where Self: Sized {
self.atom_clsi().to_expr(location)
self.atom_clsi().into_expr(location)
}
/// Wrap the atom in a clause to be placed in a [sourcefile::FileEntry].
/// Wrap the atom in a clause to be placed in a
/// [crate::parse::parsed::SourceLine].
#[must_use]
fn ast_cls(self) -> parsed::Clause
where Self: Sized + Clone {
parsed::Clause::Atom(AtomGenerator::cloner(self))
}
/// Wrap the atom in an expression to be placed in a [sourcefile::FileEntry].
/// Wrap the atom in an expression to be placed in a
/// [crate::parse::parsed::SourceLine].
#[must_use]
fn ast_exp<N: NameLike>(self, range: SourceRange) -> parsed::Expr
where Self: Sized + Clone {
self.ast_cls().into_expr(range)
}
/// Wrap this atomic value in a lexeme. This means that the atom will
/// participate in macro reproject, so it must implement [Atomic::parser_eq].
fn lexeme(self) -> Lexeme
where Self: Sized + Clone {
Lexeme::Atom(AtomGenerator::cloner(self))
}
}
/// A struct for generating any number of [Atom]s. Since atoms aren't Clone,
@@ -170,9 +188,7 @@ where Self: 'static
pub struct AtomGenerator(Arc<dyn Fn() -> Atom + Send + Sync>);
impl AtomGenerator {
/// Use a factory function to create any number of atoms
pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self {
Self(Arc::new(f))
}
pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self { Self(Arc::new(f)) }
/// Clone a representative atom when called
pub fn cloner(atom: impl Atomic + Clone) -> Self {
let lock = Mutex::new(atom);
@@ -181,26 +197,22 @@ impl AtomGenerator {
/// Generate an atom
pub fn run(&self) -> Atom { self.0() }
}
impl Debug for AtomGenerator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.run())
}
impl fmt::Debug for AtomGenerator {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.run()) }
}
impl PartialEq for AtomGenerator {
fn eq(&self, other: &Self) -> bool { self.run().0.parser_eq(&*other.run().0) }
}
/// Represents a black box unit of code with its own normalization steps.
/// Typically [ExternFn] will produce an [Atom] when applied to a [Clause],
/// this [Atom] will then forward `run` calls to the argument until it becomes
/// inert at which point the [Atom] will validate and process the argument,
/// returning a different [Atom] intended for processing by external code, a new
/// [ExternFn] to capture an additional argument, or an Orchid expression
/// to pass control back to the interpreter.
/// Represents a black box unit of data with its own normalization steps.
/// Typically Rust functions integrated with [super::fn_bridge::xfn] will
/// produce and consume [Atom]s to represent both raw data, pending
/// computational tasks, and curried partial calls awaiting their next argument.
pub struct Atom(pub Box<dyn Atomic>);
impl Atom {
/// Wrap an [Atomic] in a type-erased box
#[must_use]
pub fn new<T: 'static + Atomic>(data: T) -> Self {
Self(Box::new(data) as Box<dyn Atomic>)
}
pub fn new<T: 'static + Atomic>(data: T) -> Self { Self(Box::new(data) as Box<dyn Atomic>) }
/// Get the contained data
#[must_use]
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic }
@@ -213,7 +225,7 @@ impl Atom {
*self.0.as_any().downcast().expect("Type mismatch on Atom::cast")
}
/// Normalize the contained data
pub fn run(self, run: RunData) -> AtomicResult { self.0.run(run) }
pub fn run(self, run: RunData<'_, '_>) -> AtomicResult { self.0.run(run) }
/// Request a delegate from the encapsulated data
pub fn request<T: 'static>(&self) -> Option<T> { request(self.0.as_ref()) }
/// Downcast the atom to a concrete atomic type, or return the original atom
@@ -225,23 +237,15 @@ impl Atom {
}
}
/// Downcast an atom by reference
pub fn downcast_ref<T: Atomic>(&self) -> Option<&T> {
self.0.as_any_ref().downcast_ref()
}
pub fn downcast_ref<T: Atomic>(&self) -> Option<&T> { self.0.as_any_ref().downcast_ref() }
/// Combine the function with an argument to produce a new clause
pub fn apply(self, call: CallData) -> ExternResult<nort::Clause> {
self.0.apply(call)
}
pub fn apply(self, call: CallData) -> RTResult<nort::Clause> { self.0.apply(call) }
/// Combine the function with an argument to produce a new clause
pub fn apply_ref(&self, call: CallData) -> ExternResult<nort::Clause> {
self.0.apply_ref(call)
}
pub fn apply_mut(&mut self, call: CallData) -> RTResult<nort::Clause> { self.0.apply_mut(call) }
}
impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.data())
}
impl fmt::Debug for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.data()) }
}
impl Responder for Never {
@@ -250,9 +254,8 @@ impl Responder for Never {
impl Atomic for Never {
fn as_any(self: Box<Self>) -> Box<dyn Any> { match *self {} }
fn as_any_ref(&self) -> &dyn Any { match *self {} }
fn type_name(&self) -> &'static str { match *self {} }
fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} }
fn run(self: Box<Self>, _: RunData) -> AtomicResult { match *self {} }
fn apply_ref(&self, _: CallData) -> ExternResult<nort::Clause> {
match *self {}
}
fn apply_mut(&mut self, _: CallData) -> RTResult<nort::Clause> { match *self {} }
}
+12 -15
View File
@@ -1,13 +1,11 @@
//! Automated wrappers to make working with CPS commands easier.
use std::fmt::Debug;
use std::fmt;
use trait_set::trait_set;
use super::atom::{
Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData,
};
use super::error::{ExternError, ExternResult};
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData};
use super::error::{RTError, RTResult};
use crate::interpreter::nort::{Clause, Expr};
use crate::location::CodeLocation;
use crate::utils::ddispatch::{Request, Responder};
@@ -15,9 +13,9 @@ use crate::utils::pure_seq::pushed_ref;
trait_set! {
/// A "well behaved" type that can be used as payload in a CPS box
pub trait CPSPayload = Clone + Debug + Send + 'static;
pub trait CPSPayload = Clone + fmt::Debug + Send + 'static;
/// A function to handle a CPS box with a specific payload
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &Expr) -> ExternResult<Expr>;
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &Expr) -> RTResult<Expr>;
}
/// An Orchid Atom value encapsulating a payload and continuation points
@@ -64,9 +62,9 @@ impl<T: CPSPayload> CPSBox<T> {
}
}
fn assert_applicable(&self, err_loc: &CodeLocation) -> ExternResult<()> {
fn assert_applicable(&self, err_loc: &CodeLocation) -> RTResult<()> {
match self.argc {
0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).rc()),
0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).pack()),
_ => Ok(()),
}
}
@@ -77,18 +75,17 @@ impl<T: CPSPayload> Responder for CPSBox<T> {
impl<T: CPSPayload> Atomic for CPSBox<T> {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
fn as_any_ref(&self) -> &dyn std::any::Any { self }
fn parser_eq(&self, _: &dyn std::any::Any) -> bool { false }
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn parser_eq(&self, _: &dyn Atomic) -> bool { false }
fn redirect(&mut self) -> Option<&mut Expr> { None }
fn run(self: Box<Self>, _: RunData) -> AtomicResult {
AtomicReturn::inert(*self)
}
fn apply(mut self: Box<Self>, call: CallData) -> ExternResult<Clause> {
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
fn apply(mut self: Box<Self>, call: CallData) -> RTResult<Clause> {
self.assert_applicable(&call.location)?;
self.argc -= 1;
self.continuations.push(call.arg);
Ok(self.atom_cls())
}
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
fn apply_mut(&mut self, call: CallData) -> RTResult<Clause> {
self.assert_applicable(&call.location)?;
let new = Self {
argc: self.argc - 1,
+22 -26
View File
@@ -1,34 +1,38 @@
//! Errors produced by the interpreter
use std::error::Error;
use std::fmt::{Debug, Display};
use std::fmt;
use std::sync::Arc;
use dyn_clone::DynClone;
use crate::error::ProjectErrorObj;
use crate::location::CodeLocation;
/// Errors produced by external code
pub trait ExternError: Display + Send + Sync + DynClone {
/// Errors produced by external code when runtime-enforced assertions are
/// violated.
pub trait RTError: fmt::Display + Send + Sync + DynClone {
/// Convert into trait object
#[must_use]
fn rc(self) -> ExternErrorObj
fn pack(self) -> RTErrorObj
where Self: 'static + Sized {
Arc::new(self)
}
}
impl Debug for dyn ExternError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "ExternError({self})")
}
impl fmt::Debug for dyn RTError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ExternError({self})") }
}
impl Error for dyn ExternError {}
impl Error for dyn RTError {}
impl RTError for ProjectErrorObj {}
/// An error produced by Rust code called form Orchid. The error is type-erased.
pub type ExternErrorObj = Arc<dyn ExternError>;
pub type RTErrorObj = Arc<dyn RTError>;
/// A result produced by Rust code called from Orchid.
pub type ExternResult<T> = Result<T, ExternErrorObj>;
pub type RTResult<T> = Result<T, RTErrorObj>;
/// Some expectation (usually about the argument types of a function) did not
/// hold.
@@ -42,30 +46,22 @@ pub struct AssertionError {
impl AssertionError {
/// Construct, upcast and wrap in a Result that never succeeds for easy
/// short-circuiting
pub fn fail<T>(
location: CodeLocation,
message: &'static str,
details: String,
) -> ExternResult<T> {
pub fn fail<T>(location: CodeLocation, message: &'static str, details: String) -> RTResult<T> {
Err(Self::ext(location, message, details))
}
/// Construct and upcast to [ExternError]
pub fn ext(
location: CodeLocation,
message: &'static str,
details: String,
) -> ExternErrorObj {
Self { location, message, details }.rc()
/// Construct and upcast to [RTErrorObj]
pub fn ext(location: CodeLocation, message: &'static str, details: String) -> RTErrorObj {
Self { location, message, details }.pack()
}
}
impl Display for AssertionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for AssertionError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Error: expected {}", self.message)?;
write!(f, " at {}", self.location)?;
write!(f, " details: {}", self.details)
}
}
impl ExternError for AssertionError {}
impl RTError for AssertionError {}
+19 -16
View File
@@ -1,26 +1,27 @@
//! Insert Rust functions in Orchid code almost seamlessly
use std::any::{Any, TypeId};
use std::fmt::{Debug, Display};
use std::fmt;
use std::marker::PhantomData;
use intern_all::{i, Tok};
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData};
use super::error::ExternResult;
use super::error::RTResult;
use super::to_clause::ToClause;
use super::try_from_expr::TryFromExpr;
use crate::interpreter::nort::{Clause, Expr};
use crate::utils::ddispatch::Responder;
/// Return a unary lambda wrapped in this struct to take an additional argument
/// in a function passed to Orchid through a member of the [super::xfn_1ary]
/// family.
/// in a function passed to Orchid through [super::fn_bridge::xfn].
///
/// Container for a unary [FnOnce] that uniquely states the argument and return
/// type. Rust functions are never overloaded, but inexplicably the [Fn] traits
/// take the argument tuple as a generic parameter which means that it cannot
/// be a unique dispatch target.
///
/// If the function takes an instance of [Lazy], it will contain the expression
/// If the function takes an instance of [Thunk], it will contain the expression
/// the function was applied to without any specific normalization. If it takes
/// any other type, the argument will be fully normalized and cast using the
/// type's [TryFromExpr] impl.
@@ -45,11 +46,11 @@ impl<T, U, F: Clone> Clone for Param<T, U, F> {
Self { name: self.name.clone(), data: self.data.clone(), _t: PhantomData, _u: PhantomData }
}
}
impl<T, U, F> Display for Param<T, U, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.name) }
impl<T, U, F> fmt::Display for Param<T, U, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) }
}
impl<T, U, F> Debug for Param<T, U, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<T, U, F> fmt::Debug for Param<T, U, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Param").field(&*self.name).finish()
}
}
@@ -60,7 +61,7 @@ impl<T, U, F> Debug for Param<T, U, F> {
#[derive(Debug, Clone)]
pub struct Thunk(pub Expr);
impl TryFromExpr for Thunk {
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(Thunk(expr)) }
fn from_expr(expr: Expr) -> RTResult<Self> { Ok(Thunk(expr)) }
}
struct FnMiddleStage<T, U, F> {
@@ -71,8 +72,8 @@ struct FnMiddleStage<T, U, F> {
impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> {
fn clone(&self) -> Self { Self { arg: self.arg.clone(), f: self.f.clone() } }
}
impl<T, U, F> Debug for FnMiddleStage<T, U, F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<T, U, F> fmt::Debug for FnMiddleStage<T, U, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "FnMiddleStage({} {})", self.f, self.arg)
}
}
@@ -85,6 +86,7 @@ impl<
{
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
fn as_any_ref(&self) -> &dyn std::any::Any { self }
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn redirect(&mut self) -> Option<&mut Expr> {
// this should be ctfe'd
(TypeId::of::<T>() != TypeId::of::<Thunk>()).then_some(&mut self.arg)
@@ -93,7 +95,7 @@ impl<
let Self { arg, f: Param { data: f, .. } } = *self;
Ok(AtomicReturn::Change(0, f(arg.downcast()?).to_clause(r.location)))
}
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> { panic!("Atom should have decayed") }
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> { panic!("Atom should have decayed") }
}
impl<T, U, F> Responder for Param<T, U, F> {}
@@ -106,12 +108,13 @@ impl<
{
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
fn as_any_ref(&self) -> &dyn std::any::Any { self }
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn redirect(&mut self) -> Option<&mut Expr> { None }
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
fn apply_mut(&mut self, call: CallData) -> RTResult<Clause> {
Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls())
}
fn apply(self: Box<Self>, call: CallData) -> ExternResult<Clause> {
fn apply(self: Box<Self>, call: CallData) -> RTResult<Clause> {
Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls())
}
}
@@ -134,7 +137,7 @@ pub fn xfn<const N: usize, Argv, Ret>(
/// - the return type must implement [ToClause]
///
/// Take [Thunk] to consume the argument as-is, without normalization.
pub trait Xfn<const N: usize, Argv, Ret>: Clone + 'static {
pub trait Xfn<const N: usize, Argv, Ret>: Clone + Send + 'static {
/// Convert Rust type to Orchid function, given a name for logging
fn to_atomic(self, name: Tok<String>) -> impl Atomic + Clone;
}
-169
View File
@@ -1,169 +0,0 @@
use std::any::Any;
use std::fmt::Debug;
use super::atom::{Atom, Atomic, AtomicResult, CallData, RunData};
use super::error::{ExternErrorObj, ExternResult};
use super::process::Unstable;
use super::to_clause::ToClause;
use crate::gen::tpl;
use crate::gen::traits::Gen;
use crate::interpreter::error::RunError;
use crate::interpreter::gen_nort::nort_gen;
use crate::interpreter::nort::{Clause, Expr};
use crate::location::CodeLocation;
use crate::utils::clonable_iter::Clonable;
use crate::utils::ddispatch::Responder;
impl<T: ToClause> ToClause for Option<T> {
fn to_clause(self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self {
None => tpl::C("std::option::none").template(ctx, []),
Some(t) => tpl::A(tpl::C("std::option::some"), tpl::Slot)
.template(ctx, [t.to_clause(location)]),
}
}
}
impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
fn to_clause(self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self {
Ok(t) => tpl::A(tpl::C("std::result::ok"), tpl::Slot)
.template(ctx, [t.to_clause(location)]),
Err(e) => tpl::A(tpl::C("std::result::err"), tpl::Slot)
.template(ctx, [e.to_clause(location)]),
}
}
}
struct PendingError(ExternErrorObj);
impl Responder for PendingError {}
impl Debug for PendingError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "PendingError({})", self.0)
}
}
impl Atomic for PendingError {
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
fn redirect(&mut self) -> Option<&mut Expr> { None }
fn run(self: Box<Self>, _: RunData) -> AtomicResult {
Err(RunError::Extern(self.0))
}
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
panic!("This atom decays instantly")
}
}
impl<T: ToClause> ToClause for ExternResult<T> {
fn to_clause(self, location: CodeLocation) -> Clause {
match self {
Err(e) => PendingError(e).atom_cls(),
Ok(t) => t.to_clause(location),
}
}
}
struct ListGen<I>(Clonable<I>)
where
I: Iterator + Send,
I::Item: ToClause + Send;
impl<I> Clone for ListGen<I>
where
I: Iterator + Send,
I::Item: ToClause + Send,
{
fn clone(&self) -> Self { Self(self.0.clone()) }
}
impl<I> ToClause for ListGen<I>
where
I: Iterator + Send + 'static,
I::Item: ToClause + Clone + Send,
{
fn to_clause(mut self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self.0.next() {
None => tpl::C("std::lit::end").template(ctx, []),
Some(val) => {
let atom = Unstable::new(|run| self.to_clause(run.location));
tpl::a2(tpl::C("std::lit::cons"), tpl::Slot, tpl::V(atom))
.template(ctx, [val.to_clause(location)])
},
}
}
}
/// Convert an iterator into a lazy-evaluated Orchid list.
pub fn list<I>(items: I) -> impl ToClause
where
I: IntoIterator + Clone + Send + Sync + 'static,
I::IntoIter: Send,
I::Item: ToClause + Clone + Send,
{
Unstable::new(move |RunData { location, .. }| {
ListGen(Clonable::new(
items.clone().into_iter().map(move |t| t.to_clsi(location.clone())),
))
})
}
impl<T: ToClause + Clone + Send + Sync + 'static> ToClause for Vec<T> {
fn to_clause(self, location: CodeLocation) -> Clause {
list(self).to_clause(location)
}
}
impl ToClause for Atom {
fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) }
}
mod tuple_impls {
use std::sync::Arc;
use super::ToClause;
use crate::foreign::atom::Atomic;
use crate::foreign::error::AssertionError;
use crate::foreign::implementations::ExternResult;
use crate::foreign::inert::Inert;
use crate::foreign::try_from_expr::TryFromExpr;
use crate::interpreter::nort::{Clause, Expr};
use crate::libs::std::tuple::Tuple;
use crate::location::CodeLocation;
macro_rules! gen_tuple_impl {
( ($($T:ident)*) ($($t:ident)*)) => {
impl<$($T: ToClause),*> ToClause for ($($T,)*) {
fn to_clause(self, location: CodeLocation) -> Clause {
let ($($t,)*) = self;
Inert(Tuple(Arc::new(vec![
$($t.to_expr(location.clone()),)*
]))).atom_cls()
}
}
impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) {
fn from_expr(ex: Expr) -> ExternResult<Self> {
let Inert(Tuple(slice)) = ex.clone().downcast()?;
match &slice[..] {
[$($t),*] => Ok(($($t.clone().downcast()?,)*)),
_ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}"))
}
}
}
};
}
gen_tuple_impl!((A)(a));
gen_tuple_impl!((A B) (a b));
gen_tuple_impl!((A B C) (a b c));
gen_tuple_impl!((A B C D) (a b c d));
gen_tuple_impl!((A B C D E) (a b c d e));
gen_tuple_impl!((A B C D E F) (a b c d e f));
gen_tuple_impl!((A B C D E F G) (a b c d e f g));
gen_tuple_impl!((A B C D E F G H) (a b c d e f g h));
gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i));
gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j));
gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k));
gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l));
}
+29 -37
View File
@@ -1,13 +1,13 @@
//! An [Atomic] that wraps inert data.
use std::any::Any;
use std::fmt::{Debug, Display};
use std::fmt;
use std::ops::{Deref, DerefMut};
use ordered_float::NotNan;
use super::atom::{
Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData,
};
use super::error::{ExternError, ExternResult};
use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData};
use super::error::{RTError, RTResult};
use super::try_from_expr::TryFromExpr;
use crate::foreign::error::AssertionError;
use crate::interpreter::nort::{Clause, Expr};
@@ -17,11 +17,10 @@ use crate::utils::ddispatch::{Request, Responder};
/// A proxy trait that implements [Atomic] for blobs of data in Rust code that
/// cannot be processed and always report inert. Since these are expected to be
/// parameters of functions defined with [define_fn] it also automatically
/// implements [TryFromExpr] so that a conversion doesn't have to be
/// provided in argument lists.
pub trait InertPayload: Debug + Clone + Send + 'static {
/// Typename to be shown in the error when a conversion from [ExprInst] fails
/// parameters of Rust functions it also automatically implements [TryFromExpr]
/// so that `Inert<MyType>` arguments can be parsed directly.
pub trait InertPayload: fmt::Debug + Clone + Send + 'static {
/// Typename to be shown in the error when a conversion from [Expr] fails
///
/// This will default to `type_name::<Self>()` when it becomes stable
const TYPE_STR: &'static str;
@@ -37,7 +36,8 @@ pub trait InertPayload: Debug + Clone + Send + 'static {
/// ```ignore
/// fn strict_eq(&self, other: &Self) -> bool { self == other }
/// ```
fn strict_eq(&self, _: &Self) -> bool { false }
#[allow(unused_variables)]
fn strict_eq(&self, other: &Self) -> bool { false }
}
/// An atom that stores a value and rejects all interpreter interactions. It is
@@ -61,42 +61,36 @@ impl<T: InertPayload> DerefMut for Inert<T> {
impl<T: InertPayload> Responder for Inert<T> {
fn respond(&self, mut request: Request) {
if request.can_serve::<T>() {
request.serve(self.0.clone())
} else {
self.0.respond(request)
}
if request.can_serve::<T>() { request.serve(self.0.clone()) } else { self.0.respond(request) }
}
}
impl<T: InertPayload> Atomic for Inert<T> {
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn redirect(&mut self) -> Option<&mut Expr> { None }
fn run(self: Box<Self>, _: RunData) -> AtomicResult {
AtomicReturn::inert(*self)
fn run(self: Box<Self>, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) }
fn apply_mut(&mut self, call: CallData) -> RTResult<Clause> {
Err(NotAFunction(self.clone().atom_expr(call.location)).pack())
}
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
Err(NotAFunction(self.clone().atom_expr(call.location)).rc())
}
fn parser_eq(&self, other: &dyn Any) -> bool {
(other.downcast_ref::<Self>())
.map_or(false, |other| self.0.strict_eq(&other.0))
fn parser_eq(&self, other: &dyn Atomic) -> bool {
other.as_any_ref().downcast_ref::<Self>().map_or(false, |other| self.0.strict_eq(&other.0))
}
}
impl<T: InertPayload> TryFromExpr for Inert<T> {
fn from_expr(expr: Expr) -> ExternResult<Self> {
fn from_expr(expr: Expr) -> RTResult<Self> {
let Expr { clause, location } = expr;
match clause.try_unwrap() {
Ok(Clause::Atom(at)) => at.try_downcast::<Self>().map_err(|a| {
AssertionError::ext(location, T::TYPE_STR, format!("{a:?}"))
}),
Err(inst) => match &*inst.cls() {
Clause::Atom(at) =>
at.downcast_ref::<Self>().cloned().ok_or_else(|| {
AssertionError::ext(location, T::TYPE_STR, format!("{inst}"))
}),
Ok(Clause::Atom(at)) => at
.try_downcast::<Self>()
.map_err(|a| AssertionError::ext(location, T::TYPE_STR, format!("{a:?}"))),
Err(inst) => match &*inst.cls_mut() {
Clause::Atom(at) => at
.downcast_ref::<Self>()
.cloned()
.ok_or_else(|| AssertionError::ext(location, T::TYPE_STR, format!("{inst}"))),
cls => AssertionError::fail(location, "atom", format!("{cls}")),
},
Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")),
@@ -104,10 +98,8 @@ impl<T: InertPayload> TryFromExpr for Inert<T> {
}
}
impl<T: InertPayload + Display> Display for Inert<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
impl<T: InertPayload + fmt::Display> fmt::Display for Inert<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) }
}
impl InertPayload for bool {
-1
View File
@@ -6,7 +6,6 @@ pub mod atom;
pub mod cps_box;
pub mod error;
pub mod fn_bridge;
pub mod implementations;
pub mod inert;
pub mod process;
pub mod to_clause;
+16 -13
View File
@@ -1,7 +1,11 @@
use std::fmt::Debug;
//! An [Atomic] implementor that runs a callback and turns into the return
//! value. Useful to generate expressions that depend on values in the
//! interpreter's context, and to defer the generation of expensive
//! subexpressions
use std::fmt;
use super::atom::{Atomic, AtomicReturn, CallData, RunData};
use super::error::ExternResult;
use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData};
use super::error::RTResult;
use super::to_clause::ToClause;
use crate::interpreter::nort::{Clause, Expr};
use crate::utils::ddispatch::Responder;
@@ -16,21 +20,20 @@ impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Unstable<F> {
pub const fn new(f: F) -> Self { Self(f) }
}
impl<F> Responder for Unstable<F> {}
impl<F> Debug for Unstable<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<F> fmt::Debug for Unstable<F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Unstable").finish_non_exhaustive()
}
}
impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Atomic
for Unstable<F>
{
impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Atomic for Unstable<F> {
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
fn as_any_ref(&self) -> &dyn std::any::Any { self }
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
panic!("This atom decays instantly")
}
fn run(self: Box<Self>, run: RunData) -> super::atom::AtomicResult {
let clause = self.0(run.clone()).to_clause(run.location.clone());
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> { panic!("This atom decays instantly") }
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
let loc = run.location.clone();
let clause = self.0(run).to_clause(loc);
Ok(AtomicReturn::Change(0, clause))
}
fn redirect(&mut self) -> Option<&mut Expr> { None }
+168 -9
View File
@@ -1,10 +1,18 @@
use super::atom::Atomic;
//! Conversions from Rust values to Orchid expressions. Many APIs and
//! [super::fn_bridge] in particular use this to automatically convert values on
//! the boundary. The opposite conversion is [super::try_from_expr::TryFromExpr]
use super::atom::{Atomic, RunData};
use super::process::Unstable;
use crate::gen::tpl;
use crate::gen::traits::Gen;
use crate::interpreter::gen_nort::nort_gen;
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
use crate::location::CodeLocation;
use crate::utils::clonable_iter::Clonable;
/// A trait for things that are infallibly convertible to [ClauseInst]. These
/// types can be returned by callbacks passed to the [super::xfn_1ary] family of
/// functions.
/// types can be returned by callbacks passed to [super::fn_bridge::xfn].
pub trait ToClause: Sized {
/// Convert this value to a [Clause]. If your value can only be directly
/// converted to a [ClauseInst], you can call `ClauseInst::to_clause` to
@@ -29,15 +37,166 @@ impl ToClause for Clause {
fn to_clause(self, _: CodeLocation) -> Clause { self }
}
impl ToClause for ClauseInst {
fn to_clause(self, _: CodeLocation) -> Clause {
self.into_cls()
}
fn to_clause(self, _: CodeLocation) -> Clause { self.into_cls() }
fn to_clsi(self, _: CodeLocation) -> ClauseInst { self }
}
impl ToClause for Expr {
fn to_clause(self, location: CodeLocation) -> Clause {
self.clause.to_clause(location)
}
fn to_clause(self, location: CodeLocation) -> Clause { self.clause.to_clause(location) }
fn to_clsi(self, _: CodeLocation) -> ClauseInst { self.clause }
fn to_expr(self, _: CodeLocation) -> Expr { self }
}
struct ListGen<I>(Clonable<I>)
where
I: Iterator + Send,
I::Item: ToClause + Send;
impl<I> Clone for ListGen<I>
where
I: Iterator + Send,
I::Item: ToClause + Send,
{
fn clone(&self) -> Self { Self(self.0.clone()) }
}
impl<I> ToClause for ListGen<I>
where
I: Iterator + Send + 'static,
I::Item: ToClause + Clone + Send,
{
fn to_clause(mut self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self.0.next() {
None => tpl::C("std::list::end").template(ctx, []),
Some(val) => {
let atom = Unstable::new(|run| self.to_clause(run.location));
tpl::a2(tpl::C("std::list::cons"), tpl::Slot, tpl::V(atom))
.template(ctx, [val.to_clause(location)])
},
}
}
}
/// Convert an iterator into a lazy-evaluated Orchid list.
pub fn list<I>(items: I) -> impl ToClause
where
I: IntoIterator + Clone + Send + Sync + 'static,
I::IntoIter: Send,
I::Item: ToClause + Clone + Send,
{
Unstable::new(move |RunData { location, .. }| {
ListGen(Clonable::new(items.clone().into_iter().map(move |t| t.to_clsi(location.clone()))))
})
}
mod implementations {
use std::any::Any;
use std::fmt;
use std::sync::Arc;
use super::{list, ToClause};
use crate::foreign::atom::{Atom, Atomic, AtomicResult, CallData, RunData};
use crate::foreign::error::{AssertionError, RTErrorObj, RTResult};
use crate::foreign::inert::Inert;
use crate::foreign::try_from_expr::TryFromExpr;
use crate::gen::tpl;
use crate::gen::traits::Gen;
use crate::interpreter::gen_nort::nort_gen;
use crate::interpreter::nort::{Clause, Expr};
use crate::libs::std::tuple::Tuple;
use crate::location::CodeLocation;
use crate::utils::ddispatch::Responder;
impl<T: ToClause> ToClause for Option<T> {
fn to_clause(self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self {
None => tpl::C("std::option::none").template(ctx, []),
Some(t) =>
tpl::A(tpl::C("std::option::some"), tpl::Slot).template(ctx, [t.to_clause(location)]),
}
}
}
impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
fn to_clause(self, location: CodeLocation) -> Clause {
let ctx = nort_gen(location.clone());
match self {
Ok(t) =>
tpl::A(tpl::C("std::result::ok"), tpl::Slot).template(ctx, [t.to_clause(location)]),
Err(e) =>
tpl::A(tpl::C("std::result::err"), tpl::Slot).template(ctx, [e.to_clause(location)]),
}
}
}
struct PendingError(RTErrorObj);
impl Responder for PendingError {}
impl fmt::Debug for PendingError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PendingError({})", self.0)
}
}
impl Atomic for PendingError {
fn as_any(self: Box<Self>) -> Box<dyn Any> { self }
fn as_any_ref(&self) -> &dyn Any { self }
fn type_name(&self) -> &'static str { std::any::type_name::<Self>() }
fn redirect(&mut self) -> Option<&mut Expr> { None }
fn run(self: Box<Self>, _: RunData) -> AtomicResult { Err(self.0) }
fn apply_mut(&mut self, _: CallData) -> RTResult<Clause> {
panic!("This atom decays instantly")
}
}
impl<T: ToClause> ToClause for RTResult<T> {
fn to_clause(self, location: CodeLocation) -> Clause {
match self {
Err(e) => PendingError(e).atom_cls(),
Ok(t) => t.to_clause(location),
}
}
}
impl<T: ToClause + Clone + Send + Sync + 'static> ToClause for Vec<T> {
fn to_clause(self, location: CodeLocation) -> Clause { list(self).to_clause(location) }
}
impl ToClause for Atom {
fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) }
}
macro_rules! gen_tuple_impl {
( ($($T:ident)*) ($($t:ident)*)) => {
impl<$($T: ToClause),*> ToClause for ($($T,)*) {
fn to_clause(self, location: CodeLocation) -> Clause {
let ($($t,)*) = self;
Inert(Tuple(Arc::new(vec![
$($t.to_expr(location.clone()),)*
]))).atom_cls()
}
}
impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) {
fn from_expr(ex: Expr) -> RTResult<Self> {
let Inert(Tuple(slice)) = ex.clone().downcast()?;
match &slice[..] {
[$($t),*] => Ok(($($t.clone().downcast()?,)*)),
_ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}"))
}
}
}
};
}
gen_tuple_impl!((A)(a));
gen_tuple_impl!((A B) (a b));
gen_tuple_impl!((A B C) (a b c));
gen_tuple_impl!((A B C D) (a b c d));
gen_tuple_impl!((A B C D E) (a b c d e));
gen_tuple_impl!((A B C D E F) (a b c d e f));
gen_tuple_impl!((A B C D E F G) (a b c d e f g));
gen_tuple_impl!((A B C D E F G H) (a b c d e f g h));
gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i));
gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j));
gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k));
gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l));
}
+9 -7
View File
@@ -1,4 +1,8 @@
use super::error::ExternResult;
//! Conversions from Orchid expressions to Rust values. Many APIs and
//! [super::fn_bridge] in particular use this to automatically convert values on
//! the boundary. Failures cause an interpreter exit
use super::error::RTResult;
use crate::interpreter::nort::{ClauseInst, Expr};
use crate::location::CodeLocation;
@@ -6,15 +10,15 @@ use crate::location::CodeLocation;
/// foreign functions request automatic argument downcasting.
pub trait TryFromExpr: Sized {
/// Match and clone the value out of an [Expr]
fn from_expr(expr: Expr) -> ExternResult<Self>;
fn from_expr(expr: Expr) -> RTResult<Self>;
}
impl TryFromExpr for Expr {
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr) }
fn from_expr(expr: Expr) -> RTResult<Self> { Ok(expr) }
}
impl TryFromExpr for ClauseInst {
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr.clsi()) }
fn from_expr(expr: Expr) -> RTResult<Self> { Ok(expr.clsi()) }
}
/// Request a value of a particular type and also return its location for
@@ -22,7 +26,5 @@ impl TryFromExpr for ClauseInst {
#[derive(Debug, Clone)]
pub struct WithLoc<T>(pub CodeLocation, pub T);
impl<T: TryFromExpr> TryFromExpr for WithLoc<T> {
fn from_expr(expr: Expr) -> ExternResult<Self> {
Ok(Self(expr.location(), T::from_expr(expr)?))
}
fn from_expr(expr: Expr) -> RTResult<Self> { Ok(Self(expr.location(), T::from_expr(expr)?)) }
}
+3 -13
View File
@@ -1,8 +1,6 @@
//! Various elemental components to build expression trees that all implement
//! [GenClause].
use std::fmt::Debug;
use super::traits::{GenClause, Generable};
use crate::foreign::atom::{Atom, AtomGenerator, Atomic};
@@ -44,11 +42,7 @@ impl<F: GenClause, X: GenClause> GenClause for A<F, X> {
}
/// Apply a function to two arguments
pub fn a2(
f: impl GenClause,
x: impl GenClause,
y: impl GenClause,
) -> impl GenClause {
pub fn a2(f: impl GenClause, x: impl GenClause, y: impl GenClause) -> impl GenClause {
A(A(f, x), y)
}
@@ -65,16 +59,12 @@ impl<B: GenClause> GenClause for L<B> {
#[derive(Debug, Clone)]
pub struct P(pub &'static str);
impl GenClause for P {
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T {
T::arg(ctx, self.0)
}
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T { T::arg(ctx, self.0) }
}
/// Slot for an Orchid value to be specified during execution
#[derive(Debug, Clone)]
pub struct Slot;
impl GenClause for Slot {
fn generate<T: Generable>(&self, _: T::Ctx<'_>, pop: &impl Fn() -> T) -> T {
pop()
}
fn generate<T: Generable>(&self, _: T::Ctx<'_>, pop: &impl Fn() -> T) -> T { pop() }
}
+14 -17
View File
@@ -3,7 +3,7 @@
use std::backtrace::Backtrace;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt::Debug;
use std::fmt;
use crate::foreign::atom::Atom;
@@ -15,10 +15,7 @@ pub trait Generable: Sized {
/// Wrap external data.
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self;
/// Generate a reference to a constant
fn constant<'a>(
ctx: Self::Ctx<'_>,
name: impl IntoIterator<Item = &'a str>,
) -> Self;
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self;
/// Generate a function call given the function and its argument
fn apply(
ctx: Self::Ctx<'_>,
@@ -27,11 +24,7 @@ pub trait Generable: Sized {
) -> Self;
/// Generate a function. The argument name is only valid within the same
/// [Generable].
fn lambda(
ctx: Self::Ctx<'_>,
name: &str,
body: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self;
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self;
/// Generate a reference to a function argument. The argument name is only
/// valid within the same [Generable].
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self;
@@ -39,11 +32,11 @@ pub trait Generable: Sized {
/// Expression templates which can be instantiated in multiple representations
/// of Orchid. Expressions can be built from the elements defined in
/// [super::lit].
/// [super::tpl].
///
/// Do not depend on this trait, use [Gen] instead. Conversely, implement this
/// instead of [Gen].
pub trait GenClause: Debug + Sized {
pub trait GenClause: fmt::Debug + Sized {
/// Enact the template at runtime to build a given type.
/// `pop` pops from the runtime template parameter list passed to the
/// generator.
@@ -56,7 +49,7 @@ pub trait GenClause: Debug + Sized {
///
/// Do not implement this trait, it's the frontend for [GenClause]. Conversely,
/// do not consume [GenClause].
pub trait Gen<T: Generable, U>: Debug {
pub trait Gen<T: Generable, U>: fmt::Debug {
/// Create an instance of this template with some parameters
fn template(&self, ctx: T::Ctx<'_>, params: U) -> T;
}
@@ -64,11 +57,15 @@ pub trait Gen<T: Generable, U>: Debug {
impl<T: Generable, I: IntoIterator<Item = T>, G: GenClause> Gen<T, I> for G {
fn template(&self, ctx: T::Ctx<'_>, params: I) -> T {
let values = RefCell::new(params.into_iter().collect::<VecDeque<_>>());
let t = self.generate(ctx, &|| {
values.borrow_mut().pop_front().expect("Not enough values provided")
});
let t =
self.generate(ctx, &|| values.borrow_mut().pop_front().expect("Not enough values provided"));
let leftover = values.borrow().len();
assert_eq!(leftover, 0, "Too many values provided ({leftover} left) {}", Backtrace::force_capture());
assert_eq!(
leftover,
0,
"Too many values provided ({leftover} left) {}",
Backtrace::force_capture()
);
t
}
}
+27 -13
View File
@@ -1,7 +1,7 @@
//! Components to build in-memory module trees that in Orchid. These modules
//! can only contain constants and other modules.
use std::fmt::Debug;
use std::fmt;
use dyn_clone::{clone_box, DynClone};
use intern_all::Tok;
@@ -20,8 +20,8 @@ use crate::tree::{ModEntry, ModMember, TreeConflict};
use crate::utils::combine::Combine;
trait_set! {
trait TreeLeaf = Gen<Expr, [Expr; 0]> + DynClone;
trait XfnCB = FnOnce(Substack<Tok<String>>) -> AtomGenerator + DynClone;
trait TreeLeaf = Gen<Expr, [Expr; 0]> + DynClone + Send;
trait XfnCB = FnOnce(Substack<Tok<String>>) -> AtomGenerator + DynClone + Send;
}
enum GCKind {
@@ -32,11 +32,14 @@ enum GCKind {
/// A leaf in the [ConstTree]
pub struct GenConst(GCKind);
impl GenConst {
fn c(data: impl GenClause + Clone + 'static) -> Self { Self(GCKind::Const(Box::new(data))) }
fn c(data: impl GenClause + Send + Clone + 'static) -> Self {
Self(GCKind::Const(Box::new(data)))
}
fn f<const N: usize, Argv, Ret>(f: impl Xfn<N, Argv, Ret>) -> Self {
Self(GCKind::Xfn(N, Box::new(move |stck| {
AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f))
})))
Self(GCKind::Xfn(
N,
Box::new(move |stck| AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f))),
))
}
/// Instantiate as [crate::interpreter::nort]
pub fn gen_nort(self, stck: Substack<Tok<String>>, location: CodeLocation) -> Expr {
@@ -46,8 +49,8 @@ impl GenConst {
}
}
}
impl Debug for GenConst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for GenConst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
GCKind::Const(c) => write!(f, "{c:?}"),
GCKind::Xfn(n, _) => write!(f, "xfn/{n}"),
@@ -75,19 +78,30 @@ impl Combine for GenConst {
}
/// A lightweight module tree that can be built declaratively by hand to
/// describe libraries of external functions in Rust. It implements [Add] for
/// added convenience
/// describe libraries of external functions in Rust. It implements [Combine]
/// for merging libraries.
pub type ConstTree = ModEntry<GenConst, (), ()>;
/// Describe a constant
#[must_use]
pub fn leaf(value: impl GenClause + Clone + 'static) -> ConstTree {
pub fn leaf(value: impl GenClause + Clone + Send + 'static) -> ConstTree {
ModEntry::wrap(ModMember::Item(GenConst::c(value)))
}
/// Describe a constant which appears in [ConstTree::tree].
///
/// The unarray tricks rustfmt into keeping this call as a single line even if
/// it chooses to break the argument into a block.
pub fn ent<K: AsRef<str>>(
key: K,
[g]: [impl GenClause + Clone + Send + 'static; 1],
) -> (K, ConstTree) {
(key, leaf(g))
}
/// Describe an [Atomic]
#[must_use]
pub fn atom_leaf(atom: impl Atomic + Clone + 'static) -> ConstTree { leaf(tpl::V(atom)) }
pub fn atom_leaf(atom: impl Atomic + Clone + Send + 'static) -> ConstTree { leaf(tpl::V(atom)) }
/// Describe an [Atomic] which appears as an entry in a [ConstTree::tree]
///
+32 -34
View File
@@ -7,7 +7,7 @@ use substack::Substack;
use super::ir;
use crate::error::{ProjectError, ProjectResult};
use crate::location::{CodeLocation, SourceRange};
use crate::location::{CodeOrigin, SourceRange};
use crate::name::Sym;
use crate::parse::parsed;
use crate::utils::unwrap_or::unwrap_or;
@@ -17,20 +17,24 @@ trait IRErrorKind: Clone + Send + Sync + 'static {
}
#[derive(Clone)]
struct IRError<T: IRErrorKind>(SourceRange, Sym, T);
struct IRError<T: IRErrorKind> {
at: SourceRange,
sym: SourceRange,
_kind: T,
}
impl<T: IRErrorKind> IRError<T> {
fn new(at: SourceRange, sym: SourceRange, _kind: T) -> Self { Self { at, sym, _kind } }
}
impl<T: IRErrorKind> ProjectError for IRError<T> {
const DESCRIPTION: &'static str = T::DESCR;
fn message(&self) -> String { format!("In {}, {}", self.1, T::DESCR) }
fn one_position(&self) -> CodeLocation {
CodeLocation::Source(self.0.clone())
}
fn message(&self) -> String { format!("In {}, {}", self.sym, T::DESCR) }
fn one_position(&self) -> CodeOrigin { CodeOrigin::Source(self.at.clone()) }
}
#[derive(Clone)]
struct EmptyS;
impl IRErrorKind for EmptyS {
const DESCR: &'static str =
"`()` as a clause is meaningless in lambda calculus";
const DESCR: &'static str = "`()` as a clause is meaningless in lambda calculus";
}
#[derive(Clone)]
@@ -54,26 +58,29 @@ impl IRErrorKind for PhLeak {
}
/// Try to convert an expression from AST format to typed lambda
pub fn ast_to_ir(expr: parsed::Expr, symbol: Sym) -> ProjectResult<ir::Expr> {
expr_rec(expr, Context::new(symbol))
pub fn ast_to_ir(expr: parsed::Expr, symbol: SourceRange, module: Sym) -> ProjectResult<ir::Expr> {
expr_rec(expr, Context::new(symbol, module))
}
#[derive(Clone)]
struct Context<'a> {
names: Substack<'a, Sym>,
symbol: Sym,
range: SourceRange,
module: Sym,
}
impl<'a> Context<'a> {
#[must_use]
fn w_name<'b>(&'b self, name: Sym) -> Context<'b>
where 'a: 'b {
Context { names: self.names.push(name), symbol: self.symbol.clone() }
Context { names: self.names.push(name), range: self.range.clone(), module: self.module.clone() }
}
}
impl Context<'static> {
#[must_use]
fn new(symbol: Sym) -> Self { Self { names: Substack::Bottom, symbol } }
fn new(symbol: SourceRange, module: Sym) -> Self {
Self { names: Substack::Bottom, range: symbol, module }
}
}
/// Process an expression sequence
@@ -83,29 +90,25 @@ fn exprv_rec(
location: SourceRange,
) -> ProjectResult<ir::Expr> {
let last = unwrap_or! {v.pop_back(); {
return Err(IRError(location, ctx.symbol, EmptyS).pack());
return Err(IRError::new(location, ctx.range, EmptyS).pack());
}};
let v_end = match v.back() {
None => return expr_rec(last, ctx),
Some(penultimate) => penultimate.range.range.end,
};
let f = exprv_rec(v, ctx.clone(), location.map_range(|r| r.start..v_end))?;
let x = expr_rec(last, ctx)?;
let x = expr_rec(last, ctx.clone())?;
let value = ir::Clause::Apply(Rc::new(f), Rc::new(x));
Ok(ir::Expr { value, location: CodeLocation::Source(location) })
Ok(ir::Expr::new(value, location, ctx.module))
}
/// Process an expression
fn expr_rec(
parsed::Expr { value, range }: parsed::Expr,
ctx: Context,
) -> ProjectResult<ir::Expr> {
fn expr_rec(parsed::Expr { value, range }: parsed::Expr, ctx: Context) -> ProjectResult<ir::Expr> {
match value {
parsed::Clause::S(parsed::PType::Par, body) => {
return exprv_rec(body.to_vec().into(), ctx, range);
},
parsed::Clause::S(..) =>
return Err(IRError(range, ctx.symbol, BadGroup).pack()),
parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()),
_ => (),
}
let value = match value {
@@ -114,29 +117,24 @@ fn expr_rec(
let name = match &arg[..] {
[parsed::Expr { value: parsed::Clause::Name(name), .. }] => name,
[parsed::Expr { value: parsed::Clause::Placeh { .. }, .. }] =>
return Err(IRError(range.clone(), ctx.symbol, PhLeak).pack()),
_ => return Err(IRError(range.clone(), ctx.symbol, InvalidArg).pack()),
return Err(IRError::new(range.clone(), ctx.range, PhLeak).pack()),
_ => return Err(IRError::new(range.clone(), ctx.range, InvalidArg).pack()),
};
let body_ctx = ctx.w_name(name.clone());
let body = exprv_rec(b.to_vec().into(), body_ctx, range.clone())?;
ir::Clause::Lambda(Rc::new(body))
},
parsed::Clause::Name(name) => {
let lvl_opt = (ctx.names.iter())
.enumerate()
.find(|(_, n)| **n == name)
.map(|(lvl, _)| lvl);
let lvl_opt = (ctx.names.iter()).enumerate().find(|(_, n)| **n == name).map(|(lvl, _)| lvl);
match lvl_opt {
Some(lvl) => ir::Clause::LambdaArg(lvl),
None => ir::Clause::Constant(name.clone()),
}
},
parsed::Clause::S(parsed::PType::Par, entries) =>
exprv_rec(entries.to_vec().into(), ctx, range.clone())?.value,
parsed::Clause::S(..) =>
return Err(IRError(range, ctx.symbol, BadGroup).pack()),
parsed::Clause::Placeh { .. } =>
return Err(IRError(range, ctx.symbol, PhLeak).pack()),
exprv_rec(entries.to_vec().into(), ctx.clone(), range.clone())?.value,
parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()),
parsed::Clause::Placeh { .. } => return Err(IRError::new(range, ctx.range, PhLeak).pack()),
};
Ok(ir::Expr::new(value, range.clone()))
Ok(ir::Expr::new(value, range, ctx.module))
}
+33 -34
View File
@@ -3,7 +3,7 @@
//! innovations in the processing and execution of code will likely operate on
//! this representation.
use std::fmt::{Debug, Write};
use std::fmt;
use std::rc::Rc;
use crate::foreign::atom::AtomGenerator;
@@ -16,40 +16,44 @@ use crate::utils::string_from_charset::string_from_charset;
#[derive(PartialEq, Eq, Clone, Copy)]
struct Wrap(bool, bool);
/// Code element with associated metadata
#[derive(Clone)]
pub struct Expr {
/// Code element
pub value: Clause,
/// Location metadata
pub location: CodeLocation,
}
impl Expr {
pub fn new(value: Clause, location: SourceRange) -> Self {
Self { value, location: CodeLocation::Source(location) }
/// Create an IR expression
pub fn new(value: Clause, location: SourceRange, module: Sym) -> Self {
Self { value, location: CodeLocation::new_src(location, module) }
}
fn deep_fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
depth: usize,
tr: Wrap,
) -> std::fmt::Result {
fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, tr: Wrap) -> fmt::Result {
let Expr { value, .. } = self;
value.deep_fmt(f, depth, tr)?;
Ok(())
}
}
impl Debug for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deep_fmt(f, 0, Wrap(false, false))
}
}
/// Semantic code element
#[derive(Clone)]
pub enum Clause {
/// Function call expression
Apply(Rc<Expr>, Rc<Expr>),
/// Function expression
Lambda(Rc<Expr>),
/// Reference to an external constant
Constant(Sym),
/// Reference to a function argument
LambdaArg(usize),
/// An opaque non-callable value, eg. a file handle
Atom(AtomGenerator),
@@ -58,32 +62,27 @@ pub enum Clause {
const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz";
fn parametric_fmt(
f: &mut std::fmt::Formatter<'_>,
f: &mut fmt::Formatter<'_>,
depth: usize,
prefix: &str,
body: &Expr,
wrap_right: bool,
) -> std::fmt::Result {
// if wrap_right {
f.write_char('(')?;
// }
) -> fmt::Result {
if wrap_right {
write!(f, "(")?;
}
f.write_str(prefix)?;
f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?;
f.write_str(".")?;
body.deep_fmt(f, depth + 1, Wrap(false, false))?;
// if wrap_right {
f.write_char(')')?;
// }
if wrap_right {
write!(f, ")")?;
}
Ok(())
}
impl Clause {
fn deep_fmt(
&self,
f: &mut std::fmt::Formatter<'_>,
depth: usize,
Wrap(wl, wr): Wrap,
) -> std::fmt::Result {
fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) -> fmt::Result {
match self {
Self::Atom(a) => write!(f, "{a:?}"),
Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr),
@@ -92,15 +91,15 @@ impl Clause {
f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET))
},
Self::Apply(func, x) => {
// if wl {
f.write_char('(')?;
// }
if wl {
write!(f, "(")?;
}
func.deep_fmt(f, depth, Wrap(false, true))?;
f.write_char(' ')?;
write!(f, " ")?;
x.deep_fmt(f, depth, Wrap(true, wr && !wl))?;
// if wl {
f.write_char(')')?;
// }
if wl {
write!(f, ")")?;
}
Ok(())
},
Self::Constant(token) => write!(f, "{token}"),
@@ -108,8 +107,8 @@ impl Clause {
}
}
impl Debug for Clause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for Clause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.deep_fmt(f, 0, Wrap(false, false))
}
}
+3 -2
View File
@@ -5,7 +5,7 @@ use crate::interpreter::nort;
use crate::interpreter::nort_builder::NortBuilder;
fn expr(expr: &ir::Expr, ctx: NortBuilder<(), usize>) -> nort::Expr {
clause(&expr.value, ctx).to_expr(expr.location.clone())
clause(&expr.value, ctx).into_expr(expr.location.clone())
}
fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause {
@@ -21,6 +21,7 @@ fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause {
}
}
/// Convert an expression.
pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr {
let c = NortBuilder::new(&|count| {
let mut count: usize = *count;
@@ -32,5 +33,5 @@ pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr {
},
})
});
nort::ClauseInst::new(clause(&expr.value, c)).to_expr(expr.location.clone())
nort::ClauseInst::new(clause(&expr.value, c)).into_expr(expr.location.clone())
}
+7 -3
View File
@@ -1,3 +1,7 @@
pub(crate) mod ast_to_ir;
pub(crate) mod ir;
pub(crate) mod ir_to_nort;
//! Intermediate representation. Currently just an indirection between
//! [super::parse::parsed] and [super::interpreter::nort], in the future
//! hopefully a common point for alternate encodings, optimizations and targets.
pub mod ast_to_ir;
pub mod ir;
pub mod ir_to_nort;
+17 -12
View File
@@ -1,10 +1,10 @@
use never::Never;
use super::context::RunContext;
use super::error::RunError;
use super::context::{RunEnv, RunParams};
use super::nort::{Clause, ClauseInst, Expr};
use super::path_set::{PathSet, Step};
use crate::foreign::atom::CallData;
use crate::foreign::error::RTResult;
/// Process the clause at the end of the provided path. Note that paths always
/// point to at least one target. Note also that this is not cached as a
@@ -16,12 +16,12 @@ fn map_at<E>(
) -> Result<Clause, E> {
// Pass through some unambiguous wrapper clauses
match source {
Clause::Identity(alt) => return map_at(path, &alt.cls(), mapper),
Clause::Identity(alt) => return map_at(path, &alt.cls_mut(), mapper),
Clause::Lambda { args, body: Expr { location: b_loc, clause } } =>
return Ok(Clause::Lambda {
args: args.clone(),
body: Expr {
clause: map_at(path, &clause.cls(), mapper)?.to_inst(),
clause: map_at(path, &clause.cls_mut(), mapper)?.into_inst(),
location: b_loc.clone(),
},
}),
@@ -33,7 +33,7 @@ fn map_at<E>(
(val, None) => mapper(val)?,
// If it's an Apply, execute the next step in the path
(Clause::Apply { f, x }, Some(head)) => {
let proc = |x: &Expr| Ok(map_at(path, &x.cls(), mapper)?.to_expr(x.location()));
let proc = |x: &Expr| Ok(map_at(path, &x.cls_mut(), mapper)?.into_expr(x.location()));
match head {
None => Clause::Apply { f: proc(f)?, x: x.clone() },
Some(n) => {
@@ -68,12 +68,12 @@ pub fn substitute(
let mut argv = x.clone();
let f = match conts.get(&None) {
None => f.clone(),
Some(sp) => substitute(sp, value.clone(), &f.cls(), on_sub).to_expr(f.location()),
Some(sp) => substitute(sp, value.clone(), &f.cls_mut(), on_sub).into_expr(f.location()),
};
for (i, old) in argv.iter_mut().rev().enumerate() {
if let Some(sp) = conts.get(&Some(i)) {
let tmp = substitute(sp, value.clone(), &old.cls(), on_sub);
*old = tmp.to_expr(old.location());
let tmp = substitute(sp, value.clone(), &old.cls_mut(), on_sub);
*old = tmp.into_expr(old.location());
}
}
Ok(Clause::Apply { f, x: argv })
@@ -89,15 +89,20 @@ pub fn substitute(
.unwrap_or_else(|e| match e {})
}
pub(super) fn apply_as_atom(f: Expr, arg: Expr, ctx: RunContext) -> Result<Clause, RunError> {
let call = CallData { location: f.location(), arg, ctx };
pub(super) fn apply_as_atom(
f: Expr,
arg: Expr,
env: &RunEnv,
params: &mut RunParams,
) -> RTResult<Clause> {
let call = CallData { location: f.location(), arg, env, params };
match f.clause.try_unwrap() {
Ok(clause) => match clause {
Clause::Atom(atom) => Ok(atom.apply(call)?),
_ => panic!("Not an atom"),
},
Err(clsi) => match &*clsi.cls() {
Clause::Atom(atom) => Ok(atom.apply_ref(call)?),
Err(clsi) => match &mut *clsi.cls_mut() {
Clause::Atom(atom) => Ok(atom.apply_mut(call)?),
_ => panic!("Not an atom"),
},
}
+77 -42
View File
@@ -1,19 +1,67 @@
//! Addiitional information passed to the interpreter
use std::cell::RefCell;
use std::fmt;
use hashbrown::HashMap;
use super::nort::Expr;
use super::handler::HandlerTable;
use super::nort::{Clause, Expr};
use crate::foreign::error::{RTError, RTErrorObj, RTResult};
use crate::location::CodeLocation;
use crate::name::Sym;
/// All the data associated with an interpreter run
#[derive(Clone)]
pub struct RunContext<'a> {
/// Table used to resolve constants
pub symbols: &'a HashMap<Sym, Expr>,
/// The number of reduction steps the interpreter can take before returning
pub gas: Option<usize>,
/// The limit of recursion
pub stack_size: usize,
/// Data that must not change except in well-defined ways while any data
/// associated with this process persists
pub struct RunEnv<'a> {
/// Mutable callbacks the code can invoke with continuation passing
pub handlers: HandlerTable<'a>,
/// Constants referenced in the code in [super::nort::Clause::Constant] nodes
pub symbols: RefCell<HashMap<Sym, RTResult<Expr>>>,
/// Callback to invoke when a symbol is not found
pub symbol_cb: Box<dyn Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a>,
}
impl<'a> RunContext<'a> {
impl<'a> RunEnv<'a> {
/// Create a new context. The return values of the symbol callback are cached
pub fn new(
handlers: HandlerTable<'a>,
symbol_cb: impl Fn(Sym, CodeLocation) -> RTResult<Expr> + 'a,
) -> Self {
Self { handlers, symbols: RefCell::new(HashMap::new()), symbol_cb: Box::new(symbol_cb) }
}
/// Produce an error indicating that a symbol was missing
pub fn sym_not_found(sym: Sym, location: CodeLocation) -> RTErrorObj {
MissingSymbol { location, sym }.pack()
}
/// Load a symbol from cache or invoke the callback
pub fn load(&self, sym: Sym, location: CodeLocation) -> RTResult<Expr> {
let mut guard = self.symbols.borrow_mut();
let (_, r) = (guard.raw_entry_mut().from_key(&sym))
.or_insert_with(|| (sym.clone(), (self.symbol_cb)(sym, location)));
r.clone()
}
/// Attempt to resolve the command with the command handler table
pub fn dispatch(&self, expr: &Clause, location: CodeLocation) -> Option<Expr> {
match expr {
Clause::Atom(at) => self.handlers.dispatch(&*at.0, location),
_ => None,
}
}
}
/// Limits and other context that is subject to change
pub struct RunParams {
/// Number of reduction steps permitted before the program is preempted
pub gas: Option<usize>,
/// Maximum recursion depth. Orchid uses a soft stack so this can be very
/// large, but it must not be
pub stack: usize,
}
impl RunParams {
/// Consume some gas if it is being counted
pub fn use_gas(&mut self, amount: usize) {
if let Some(g) = self.gas.as_mut() {
@@ -22,39 +70,26 @@ impl<'a> RunContext<'a> {
}
/// Gas is being counted and there is none left
pub fn no_gas(&self) -> bool { self.gas == Some(0) }
}
/// All the data produced by an interpreter run
#[derive(Clone)]
pub struct Halt {
/// The new expression tree
pub state: Expr,
/// Leftover [Context::gas] if counted
pub gas: Option<usize>,
/// If true, the next run would not modify the expression
pub inert: bool,
}
impl Halt {
/// Check if gas has run out. Returns false if gas is not being used
pub fn preempted(&self) -> bool { self.gas.map_or(false, |g| g == 0) }
/// Returns a general report of the return
pub fn status(&self) -> ReturnStatus {
if self.preempted() {
ReturnStatus::Preempted
} else if self.inert {
ReturnStatus::Inert
} else {
ReturnStatus::Active
/// Add gas to make execution longer, or to resume execution in a preempted
/// expression
pub fn add_gas(&mut self, amount: usize) {
if let Some(g) = self.gas.as_mut() {
*g = g.saturating_add(amount)
}
}
}
/// Possible states of a [Return]
pub enum ReturnStatus {
/// The data is not normalizable any further
Inert,
/// Gas is being used and it ran out
Preempted,
/// Normalization stopped for a different reason and should continue.
Active,
/// The interpreter's sole output excluding error conditions is an expression
pub type Halt = Expr;
#[derive(Clone)]
pub(crate) struct MissingSymbol {
pub sym: Sym,
pub location: CodeLocation,
}
impl RTError for MissingSymbol {}
impl fmt::Display for MissingSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, called at {} is not loaded", self.sym, self.location)
}
}
+16 -49
View File
@@ -1,66 +1,33 @@
use std::fmt::{self, Debug, Display};
//! Error produced by the interpreter.
use itertools::Itertools;
use std::fmt;
use super::nort::Expr;
use super::run::Interrupted;
use crate::foreign::error::{ExternError, ExternErrorObj};
use crate::location::CodeLocation;
use crate::name::Sym;
use super::run::State;
use crate::foreign::error::{RTError, RTErrorObj};
/// Print a stack trace
pub fn strace(stack: &[Expr]) -> String {
stack.iter().rev().map(|x| format!("{x}\n at {}", x.location)).join("\n")
}
/// Problems in the process of execution
#[derive(Debug, Clone)]
pub enum RunError {
/// Error produced by the interpreter. This could be because the code is faulty,
/// but equally because gas was being counted and it ran out.
#[derive(Debug)]
pub enum RunError<'a> {
/// A Rust function encountered an error
Extern(ExternErrorObj),
Extern(RTErrorObj),
/// Ran out of gas
Interrupted(Interrupted),
Interrupted(State<'a>),
}
impl<T: ExternError + 'static> From<T> for RunError {
fn from(value: T) -> Self { Self::Extern(value.rc()) }
impl<'a, T: RTError + 'static> From<T> for RunError<'a> {
fn from(value: T) -> Self { Self::Extern(value.pack()) }
}
impl From<ExternErrorObj> for RunError {
fn from(value: ExternErrorObj) -> Self { Self::Extern(value) }
impl<'a> From<RTErrorObj> for RunError<'a> {
fn from(value: RTErrorObj) -> Self { Self::Extern(value) }
}
impl Display for RunError {
impl<'a> fmt::Display for RunError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Interrupted(i) => {
write!(f, "Ran out of gas:\n{}", strace(&i.stack))
},
Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"),
Self::Extern(e) => write!(f, "Program fault: {e}"),
}
}
}
#[derive(Clone)]
pub(crate) struct StackOverflow {
pub stack: Vec<Expr>,
}
impl ExternError for StackOverflow {}
impl Display for StackOverflow {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let limit = self.stack.len() - 2; // 1 for failed call, 1 for current
write!(f, "Stack depth exceeded {limit}:\n{}", strace(&self.stack))
}
}
#[derive(Clone)]
pub(crate) struct MissingSymbol {
pub sym: Sym,
pub loc: CodeLocation,
}
impl ExternError for MissingSymbol {}
impl Display for MissingSymbol {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}, called at {} is not loaded", self.sym, self.loc)
}
}
+22 -54
View File
@@ -26,56 +26,33 @@ impl Generable for Expr {
f_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
x_cb: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
(ctx
.1
.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c))))
.to_expr(ctx.0.clone())
(ctx.1.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c))))
.into_expr(ctx.0.clone())
}
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
Clause::arg(ctx.clone(), name).to_expr(ctx.0.clone())
Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone())
}
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
Clause::atom(ctx.clone(), a).to_expr(ctx.0.clone())
Clause::atom(ctx.clone(), a).into_expr(ctx.0.clone())
}
fn constant<'a>(
ctx: Self::Ctx<'_>,
name: impl IntoIterator<Item = &'a str>,
) -> Self {
Clause::constant(ctx.clone(), name).to_expr(ctx.0.clone())
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
Clause::constant(ctx.clone(), name).into_expr(ctx.0.clone())
}
fn lambda(
ctx: Self::Ctx<'_>,
name: &str,
body: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c))))
.to_expr(ctx.0.clone())
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)))).into_expr(ctx.0.clone())
}
}
impl Generable for ClauseInst {
type Ctx<'a> = NortGenCtx<'a>;
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
Clause::arg(ctx, name).to_inst()
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { Clause::arg(ctx, name).into_inst() }
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { Clause::atom(ctx, a).into_inst() }
fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
Clause::constant(ctx, name).into_inst()
}
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
Clause::atom(ctx, a).to_inst()
}
fn constant<'a>(
ctx: Self::Ctx<'_>,
name: impl IntoIterator<Item = &'a str>,
) -> Self {
Clause::constant(ctx, name).to_inst()
}
fn lambda(
ctx: Self::Ctx<'_>,
name: &str,
body: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
(ctx
.1
.lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone())))
.to_clsi(ctx.0.clone())
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
(ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone())))
.to_clsi(ctx.0.clone())
}
fn apply(
ctx: Self::Ctx<'_>,
@@ -83,8 +60,8 @@ impl Generable for ClauseInst {
x: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
(ctx.1.apply_logic(
|c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
))
.to_clsi(ctx.0.clone())
}
@@ -93,10 +70,7 @@ impl Generable for ClauseInst {
impl Generable for Clause {
type Ctx<'a> = NortGenCtx<'a>;
fn atom(_: Self::Ctx<'_>, a: Atom) -> Self { Clause::Atom(a) }
fn constant<'a>(
_: Self::Ctx<'_>,
name: impl IntoIterator<Item = &'a str>,
) -> Self {
fn constant<'a>(_: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant");
Clause::Constant(sym)
}
@@ -106,21 +80,15 @@ impl Generable for Clause {
x: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
ctx.1.apply_logic(
|c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()),
|c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
|c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()),
)
}
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
ctx.1.arg_logic(name);
Clause::LambdaArg
}
fn lambda(
ctx: Self::Ctx<'_>,
name: &str,
body: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
ctx
.1
.lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone()))
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone()))
}
}
+68 -49
View File
@@ -1,25 +1,39 @@
//! Impure functions that can be triggered by Orchid code when a command
//! evaluates to an atom representing a command
use std::any::{Any, TypeId};
use std::cell::RefCell;
use hashbrown::HashMap;
use trait_set::trait_set;
use super::context::{Halt, RunContext};
use super::error::RunError;
use super::nort::{Clause, Expr};
use super::run::run;
use crate::foreign::atom::{Atom, Atomic};
use crate::foreign::error::ExternResult;
use super::nort::Expr;
use crate::foreign::atom::Atomic;
use crate::foreign::error::RTResult;
use crate::foreign::to_clause::ToClause;
use crate::location::CodeLocation;
trait_set! {
trait Handler = for<'a> FnMut(&'a dyn Any, CodeLocation) -> Expr;
trait Handler = for<'a> Fn(&'a dyn Any, CodeLocation) -> Expr;
}
/// A table of command handlers
enum HTEntry<'a> {
Handler(Box<dyn Handler + 'a>),
Forward(&'a (dyn Handler + 'a)),
}
impl<'a> AsRef<dyn Handler + 'a> for HTEntry<'a> {
fn as_ref(&self) -> &(dyn Handler + 'a) {
match self {
HTEntry::Handler(h) => &**h,
HTEntry::Forward(h) => *h,
}
}
}
/// A table of impure command handlers exposed to Orchid
#[derive(Default)]
pub struct HandlerTable<'a> {
handlers: HashMap<TypeId, Box<dyn Handler + 'a>>,
handlers: HashMap<TypeId, HTEntry<'a>>,
}
impl<'a> HandlerTable<'a> {
/// Create a new [HandlerTable]
@@ -28,35 +42,25 @@ impl<'a> HandlerTable<'a> {
/// Add a handler function to interpret a command and select the continuation.
/// See [HandlerTable#with] for a declarative option.
pub fn register<T: 'static, R: ToClause>(
&mut self,
mut f: impl for<'b> FnMut(&'b T) -> R + 'a,
) {
pub fn register<T: 'static, R: ToClause>(&mut self, f: impl for<'b> FnMut(&'b T) -> R + 'a) {
let cell = RefCell::new(f);
let cb = move |a: &dyn Any, loc: CodeLocation| {
f(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
cell.borrow_mut()(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
};
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
let prev = self.handlers.insert(TypeId::of::<T>(), HTEntry::Handler(Box::new(cb)));
assert!(prev.is_none(), "A handler for this type is already registered");
}
/// Add a handler function to interpret a command and select the continuation.
/// See [HandlerTable#register] for a procedural option.
pub fn with<T: 'static>(
mut self,
f: impl FnMut(&T) -> ExternResult<Expr> + 'a,
) -> Self {
pub fn with<T: 'static>(mut self, f: impl FnMut(&T) -> RTResult<Expr> + 'a) -> Self {
self.register(f);
self
}
/// Find and execute the corresponding handler for this type
pub fn dispatch(
&mut self,
arg: &dyn Atomic,
loc: CodeLocation,
) -> Option<Expr> {
(self.handlers.get_mut(&arg.as_any_ref().type_id()))
.map(|f| f(arg.as_any_ref(), loc))
pub fn dispatch(&self, arg: &dyn Atomic, loc: CodeLocation) -> Option<Expr> {
(self.handlers.get(&arg.as_any_ref().type_id())).map(|ent| ent.as_ref()(arg.as_any_ref(), loc))
}
/// Combine two non-overlapping handler sets
@@ -68,30 +72,45 @@ impl<'a> HandlerTable<'a> {
}
self
}
}
/// [run] orchid code, executing any commands it returns using the specified
/// [Handler]s.
pub fn run_handler(
mut state: Expr,
handlers: &mut HandlerTable,
mut ctx: RunContext,
) -> Result<Halt, RunError> {
loop {
let halt = run(state, ctx.clone())?;
state = halt.state;
ctx.use_gas(halt.gas.unwrap_or(0));
let state_cls = state.cls();
if let Clause::Atom(Atom(a)) = &*state_cls {
if let Some(res) = handlers.dispatch(a.as_ref(), state.location()) {
drop(state_cls);
state = res;
continue;
}
}
if halt.inert || ctx.no_gas() {
drop(state_cls);
break Ok(Halt { gas: ctx.gas, inert: halt.inert, state });
/// Add entries that forward requests to a borrowed non-overlapping handler
/// set
pub fn link<'b: 'a>(mut self, other: &'b HandlerTable<'b>) -> Self {
for (key, value) in other.handlers.iter() {
let prev = self.handlers.insert(*key, HTEntry::Forward(value.as_ref()));
assert!(prev.is_none(), "Duplicate handlers")
}
self
}
}
#[cfg(test)]
#[allow(unconditional_recursion)]
#[allow(clippy::ptr_arg)]
mod test {
use std::marker::PhantomData;
use super::HandlerTable;
/// Ensure that the method I use to verify covariance actually passes with
/// covariant and fails with invariant
///
/// The failing case:
/// ```
/// struct Cov2<'a>(PhantomData<&'a mut &'a ()>);
/// fn fail<'a>(_c: &Cov2<'a>, _s: &'a String) { fail(_c, &String::new()) }
/// ```
#[allow(unused)]
fn covariant_control() {
struct Cov<'a>(PhantomData<&'a ()>);
fn pass<'a>(_c: &Cov<'a>, _s: &'a String) { pass(_c, &String::new()) }
}
/// The &mut ensures that 'a in the two functions must be disjoint, and that
/// ht must outlive both. For this to compile, Rust has to cast ht to the
/// shorter lifetimes, ensuring covariance
#[allow(unused)]
fn assert_covariant() {
fn pass<'a>(_ht: HandlerTable<'a>, _s: &'a String) { pass(_ht, &String::new()) }
}
}
+3 -3
View File
@@ -1,10 +1,10 @@
//! functions to interact with Orchid code
pub mod apply;
//! functions to execute Orchid code
mod apply;
pub mod context;
pub mod error;
pub mod gen_nort;
pub mod handler;
pub mod nort_builder;
pub mod nort;
pub mod nort_builder;
pub(crate) mod path_set;
pub mod run;
+40 -145
View File
@@ -13,25 +13,23 @@
//! function calls store multiple arguments in a deque.
use std::collections::VecDeque;
use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut};
use std::sync::{Arc, Mutex, TryLockError};
use std::fmt;
use std::ops::DerefMut;
use std::sync::{Arc, Mutex, MutexGuard, TryLockError};
use itertools::Itertools;
use super::error::RunError;
use super::path_set::PathSet;
use crate::foreign::atom::Atom;
#[allow(unused)] // for doc
use crate::foreign::atom::Atomic;
use crate::foreign::error::ExternResult;
use crate::foreign::error::{RTErrorObj, RTResult};
use crate::foreign::try_from_expr::TryFromExpr;
use crate::location::CodeLocation;
use crate::name::Sym;
#[allow(unused)] // for doc
use crate::parse::parsed;
use crate::utils::ddispatch::request;
use crate::utils::take_with_output::take_with_output;
/// Kinda like [AsMut] except it supports a guard
pub(crate) trait AsDerefMut<T> {
@@ -48,19 +46,17 @@ pub struct Expr {
}
impl Expr {
/// Constructor
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self {
Self { clause, location }
}
pub fn new(clause: ClauseInst, location: CodeLocation) -> Self { Self { clause, location } }
/// Obtain the location of the expression
pub fn location(&self) -> CodeLocation { self.location.clone() }
/// Convert into any type that implements [TryFromExpr]. Calls to this
/// function are generated wherever a conversion is elided in an extern
/// function.
pub fn downcast<T: TryFromExpr>(self) -> ExternResult<T> {
pub fn downcast<T: TryFromExpr>(self) -> RTResult<T> {
let Expr { mut clause, location } = self;
loop {
let cls_deref = clause.cls();
let cls_deref = clause.cls_mut();
match &*cls_deref {
Clause::Identity(alt) => {
let temp = alt.clone();
@@ -79,22 +75,16 @@ impl Expr {
/// returning [Some]
///
/// See also [parsed::Expr::search_all]
pub fn search_all<T>(
&self,
predicate: &mut impl FnMut(&Self) -> Option<T>,
) -> Option<T> {
pub fn search_all<T>(&self, predicate: &mut impl FnMut(&Self) -> Option<T>) -> Option<T> {
if let Some(t) = predicate(self) {
return Some(t);
}
self.clause.inspect(|c| match c {
Clause::Identity(_alt) => unreachable!("Handled by inspect"),
Clause::Apply { f, x } => (f.search_all(predicate))
.or_else(|| x.iter().find_map(|x| x.search_all(predicate))),
Clause::Apply { f, x } =>
(f.search_all(predicate)).or_else(|| x.iter().find_map(|x| x.search_all(predicate))),
Clause::Lambda { body, .. } => body.search_all(predicate),
Clause::Constant(_)
| Clause::LambdaArg
| Clause::Atom(_)
| Clause::Bottom(_) => None,
Clause::Constant(_) | Clause::LambdaArg | Clause::Atom(_) | Clause::Bottom(_) => None,
})
}
@@ -102,47 +92,28 @@ impl Expr {
#[must_use]
pub fn clsi(&self) -> ClauseInst { self.clause.clone() }
/// Readonly access to the [Clause]
///
/// # Panics
///
/// if the clause is already borrowed
#[must_use]
pub fn cls(&self) -> impl Deref<Target = Clause> + '_ { self.clause.cls() }
/// Read-Write access to the [Clause]
///
/// # Panics
///
/// if the clause is already borrowed
#[must_use]
pub fn cls_mut(&self) -> impl DerefMut<Target = Clause> + '_ {
self.clause.cls_mut()
}
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.clause.cls_mut() }
}
impl Debug for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}@{}", self.clause, self.location)
}
}
impl Display for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.clause)
}
impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.clause) }
}
impl AsDerefMut<Clause> for Expr {
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ {
self.clause.cls_mut()
}
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.clause.cls_mut() }
}
/// [ExprInst::with_literal] produces this marker unit to indicate that the
/// expression is not a literal
pub struct NotALiteral;
/// A wrapper around expressions to handle their multiple occurences in
/// the tree together
#[derive(Clone)]
@@ -159,91 +130,17 @@ impl ClauseInst {
Arc::try_unwrap(self.0).map(|c| c.into_inner().unwrap()).map_err(Self)
}
/// Read-only access to the shared clause instance
///
/// # Panics
///
/// if the clause is already borrowed in read-write mode
#[must_use]
pub fn cls(&self) -> impl Deref<Target = Clause> + '_ {
self.0.lock().unwrap()
}
/// Read-Write access to the shared clause instance
///
/// # Panics
///
/// if the clause is already borrowed
#[must_use]
pub fn cls_mut(&self) -> impl DerefMut<Target = Clause> + '_ {
self.0.lock().unwrap()
}
/// Call a normalization function on the expression. The expr is
/// updated with the new clause which affects all copies of it
/// across the tree.
///
/// This function bypasses and collapses identities, but calling it in a plain
/// loop intermittently re-acquires the mutex, and looping inside of it breaks
/// identity collapsing. [ClauseInst::try_normalize_trampoline] solves these
/// problems.
pub fn try_normalize<T>(
&self,
mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>,
) -> Result<(Self, T), RunError> {
enum Report<T> {
Nested(ClauseInst, T),
Plain(T),
}
let ret = take_with_output(&mut *self.cls_mut(), |clause| match &clause {
// don't modify identities, instead update and return the nested clause
Clause::Identity(alt) => match alt.try_normalize(mapper) {
Ok((nested, t)) => (clause, Ok(Report::Nested(nested, t))),
Err(e) => (Clause::Bottom(e.clone()), Err(e)),
},
_ => match mapper(clause) {
Err(e) => (Clause::Bottom(e.clone()), Err(e)),
Ok((clause, t)) => (clause, Ok(Report::Plain(t))),
},
})?;
Ok(match ret {
Report::Nested(nested, t) => (nested, t),
Report::Plain(t) => (self.clone(), t),
})
}
/// Repeatedly call a normalization function on the held clause, switching
/// [ClauseInst] values as needed to ensure that
pub fn try_normalize_trampoline<T>(
mut self,
mut mapper: impl FnMut(Clause) -> Result<(Clause, Option<T>), RunError>,
) -> Result<(Self, T), RunError> {
loop {
let (next, exit) = self.try_normalize(|mut cls| {
loop {
if matches!(cls, Clause::Identity(_)) {
break Ok((cls, None));
}
let (next, exit) = mapper(cls)?;
if let Some(exit) = exit {
break Ok((next, Some(exit)));
}
cls = next;
}
})?;
if let Some(exit) = exit {
break Ok((next, exit));
}
self = next
}
}
/// if the clause is already borrowed, this will block until it is released.
pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.0.lock().unwrap() }
/// Call a predicate on the clause, returning whatever the
/// predicate returns. This is a convenience function for reaching
/// through the [Mutex]. The clause will never be [Clause::Identity].
#[must_use]
pub fn inspect<T>(&self, predicate: impl FnOnce(&Clause) -> T) -> T {
match &*self.cls() {
match &*self.cls_mut() {
Clause::Identity(sub) => sub.inspect(predicate),
x => predicate(x),
}
@@ -253,7 +150,7 @@ impl ClauseInst {
/// If it's not an atomic, fail the request automatically.
#[must_use = "your request might not have succeeded"]
pub fn request<T: 'static>(&self) -> Option<T> {
match &*self.cls() {
match &*self.cls_mut() {
Clause::Atom(a) => request(&*a.0),
Clause::Identity(alt) => alt.request(),
_ => None,
@@ -261,7 +158,7 @@ impl ClauseInst {
}
/// Associate a location with this clause
pub fn to_expr(self, location: CodeLocation) -> Expr {
pub fn into_expr(self, location: CodeLocation) -> Expr {
Expr { clause: self.clone(), location: location.clone() }
}
/// Check ahead-of-time if this clause contains an atom. Calls
@@ -269,7 +166,7 @@ impl ClauseInst {
///
/// Since atoms cannot become normalizable, if this is true and previous
/// normalization failed, the atom is known to be in normal form.
pub fn is_atom(&self) -> bool { matches!(&*self.cls(), Clause::Atom(_)) }
pub fn is_atom(&self) -> bool { matches!(&*self.cls_mut(), Clause::Atom(_)) }
/// Tries to unwrap the [Arc]. If that fails, clones it field by field.
/// If it's a [Clause::Atom] which cannot be cloned, wraps it in a
@@ -278,21 +175,24 @@ impl ClauseInst {
/// Implementation of [crate::foreign::to_clause::ToClause::to_clause]. The
/// trait is more general so it requires a location which this one doesn't.
pub fn into_cls(self) -> Clause {
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls() {
self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls_mut() {
Clause::Apply { f, x } => Clause::Apply { f: f.clone(), x: x.clone() },
Clause::Atom(_) => Clause::Identity(clsi.clone()),
Clause::Bottom(e) => Clause::Bottom(e.clone()),
Clause::Constant(c) => Clause::Constant(c.clone()),
Clause::Identity(sub) => Clause::Identity(sub.clone()),
Clause::Lambda { args, body } =>
Clause::Lambda { args: args.clone(), body: body.clone() },
Clause::Lambda { args, body } => Clause::Lambda { args: args.clone(), body: body.clone() },
Clause::LambdaArg => Clause::LambdaArg,
})
}
/// Decides if this clause is the exact same instance as another. Most useful
/// to detect potential deadlocks.
pub fn is_same(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) }
}
impl Debug for ClauseInst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Debug for ClauseInst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.try_lock() {
Ok(expr) => write!(f, "{expr:?}"),
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
@@ -301,8 +201,8 @@ impl Debug for ClauseInst {
}
}
impl Display for ClauseInst {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for ClauseInst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.0.try_lock() {
Ok(expr) => write!(f, "{expr}"),
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
@@ -312,16 +212,14 @@ impl Display for ClauseInst {
}
impl AsDerefMut<Clause> for ClauseInst {
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ {
self.cls_mut()
}
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self.cls_mut() }
}
/// Distinct types of expressions recognized by the interpreter
#[derive(Debug)]
pub enum Clause {
/// An expression that causes an error
Bottom(RunError),
Bottom(RTErrorObj),
/// Indicates that this [ClauseInst] has the same value as the other
/// [ClauseInst]. This has two benefits;
///
@@ -358,21 +256,18 @@ pub enum Clause {
}
impl Clause {
/// Wrap a clause in a refcounted lock
pub fn to_inst(self) -> ClauseInst { ClauseInst::new(self) }
pub fn into_inst(self) -> ClauseInst { ClauseInst::new(self) }
/// Wrap a clause in an expression.
pub fn to_expr(self, location: CodeLocation) -> Expr {
self.to_inst().to_expr(location)
}
pub fn into_expr(self, location: CodeLocation) -> Expr { self.into_inst().into_expr(location) }
}
impl Display for Clause {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl fmt::Display for Clause {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Clause::Atom(a) => write!(f, "{a:?}"),
Clause::Bottom(err) => write!(f, "bottom({err})"),
Clause::LambdaArg => write!(f, "arg"),
Clause::Apply { f: fun, x } =>
write!(f, "({fun} {})", x.iter().join(" ")),
Clause::Apply { f: fun, x } => write!(f, "({fun} {})", x.iter().join(" ")),
Clause::Lambda { args, body } => match args {
Some(path) => write!(f, "[\\{path}.{body}]"),
None => write!(f, "[\\_.{body}]"),
+5 -8
View File
@@ -1,3 +1,5 @@
//! Helper for generating the interpreter's internal representation
use std::cell::RefCell;
use std::mem;
@@ -34,8 +36,7 @@ impl ArgCollector {
/// the callback it returns is called on every lambda ancestor's associated
/// data from closest to outermost ancestor. The first lambda where this
/// callback returns true is considered to own the argument.
pub type LambdaPicker<'a, T, U> =
&'a dyn for<'b> Fn(&'b U) -> Box<dyn FnMut(&T) -> bool + 'b>;
pub type LambdaPicker<'a, T, U> = &'a dyn for<'b> Fn(&'b U) -> Box<dyn FnMut(&T) -> bool + 'b>;
/// Bundle of information passed down through recursive fnuctions to instantiate
/// runtime [Expr], [super::nort::ClauseInst] or [Clause].
@@ -83,7 +84,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
IntGenData::Apply(_) => panic!("This is removed after handling"),
IntGenData::Lambda(n, rc) => match lambda_chk(n) {
false => Ok(path),
true => Err((path, *rc))
true => Err((path, *rc)),
},
IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))),
IntGenData::AppF => Ok(pushed(path, None)),
@@ -100,11 +101,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
/// Push a stackframe corresponding to a lambda expression, build the body,
/// then record the path set collected by [NortBuilder::arg_logic] calls
/// within the body.
pub fn lambda_logic(
self,
name: &T,
body: impl FnOnce(NortBuilder<T, U>) -> Expr,
) -> Clause {
pub fn lambda_logic(self, name: &T, body: impl FnOnce(NortBuilder<T, U>) -> Expr) -> Clause {
let coll = ArgCollector::new();
let frame = IntGenData::Lambda(name, &coll.0);
let body = self.non_app_step(|ctx| body(ctx.push(frame)));
+17 -22
View File
@@ -10,7 +10,7 @@ use crate::utils::join::join_maps;
/// function. If [Some(n)], it steps to the `n`th _last_ argument.
pub type Step = Option<usize>;
fn print_step(step: Step) -> String {
if let Some(n) = step { format!("{n}>") } else { "f>".to_string() }
if let Some(n) = step { format!("{n}") } else { "f".to_string() }
}
/// A branching path selecting some placeholders (but at least one) in a Lambda
@@ -59,10 +59,7 @@ impl PathSet {
};
let short_len = short.steps.len();
let long_len = long.steps.len();
let match_len = (short.steps.iter())
.zip(long.steps.iter())
.take_while(|(a, b)| a == b)
.count();
let match_len = (short.steps.iter()).zip(long.steps.iter()).take_while(|(a, b)| a == b).count();
// fact: match_len <= short_len <= long_len
if short_len == match_len && match_len == long_len {
// implies match_len == short_len == long_len
@@ -71,10 +68,8 @@ impl PathSet {
(Some(_), None) | (None, Some(_)) => {
panic!("One of these paths is faulty")
},
(Some(s), Some(l)) => Self::branch(
short.steps.iter().cloned(),
join_maps(s, l, |_, l, r| l.overlay(r)),
),
(Some(s), Some(l)) =>
Self::branch(short.steps.iter().cloned(), join_maps(s, l, |_, l, r| l.overlay(r))),
}
} else if short_len == match_len {
// implies match_len == short_len < long_len
@@ -102,10 +97,7 @@ impl PathSet {
let new_short = Self { next: short.next.clone(), steps: new_short_steps };
let new_long_steps = long.steps.split_off(match_len + 1);
let new_long = Self { next: long.next.clone(), steps: new_long_steps };
Self::branch(short.steps, [
(short_last, new_short),
(long.steps[match_len], new_long),
])
Self::branch(short.steps, [(short_last, new_short), (long.steps[match_len], new_long)])
}
}
@@ -119,22 +111,25 @@ impl PathSet {
impl fmt::Display for PathSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let step_s = self.steps.iter().copied().map(print_step).join("");
let step_s = self.steps.iter().copied().map(print_step).join(">");
match &self.next {
Some(conts) => {
let opts =
conts.iter().map(|(h, t)| format!("{}{t}", print_step(*h))).join("|");
write!(f, "{step_s}({opts})")
let opts = (conts.iter())
.sorted_unstable_by_key(|(k, _)| k.map_or(0, |n| n + 1))
.map(|(h, t)| format!("{}>{t}", print_step(*h)))
.join("|");
if !step_s.is_empty() {
write!(f, "{step_s}>")?;
}
write!(f, "({opts})")
},
None => write!(f, "{step_s}x"),
None => write!(f, "{step_s}"),
}
}
}
impl fmt::Debug for PathSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "PathSet({self})")
}
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PathSet({self})") }
}
#[cfg(test)]
@@ -146,7 +141,7 @@ mod tests {
let ps1 = PathSet { next: None, steps: VecDeque::from([Some(2), None]) };
let ps2 = PathSet { next: None, steps: VecDeque::from([Some(3), Some(1)]) };
let sum = ps1.clone().overlay(ps2.clone());
assert_eq!(format!("{sum}"), "(2>f>x|3>1>x)");
assert_eq!(format!("{sum}"), "(2>f|3>1)");
}
fn extend_scaffold() -> PathSet {
+234 -114
View File
<
@@ -1,129 +1,249 @@
use std::mem;
//! Executes Orchid code
use super::context::{Halt, RunContext};
use std::ops::{Deref, DerefMut};
use std::sync::{MutexGuard, TryLockError};
use std::{fmt, mem};
use bound::Bound;
use itertools::Itertools;
use super::context::{Halt, RunEnv, RunParams};
use super::error::RunError;
use super::nort::{Clause, Expr};
use crate::foreign::atom::{AtomicReturn, RunData};
use crate::foreign::error::ExternError;
use crate::foreign::error::{RTError, RTErrorObj};
use crate::interpreter::apply::{apply_as_atom, substitute};
use crate::interpreter::error::{strace, MissingSymbol, StackOverflow};
use crate::utils::pure_seq::pushed;
use crate::location::CodeLocation;
use crate::utils::take_with_output::take_with_output;
#[derive(Debug)]
struct Stackframe {
expr: Expr,
cls: Bound<MutexGuard<'static, Clause>, Expr>,
}
impl Stackframe {
pub fn new(expr: Expr) -> Option<Self> {
match Bound::try_new(expr.clone(), |e| e.clause.0.try_lock()) {
Ok(cls) => Some(Stackframe { cls, expr }),
Err(bound_e) if matches!(bound_e.wrapped(), TryLockError::WouldBlock) => None,
Err(bound_e) => panic!("{:?}", bound_e.wrapped()),
}
}
pub fn wait_new(expr: Expr) -> Self {
Self { cls: Bound::new(expr.clone(), |e| e.clause.0.lock().unwrap()), expr }
}
pub fn record_cycle(&mut self) -> RTErrorObj {
let err = CyclicalExpression(self.expr.clone()).pack();
*self.cls = Clause::Bottom(err.clone());
err
}
}
impl Deref for Stackframe {
type Target = Clause;
fn deref(&self) -> &Self::Target { &self.cls }
}
impl DerefMut for Stackframe {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cls }
}
impl fmt::Display for Stackframe {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}\n at {}", *self.cls, self.expr.location)
}
}
/// Interpreter state when processing was interrupted
#[derive(Debug, Clone)]
pub struct Interrupted {
/// Cached soft stack to save the interpreter having to rebuild it from the
/// bottom.
pub stack: Vec<Expr>,
}
impl Interrupted {
/// Continue processing where it was interrupted
pub fn resume(self, ctx: RunContext) -> Result<Halt, RunError> { run_stack(self.stack, ctx) }
pub struct State<'a> {
stack: Vec<Stackframe>,
popped: Option<Expr>,
env: &'a RunEnv<'a>,
}
impl<'a> State<'a> {
/// Create a new trivial state with a specified stack size and a single
/// element on the stack
fn new(base: Expr, env: &'a RunEnv<'a>) -> Self {
let stack = vec![Stackframe::new(base).expect("Initial state should not be locked")];
State { stack, popped: None, env }
}
/// Normalize an expression using beta reduction with memoization
pub fn run(expr: Expr, ctx: RunContext) -> Result<Halt, RunError> {
let mut v = Vec::with_capacity(1000);
v.push(expr);
run_stack(v, ctx)
}
/// Try to push an expression on the stack, raise appropriate errors if the
/// expression is already on the stack (and thus references itself), or if the
/// stack now exceeds the pre-defined height
fn push_expr(&'_ mut self, expr: Expr, params: &RunParams) -> Result<(), RunError<'a>> {
let sf = match Stackframe::new(expr.clone()) {
Some(sf) => sf,
None => match self.stack.iter_mut().rev().find(|sf| sf.expr.clause.is_same(&expr.clause)) {
None => Stackframe::wait_new(expr),
Some(sf) => return Err(RunError::Extern(sf.record_cycle())),
},
};
self.stack.push(sf);
if params.stack < self.stack.len() {
let so = StackOverflow(self.stack.iter().map(|sf| sf.expr.clone()).collect());
return Err(RunError::Extern(so.pack()));
}
Ok(())
}
fn run_stack(mut stack: Vec<Expr>, mut ctx: RunContext) -> Result<Halt, RunError> {
let mut expr = stack.pop().expect("Empty stack");
let mut popped = false;
loop {
// print!("Now running {expr}");
// let trace = strace(&stack);
// if trace.is_empty() {
// println!("\n")
// } else {
// println!("\n{trace}\n")
// };
if ctx.no_gas() {
return Err(RunError::Interrupted(Interrupted { stack: pushed(stack, expr) }));
}
ctx.use_gas(1);
enum Res {
Inert,
Cont,
Push(Expr),
}
let (next_clsi, res) = expr.clause.try_normalize(|cls| match cls {
Clause::Identity(_) => panic!("Passed by try_normalize"),
Clause::LambdaArg => panic!("Unbound argument"),
Clause::Lambda { .. } => Ok((cls, Res::Inert)),
Clause::Bottom(b) => Err(b),
Clause::Constant(n) => match ctx.symbols.get(&n) {
Some(expr) => Ok((Clause::Identity(expr.clsi()), Res::Cont)),
None => Err(RunError::Extern(MissingSymbol { sym: n.clone(), loc: expr.location() }.rc())),
},
Clause::Atom(mut a) => {
if !popped {
if let Some(delegate) = a.0.redirect() {
let next = delegate.clone();
return Ok((Clause::Atom(a), Res::Push(next)));
}
/// Process this state until it either completes, runs out of gas as specified
/// in the context, or produces an error.
pub fn run(mut self, params: &mut RunParams) -> Result<Halt, RunError<'a>> {
loop {
if params.no_gas() {
return Err(RunError::Interrupted(self));
}
params.use_gas(1);
let top = self.stack.last_mut().expect("Stack never empty");
let location = top.expr.location();
let op = take_with_output(&mut *top.cls, |c| {
match step(c, self.popped, location, self.env, params) {
Err(e) => (Clause::Bottom(e.clone()), Err(RunError::Extern(e))),
Ok((cls, cmd)) => (cls, Ok(cmd)),
}
let rd = RunData { ctx: ctx.clone(), location: expr.location() };
match a.run(rd)? {
AtomicReturn::Inert(c) => Ok((c, Res::Inert)),
AtomicReturn::Change(gas, c) => {
ctx.use_gas(gas);
Ok((c, Res::Cont))
},
}
},
Clause::Apply { f, mut x } => {
if x.is_empty() {
return Ok((Clause::Identity(f.clsi()), Res::Cont));
}
match &*f.cls() {
Clause::Identity(f2) =>
return Ok((Clause::Apply { f: f2.clone().to_expr(f.location()), x }, Res::Cont)),
Clause::Apply { f, x: x2 } => {
for item in x2.iter().rev() {
x.push_front(item.clone())
})?;
self.popped = None;
match op {
StackOp::Nop => continue,
StackOp::Push(ex) => self.push_expr(ex, params)?,
StackOp::Swap(ex) => {
self.stack.pop().expect("Stack never empty");
self.push_expr(ex, params)?
},
StackOp::Pop => {
let ret = self.stack.pop().expect("last_mut called above");
if self.stack.is_empty() {
if let Some(alt) = self.env.dispatch(&ret.cls, ret.expr.location()) {
self.push_expr(alt, params)?;
params.use_gas(1);
continue;
}
return Ok((Clause::Apply { f: f.clone(), x }, Res::Cont));
},
_ => (),
}
if !popped {
return Ok((Clause::Apply { f: f.clone(), x }, Res::Push(f)));
}
let f_cls = f.cls();
let arg = x.pop_front().expect("checked above");
let loc = f.location();
let f = match &*f_cls {
Clause::Atom(_) => {
mem::drop(f_cls);
apply_as_atom(f, arg, ctx.clone())?
},
Clause::Lambda { args, body } => match args {
None => body.clsi().into_cls(),
Some(args) => substitute(args, arg.clsi(), &body.cls(), &mut || ctx.use_gas(1)),
},
c => panic!("Run should never settle on {c}"),
};
Ok((Clause::Apply { f: f.to_expr(loc), x }, Res::Cont))
},
})?;
expr.clause = next_clsi;