forked from Orchid/orchid
Backup commit
My backspace key started ghosting. Nothing works atm.
This commit is contained in:
294
Cargo.lock
generated
294
Cargo.lock
generated
@@ -30,16 +30,15 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.3.2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"is-terminal",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
@@ -69,14 +68,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "1.0.1"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
@@ -128,33 +138,31 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.3.4"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed"
|
||||
checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.3.4"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636"
|
||||
checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"bitflags 1.3.2",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.3.2"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -164,9 +172,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.5.0"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
@@ -183,6 +191,26 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673"
|
||||
dependencies = [
|
||||
"const_format_proc_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const_format_proc_macros"
|
||||
version = "0.2.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.7"
|
||||
@@ -192,6 +220,30 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.16"
|
||||
@@ -285,9 +337,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.0"
|
||||
version = "0.14.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
|
||||
checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
@@ -301,54 +353,54 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.7"
|
||||
name = "intern-all"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f"
|
||||
checksum = "d79d55e732e243f6762e0fc7b245bfd9fa0e0246356ed6cfdba62d9c707e36c1"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"io-lifetimes",
|
||||
"rustix 0.37.19",
|
||||
"windows-sys",
|
||||
"hashbrown",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.11.0"
|
||||
version = "0.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
|
||||
checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kernel32-sys"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
|
||||
dependencies = [
|
||||
"winapi 0.2.8",
|
||||
"winapi-build",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.148"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.7"
|
||||
@@ -370,6 +422,30 @@ version = "2.5.0"
|
||||
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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "never"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
@@ -380,24 +456,38 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.18.0"
|
||||
name = "numtoa"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "orchidlang"
|
||||
version = "0.2.2"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"const_format",
|
||||
"dyn-clone",
|
||||
"hashbrown",
|
||||
"intern-all",
|
||||
"itertools",
|
||||
"memorize",
|
||||
"never",
|
||||
"once_cell",
|
||||
"ordered-float",
|
||||
"paste",
|
||||
"polling",
|
||||
"rayon",
|
||||
"rust-embed",
|
||||
"substack",
|
||||
"take_mut",
|
||||
"termsize",
|
||||
"trait-set",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
@@ -425,14 +515,14 @@ checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.0.0"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51348b98db9d4a18ada4fdf7ff5274666e7e6c5a50c42a7d77c5e5c0cb6b036b"
|
||||
checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"concurrent-queue",
|
||||
"pin-project-lite",
|
||||
"rustix 0.38.13",
|
||||
"rustix",
|
||||
"tracing",
|
||||
"windows-sys",
|
||||
]
|
||||
@@ -455,6 +545,41 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_termios"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.7.3"
|
||||
@@ -507,20 +632,6 @@ dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys 0.3.7",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.13"
|
||||
@@ -530,7 +641,7 @@ dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.7",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
@@ -543,6 +654,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.160"
|
||||
@@ -566,6 +683,12 @@ version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "substack"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffccc3d80f0a489de67aa74ff31ab852abb973e1c6dacf3704889e00ca544e7f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
@@ -594,6 +717,31 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
|
||||
|
||||
[[package]]
|
||||
name = "termion"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"numtoa",
|
||||
"redox_syscall",
|
||||
"redox_termios",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termsize"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e86d824a8e90f342ad3ef4bd51ef7119a9b681b0cc9f8ee7b2852f02ccd2517"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"kernel32-sys",
|
||||
"libc",
|
||||
"termion",
|
||||
"winapi 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.37"
|
||||
@@ -640,6 +788,12 @@ version = "1.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
@@ -662,6 +816,12 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -672,6 +832,12 @@ dependencies = [
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-build"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
@@ -684,7 +850,7 @@ version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
14
Cargo.toml
14
Cargo.toml
@@ -24,12 +24,20 @@ doc = false
|
||||
[dependencies]
|
||||
hashbrown = "0.14"
|
||||
ordered-float = "4.1"
|
||||
itertools = "0.11"
|
||||
itertools = "0.12"
|
||||
dyn-clone = "1.0"
|
||||
clap = { version = "4.3", features = ["derive"] }
|
||||
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.0.0"
|
||||
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"
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import system::(io, fs, async)
|
||||
import std::(to_string, to_uint, inspect)
|
||||
|
||||
const folder_view := (path, next) => do{
|
||||
--[
|
||||
const folder_view_old := \path. do{
|
||||
cps println $ "Contents of " ++ fs::os_print path;
|
||||
cps entries = async::block_on $ fs::read_dir path;
|
||||
cps list::enumerate entries
|
||||
@@ -13,13 +14,13 @@ const folder_view := (path, next) => do{
|
||||
cps choice = readln;
|
||||
if (choice == "..") then do {
|
||||
let parent_path = fs::pop_path path
|
||||
|> option::unwrap
|
||||
|> option::assume
|
||||
|> tuple::pick 0 2;
|
||||
next parent_path
|
||||
} else do {
|
||||
let t[subname, is_dir] = to_uint choice
|
||||
|> (list::get entries)
|
||||
|> option::unwrap;
|
||||
|> option::assume;
|
||||
let subpath = fs::join_paths path subname;
|
||||
if is_dir then next subpath
|
||||
else do {
|
||||
@@ -30,8 +31,16 @@ const folder_view := (path, next) => do{
|
||||
}
|
||||
}
|
||||
}
|
||||
]--
|
||||
|
||||
const main := loop_over (path = fs::cwd) {
|
||||
cps path = folder_view 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
|
||||
}
|
||||
|
||||
const main := loop_over (path = fs::cwd) {
|
||||
cps folder_view path;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::to_string
|
||||
import std::conv::to_string
|
||||
|
||||
const fizz_buzz := n => (
|
||||
(recursive r (i=0) list::cons i $ r (i + 1))
|
||||
@@ -10,7 +10,7 @@ const fizz_buzz := n => (
|
||||
)
|
||||
|> list::take n
|
||||
|> list::reduce ((l, r) => l ++ "\n" ++ r)
|
||||
|> option::unwrap
|
||||
|> option::assume
|
||||
)
|
||||
|
||||
const main := fizz_buzz 100
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import std::exit_status
|
||||
import std::conv
|
||||
|
||||
const main := (
|
||||
const main2 := (
|
||||
println "Hello, world!"
|
||||
exit_status::success
|
||||
)
|
||||
-- main := "Hello, World!\n"
|
||||
|
||||
const main := conv::to_string t[1, 2, 3]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::to_string
|
||||
import std::conv::to_string
|
||||
|
||||
export const main := do{
|
||||
let foo = list::new[1, 2, 3, 4, 5, 6];
|
||||
@@ -7,7 +7,7 @@ export const main := do{
|
||||
|> list::skip 2
|
||||
|> list::take 3
|
||||
|> list::reduce ((a, b) => a + b)
|
||||
|> option::unwrap;
|
||||
|> option::assume;
|
||||
cps println $ to_string sum;
|
||||
0
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const main := do{
|
||||
"bar" = 4
|
||||
];
|
||||
let num = map::get foo "bar"
|
||||
|> option::unwrap;
|
||||
|> option::assume;
|
||||
cps println $ to_string num;
|
||||
0
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import std::to_string
|
||||
import std::(conv, reflect)
|
||||
|
||||
const foo := t[option::some "world!", option::none]
|
||||
|
||||
@@ -18,4 +18,6 @@ const test2 := match bar {
|
||||
map::having ["is_alive" = true, "greeting" = foo] => foo
|
||||
}
|
||||
|
||||
const main := test2 ++ ", " ++ test1
|
||||
const tests := test2 ++ ", " ++ test1
|
||||
|
||||
const main := conv::to_string bar
|
||||
|
||||
@@ -14,10 +14,10 @@ wrap_comments = true
|
||||
overflow_delimited_expr = true
|
||||
use_small_heuristics = "Max"
|
||||
fn_single_line = true
|
||||
where_single_line = true
|
||||
|
||||
# literals
|
||||
hex_literal_case = "Lower"
|
||||
format_strings = true
|
||||
|
||||
# delimiters
|
||||
match_arm_blocks = false
|
||||
|
||||
62
src/bin/features/macro_debug.rs
Normal file
62
src/bin/features/macro_debug.rs
Normal file
@@ -0,0 +1,62 @@
|
||||
use itertools::Itertools;
|
||||
use orchidlang::facade::macro_runner::MacroRunner;
|
||||
use orchidlang::libs::std::exit_status::ExitStatus;
|
||||
use orchidlang::name::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;
|
||||
},
|
||||
};
|
||||
print!("Debugging macros in {outname} defined at {location}");
|
||||
println!("\nInitial state: {code}");
|
||||
// print_for_debug(&code);
|
||||
let mut steps = macro_runner.step(sym).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}");
|
||||
},
|
||||
},
|
||||
"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}")
|
||||
},
|
||||
"d" | "dump" => print!("Rules: {}", macro_runner.repo),
|
||||
"q" | "quit" => return ExitStatus::Success,
|
||||
"complete" => {
|
||||
match steps.last() {
|
||||
Some((idx, c)) => print!("Step {idx}: {c}"),
|
||||
None => print!("Already halted"),
|
||||
}
|
||||
return ExitStatus::Success;
|
||||
},
|
||||
"h" | "help" => print!(
|
||||
"Available commands:
|
||||
\t<blank>, n, next\t\ttake a step
|
||||
\tp, print\t\tprint the current state
|
||||
\td, dump\t\tprint the rule table
|
||||
\tq, quit\t\texit
|
||||
\th, help\t\tprint this text"
|
||||
),
|
||||
_ => {
|
||||
print!("unrecognized command \"{}\", try \"help\"", cmd);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
2
src/bin/features/mod.rs
Normal file
2
src/bin/features/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod macro_debug;
|
||||
pub mod print_project;
|
||||
59
src/bin/features/print_project.rs
Normal file
59
src/bin/features/print_project.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
use itertools::Itertools;
|
||||
use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectMod};
|
||||
use orchidlang::tree::{ModEntry, ModMember};
|
||||
|
||||
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
|
||||
pub struct ProjPrintOpts {
|
||||
pub width: u16,
|
||||
pub hide_locations: bool,
|
||||
}
|
||||
|
||||
fn indent(amount: u16) -> String { " ".repeat(amount.into()) }
|
||||
|
||||
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 {
|
||||
let mut line_acc = String::new();
|
||||
for c in &x.comments {
|
||||
line_acc += &format!("{tab}, --[|{}|]--\n", c);
|
||||
}
|
||||
if x.exported {
|
||||
line_acc += &format!("{tab}export ");
|
||||
} else {
|
||||
line_acc += &tab
|
||||
}
|
||||
match member {
|
||||
ModMember::Sub(module) => {
|
||||
line_acc += &format!("module {key} {{\n");
|
||||
line_acc += &print_proj_mod(module, lvl + 1, opts);
|
||||
line_acc += &format!("{tab}}}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::None }) => {
|
||||
line_acc += &format!("keyword {key}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::Alias(tgt) }) => {
|
||||
line_acc += &format!("alias {key} => {tgt}");
|
||||
},
|
||||
ModMember::Item(ProjItem { kind: ItemKind::Const(val) }) => {
|
||||
line_acc += &format!("const {key} := {val}");
|
||||
},
|
||||
}
|
||||
if !x.locations.is_empty() && !opts.hide_locations {
|
||||
let locs = x.locations.iter().map(|l| l.to_string()).join(", ");
|
||||
let line_len = line_acc.split('\n').last().unwrap().len();
|
||||
match usize::from(opts.width).checked_sub(locs.len() + line_len + 4) {
|
||||
Some(padding) => line_acc += &" ".repeat(padding),
|
||||
None => line_acc += &format!("\n{tab} @ "),
|
||||
}
|
||||
line_acc += &locs;
|
||||
}
|
||||
line_acc += "\n";
|
||||
acc += &line_acc
|
||||
}
|
||||
acc
|
||||
}
|
||||
393
src/bin/orcx.rs
393
src/bin/orcx.rs
@@ -1,39 +1,81 @@
|
||||
mod cli;
|
||||
mod features;
|
||||
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::path::PathBuf;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::ExitCode;
|
||||
use std::thread::available_parallelism;
|
||||
|
||||
use clap::Parser;
|
||||
use clap::{Parser, Subcommand};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use itertools::Itertools;
|
||||
use orchidlang::facade::{Environment, PreMacro};
|
||||
use orchidlang::systems::asynch::AsynchSystem;
|
||||
use orchidlang::systems::stl::{ExitStatus, StlConfig};
|
||||
use orchidlang::systems::{directfs, io, scheduler};
|
||||
use orchidlang::{ast, interpreted, interpreter, Interner, Sym, VName};
|
||||
use orchidlang::error::{ProjectError, ProjectErrorObj, ProjectResult};
|
||||
use orchidlang::facade::loader::Loader;
|
||||
use orchidlang::facade::macro_runner::MacroRunner;
|
||||
use orchidlang::facade::merge_trees::merge_trees;
|
||||
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::name::Sym;
|
||||
use orchidlang::tree::{ModMemberRef, TreeTransforms};
|
||||
use rayon::prelude::ParallelIterator;
|
||||
use rayon::slice::ParallelSlice;
|
||||
|
||||
use crate::cli::cmd_prompt;
|
||||
use crate::features::macro_debug;
|
||||
use crate::features::print_project::{print_proj_mod, ProjPrintOpts};
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
/// Run unit tests, any constant annotated --[[ test ]]--
|
||||
Test {
|
||||
/// Specify an exact test to run
|
||||
#[arg(long)]
|
||||
only: Option<String>,
|
||||
#[arg(long, short)]
|
||||
threads: Option<usize>,
|
||||
#[arg(long)]
|
||||
system: Option<String>,
|
||||
},
|
||||
#[command(arg_required_else_help = true)]
|
||||
MacroDebug {
|
||||
symbol: String,
|
||||
},
|
||||
ListMacros,
|
||||
ProjectTree {
|
||||
#[arg(long, default_value_t = false)]
|
||||
hide_locations: bool,
|
||||
#[arg(long)]
|
||||
width: Option<u16>,
|
||||
},
|
||||
}
|
||||
/// Orchid interpreter
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(name = "Orchid Executor")]
|
||||
#[command(author = "Lawrence Bethlenfalvy <lbfalvy@protonmail.com>")]
|
||||
#[command(long_about = Some("Execute Orchid projects from the file system"))]
|
||||
struct Args {
|
||||
/// Folder containing main.orc or the manually specified entry module
|
||||
#[arg(short, long, default_value = ".")]
|
||||
pub dir: String,
|
||||
/// Entrypoint for the interpreter
|
||||
#[arg(short, long, default_value = "main::main")]
|
||||
pub main: String,
|
||||
/// Alternative entrypoint for the interpreter
|
||||
#[arg(short, long)]
|
||||
pub main: Option<String>,
|
||||
/// Maximum number of steps taken by the macro executor
|
||||
#[arg(long, default_value_t = 10_000)]
|
||||
pub macro_limit: usize,
|
||||
/// Print the parsed ruleset and exit
|
||||
#[arg(long)]
|
||||
pub list_macros: bool,
|
||||
/// Step through the macro execution process in the specified symbol
|
||||
#[arg(long, default_value = "")]
|
||||
pub macro_debug: String,
|
||||
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
}
|
||||
impl Args {
|
||||
/// Validate the project directory and the
|
||||
@@ -42,21 +84,25 @@ impl Args {
|
||||
if !dir_path.is_dir() {
|
||||
return Err(format!("{} is not a directory", dir_path.display()));
|
||||
}
|
||||
let segs = self.main.split("::").collect::<Vec<_>>();
|
||||
let segs = match &self.main {
|
||||
Some(s) => s.split("::").collect::<Vec<_>>(),
|
||||
None => match File::open("./main.orc") {
|
||||
Ok(_) => return Ok(()),
|
||||
Err(e) => return Err(format!("Cannot open './main.orc'\n{e}")),
|
||||
},
|
||||
};
|
||||
if segs.len() < 2 {
|
||||
return Err("Entry point too short".to_string());
|
||||
}
|
||||
let (pathsegs, _) = segs.split_at(segs.len() - 1);
|
||||
};
|
||||
let (_, pathsegs) = segs.split_last().unwrap();
|
||||
let mut possible_files = pathsegs.iter().scan(dir_path, |path, seg| {
|
||||
path.push(seg);
|
||||
Some(path.with_extension("orc"))
|
||||
});
|
||||
if possible_files.all(|p| File::open(p).is_err()) {
|
||||
return Err(format!(
|
||||
"{} not found in {}",
|
||||
pathsegs.join("::"),
|
||||
PathBuf::from(&self.dir).display()
|
||||
));
|
||||
let out_path = pathsegs.join("::");
|
||||
let pbuf = PathBuf::from(&self.dir);
|
||||
return Err(format!("{out_path} not found in {}", pbuf.display()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -64,121 +110,216 @@ impl Args {
|
||||
pub fn chk_proj(&self) -> Result<(), String> { self.chk_dir_main() }
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn to_vname(data: &str, i: &Interner) -> VName {
|
||||
data.split("::").map(|s| i.i(s)).collect::<Vec<_>>()
|
||||
macro_rules! unwrap_exit {
|
||||
($param:expr) => {
|
||||
match $param {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
eprintln!("{e}");
|
||||
return ExitCode::FAILURE;
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
fn print_for_debug(e: &ast::Expr<Sym>) {
|
||||
print!(
|
||||
"code: {}\nglossary: {}",
|
||||
e,
|
||||
(e.value.collect_names().into_iter())
|
||||
.map(|t| t.iter().join("::"))
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
|
||||
/// A little utility to step through the resolution of a macro set
|
||||
pub fn macro_debug(premacro: PreMacro, sym: Sym) -> ExitCode {
|
||||
let (mut code, location) = (premacro.consts.get(&sym))
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"Symbol {} not found\nvalid symbols: \n\t{}\n",
|
||||
sym.iter().join("::"),
|
||||
(premacro.consts.keys()).map(|t| t.iter().join("::")).join("\n\t")
|
||||
)
|
||||
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)
|
||||
})
|
||||
.clone();
|
||||
println!(
|
||||
"Debugging macros in {} defined at {}.
|
||||
Initial state: ",
|
||||
sym.iter().join("::"),
|
||||
location
|
||||
);
|
||||
print_for_debug(&code);
|
||||
let mut steps = premacro.step(sym).enumerate();
|
||||
loop {
|
||||
let (cmd, _) = cmd_prompt("\ncmd> ").unwrap();
|
||||
match cmd.trim() {
|
||||
"" | "n" | "next" =>
|
||||
if let Some((idx, c)) = steps.next() {
|
||||
code = c;
|
||||
print!("Step {idx}: ");
|
||||
print_for_debug(&code);
|
||||
} else {
|
||||
print!("Halted")
|
||||
},
|
||||
"p" | "print" => print_for_debug(&code),
|
||||
"d" | "dump" => print!("Rules: {}", premacro.repo),
|
||||
"q" | "quit" => return ExitCode::SUCCESS,
|
||||
"complete" => {
|
||||
if let Some((idx, c)) = steps.last() {
|
||||
code = c;
|
||||
print!("Step {idx}: ");
|
||||
print_for_debug(&code);
|
||||
} else {
|
||||
print!("Already halted")
|
||||
}
|
||||
return ExitCode::SUCCESS;
|
||||
},
|
||||
"h" | "help" => print!(
|
||||
"Available commands:
|
||||
\t<blank>, n, next\t\ttake a step
|
||||
\tp, print\t\tprint the current state
|
||||
\td, dump\t\tprint the rule table
|
||||
\tq, quit\t\texit
|
||||
\th, help\t\tprint this text"
|
||||
),
|
||||
_ => {
|
||||
print!("unrecognized command \"{}\", try \"help\"", cmd);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
// 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();
|
||||
args.chk_proj().unwrap_or_else(|e| panic!("{e}"));
|
||||
let dir = PathBuf::try_from(args.dir).unwrap();
|
||||
let i = Interner::new();
|
||||
let main = to_vname(&args.main, &i);
|
||||
let mut asynch = AsynchSystem::new();
|
||||
let scheduler = scheduler::SeqScheduler::new(&mut asynch);
|
||||
let std_streams = [
|
||||
("stdin", io::Stream::Source(BufReader::new(Box::new(std::io::stdin())))),
|
||||
("stdout", io::Stream::Sink(Box::new(std::io::stdout()))),
|
||||
// ("stderr", io::Stream::Sink(Box::new(std::io::stderr()))),
|
||||
];
|
||||
let env = Environment::new(&i)
|
||||
.add_system(StlConfig { impure: true })
|
||||
.add_system(asynch)
|
||||
.add_system(scheduler.clone())
|
||||
.add_system(io::Service::new(scheduler.clone(), std_streams))
|
||||
.add_system(directfs::DirectFS::new(scheduler));
|
||||
let premacro = env.load_dir(&dir, &main).unwrap();
|
||||
if args.list_macros {
|
||||
println!("Parsed rules: {}", premacro.repo);
|
||||
return ExitCode::SUCCESS;
|
||||
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"),
|
||||
);
|
||||
|
||||
// subcommands
|
||||
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));
|
||||
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 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));
|
||||
let symbol = Sym::parse(&symbol).expect("macro-debug needs an argument");
|
||||
macro_debug::main(unwrap_exit!(MacroRunner::new(&tree)), symbol).code()
|
||||
}),
|
||||
Some(Command::Test { only: Some(_), threads: Some(_), .. }) => {
|
||||
eprintln!(
|
||||
"Each test case runs in a single thread.
|
||||
--only and --threads cannot both be specified"
|
||||
);
|
||||
ExitCode::FAILURE
|
||||
},
|
||||
Some(Command::Test { only: Some(_), system: Some(_), .. }) => {
|
||||
eprintln!(
|
||||
"Conflicting test filters applied. --only runs a single test by
|
||||
symbol name, while --system runs all tests in a system"
|
||||
);
|
||||
ExitCode::FAILURE
|
||||
},
|
||||
Some(Command::Test { only: None, threads, system: None }) => {
|
||||
let tree_tests = unwrap_exit!(get_tree_tests(&dir));
|
||||
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
|
||||
},
|
||||
Some(Command::Test { only: None, threads, system: Some(system) }) => {
|
||||
let subtrees = unwrap_exit!(with_std_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");
|
||||
paths.insert(name);
|
||||
}
|
||||
if !args.macro_debug.is_empty() {
|
||||
let sym = i.i(&to_vname(&args.macro_debug, &i));
|
||||
return macro_debug(premacro, sym);
|
||||
});
|
||||
Ok(paths)
|
||||
},
|
||||
}
|
||||
let mut proc = premacro.build_process(Some(args.macro_limit)).unwrap();
|
||||
proc.validate_refs().unwrap();
|
||||
let main = interpreted::Clause::Constant(i.i(&main)).wrap();
|
||||
let ret = proc.run(main, None).unwrap();
|
||||
let interpreter::Return { state, inert, .. } = ret;
|
||||
}));
|
||||
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)
|
||||
}));
|
||||
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;
|
||||
drop(proc);
|
||||
assert!(inert, "Gas is not used, only inert data should be yielded");
|
||||
match state.clone().downcast::<ExitStatus>() {
|
||||
Ok(ExitStatus::Success) => ExitCode::SUCCESS,
|
||||
Ok(ExitStatus::Failure) => ExitCode::FAILURE,
|
||||
match state.clone().downcast() {
|
||||
Ok(Inert(ExitStatus::Success)) => ExitCode::SUCCESS,
|
||||
Ok(Inert(ExitStatus::Failure)) => ExitCode::FAILURE,
|
||||
Err(_) => {
|
||||
println!("{}", state.expr().clause);
|
||||
println!("{}", state.clause);
|
||||
ExitCode::SUCCESS
|
||||
},
|
||||
}
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
185
src/error.rs
Normal file
185
src/error.rs
Normal file
@@ -0,0 +1,185 @@
|
||||
//! 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::sync::Arc;
|
||||
|
||||
use dyn_clone::{clone_box, DynClone};
|
||||
|
||||
use crate::location::CodeLocation;
|
||||
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
|
||||
pub struct ErrorPosition {
|
||||
/// The suspected location
|
||||
pub location: CodeLocation,
|
||||
/// Any information about the role of this location
|
||||
pub message: Option<String>,
|
||||
}
|
||||
impl From<CodeLocation> for ErrorPosition {
|
||||
fn from(location: CodeLocation) -> Self { Self { location, message: None } }
|
||||
}
|
||||
|
||||
/// Errors addressed to the developer which are to be resolved with
|
||||
/// code changes
|
||||
pub trait ProjectError: Sized + Send + Sync + 'static {
|
||||
/// A general description of this type of error
|
||||
const DESCRIPTION: &'static str;
|
||||
/// A formatted message that includes specific parameters
|
||||
#[must_use]
|
||||
fn message(&self) -> String { self.description().to_string() }
|
||||
/// 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 })
|
||||
}
|
||||
/// Short way to provide a single location. If you don't implement this, you
|
||||
/// must implement [ProjectError::positions]
|
||||
#[must_use]
|
||||
fn one_position(&self) -> CodeLocation { unimplemented!() }
|
||||
/// Convert the error into an `Arc<dyn DynProjectError>` to be able to
|
||||
/// handle various errors together
|
||||
#[must_use]
|
||||
fn pack(self) -> ProjectErrorObj { Arc::new(self) }
|
||||
}
|
||||
|
||||
/// Object-safe version of [ProjectError]. Implement that instead of this.
|
||||
pub trait DynProjectError: Send + Sync {
|
||||
/// Access type information about this error
|
||||
#[must_use]
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
/// A general description of this type of error
|
||||
#[must_use]
|
||||
fn description(&self) -> &str;
|
||||
/// A formatted message that includes specific parameters
|
||||
#[must_use]
|
||||
fn message(&self) -> String { self.description().to_string() }
|
||||
/// Code positions relevant to this error.
|
||||
#[must_use]
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition>;
|
||||
}
|
||||
|
||||
impl<T> DynProjectError for T
|
||||
where T: ProjectError
|
||||
{
|
||||
fn as_any(&self) -> &dyn Any { self }
|
||||
fn description(&self) -> &str { T::DESCRIPTION }
|
||||
fn message(&self) -> String { ProjectError::message(self) }
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> {
|
||||
Box::new(ProjectError::positions(self).into_iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for dyn DynProjectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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")?;
|
||||
} else {
|
||||
for ErrorPosition { location, message } in positions {
|
||||
match message {
|
||||
None => writeln!(f, "@{location}"),
|
||||
Some(msg) => writeln!(f, "@{location}: {msg}"),
|
||||
}?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for dyn DynProjectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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.
|
||||
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 {
|
||||
/// General description of the error condition
|
||||
const DESCRIPTION: &'static str;
|
||||
/// Specific description of the error including code fragments or concrete
|
||||
/// data if possible
|
||||
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) }
|
||||
}
|
||||
|
||||
/// Object-safe equivalent to [ErrorSansLocation]. Implement that one instead of
|
||||
/// this. Typically found as [ErrorSansLocationObj]
|
||||
pub trait DynErrorSansLocation: 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;
|
||||
/// Generic description of the error condition
|
||||
fn description(&self) -> &str;
|
||||
/// Specific description of this particular error
|
||||
fn message(&self) -> String;
|
||||
}
|
||||
|
||||
/// 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>;
|
||||
|
||||
impl<T: ErrorSansLocation + 'static> DynErrorSansLocation for T {
|
||||
fn description(&self) -> &str { Self::DESCRIPTION }
|
||||
fn message(&self) -> String { self.message() }
|
||||
fn as_any_ref(&self) -> &dyn Any { self }
|
||||
}
|
||||
impl Clone for ErrorSansLocationObj {
|
||||
fn clone(&self) -> Self { clone_box(&**self) }
|
||||
}
|
||||
impl DynErrorSansLocation for ErrorSansLocationObj {
|
||||
fn description(&self) -> &str { (**self).description() }
|
||||
fn message(&self) -> String { (**self).message() }
|
||||
fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() }
|
||||
}
|
||||
impl Display for ErrorSansLocationObj {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
writeln!(f, "{}\nLocation missing from error", self.message())
|
||||
}
|
||||
}
|
||||
impl Debug for ErrorSansLocationObj {
|
||||
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() }
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a location to an [ErrorSansLocation]
|
||||
pub fn bundle_location(
|
||||
location: &CodeLocation,
|
||||
details: &dyn DynErrorSansLocation,
|
||||
) -> ProjectErrorObj {
|
||||
Arc::new(LocationBundle(location.clone(), clone_box(details)))
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
use std::fmt::Display;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::foreign::{ExternError, XfnResult};
|
||||
use crate::Location;
|
||||
|
||||
/// Some expectation (usually about the argument types of a function) did not
|
||||
/// hold.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionError {
|
||||
location: Location,
|
||||
message: &'static str,
|
||||
}
|
||||
|
||||
impl AssertionError {
|
||||
/// Construct, upcast and wrap in a Result that never succeeds for easy
|
||||
/// short-circuiting
|
||||
pub fn fail<T>(location: Location, message: &'static str) -> XfnResult<T> {
|
||||
Err(Self::ext(location, message))
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(
|
||||
location: Location,
|
||||
message: &'static str,
|
||||
) -> Arc<dyn ExternError> {
|
||||
Self { location, message }.into_extern()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AssertionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error: expected {}", self.message)?;
|
||||
if self.location != Location::Unknown {
|
||||
write!(f, " at {}", self.location)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for AssertionError {}
|
||||
@@ -1,31 +0,0 @@
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Location, VName};
|
||||
|
||||
/// Error raised if the same name ends up assigned to more than one thing.
|
||||
/// A name in Orchid has exactly one meaning, either a value or a module.
|
||||
pub struct ConflictingRoles {
|
||||
/// Name assigned to multiple things
|
||||
pub name: VName,
|
||||
/// Location of at least two occurrences
|
||||
pub locations: Vec<Location>,
|
||||
}
|
||||
impl ProjectError for ConflictingRoles {
|
||||
fn description(&self) -> &str {
|
||||
"The same name is assigned multiple times to conflicting items"
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"{} has multiple conflicting meanings",
|
||||
self.name.iter().map(|t| t.as_str()).join("::")
|
||||
)
|
||||
}
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> {
|
||||
Box::new(
|
||||
(self.locations.iter())
|
||||
.map(|l| ErrorPosition { location: l.clone(), message: None }),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::ProjectError;
|
||||
use crate::representations::location::Location;
|
||||
use crate::VName;
|
||||
|
||||
/// Error produced for the statement `import *`
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct ImportAll {
|
||||
/// The file containing the offending import
|
||||
pub offender_file: Arc<VName>,
|
||||
/// The module containing the offending import
|
||||
pub offender_mod: Arc<VName>,
|
||||
}
|
||||
impl ProjectError for ImportAll {
|
||||
fn description(&self) -> &str { "a top-level glob import was used" }
|
||||
fn message(&self) -> String {
|
||||
format!("{} imports *", self.offender_mod.iter().join("::"))
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location {
|
||||
Location::File(self.offender_file.clone())
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
//! Various errors the pipeline can produce
|
||||
mod assertion_error;
|
||||
mod conflicting_roles;
|
||||
mod import_all;
|
||||
mod no_targets;
|
||||
mod not_exported;
|
||||
mod project_error;
|
||||
mod runtime_error;
|
||||
mod too_many_supers;
|
||||
mod unexpected_directory;
|
||||
mod visibility_mismatch;
|
||||
|
||||
pub use assertion_error::AssertionError;
|
||||
pub use conflicting_roles::ConflictingRoles;
|
||||
pub use import_all::ImportAll;
|
||||
pub use no_targets::NoTargets;
|
||||
pub use not_exported::NotExported;
|
||||
pub use project_error::{ErrorPosition, ProjectError, ProjectResult};
|
||||
pub use runtime_error::RuntimeError;
|
||||
pub use too_many_supers::TooManySupers;
|
||||
pub use unexpected_directory::UnexpectedDirectory;
|
||||
pub use visibility_mismatch::VisibilityMismatch;
|
||||
@@ -1,20 +0,0 @@
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
#[allow(unused)] // for doc
|
||||
use crate::parse_layer;
|
||||
use crate::utils::boxed_iter::box_empty;
|
||||
use crate::utils::BoxedIter;
|
||||
|
||||
/// Error produced when [parse_layer] is called without targets. This function
|
||||
/// produces an error instead of returning a straightforward empty tree because
|
||||
/// the edge case of no targets is often an error and should generally be
|
||||
/// handled explicitly
|
||||
#[derive(Debug)]
|
||||
pub struct NoTargets;
|
||||
|
||||
impl ProjectError for NoTargets {
|
||||
fn description(&self) -> &str {
|
||||
"No targets were specified for layer parsing"
|
||||
}
|
||||
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> { box_empty() }
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{ErrorPosition, ProjectError};
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// An import refers to a symbol which exists but is not exported.
|
||||
#[derive(Debug)]
|
||||
pub struct NotExported {
|
||||
/// The containing file - files are always exported
|
||||
pub file: VName,
|
||||
/// The path leading to the unexported module
|
||||
pub subpath: VName,
|
||||
/// The offending file
|
||||
pub referrer_file: VName,
|
||||
/// The module containing the offending import
|
||||
pub referrer_subpath: VName,
|
||||
}
|
||||
impl ProjectError for NotExported {
|
||||
fn description(&self) -> &str {
|
||||
"An import refers to a symbol that exists but isn't exported"
|
||||
}
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> {
|
||||
Box::new(
|
||||
[
|
||||
ErrorPosition {
|
||||
location: Location::File(Arc::new(self.file.clone())),
|
||||
message: Some(format!(
|
||||
"{} isn't exported",
|
||||
Interner::extern_all(&self.subpath).join("::")
|
||||
)),
|
||||
},
|
||||
ErrorPosition {
|
||||
location: Location::File(Arc::new(self.referrer_file.clone())),
|
||||
message: Some(format!(
|
||||
"{} cannot see this symbol",
|
||||
Interner::extern_all(&self.referrer_subpath).join("::")
|
||||
)),
|
||||
},
|
||||
]
|
||||
.into_iter(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::representations::location::Location;
|
||||
use crate::utils::boxed_iter::box_once;
|
||||
use crate::utils::BoxedIter;
|
||||
|
||||
/// A point of interest in resolving the error, such as the point where
|
||||
/// processing got stuck, a command that is likely to be incorrect
|
||||
pub struct ErrorPosition {
|
||||
/// The suspected location
|
||||
pub location: Location,
|
||||
/// Any information about the role of this location
|
||||
pub message: Option<String>,
|
||||
}
|
||||
|
||||
/// Errors addressed to the developer which are to be resolved with
|
||||
/// code changes
|
||||
pub trait ProjectError {
|
||||
/// A general description of this type of error
|
||||
#[must_use]
|
||||
fn description(&self) -> &str;
|
||||
/// A formatted message that includes specific parameters
|
||||
#[must_use]
|
||||
fn message(&self) -> String { self.description().to_string() }
|
||||
/// Code positions relevant to this error. If you don't implement this, you
|
||||
/// must implement [ProjectError::one_position]
|
||||
#[must_use]
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> {
|
||||
box_once(ErrorPosition { location: self.one_position(), message: None })
|
||||
}
|
||||
/// Short way to provide a single location. If you don't implement this, you
|
||||
/// must implement [ProjectError::positions]
|
||||
#[must_use]
|
||||
fn one_position(&self) -> Location { unimplemented!() }
|
||||
/// Convert the error into an `Rc<dyn ProjectError>` to be able to
|
||||
/// handle various errors together
|
||||
#[must_use]
|
||||
fn rc(self) -> Rc<dyn ProjectError>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Rc::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for dyn ProjectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let description = self.description();
|
||||
let message = self.message();
|
||||
let positions = self.positions();
|
||||
writeln!(f, "Project error: {description}\n{message}")?;
|
||||
for ErrorPosition { location, message } in positions {
|
||||
writeln!(
|
||||
f,
|
||||
"@{location}: {}",
|
||||
message.unwrap_or("location of interest".to_string())
|
||||
)?
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for dyn ProjectError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
pub type ProjectResult<T> = Result<T, Rc<dyn ProjectError>>;
|
||||
@@ -1,27 +0,0 @@
|
||||
use super::ProjectError;
|
||||
use crate::representations::location::Location;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Error produced when an import path starts with more `super` segments
|
||||
/// than the current module's absolute path
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct TooManySupers {
|
||||
/// The offending import path
|
||||
pub path: VName,
|
||||
/// The faulty import statement
|
||||
pub location: Location,
|
||||
}
|
||||
impl ProjectError for TooManySupers {
|
||||
fn description(&self) -> &str {
|
||||
"an import path starts with more `super` segments than the current \
|
||||
module's absolute path"
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"path {} contains too many `super` steps.",
|
||||
Interner::extern_all(&self.path).join("::"),
|
||||
)
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::ProjectError;
|
||||
use crate::{Interner, Location, VName};
|
||||
|
||||
/// Produced when a stage that deals specifically with code encounters
|
||||
/// a path that refers to a directory
|
||||
#[derive(Debug)]
|
||||
pub struct UnexpectedDirectory {
|
||||
/// Path to the offending collection
|
||||
pub path: VName,
|
||||
}
|
||||
impl ProjectError for UnexpectedDirectory {
|
||||
fn description(&self) -> &str {
|
||||
"A stage that deals specifically with code encountered a path that refers \
|
||||
to a directory"
|
||||
}
|
||||
fn one_position(&self) -> crate::Location {
|
||||
Location::File(Arc::new(self.path.clone()))
|
||||
}
|
||||
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"{} was expected to be a file but a directory was found",
|
||||
Interner::extern_all(&self.path).join("/")
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::project_error::ProjectError;
|
||||
use crate::representations::location::Location;
|
||||
use crate::{Interner, VName};
|
||||
|
||||
/// Multiple occurences of the same namespace with different visibility
|
||||
#[derive(Debug)]
|
||||
pub struct VisibilityMismatch {
|
||||
/// The namespace with ambiguous visibility
|
||||
pub namespace: VName,
|
||||
/// The file containing the namespace
|
||||
pub file: VName,
|
||||
}
|
||||
impl ProjectError for VisibilityMismatch {
|
||||
fn description(&self) -> &str {
|
||||
"Some occurences of a namespace are exported but others are not"
|
||||
}
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"{} is opened multiple times with different visibilities",
|
||||
Interner::extern_all(&self.namespace).join("::")
|
||||
)
|
||||
}
|
||||
fn one_position(&self) -> Location {
|
||||
Location::File(Arc::new(self.file.clone()))
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
use std::iter;
|
||||
use std::path::Path;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::system::{IntoSystem, System};
|
||||
use super::PreMacro;
|
||||
use crate::error::ProjectResult;
|
||||
use crate::pipeline::file_loader;
|
||||
use crate::sourcefile::FileEntry;
|
||||
use crate::utils::never;
|
||||
use crate::{
|
||||
from_const_tree, parse_layer, vname_to_sym_tree, Interner, ProjectTree, Stok,
|
||||
VName,
|
||||
};
|
||||
|
||||
/// A compiled environment ready to load user code. It stores the list of
|
||||
/// systems and combines with usercode to produce a [Process]
|
||||
pub struct Environment<'a> {
|
||||
/// [Interner] pseudo-global
|
||||
pub i: &'a Interner,
|
||||
systems: Vec<System<'a>>,
|
||||
}
|
||||
impl<'a> Environment<'a> {
|
||||
/// Initialize a new environment
|
||||
#[must_use]
|
||||
pub fn new(i: &'a Interner) -> Self { Self { i, systems: Vec::new() } }
|
||||
|
||||
/// Register a new system in the environment
|
||||
#[must_use]
|
||||
pub fn add_system<'b: 'a>(mut self, is: impl IntoSystem<'b> + 'b) -> Self {
|
||||
self.systems.push(Box::new(is).into_system(self.i));
|
||||
self
|
||||
}
|
||||
|
||||
/// Compile the environment from the set of systems and return it directly.
|
||||
/// See [#load_dir]
|
||||
pub fn compile(self) -> ProjectResult<CompiledEnv<'a>> {
|
||||
let Self { i, systems, .. } = self;
|
||||
let mut tree = from_const_tree(HashMap::new(), &[i.i("none")]);
|
||||
for sys in systems.iter() {
|
||||
let system_tree = from_const_tree(sys.constants.clone(), &sys.vname(i));
|
||||
tree = ProjectTree(never::unwrap_always(tree.0.overlay(system_tree.0)));
|
||||
}
|
||||
let mut lexer_plugins = vec![];
|
||||
let mut line_parsers = vec![];
|
||||
let mut prelude = vec![];
|
||||
for sys in systems.iter() {
|
||||
lexer_plugins.extend(sys.lexer_plugins.iter().map(|b| &**b));
|
||||
line_parsers.extend(sys.line_parsers.iter().map(|b| &**b));
|
||||
if !sys.code.is_empty() {
|
||||
tree = parse_layer(
|
||||
sys.code.keys().map(|sym| &sym[..]),
|
||||
&|k, referrer| sys.load_file(k, referrer),
|
||||
&tree,
|
||||
&prelude,
|
||||
&lexer_plugins,
|
||||
&line_parsers,
|
||||
i,
|
||||
)?;
|
||||
}
|
||||
prelude.extend_from_slice(&sys.prelude);
|
||||
}
|
||||
Ok(CompiledEnv { prelude, tree, systems })
|
||||
}
|
||||
|
||||
/// Load a directory from the local file system as an Orchid project.
|
||||
pub fn load_dir(
|
||||
self,
|
||||
dir: &Path,
|
||||
target: &[Stok],
|
||||
) -> ProjectResult<PreMacro<'a>> {
|
||||
let i = self.i;
|
||||
let CompiledEnv { prelude, systems, tree } = self.compile()?;
|
||||
let file_cache = file_loader::mk_dir_cache(dir.to_path_buf());
|
||||
let lexer_plugins = (systems.iter())
|
||||
.flat_map(|s| s.lexer_plugins.iter().map(|b| &**b))
|
||||
.collect::<Vec<_>>();
|
||||
let line_parsers = (systems.iter())
|
||||
.flat_map(|s| s.line_parsers.iter().map(|b| &**b))
|
||||
.collect::<Vec<_>>();
|
||||
let vname_tree = parse_layer(
|
||||
iter::once(target),
|
||||
&|path, _| file_cache.find(path),
|
||||
&tree,
|
||||
&prelude,
|
||||
&lexer_plugins,
|
||||
&line_parsers,
|
||||
i,
|
||||
)?;
|
||||
let tree = vname_to_sym_tree(vname_tree, i);
|
||||
PreMacro::new(tree, systems, i)
|
||||
}
|
||||
}
|
||||
|
||||
/// Compiled environment waiting for usercode. An intermediate step between
|
||||
/// [Environment] and [Process]
|
||||
pub struct CompiledEnv<'a> {
|
||||
/// Namespace tree for pre-defined symbols with symbols at the leaves and
|
||||
/// rules defined on the nodes
|
||||
pub tree: ProjectTree<VName>,
|
||||
/// Lines prepended to each usercode file
|
||||
pub prelude: Vec<FileEntry>,
|
||||
/// List of systems to source handlers for the interpreter
|
||||
pub systems: Vec<System<'a>>,
|
||||
}
|
||||
143
src/facade/loader.rs
Normal file
143
src/facade/loader.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{fs, iter};
|
||||
|
||||
use intern_all::{i, Tok};
|
||||
use substack::Substack;
|
||||
|
||||
use super::system::{IntoSystem, System};
|
||||
use crate::error::ProjectResult;
|
||||
use crate::gen::tree::ConstTree;
|
||||
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::pipeline::project::ProjectTree;
|
||||
use crate::utils::combine::Combine;
|
||||
use crate::utils::sequence::Sequence;
|
||||
use crate::utils::unwrap_or::unwrap_or;
|
||||
use crate::virt_fs::{DeclTree, DirNode, VirtFS};
|
||||
|
||||
/// A compiled environment ready to load user code. It stores the list of
|
||||
/// systems and combines with usercode to produce a [Process]
|
||||
pub struct Loader<'a> {
|
||||
systems: Vec<System<'a>>,
|
||||
}
|
||||
impl<'a> Loader<'a> {
|
||||
/// Initialize a new environment
|
||||
#[must_use]
|
||||
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()
|
||||
}
|
||||
|
||||
/// Register a new system in the environment
|
||||
#[must_use]
|
||||
pub fn add_system<'b: 'a>(mut self, is: impl IntoSystem<'b> + 'b) -> Self {
|
||||
self.systems.push(Box::new(is).into_system());
|
||||
self
|
||||
}
|
||||
|
||||
/// Extract the systems from the environment
|
||||
pub fn into_systems(self) -> Vec<System<'a>> { self.systems }
|
||||
|
||||
/// Initialize an environment with a prepared list of systems
|
||||
pub fn from_systems(sys: impl IntoIterator<Item = System<'a>>) -> Self {
|
||||
Self { systems: sys.into_iter().collect() }
|
||||
}
|
||||
|
||||
/// 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())
|
||||
})
|
||||
.expect("Conflicting const trees")
|
||||
}
|
||||
|
||||
pub fn handlers(self) -> HandlerTable<'a> {
|
||||
(self.systems.into_iter())
|
||||
.fold(HandlerTable::new(), |t, sys| t.combine(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 {
|
||||
lexer_plugins: Sequence::new(|| {
|
||||
self.systems().flat_map(|sys| &sys.lexer_plugins).map(|b| &**b)
|
||||
}),
|
||||
line_parsers: Sequence::new(|| {
|
||||
self.systems().flat_map(|sys| &sys.line_parsers).map(|b| &**b)
|
||||
}),
|
||||
preludes: Sequence::new(|| self.systems().flat_map(|sys| &sys.prelude)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Combine source code from all systems with the specified directory into a
|
||||
/// common [VirtFS]
|
||||
pub fn make_dir_tree(&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())))
|
||||
.expect("Conflicting system trees")
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
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)))
|
||||
}
|
||||
}
|
||||
}
|
||||
84
src/facade/macro_runner.rs
Normal file
84
src/facade/macro_runner.rs
Normal file
@@ -0,0 +1,84 @@
|
||||
use std::iter;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::error::{ProjectError, ProjectResult};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
use crate::parse::parsed;
|
||||
use crate::pipeline::project::{
|
||||
ConstReport, ProjectTree,
|
||||
};
|
||||
use crate::rule::repository::Repo;
|
||||
|
||||
pub struct MacroRunner {
|
||||
/// Optimized catalog of substitution rules
|
||||
pub repo: Repo,
|
||||
/// Runtime code containing macro invocations
|
||||
pub consts: HashMap<Sym, ConstReport>,
|
||||
}
|
||||
impl MacroRunner {
|
||||
pub fn new(tree: &ProjectTree) -> ProjectResult<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() })
|
||||
}
|
||||
|
||||
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() });
|
||||
}
|
||||
Ok(symbols)
|
||||
}
|
||||
|
||||
/// 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();
|
||||
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() }
|
||||
}
|
||||
61
src/facade/merge_trees.rs
Normal file
61
src/facade/merge_trees.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use never::Never;
|
||||
use substack::Substack;
|
||||
|
||||
use super::system::System;
|
||||
use crate::error::ProjectResult;
|
||||
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, VPath};
|
||||
use crate::pipeline::project::ConstReport;
|
||||
use crate::tree::{ModMember, ModMemberRef, TreeTransforms};
|
||||
|
||||
/// Equivalent of [crate::pipeline::project::ConstReport] for the interpreter's
|
||||
/// representation, [crate::interpreter::nort].
|
||||
pub struct NortConst {
|
||||
/// Comments associated with the constant which may affect its interpretation
|
||||
pub comments: Vec<Arc<String>>,
|
||||
/// Location of the definition, if known
|
||||
pub location: CodeLocation,
|
||||
/// Value assigned to the constant
|
||||
pub value: nort::Expr,
|
||||
}
|
||||
|
||||
/// 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,
|
||||
systems: impl IntoIterator<Item = &'b System<'a>> + 'b,
|
||||
) -> ProjectResult<impl IntoIterator<Item = (Sym, NortConst)> + 'static> {
|
||||
let mut out = HashMap::new();
|
||||
for (name, rep) in source {
|
||||
out.insert(name.clone(), NortConst {
|
||||
value: ir_to_nort(&ast_to_ir(rep.value, name)?),
|
||||
location: CodeLocation::Source(rep.range),
|
||||
comments: rep.comments,
|
||||
});
|
||||
}
|
||||
for sys in systems {
|
||||
let const_module = sys.constants.unwrap_mod_ref();
|
||||
const_module.search_all((), |path, node, ()| {
|
||||
let m = if let ModMemberRef::Mod(m) = node { m } else { return };
|
||||
for (key, ent) in &m.entries {
|
||||
if let ModMember::Item(c) = &ent.member {
|
||||
let path = VPath::new(path.unreverse()).as_prefix_of(key.clone());
|
||||
let location = CodeLocation::Gen(CodeGenInfo::details(
|
||||
"constant from",
|
||||
format!("system.name={}", sys.name),
|
||||
));
|
||||
let value = c.gen_nort(location.clone());
|
||||
let crep = NortConst { value, comments: vec![], location };
|
||||
out.insert(path.to_sym(), crep);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
@@ -1,12 +1,8 @@
|
||||
//! A simplified set of commands each grouping a large subset of the operations
|
||||
//! exposed by Orchid to make writing embeddings faster in the typical case.
|
||||
|
||||
mod environment;
|
||||
mod pre_macro;
|
||||
mod process;
|
||||
mod system;
|
||||
|
||||
pub use environment::{CompiledEnv, Environment};
|
||||
pub use pre_macro::{MacroTimeout, PreMacro};
|
||||
pub use process::Process;
|
||||
pub use system::{IntoSystem, MissingSystemCode, System};
|
||||
pub mod loader;
|
||||
pub mod macro_runner;
|
||||
pub mod process;
|
||||
pub mod system;
|
||||
pub mod merge_trees;
|
||||
|
||||
@@ -1,98 +1,80 @@
|
||||
use std::iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use never::Never;
|
||||
|
||||
use super::{Process, System};
|
||||
use crate::error::{ProjectError, ProjectResult};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::rule::Repo;
|
||||
use crate::{
|
||||
ast, ast_to_interpreted, collect_consts, collect_rules, rule, Interner,
|
||||
Location, ProjectTree, Sym,
|
||||
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, (ast::Expr<Sym>, Location)>,
|
||||
pub consts: HashMap<Sym, (ConstReport, CodeLocation)>,
|
||||
/// Libraries and plug-ins
|
||||
pub systems: Vec<System<'a>>,
|
||||
/// [Interner] pseudo-global
|
||||
pub i: &'a Interner,
|
||||
}
|
||||
impl<'a> PreMacro<'a> {
|
||||
/// Build a [PreMacro] from a source tree and system list
|
||||
pub fn new(
|
||||
tree: ProjectTree<Sym>,
|
||||
tree: &ProjectTree,
|
||||
systems: Vec<System<'a>>,
|
||||
i: &'a Interner,
|
||||
) -> ProjectResult<Self> {
|
||||
let consts = collect_consts(&tree, i);
|
||||
let rules = collect_rules(&tree);
|
||||
let repo = match rule::Repo::new(rules, i) {
|
||||
Ok(r) => r,
|
||||
Err((rule, error)) => {
|
||||
return Err(error.to_project_error(&rule));
|
||||
},
|
||||
};
|
||||
Ok(Self {
|
||||
repo,
|
||||
consts: (consts.into_iter())
|
||||
.map(|(name, expr)| {
|
||||
// Figure out the location of the constant
|
||||
let location = (name.split_last())
|
||||
.and_then(|(_, path)| {
|
||||
let origin = (tree.0)
|
||||
.walk_ref(&[], path, false)
|
||||
.unwrap_or_else(|_| panic!("path sourced from symbol names"));
|
||||
(origin.extra.file.as_ref()).cloned()
|
||||
})
|
||||
.map(|p| Location::File(Arc::new(p)))
|
||||
.unwrap_or(Location::Unknown);
|
||||
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(),
|
||||
i,
|
||||
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 build_process(
|
||||
pub fn run_macros(
|
||||
self,
|
||||
timeout: Option<usize>,
|
||||
) -> ProjectResult<Process<'a>> {
|
||||
let Self { i, systems, repo, consts } = self;
|
||||
let mut symbols = HashMap::new();
|
||||
for (name, (source, source_location)) in consts.iter() {
|
||||
let unmatched = if let Some(limit) = timeout {
|
||||
let (unmatched, steps_left) = repo.long_step(source, limit + 1);
|
||||
if steps_left == 0 {
|
||||
return Err(
|
||||
MacroTimeout {
|
||||
location: source_location.clone(),
|
||||
symbol: name.clone(),
|
||||
limit,
|
||||
}
|
||||
.rc(),
|
||||
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),
|
||||
);
|
||||
} else {
|
||||
unmatched
|
||||
symbols
|
||||
.insert(path.to_sym(), c.gen_nort(CodeLocation::Gen(cginfo)));
|
||||
}
|
||||
} else {
|
||||
repo.pass(source).unwrap_or_else(|| source.clone())
|
||||
};
|
||||
let runtree =
|
||||
ast_to_interpreted(&unmatched, name.clone()).map_err(|e| e.rc())?;
|
||||
symbols.insert(name.clone(), runtree);
|
||||
}
|
||||
Ok::<(), Never>(())
|
||||
});
|
||||
}
|
||||
Ok(Process {
|
||||
symbols,
|
||||
i,
|
||||
handlers: (systems.into_iter())
|
||||
.fold(HandlerTable::new(), |tbl, sys| tbl.combine(sys.handlers)),
|
||||
})
|
||||
@@ -100,8 +82,9 @@ impl<'a> PreMacro<'a> {
|
||||
|
||||
/// Obtain an iterator that steps through the preprocessing of a constant
|
||||
/// for debugging macros
|
||||
pub fn step(&self, sym: Sym) -> impl Iterator<Item = ast::Expr<Sym>> + '_ {
|
||||
let mut target = self.consts.get(&sym).expect("Target not found").0.clone();
|
||||
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())
|
||||
@@ -112,20 +95,17 @@ impl<'a> PreMacro<'a> {
|
||||
/// Error raised when a macro runs too long
|
||||
#[derive(Debug)]
|
||||
pub struct MacroTimeout {
|
||||
location: Location,
|
||||
location: CodeLocation,
|
||||
symbol: Sym,
|
||||
limit: usize,
|
||||
}
|
||||
impl ProjectError for MacroTimeout {
|
||||
fn description(&self) -> &str { "Macro execution has not halted" }
|
||||
const DESCRIPTION: &'static str = "Macro execution has not halted";
|
||||
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"Macro execution during the processing of {} took more than {} steps",
|
||||
self.symbol.extern_vec().join("::"),
|
||||
self.limit
|
||||
)
|
||||
let Self { symbol, limit, .. } = self;
|
||||
format!("Macro processing in {symbol} took more than {limit} steps")
|
||||
}
|
||||
|
||||
fn one_position(&self) -> Location { self.location.clone() }
|
||||
fn one_position(&self) -> CodeLocation { self.location.clone() }
|
||||
}
|
||||
|
||||
@@ -1,23 +1,32 @@
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::merge_trees::NortConst;
|
||||
use crate::error::{ErrorPosition, ProjectError, ProjectResult};
|
||||
use crate::interpreted::{self, ExprInst};
|
||||
#[allow(unused)] // for doc
|
||||
use crate::interpreter;
|
||||
use crate::interpreter::{
|
||||
run_handler, Context, HandlerTable, Return, RuntimeError,
|
||||
};
|
||||
use crate::{Interner, Location, Sym};
|
||||
use crate::interpreter::context::{Halt, RunContext};
|
||||
use crate::interpreter::error::RunError;
|
||||
use crate::interpreter::handler::{run_handler, HandlerTable};
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
use crate::utils::boxed_iter::BoxedIter;
|
||||
|
||||
/// 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, ExprInst>,
|
||||
pub(crate) symbols: HashMap<Sym, Expr>,
|
||||
pub(crate) handlers: HandlerTable<'a>,
|
||||
pub(crate) i: &'a Interner,
|
||||
}
|
||||
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 }
|
||||
}
|
||||
|
||||
/// Execute the given command in this process. If gas is specified, at most as
|
||||
/// many steps will be executed and then the partial result returned.
|
||||
///
|
||||
@@ -25,24 +34,23 @@ impl<'a> Process<'a> {
|
||||
/// yields
|
||||
pub fn run(
|
||||
&mut self,
|
||||
prompt: ExprInst,
|
||||
prompt: Expr,
|
||||
gas: Option<usize>,
|
||||
) -> Result<Return, RuntimeError> {
|
||||
let ctx = Context { gas, interner: self.i, symbols: &self.symbols };
|
||||
) -> Result<Halt, RunError> {
|
||||
let ctx = RunContext { gas, symbols: &self.symbols };
|
||||
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, Location)> {
|
||||
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: &ExprInst| {
|
||||
let expr = s.expr();
|
||||
if let interpreted::Clause::Constant(sym) = &expr.clause {
|
||||
sym.search_all(&mut |s: &Expr| {
|
||||
if let Clause::Constant(sym) = &*s.clause.cls() {
|
||||
if !self.symbols.contains_key(sym) {
|
||||
errors.push((sym.clone(), expr.location.clone()))
|
||||
errors.push((sym.clone(), s.location()))
|
||||
}
|
||||
}
|
||||
None::<()>
|
||||
@@ -51,8 +59,8 @@ impl<'a> Process<'a> {
|
||||
}
|
||||
|
||||
/// Assert that the code contains no invalid constants. This ensures that,
|
||||
/// unless [interpreted::Clause::Constant]s are created procedurally,
|
||||
/// a [interpreter::RuntimeError::MissingSymbol] cannot be produced
|
||||
/// 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() {
|
||||
@@ -66,45 +74,39 @@ impl<'a> Process<'a> {
|
||||
}
|
||||
match errors.is_empty() {
|
||||
true => Ok(()),
|
||||
false => Err(MissingSymbols { errors }.rc()),
|
||||
false => Err(MissingSymbols { errors }.pack()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MissingSymbol {
|
||||
struct MissingSymbol {
|
||||
referrer: Sym,
|
||||
location: Location,
|
||||
location: CodeLocation,
|
||||
symbol: Sym,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub struct MissingSymbols {
|
||||
struct MissingSymbols {
|
||||
errors: Vec<MissingSymbol>,
|
||||
}
|
||||
impl ProjectError for MissingSymbols {
|
||||
fn description(&self) -> &str {
|
||||
"A name not referring to a known symbol was found in the source after \
|
||||
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."
|
||||
}
|
||||
that macro execution didn't correctly halt.";
|
||||
|
||||
fn message(&self) -> String {
|
||||
format!(
|
||||
"The following symbols do not exist:\n{}",
|
||||
(self.errors.iter())
|
||||
.map(|e| format!(
|
||||
"{} referenced in {} ",
|
||||
e.symbol.extern_vec().join("::"),
|
||||
e.referrer.extern_vec().join("::")
|
||||
.map(|MissingSymbol { symbol, referrer, .. }| format!(
|
||||
"{symbol} referenced in {referrer}"
|
||||
))
|
||||
.join("\n")
|
||||
)
|
||||
}
|
||||
|
||||
fn positions(&self) -> crate::utils::BoxedIter<crate::error::ErrorPosition> {
|
||||
Box::new(
|
||||
(self.errors.clone().into_iter())
|
||||
.map(|i| ErrorPosition { location: i.location, message: None }),
|
||||
)
|
||||
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> {
|
||||
(self.errors.iter())
|
||||
.map(|i| ErrorPosition { location: i.location.clone(), message: None })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,59 +1,41 @@
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::error::{ErrorPosition, ProjectError};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::parse::{LexerPlugin, LineParser};
|
||||
use crate::pipeline::file_loader::{IOResult, Loaded};
|
||||
use crate::sourcefile::FileEntry;
|
||||
use crate::utils::boxed_iter::box_empty;
|
||||
use crate::utils::BoxedIter;
|
||||
use crate::{ConstTree, Interner, Tok, VName};
|
||||
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::virt_fs::DeclTree;
|
||||
|
||||
/// A description of every point where an external library can hook into Orchid.
|
||||
/// Intuitively, this can be thought of as a plugin
|
||||
pub struct System<'a> {
|
||||
/// An identifier for the system used eg. in error reporting.
|
||||
pub name: Vec<String>,
|
||||
pub name: &'a str,
|
||||
/// External functions and other constant values defined in AST form
|
||||
pub constants: HashMap<Tok<String>, ConstTree>,
|
||||
pub constants: ConstTree,
|
||||
/// Orchid libraries defined by this system
|
||||
pub code: HashMap<VName, Loaded>,
|
||||
/// Prelude lines to be added to **subsequent** systems and usercode to
|
||||
/// expose the functionality of this system. The prelude is not added during
|
||||
/// the loading of this system
|
||||
pub prelude: Vec<FileEntry>,
|
||||
pub code: DeclTree,
|
||||
/// Prelude lines to be added to the head of files to expose the
|
||||
/// functionality of this system. A glob import from the first path is
|
||||
/// added to every file outside the prefix specified by the second path
|
||||
pub prelude: Vec<Prelude>,
|
||||
/// Handlers for actions defined in this system
|
||||
pub handlers: HandlerTable<'a>,
|
||||
/// Custom lexer for the source code representation atomic data.
|
||||
/// These take priority over builtin lexers so the syntax they
|
||||
/// match should be unambiguous
|
||||
pub lexer_plugins: Vec<Box<dyn LexerPlugin>>,
|
||||
pub lexer_plugins: Vec<Box<dyn LexerPlugin + 'a>>,
|
||||
/// Parser that processes custom line types into their representation in the
|
||||
/// module tree
|
||||
pub line_parsers: Vec<Box<dyn LineParser>>,
|
||||
pub line_parsers: Vec<Box<dyn ParseLinePlugin>>,
|
||||
}
|
||||
impl<'a> System<'a> {
|
||||
/// Intern the name of the system so that it can be used as an Orchid
|
||||
/// namespace
|
||||
#[must_use]
|
||||
pub fn vname(&self, i: &Interner) -> VName {
|
||||
self.name.iter().map(|s| i.i(s)).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
/// Load a file from the system
|
||||
pub fn load_file(
|
||||
&self,
|
||||
path: &[Tok<String>],
|
||||
referrer: &[Tok<String>],
|
||||
) -> IOResult {
|
||||
(self.code.get(path)).cloned().ok_or_else(|| {
|
||||
let err = MissingSystemCode {
|
||||
path: path.to_vec(),
|
||||
system: self.name.clone(),
|
||||
referrer: referrer.to_vec(),
|
||||
};
|
||||
err.rc()
|
||||
})
|
||||
pub fn vname(&self) -> VName {
|
||||
VName::parse(self.name).expect("Systems must have a non-empty name")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,23 +48,22 @@ pub struct MissingSystemCode {
|
||||
referrer: VName,
|
||||
}
|
||||
impl ProjectError for MissingSystemCode {
|
||||
fn description(&self) -> &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",
|
||||
Interner::extern_all(&self.path).join("::"),
|
||||
Interner::extern_all(&self.referrer).join("::"),
|
||||
self.path,
|
||||
self.referrer,
|
||||
self.system.join("::")
|
||||
)
|
||||
}
|
||||
fn positions(&self) -> BoxedIter<ErrorPosition> { box_empty() }
|
||||
fn positions(&self) -> impl IntoIterator<Item = ErrorPosition> { [] }
|
||||
}
|
||||
|
||||
/// Trait for objects that can be converted into a [System] in the presence
|
||||
/// of an [Interner].
|
||||
pub trait IntoSystem<'a> {
|
||||
/// Convert this object into a system using an interner
|
||||
fn into_system(self, i: &Interner) -> System<'a>;
|
||||
fn into_system(self) -> System<'a>;
|
||||
}
|
||||
|
||||
@@ -1,45 +1,79 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use never::Never;
|
||||
|
||||
use super::XfnResult;
|
||||
use crate::ddispatch::request;
|
||||
use crate::error::AssertionError;
|
||||
use crate::interpreted::{ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{Context, RuntimeError};
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
use crate::{ast, NameLike};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::context::RunContext;
|
||||
use crate::interpreter::error::RunError;
|
||||
use crate::interpreter::nort;
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::name::NameLike;
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::ddispatch::{request, Request, Responder};
|
||||
|
||||
/// Information returned by [Atomic::run]. This mirrors
|
||||
/// [crate::interpreter::Return] but with a clause instead of an Expr.
|
||||
pub struct AtomicReturn {
|
||||
/// The next form of the expression
|
||||
pub clause: Clause,
|
||||
pub clause: nort::Clause,
|
||||
/// Remaining gas
|
||||
pub gas: Option<usize>,
|
||||
/// Whether further normalization is possible by repeated calls to
|
||||
/// [Atomic::run]
|
||||
pub inert: bool,
|
||||
}
|
||||
impl AtomicReturn {
|
||||
/// Report indicating that the value is inert
|
||||
pub fn inert<T: Atomic, E>(this: T, ctx: RunContext) -> Result<Self, E> {
|
||||
Ok(Self { clause: this.atom_cls(), gas: ctx.gas, inert: true })
|
||||
}
|
||||
/// Report indicating that the value has been processed
|
||||
pub fn run<E>(clause: nort::Clause, run: RunData) -> Result<Self, E> {
|
||||
Ok(Self { clause, gas: run.ctx.gas, inert: false })
|
||||
}
|
||||
}
|
||||
|
||||
/// Returned by [Atomic::run]
|
||||
pub type AtomicResult = Result<AtomicReturn, RuntimeError>;
|
||||
pub type AtomicResult = Result<AtomicReturn, RunError>;
|
||||
|
||||
/// Trait for things that are _definitely_ equal.
|
||||
pub trait StrictEq {
|
||||
/// must return true if the objects were produced via the exact same sequence
|
||||
/// of transformations, including any relevant context data. Must return false
|
||||
/// if the objects are of different type, or if their type is [PartialEq]
|
||||
/// and [PartialEq::eq] returns false.
|
||||
fn strict_eq(&self, other: &dyn Any) -> bool;
|
||||
/// 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 {
|
||||
write!(f, "{:?} is not a function", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Functionality the interpreter needs to handle a value
|
||||
pub trait Atomic: Any + Debug + DynClone + StrictEq + Responder + Send
|
||||
where
|
||||
Self: 'static,
|
||||
///
|
||||
/// # Lifecycle methods
|
||||
///
|
||||
/// Atomics expose the methods [Atomic::redirect], [Atomic::run],
|
||||
/// [Atomic::apply] and [Atomic::apply_ref] 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
|
||||
/// upon whether the atom is referenced elsewhere. `apply` falls back to
|
||||
/// `apply_ref` so implementing it is considered an optimization to avoid
|
||||
/// excessive copying.
|
||||
///
|
||||
/// Atoms don't generally have to be copyable because clauses are refcounted in
|
||||
/// the interpreter, but Orchid code is always free to duplicate the references
|
||||
/// 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
|
||||
where Self: 'static
|
||||
{
|
||||
/// Casts this value to [Any] so that its original value can be salvaged
|
||||
/// during introspection by other external code.
|
||||
@@ -55,45 +89,90 @@ where
|
||||
#[must_use]
|
||||
fn as_any_ref(&self) -> &dyn Any;
|
||||
|
||||
/// Returns a reference to a possible expression held inside the atom which
|
||||
/// can be reduced. For an overview of the lifecycle see [Atomic]
|
||||
fn redirect(&mut self) -> Option<&mut nort::ClauseInst>;
|
||||
|
||||
/// Attempt to normalize this value. If it wraps a value, this should report
|
||||
/// inert. If it wraps a computation, it should execute one logical step of
|
||||
/// the computation and return a structure representing the ntext.
|
||||
fn run(self: Box<Self>, ctx: Context) -> AtomicResult;
|
||||
/// the computation and return a structure representing the next.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
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.
|
||||
///
|
||||
/// For an overview of the lifecycle see [Atomic]
|
||||
fn apply(self: Box<Self>, call: CallData) -> ExternResult<nort::Clause> {
|
||||
self.apply_ref(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>;
|
||||
|
||||
/// 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 }
|
||||
|
||||
/// Wrap the atom in a clause to be placed in an [AtomicResult].
|
||||
#[must_use]
|
||||
fn atom_cls(self) -> Clause
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Clause::Atom(Atom(Box::new(self)))
|
||||
fn atom_cls(self) -> nort::Clause
|
||||
where Self: Sized {
|
||||
nort::Clause::Atom(Atom(Box::new(self)))
|
||||
}
|
||||
|
||||
/// Shorthand for `self.atom_cls().to_inst()`
|
||||
fn atom_clsi(self) -> nort::ClauseInst
|
||||
where Self: Sized {
|
||||
self.atom_cls().to_inst()
|
||||
}
|
||||
|
||||
/// Wrap the atom in a new expression instance to be placed in a tree
|
||||
#[must_use]
|
||||
fn atom_exi(self) -> ExprInst
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.atom_cls().wrap()
|
||||
fn atom_expr(self, location: CodeLocation) -> nort::Expr
|
||||
where Self: Sized {
|
||||
self.atom_clsi().to_expr(location)
|
||||
}
|
||||
|
||||
/// Wrap the atom in a clause to be placed in a [sourcefile::FileEntry].
|
||||
#[must_use]
|
||||
fn ast_cls<N: NameLike>(self) -> ast::Clause<N>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ast::Clause::Atom(Atom::new(self))
|
||||
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].
|
||||
#[must_use]
|
||||
fn ast_exp<N: NameLike>(self) -> ast::Expr<N>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.ast_cls().into_expr()
|
||||
fn ast_exp<N: NameLike>(self, range: SourceRange) -> parsed::Expr
|
||||
where Self: Sized + Clone {
|
||||
self.ast_cls().into_expr(range)
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct for generating any number of [Atom]s. Since atoms aren't Clone,
|
||||
/// this represents the ability to create any number of instances of an atom
|
||||
#[derive(Clone)]
|
||||
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))
|
||||
}
|
||||
/// Clone a representative atom when called
|
||||
pub fn cloner(atom: impl Atomic + Clone) -> Self {
|
||||
let lock = Mutex::new(atom);
|
||||
Self::new(move || Atom::new(lock.lock().unwrap().clone()))
|
||||
}
|
||||
/// 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 {
|
||||
f.debug_struct("AtomGenerator").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +202,7 @@ impl Atom {
|
||||
*self.0.as_any().downcast().expect("Type mismatch on Atom::cast")
|
||||
}
|
||||
/// Normalize the contained data
|
||||
pub fn run(self, ctx: Context) -> AtomicResult { self.0.run(ctx) }
|
||||
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
|
||||
@@ -134,10 +213,18 @@ impl Atom {
|
||||
false => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Atom {
|
||||
fn clone(&self) -> Self { Self(dyn_clone::clone_box(self.data())) }
|
||||
/// Downcast an atom by reference
|
||||
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)
|
||||
}
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Atom {
|
||||
@@ -146,12 +233,15 @@ impl Debug for Atom {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFromExprInst for Atom {
|
||||
fn from_exi(exi: ExprInst) -> XfnResult<Self> {
|
||||
let loc = exi.location();
|
||||
match exi.expr_val().clause {
|
||||
Clause::Atom(a) => Ok(a),
|
||||
_ => AssertionError::fail(loc, "atom"),
|
||||
}
|
||||
impl Responder for Never {
|
||||
fn respond(&self, _request: Request) { match *self {} }
|
||||
}
|
||||
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 redirect(&mut self) -> Option<&mut nort::ClauseInst> { match *self {} }
|
||||
fn run(self: Box<Self>, _: RunData) -> AtomicResult { match *self {} }
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<nort::Clause> {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,99 +4,97 @@ use std::fmt::Debug;
|
||||
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::{Atomic, ExternFn, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::{Context, HandlerRes};
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn, NotAFunction};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::utils::ddispatch::{Request, Responder};
|
||||
use crate::utils::pure_seq::pushed_ref;
|
||||
use crate::ConstTree;
|
||||
|
||||
trait_set! {
|
||||
/// A "well behaved" type that can be used as payload in a CPS box
|
||||
pub trait CPSPayload = Clone + Debug + Send + 'static;
|
||||
/// A function to handle a CPS box with a specific payload
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &ExprInst) -> HandlerRes;
|
||||
pub trait CPSHandler<T: CPSPayload> = FnMut(&T, &Expr) -> ExternResult<Expr>;
|
||||
}
|
||||
|
||||
/// The pre-argument version of CPSBox
|
||||
#[derive(Debug, Clone)]
|
||||
struct CPSFn<T: CPSPayload> {
|
||||
pub argc: usize,
|
||||
pub continuations: Vec<ExprInst>,
|
||||
pub payload: T,
|
||||
}
|
||||
impl<T: CPSPayload> CPSFn<T> {
|
||||
#[must_use]
|
||||
fn new(argc: usize, payload: T) -> Self {
|
||||
debug_assert!(
|
||||
argc > 0,
|
||||
"Null-ary CPS functions are invalid, use an Atom instead"
|
||||
);
|
||||
Self { argc, continuations: Vec::new(), payload }
|
||||
}
|
||||
}
|
||||
impl<T: CPSPayload> ExternFn for CPSFn<T> {
|
||||
fn name(&self) -> &str { "CPS function without argument" }
|
||||
fn apply(self: Box<Self>, arg: ExprInst, _ctx: Context) -> XfnResult<Clause> {
|
||||
let payload = self.payload.clone();
|
||||
let continuations = pushed_ref(&self.continuations, arg);
|
||||
if self.argc == 1 {
|
||||
Ok(CPSBox { payload, continuations }.atom_cls())
|
||||
} else {
|
||||
Ok(CPSFn { argc: self.argc - 1, payload, continuations }.xfn_cls())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An inert Orchid Atom value encapsulating a payload and a continuation point
|
||||
/// An Orchid Atom value encapsulating a payload and continuation points
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CPSBox<T: CPSPayload> {
|
||||
/// Number of arguments not provided yet
|
||||
pub argc: usize,
|
||||
/// Details about the command
|
||||
pub payload: T,
|
||||
/// Possible continuations, in the order they were provided
|
||||
pub continuations: Vec<ExprInst>,
|
||||
pub continuations: Vec<Expr>,
|
||||
}
|
||||
impl<T: CPSPayload> CPSBox<T> {
|
||||
/// Create a new command prepared to receive exacly N continuations
|
||||
#[must_use]
|
||||
pub fn new(argc: usize, payload: T) -> Self {
|
||||
debug_assert!(argc > 0, "Null-ary CPS functions are invalid");
|
||||
Self { argc, continuations: Vec::new(), payload }
|
||||
}
|
||||
/// Unpack the wrapped command and the continuation
|
||||
#[must_use]
|
||||
pub fn unpack1(self) -> (T, ExprInst) {
|
||||
let [cont]: [ExprInst; 1] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, cont)
|
||||
pub fn unpack1(&self) -> (&T, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[cont] => (&self.payload, cont.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
/// Unpack the wrapped command and 2 continuations (usually an async and a
|
||||
/// sync)
|
||||
#[must_use]
|
||||
pub fn unpack2(self) -> (T, ExprInst, ExprInst) {
|
||||
let [c1, c2]: [ExprInst; 2] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, c1, c2)
|
||||
pub fn unpack2(&self) -> (&T, Expr, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[c1, c2] => (&self.payload, c1.clone(), c2.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
/// Unpack the wrapped command and 3 continuations (usually an async success,
|
||||
/// an async fail and a sync)
|
||||
#[must_use]
|
||||
pub fn unpack3(self) -> (T, ExprInst, ExprInst, ExprInst) {
|
||||
let [c1, c2, c3]: [ExprInst; 3] =
|
||||
self.continuations.try_into().expect("size checked");
|
||||
(self.payload, c1, c2, c3)
|
||||
pub fn unpack3(&self) -> (&T, Expr, Expr, Expr) {
|
||||
match &self.continuations[..] {
|
||||
[c1, c2, c3] => (&self.payload, c1.clone(), c2.clone(), c3.clone()),
|
||||
_ => panic!("size mismatch"),
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_applicable(&self, err_loc: &CodeLocation) -> ExternResult<()> {
|
||||
match self.argc {
|
||||
0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).rc()),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: CPSPayload> InertAtomic for CPSBox<T> {
|
||||
fn type_str() -> &'static str { "a CPS box" }
|
||||
impl<T: CPSPayload> Responder for CPSBox<T> {
|
||||
fn respond(&self, _request: Request) {}
|
||||
}
|
||||
|
||||
/// Like [init_cps] but wrapped in a [ConstTree] for init-time usage
|
||||
#[must_use]
|
||||
pub fn const_cps<T: CPSPayload>(argc: usize, payload: T) -> ConstTree {
|
||||
ConstTree::xfn(CPSFn::new(argc, payload))
|
||||
}
|
||||
|
||||
/// Construct a CPS function which takes an argument and then acts inert
|
||||
/// so that command executors can receive it.
|
||||
///
|
||||
/// This function is meant to be used in an external function defined with
|
||||
/// [crate::define_fn]. For usage in a [ConstTree], see [mk_const]
|
||||
#[must_use]
|
||||
pub fn init_cps<T: CPSPayload>(argc: usize, payload: T) -> Clause {
|
||||
CPSFn::new(argc, payload).xfn_cls()
|
||||
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 redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, run.ctx)
|
||||
}
|
||||
fn apply(mut self: Box<Self>, call: CallData) -> ExternResult<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> {
|
||||
self.assert_applicable(&call.location)?;
|
||||
let new = Self {
|
||||
argc: self.argc - 1,
|
||||
continuations: pushed_ref(&self.continuations, call.arg),
|
||||
payload: self.payload.clone(),
|
||||
};
|
||||
Ok(new.atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
68
src/foreign/error.rs
Normal file
68
src/foreign/error.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Errors produced by external code
|
||||
pub trait ExternError: Display + Send + Sync + DynClone {
|
||||
/// Convert into trait object
|
||||
#[must_use]
|
||||
fn rc(self) -> Arc<dyn ExternError>
|
||||
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 Error for dyn ExternError {}
|
||||
|
||||
/// An error produced by Rust code called form Orchid. The error is type-erased.
|
||||
pub type ExternResult<T> = Result<T, Arc<dyn ExternError>>;
|
||||
|
||||
/// Some expectation (usually about the argument types of a function) did not
|
||||
/// hold.
|
||||
#[derive(Clone)]
|
||||
pub struct AssertionError {
|
||||
location: CodeLocation,
|
||||
message: &'static str,
|
||||
details: String,
|
||||
}
|
||||
|
||||
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> {
|
||||
Err(Self::ext(location, message, details))
|
||||
}
|
||||
|
||||
/// Construct and upcast to [ExternError]
|
||||
pub fn ext(
|
||||
location: CodeLocation,
|
||||
message: &'static str,
|
||||
details: String,
|
||||
) -> Arc<dyn ExternError> {
|
||||
Self { location, message, details }.rc()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for AssertionError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Error: expected {}", self.message)?;
|
||||
write!(f, " at {}", self.location)?;
|
||||
write!(f, " details: {}", self.details)
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternError for AssertionError {}
|
||||
@@ -1,94 +0,0 @@
|
||||
use std::error::Error;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use dyn_clone::{clone_box, DynClone};
|
||||
|
||||
use super::XfnResult;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::interpreter::Context;
|
||||
use crate::representations::interpreted::Clause;
|
||||
use crate::{ast, NameLike};
|
||||
|
||||
/// Errors produced by external code
|
||||
pub trait ExternError: Display + Send + Sync + DynClone {
|
||||
/// Convert into trait object
|
||||
#[must_use]
|
||||
fn into_extern(self) -> Arc<dyn ExternError>
|
||||
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, "{self}")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for dyn ExternError {}
|
||||
|
||||
/// Represents an externally defined function from the perspective of
|
||||
/// the executor. Since Orchid lacks basic numerical operations,
|
||||
/// these are also external functions.
|
||||
pub trait ExternFn: DynClone + Send {
|
||||
/// Display name of the function
|
||||
#[must_use]
|
||||
fn name(&self) -> &str;
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
fn apply(self: Box<Self>, arg: ExprInst, ctx: Context) -> XfnResult<Clause>;
|
||||
/// Hash the name to get a somewhat unique hash.
|
||||
fn hash(&self, mut state: &mut dyn std::hash::Hasher) {
|
||||
self.name().hash(&mut state)
|
||||
}
|
||||
/// Wrap this function in a clause to be placed in an [AtomicResult].
|
||||
#[must_use]
|
||||
fn xfn_cls(self) -> Clause
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
Clause::ExternFn(ExFn(Box::new(self)))
|
||||
}
|
||||
/// Wrap this function in a clause to be placed in a [FileEntry].
|
||||
#[must_use]
|
||||
fn xfn_ast_cls<N: NameLike>(self) -> ast::Clause<N>
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
ast::Clause::ExternFn(ExFn(Box::new(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for dyn ExternFn {}
|
||||
impl PartialEq for dyn ExternFn {
|
||||
fn eq(&self, other: &Self) -> bool { self.name() == other.name() }
|
||||
}
|
||||
impl Hash for dyn ExternFn {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name().hash(state)
|
||||
}
|
||||
}
|
||||
impl Debug for dyn ExternFn {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "##EXTERN[{}]##", self.name())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a black box function that can be applied to a [Clause] to produce
|
||||
/// a new [Clause], typically an [Atom] representing external work, a new [ExFn]
|
||||
/// to take additional arguments, or an Orchid tree to return control to the
|
||||
/// interpreter
|
||||
#[derive(Debug)]
|
||||
pub struct ExFn(pub Box<dyn ExternFn + 'static>);
|
||||
impl ExFn {
|
||||
/// Combine the function with an argument to produce a new clause
|
||||
pub fn apply(self, arg: ExprInst, ctx: Context) -> XfnResult<Clause> {
|
||||
self.0.apply(arg, ctx)
|
||||
}
|
||||
}
|
||||
impl Clone for ExFn {
|
||||
fn clone(&self) -> Self { Self(clone_box(self.0.as_ref())) }
|
||||
}
|
||||
@@ -1,47 +1,16 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::fmt::Debug;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{
|
||||
Atomic, AtomicResult, AtomicReturn, ExternError, ExternFn, XfnResult,
|
||||
};
|
||||
use crate::ddispatch::Responder;
|
||||
use crate::interpreted::{Clause, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::{run, Context, Return};
|
||||
use crate::systems::codegen::{opt, res};
|
||||
use crate::OrcString;
|
||||
|
||||
/// A trait for things that are infallibly convertible to [Clause]. These types
|
||||
/// can be returned by callbacks passed to the [super::xfn_1ary] family of
|
||||
/// functions.
|
||||
pub trait ToClause: Clone {
|
||||
/// Convert the type to a [Clause].
|
||||
fn to_clause(self) -> Clause;
|
||||
/// Convert to an expression instance via [ToClause].
|
||||
fn to_exi(self) -> ExprInst { self.to_clause().wrap() }
|
||||
}
|
||||
|
||||
impl<T: Atomic + Clone> ToClause for T {
|
||||
fn to_clause(self) -> Clause { self.atom_cls() }
|
||||
}
|
||||
impl ToClause for Clause {
|
||||
fn to_clause(self) -> Clause { self }
|
||||
}
|
||||
impl ToClause for ExprInst {
|
||||
fn to_clause(self) -> Clause { self.expr_val().clause }
|
||||
}
|
||||
impl ToClause for String {
|
||||
fn to_clause(self) -> Clause { OrcString::from(self).atom_cls() }
|
||||
}
|
||||
impl<T: ToClause> ToClause for Option<T> {
|
||||
fn to_clause(self) -> Clause { opt(self.map(|t| t.to_clause().wrap())) }
|
||||
}
|
||||
impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
|
||||
fn to_clause(self) -> Clause {
|
||||
res(self.map(|t| t.to_clause().wrap()).map_err(|u| u.to_clause().wrap()))
|
||||
}
|
||||
}
|
||||
use super::atom::{Atomic, AtomicResult, AtomicReturn};
|
||||
use super::error::ExternResult;
|
||||
use super::to_clause::ToClause;
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::context::Halt;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::{run, RunData};
|
||||
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]
|
||||
@@ -51,6 +20,11 @@ impl<T: ToClause, U: ToClause> ToClause for Result<T, U> {
|
||||
/// 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
|
||||
/// 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.
|
||||
pub struct Param<T, U, F> {
|
||||
data: F,
|
||||
_t: PhantomData<T>,
|
||||
@@ -60,9 +34,7 @@ unsafe impl<T, U, F: Send> Send for Param<T, U, F> {}
|
||||
impl<T, U, F> Param<T, U, F> {
|
||||
/// Wrap a new function in a parametric struct
|
||||
pub fn new(f: F) -> Self
|
||||
where
|
||||
F: FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
{
|
||||
where F: FnOnce(T) -> U {
|
||||
Self { data: f, _t: PhantomData, _u: PhantomData }
|
||||
}
|
||||
/// Take out the function
|
||||
@@ -74,75 +46,91 @@ impl<T, U, F: Clone> Clone for Param<T, U, F> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
> ToClause for Param<T, U, F>
|
||||
{
|
||||
fn to_clause(self) -> Clause { self.xfn_cls() }
|
||||
/// A marker struct that gets assigned an expression without normalizing it.
|
||||
/// This behaviour cannot be replicated in usercode, it's implemented with an
|
||||
/// explicit runtime [TypeId] check invoked by [Param].
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Thunk(pub Expr);
|
||||
impl TryFromExpr for Thunk {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(Thunk(expr)) }
|
||||
}
|
||||
|
||||
struct FnMiddleStage<T, U, F> {
|
||||
argument: ExprInst,
|
||||
arg: Expr,
|
||||
f: Param<T, U, F>,
|
||||
}
|
||||
impl<T, U, F> StrictEq for FnMiddleStage<T, U, F> {
|
||||
fn strict_eq(&self, _other: &dyn std::any::Any) -> bool {
|
||||
unimplemented!("This should never be able to appear in a pattern")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, F: Clone> Clone for FnMiddleStage<T, U, F> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { argument: self.argument.clone(), f: self.f.clone() }
|
||||
}
|
||||
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 {
|
||||
f.debug_struct("FnMiddleStage")
|
||||
.field("argument", &self.argument)
|
||||
.field("argument", &self.arg)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
impl<T, U, F> Responder for FnMiddleStage<T, U, F> {}
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
T: 'static + TryFromExpr,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + FnOnce(T) -> Result<U, Arc<dyn ExternError>> + Send,
|
||||
F: 'static + Clone + FnOnce(T) -> U + Any + Send,
|
||||
> Atomic for FnMiddleStage<T, U, F>
|
||||
{
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn run(self: Box<Self>, ctx: Context) -> AtomicResult {
|
||||
let Return { gas, inert, state } = run(self.argument, ctx)?;
|
||||
let clause = match inert {
|
||||
false => state.expr_val().clause,
|
||||
true => (self.f.data)(state.downcast()?)?.to_clause(),
|
||||
};
|
||||
Ok(AtomicReturn { gas, inert: false, clause })
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> {
|
||||
// this should be ctfe'd
|
||||
(TypeId::of::<T>() != TypeId::of::<Thunk>()).then(|| &mut self.arg.clause)
|
||||
}
|
||||
fn run(self: Box<Self>, r: RunData) -> AtomicResult {
|
||||
let Self { arg, f: Param { data: f, .. } } = *self;
|
||||
let clause = f(arg.downcast()?).to_clause(r.location);
|
||||
Ok(AtomicReturn { gas: r.ctx.gas, inert: false, clause })
|
||||
}
|
||||
fn apply_ref(&self, _: CallData) -> ExternResult<Clause> {
|
||||
panic!("Atom should have decayed")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U, F> Responder for Param<T, U, F> {}
|
||||
impl<T, U, F> Debug for Param<T, U, F> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Param")
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
T: 'static + TryFromExprInst,
|
||||
T: 'static + TryFromExpr + Clone,
|
||||
U: 'static + ToClause,
|
||||
F: 'static + Clone + Send + FnOnce(T) -> Result<U, Arc<dyn ExternError>>,
|
||||
> ExternFn for Param<T, U, F>
|
||||
F: 'static + Clone + Send + FnOnce(T) -> U,
|
||||
> Atomic for Param<T, U, F>
|
||||
{
|
||||
fn name(&self) -> &str { "anonymous Rust function" }
|
||||
fn apply(self: Box<Self>, arg: ExprInst, _: Context) -> XfnResult<Clause> {
|
||||
Ok(FnMiddleStage { argument: arg, f: *self }.atom_cls())
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, r: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, r.ctx)
|
||||
}
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls())
|
||||
}
|
||||
fn apply(self: Box<Self>, call: CallData) -> ExternResult<Clause> {
|
||||
Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls())
|
||||
}
|
||||
}
|
||||
|
||||
/// Conversion functions from [Fn] traits into [Atomic]. Since Rust's type
|
||||
/// system allows overloaded [Fn] implementations, we must specify the arity and
|
||||
/// argument types for this process. Arities are only defined up to 9, but the
|
||||
/// function can always return another call to `xfn_`N`ary` to consume more
|
||||
/// arguments.
|
||||
pub mod constructors {
|
||||
|
||||
|
||||
use std::sync::Arc;
|
||||
use super::super::atom::Atomic;
|
||||
use super::super::try_from_expr::TryFromExpr;
|
||||
#[allow(unused)] // for doc
|
||||
use super::Thunk;
|
||||
use super::{Param, ToClause};
|
||||
use crate::foreign::{ExternError, ExternFn};
|
||||
use crate::interpreted::TryFromExprInst;
|
||||
|
||||
macro_rules! xfn_variant {
|
||||
(
|
||||
@@ -154,18 +142,21 @@ pub mod constructors {
|
||||
#[doc = "Convert a function of " $number " argument(s) into a curried"
|
||||
" Orchid function. See also Constraints summarized:\n\n"
|
||||
"- the callback must live as long as `'static`\n"
|
||||
"- All arguments must implement [TryFromExprInst]\n"
|
||||
"- All arguments must implement [TryFromExpr]\n"
|
||||
"- all but the last argument must implement [Clone] and [Send]\n"
|
||||
"- the return type must implement [ToClause].\n\n"
|
||||
]
|
||||
#[doc = "Take [Lazy] to take the argument as-is,\n"
|
||||
"without normalization\n\n"
|
||||
]
|
||||
#[doc = "Other arities: " $( "[xfn_" $alt "ary], " )+ ]
|
||||
pub fn [< xfn_ $number ary >] <
|
||||
$( $t : TryFromExprInst + Clone + Send + 'static, )*
|
||||
TLast: TryFromExprInst + 'static,
|
||||
$( $t : TryFromExpr + Clone + Send + 'static, )*
|
||||
TLast: TryFromExpr + Clone + 'static,
|
||||
TReturn: ToClause + Send + 'static,
|
||||
TFunction: FnOnce( $( $t , )* TLast )
|
||||
-> Result<TReturn, Arc<dyn ExternError>> + Clone + Send + 'static
|
||||
>(function: TFunction) -> impl ExternFn {
|
||||
-> TReturn + Clone + Send + 'static
|
||||
>(function: TFunction) -> impl Atomic + Clone {
|
||||
xfn_variant!(@BODY_LOOP function
|
||||
( $( ( $t [< $t:lower >] ) )* )
|
||||
( $( [< $t:lower >] )* )
|
||||
@@ -178,7 +169,7 @@ pub mod constructors {
|
||||
$( ( $T:ident $t:ident ) )*
|
||||
) $full:tt) => {
|
||||
Param::new(|$next : $Next| {
|
||||
Ok(xfn_variant!(@BODY_LOOP $function ( $( ( $T $t ) )* ) $full))
|
||||
xfn_variant!(@BODY_LOOP $function ( $( ( $T $t ) )* ) $full)
|
||||
})
|
||||
};
|
||||
(@BODY_LOOP $function:ident () ( $( $t:ident )* )) => {
|
||||
|
||||
172
src/foreign/implementations.rs
Normal file
172
src/foreign/implementations.rs
Normal file
@@ -0,0 +1,172 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::atom::{Atom, Atomic, AtomicResult};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use super::process::Unstable;
|
||||
use super::to_clause::ToClause;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::error::RunError;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst};
|
||||
use crate::interpreter::run::RunData;
|
||||
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(Arc<dyn ExternError>);
|
||||
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 ClauseInst> { 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));
|
||||
}
|
||||
@@ -1,28 +1,30 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::atom::StrictEq;
|
||||
use super::{AtomicResult, AtomicReturn, XfnResult};
|
||||
use crate::error::AssertionError;
|
||||
#[allow(unused)] // for doc
|
||||
// use crate::define_fn;
|
||||
use crate::foreign::Atomic;
|
||||
use crate::interpreted::{Clause, Expr, ExprInst, TryFromExprInst};
|
||||
use crate::interpreter::Context;
|
||||
use crate::systems::stl::Numeric;
|
||||
use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, NotAFunction};
|
||||
use super::error::{ExternError, ExternResult};
|
||||
use super::try_from_expr::TryFromExpr;
|
||||
use crate::foreign::error::AssertionError;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::libs::std::number::Numeric;
|
||||
use crate::libs::std::string::OrcString;
|
||||
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 [TryFromExprInst] so that a conversion doesn't have to be
|
||||
/// implements [TryFromExpr] so that a conversion doesn't have to be
|
||||
/// provided in argument lists.
|
||||
pub trait InertAtomic: Debug + Clone + Send + 'static {
|
||||
pub trait InertPayload: Debug + Clone + Send + 'static {
|
||||
/// Typename to be shown in the error when a conversion from [ExprInst] fails
|
||||
#[must_use]
|
||||
fn type_str() -> &'static str;
|
||||
///
|
||||
/// This will default to `type_name::<Self>()` when it becomes stable
|
||||
const TYPE_STR: &'static str;
|
||||
/// Proxies to [Responder] so that you don't have to implmeent it manually if
|
||||
/// you need it, but behaves exactly as the default implementation.
|
||||
#[allow(unused_mut, unused_variables)] // definition should show likely usage
|
||||
@@ -37,59 +39,99 @@ pub trait InertAtomic: Debug + Clone + Send + 'static {
|
||||
/// ```
|
||||
fn strict_eq(&self, _: &Self) -> bool { false }
|
||||
}
|
||||
impl<T: InertAtomic> StrictEq for T {
|
||||
fn strict_eq(&self, other: &dyn Any) -> bool {
|
||||
other.downcast_ref().map_or(false, |other| self.strict_eq(other))
|
||||
}
|
||||
|
||||
/// An atom that stores a value and rejects all interpreter interactions. It is
|
||||
/// used to reference foreign data in Orchid.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Inert<T: InertPayload>(pub T);
|
||||
impl<T: InertPayload> Inert<T> {
|
||||
/// Wrap the argument in a type-erased [Atom] for embedding in Orchid
|
||||
/// structures.
|
||||
pub fn atom(t: T) -> Atom { Atom::new(Inert(t)) }
|
||||
}
|
||||
impl<T: InertAtomic> Responder for T {
|
||||
|
||||
impl<T: InertPayload> Deref for Inert<T> {
|
||||
type Target = T;
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl<T: InertPayload> DerefMut for Inert<T> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
|
||||
}
|
||||
|
||||
impl<T: InertPayload> Responder for Inert<T> {
|
||||
fn respond(&self, mut request: Request) {
|
||||
if request.can_serve::<T>() {
|
||||
request.serve(self.clone())
|
||||
request.serve(self.0.clone())
|
||||
} else {
|
||||
self.respond(request)
|
||||
self.0.respond(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<T: InertAtomic> Atomic for T {
|
||||
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 run(self: Box<Self>, ctx: Context) -> AtomicResult {
|
||||
Ok(AtomicReturn { gas: ctx.gas, inert: true, clause: self.atom_cls() })
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, run.ctx)
|
||||
}
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: InertAtomic> TryFromExprInst for T {
|
||||
fn from_exi(exi: ExprInst) -> XfnResult<Self> {
|
||||
let Expr { clause, location } = exi.expr_val();
|
||||
match clause {
|
||||
Clause::Atom(a) => match a.0.as_any().downcast() {
|
||||
Ok(t) => Ok(*t),
|
||||
Err(_) => AssertionError::fail(location, Self::type_str()),
|
||||
impl<T: InertPayload> TryFromExpr for Inert<T> {
|
||||
fn from_expr(expr: Expr) -> ExternResult<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}"))
|
||||
}),
|
||||
cls => AssertionError::fail(location, "atom", format!("{cls}")),
|
||||
},
|
||||
_ => AssertionError::fail(location, "atom"),
|
||||
Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InertAtomic for bool {
|
||||
fn type_str() -> &'static str { "bool" }
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
impl<T: InertPayload + Display> Display for Inert<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl InertAtomic for usize {
|
||||
fn type_str() -> &'static str { "usize" }
|
||||
impl InertPayload for bool {
|
||||
const TYPE_STR: &'static str = "bool";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Uint(*self))
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl InertAtomic for NotNan<f64> {
|
||||
fn type_str() -> &'static str { "NotNan<f64>" }
|
||||
impl InertPayload for usize {
|
||||
const TYPE_STR: &'static str = "usize";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Float(*self))
|
||||
request.serve(Numeric::Uint(*self));
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl InertPayload for NotNan<f64> {
|
||||
const TYPE_STR: &'static str = "NotNan<f64>";
|
||||
fn strict_eq(&self, other: &Self) -> bool { self == other }
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve(Numeric::Float(*self));
|
||||
request.serve_with(|| OrcString::from(self.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,12 @@
|
||||
//!
|
||||
//! Structures and traits used in the exposure of external functions and values
|
||||
//! to Orchid code
|
||||
mod atom;
|
||||
pub mod atom;
|
||||
pub mod cps_box;
|
||||
mod extern_fn;
|
||||
mod fn_bridge;
|
||||
mod inert;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use atom::{Atom, Atomic, AtomicResult, AtomicReturn, StrictEq};
|
||||
pub use extern_fn::{ExFn, ExternError, ExternFn};
|
||||
pub use fn_bridge::constructors::{
|
||||
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary, xfn_5ary, xfn_6ary, xfn_7ary,
|
||||
xfn_8ary, xfn_9ary,
|
||||
};
|
||||
pub use fn_bridge::{Param, ToClause};
|
||||
pub use inert::InertAtomic;
|
||||
|
||||
pub use crate::representations::interpreted::Clause;
|
||||
|
||||
/// Return type of the argument to the [xfn_1ary] family of functions
|
||||
pub type XfnResult<T> = Result<T, Arc<dyn ExternError>>;
|
||||
pub mod error;
|
||||
pub mod fn_bridge;
|
||||
pub mod implementations;
|
||||
pub mod inert;
|
||||
pub mod process;
|
||||
pub mod to_clause;
|
||||
pub mod try_from_expr;
|
||||
|
||||
39
src/foreign/process.rs
Normal file
39
src/foreign/process.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::atom::{Atomic, AtomicReturn};
|
||||
use super::error::ExternResult;
|
||||
use super::to_clause::ToClause;
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
|
||||
/// An atom that immediately decays to the result of the function when
|
||||
/// normalized. Can be used to build infinite recursive datastructures from
|
||||
/// Rust.
|
||||
#[derive(Clone)]
|
||||
pub struct Unstable<F>(F);
|
||||
impl<F: FnOnce(RunData) -> R + Send + 'static, R: ToClause> Unstable<F> {
|
||||
/// Wrap a function in an Unstable
|
||||
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 {
|
||||
f.debug_struct("Unstable").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
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());
|
||||
AtomicReturn::run(clause, run)
|
||||
}
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
}
|
||||
43
src/foreign/to_clause.rs
Normal file
43
src/foreign/to_clause.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use super::atom::Atomic;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// 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.
|
||||
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
|
||||
/// unwrap it if possible or fall back to [Clause::Identity].
|
||||
fn to_clause(self, location: CodeLocation) -> Clause;
|
||||
|
||||
/// Convert the type to a [Clause].
|
||||
fn to_clsi(self, location: CodeLocation) -> ClauseInst {
|
||||
ClauseInst::new(self.to_clause(location))
|
||||
}
|
||||
|
||||
/// Convert to an expression via [ToClause].
|
||||
fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.to_clsi(location.clone()), location }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Atomic + Clone> ToClause for T {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause { self.atom_cls() }
|
||||
}
|
||||
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_clsi(self, _: CodeLocation) -> ClauseInst { self }
|
||||
}
|
||||
impl ToClause for Expr {
|
||||
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 }
|
||||
}
|
||||
28
src/foreign/try_from_expr.rs
Normal file
28
src/foreign/try_from_expr.rs
Normal file
@@ -0,0 +1,28 @@
|
||||
use super::error::ExternResult;
|
||||
use crate::interpreter::nort::{ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Types automatically convertible from an [Expr]. Most notably, this is how
|
||||
/// 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>;
|
||||
}
|
||||
|
||||
impl TryFromExpr for Expr {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr) }
|
||||
}
|
||||
|
||||
impl TryFromExpr for ClauseInst {
|
||||
fn from_expr(expr: Expr) -> ExternResult<Self> { Ok(expr.clause.clone()) }
|
||||
}
|
||||
|
||||
/// Request a value of a particular type and also return its location for
|
||||
/// further error reporting
|
||||
#[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)?))
|
||||
}
|
||||
}
|
||||
7
src/gen/mod.rs
Normal file
7
src/gen/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
//! Abstractions and primitives for defining Orchid code in compile-time Rust
|
||||
//! constants. This is used both to generate glue code such as function call
|
||||
//! expressions at runtime and to define completely static intrinsics and
|
||||
//! constants accessible to usercode.
|
||||
pub mod tpl;
|
||||
pub mod traits;
|
||||
pub mod tree;
|
||||
80
src/gen/tpl.rs
Normal file
80
src/gen/tpl.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
//! 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};
|
||||
|
||||
/// Atom, Embed a Rust value. See also [AnyAtom]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct V<A: Atomic + Clone>(pub A);
|
||||
impl<A: Atomic + Clone> GenClause for V<A> {
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T {
|
||||
T::atom(ctx, Atom::new(self.0.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Atom, embed a Rust value of unspecified type. See also [V]
|
||||
#[derive(Debug)]
|
||||
pub struct AnyAtom(pub AtomGenerator);
|
||||
impl GenClause for AnyAtom {
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T {
|
||||
T::atom(ctx, self.0.run())
|
||||
}
|
||||
}
|
||||
|
||||
/// Const, Reference a constant from the execution environment. Unlike Orchid
|
||||
/// syntax, this doesn't include lambda arguments. For that, use [P]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct C(pub &'static str);
|
||||
impl GenClause for C {
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T {
|
||||
T::constant(ctx, self.0.split("::"))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a function to a value provided by [L]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct A<F: GenClause, X: GenClause>(pub F, pub X);
|
||||
impl<F: GenClause, X: GenClause> GenClause for A<F, X> {
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, p: &impl Fn() -> T) -> T {
|
||||
T::apply(ctx, |gen| self.0.generate(gen, p), |gen| self.1.generate(gen, p))
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a function to two arguments
|
||||
pub fn a2(
|
||||
f: impl GenClause,
|
||||
x: impl GenClause,
|
||||
y: impl GenClause,
|
||||
) -> impl GenClause {
|
||||
A(A(f, x), y)
|
||||
}
|
||||
|
||||
/// Lambda expression. The argument can be referenced with [P]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct L<B: GenClause>(pub &'static str, pub B);
|
||||
impl<B: GenClause> GenClause for L<B> {
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, p: &impl Fn() -> T) -> T {
|
||||
T::lambda(ctx, self.0, |gen| self.1.generate(gen, p))
|
||||
}
|
||||
}
|
||||
|
||||
/// Parameter to a lambda expression
|
||||
#[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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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()
|
||||
}
|
||||
}
|
||||
74
src/gen/traits.rs
Normal file
74
src/gen/traits.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
//! Abstractions used to generate Orchid expressions
|
||||
|
||||
use std::backtrace::Backtrace;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::foreign::atom::Atom;
|
||||
|
||||
/// Representations of the Orchid expression tree that can describe basic
|
||||
/// language elements.
|
||||
pub trait Generable: Sized {
|
||||
/// Context information defined by parents. Generators just forward this.
|
||||
type Ctx<'a>: 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;
|
||||
/// Generate a function call given the function and its argument
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
x: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
) -> 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;
|
||||
/// 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;
|
||||
}
|
||||
|
||||
/// Expression templates which can be instantiated in multiple representations
|
||||
/// of Orchid. Expressions can be built from the elements defined in
|
||||
/// [super::lit].
|
||||
///
|
||||
/// Do not depend on this trait, use [Gen] instead. Conversely, implement this
|
||||
/// instead of [Gen].
|
||||
pub trait GenClause: Debug + Sized {
|
||||
/// Enact the template at runtime to build a given type.
|
||||
/// `pop` pops from the runtime template parameter list passed to the
|
||||
/// generator.
|
||||
///
|
||||
/// Do not call this, it's the backing operation of [Gen#template]
|
||||
fn generate<T: Generable>(&self, ctx: T::Ctx<'_>, pop: &impl Fn() -> T) -> T;
|
||||
}
|
||||
|
||||
/// Expression generators
|
||||
///
|
||||
/// Do not implement this trait, it's the frontend for [GenClause]. Conversely,
|
||||
/// do not consume [GenClause].
|
||||
pub trait Gen<T: Generable, U>: Debug {
|
||||
/// Create an instance of this template with some parameters
|
||||
fn template(&self, ctx: T::Ctx<'_>, params: U) -> T;
|
||||
}
|
||||
|
||||
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 leftover = values.borrow().len();
|
||||
assert_eq!(leftover, 0, "Too many values provided ({leftover} left) {}", Backtrace::force_capture());
|
||||
t
|
||||
}
|
||||
}
|
||||
81
src/gen/tree.rs
Normal file
81
src/gen/tree.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
//! Components to build in-memory module trees that in Orchid. These modules
|
||||
//! can only contain constants and other modules.
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use dyn_clone::{clone_box, DynClone};
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::tpl;
|
||||
use super::traits::{Gen, GenClause};
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::Expr;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::tree::{ModEntry, ModMember, TreeConflict};
|
||||
use crate::utils::combine::Combine;
|
||||
|
||||
trait_set! {
|
||||
trait TreeLeaf = Gen<Expr, [Expr; 0]> + DynClone;
|
||||
}
|
||||
|
||||
/// A leaf in the [ConstTree]
|
||||
#[derive(Debug)]
|
||||
pub struct GenConst(Box<dyn TreeLeaf>);
|
||||
impl GenConst {
|
||||
fn new(data: impl GenClause + Clone + 'static) -> Self {
|
||||
Self(Box::new(data))
|
||||
}
|
||||
/// Instantiate template as [crate::interpreter::nort]
|
||||
pub fn gen_nort(&self, location: CodeLocation) -> Expr {
|
||||
self.0.template(nort_gen(location), [])
|
||||
}
|
||||
}
|
||||
impl Clone for GenConst {
|
||||
fn clone(&self) -> Self { Self(clone_box(&*self.0)) }
|
||||
}
|
||||
|
||||
/// Error condition when constant trees that define the the same constant are
|
||||
/// merged. Produced during system loading if multiple modules define the
|
||||
/// same constant
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ConflictingConsts;
|
||||
|
||||
impl Combine for GenConst {
|
||||
type Error = ConflictingConsts;
|
||||
fn combine(self, _: Self) -> Result<Self, Self::Error> {
|
||||
Err(ConflictingConsts)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
pub type ConstTree = ModEntry<GenConst, (), ()>;
|
||||
|
||||
/// Describe a constant
|
||||
#[must_use]
|
||||
pub fn leaf(value: impl GenClause + Clone + 'static) -> ConstTree {
|
||||
ModEntry { x: (), member: ModMember::Item(GenConst::new(value)) }
|
||||
}
|
||||
|
||||
/// Describe an [Atomic]
|
||||
#[must_use]
|
||||
pub fn atom_leaf(atom: impl Atomic + Clone + 'static) -> ConstTree {
|
||||
leaf(tpl::V(atom))
|
||||
}
|
||||
|
||||
/// Describe an [Atomic] which appears as an entry in a [ConstTree#tree]
|
||||
///
|
||||
/// The unarray is used to trick rustfmt into breaking the atom into a block
|
||||
/// without breaking this call into a block
|
||||
#[must_use]
|
||||
pub fn atom_ent<K: AsRef<str>>(
|
||||
key: K,
|
||||
[atom]: [impl Atomic + Clone + 'static; 1],
|
||||
) -> (K, ConstTree) {
|
||||
(key, atom_leaf(atom))
|
||||
}
|
||||
|
||||
/// Errors produced duriung the merger of constant trees
|
||||
pub type ConstCombineErr = TreeConflict<GenConst, (), ()>;
|
||||
142
src/intermediate/ast_to_ir.rs
Normal file
142
src/intermediate/ast_to_ir.rs
Normal file
@@ -0,0 +1,142 @@
|
||||
//! Convert the preprocessed AST into IR
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::rc::Rc;
|
||||
|
||||
use substack::Substack;
|
||||
|
||||
use super::ir;
|
||||
use crate::error::{ProjectError, ProjectResult};
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::name::Sym;
|
||||
use crate::parse::parsed;
|
||||
use crate::utils::unwrap_or::unwrap_or;
|
||||
|
||||
trait IRErrorKind: Clone + Send + Sync + 'static {
|
||||
const DESCR: &'static str;
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct IRError<T: IRErrorKind>(SourceRange, Sym, T);
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EmptyS;
|
||||
impl IRErrorKind for EmptyS {
|
||||
const DESCR: &'static str =
|
||||
"`()` as a clause is meaningless in lambda calculus";
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct BadGroup;
|
||||
impl IRErrorKind for BadGroup {
|
||||
const DESCR: &'static str = "Only `(...)` may be used after macros. \
|
||||
`[...]` and `{...}` left in the code are signs of incomplete macro execution";
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct InvalidArg;
|
||||
impl IRErrorKind for InvalidArg {
|
||||
const DESCR: &'static str = "Argument names can only be Name nodes";
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PhLeak;
|
||||
impl IRErrorKind for PhLeak {
|
||||
const DESCR: &'static str = "Placeholders shouldn't even appear \
|
||||
in the code during macro execution, this is likely a compiler bug";
|
||||
}
|
||||
|
||||
/// 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))
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Context<'a> {
|
||||
names: Substack<'a, Sym>,
|
||||
symbol: 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() }
|
||||
}
|
||||
}
|
||||
impl Context<'static> {
|
||||
#[must_use]
|
||||
fn new(symbol: Sym) -> Self { Self { names: Substack::Bottom, symbol } }
|
||||
}
|
||||
|
||||
/// Process an expression sequence
|
||||
fn exprv_rec(
|
||||
mut v: VecDeque<parsed::Expr>,
|
||||
ctx: Context<'_>,
|
||||
location: SourceRange,
|
||||
) -> ProjectResult<ir::Expr> {
|
||||
let last = unwrap_or! {v.pop_back(); {
|
||||
return Err(IRError(location, ctx.symbol, 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 value = ir::Clause::Apply(Rc::new(f), Rc::new(x));
|
||||
Ok(ir::Expr { value, location: CodeLocation::Source(location) })
|
||||
}
|
||||
|
||||
/// Process an expression
|
||||
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()),
|
||||
_ => (),
|
||||
}
|
||||
let value = match value {
|
||||
parsed::Clause::Atom(a) => ir::Clause::Atom(a.clone()),
|
||||
parsed::Clause::Lambda(arg, b) => {
|
||||
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()),
|
||||
};
|
||||
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);
|
||||
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()),
|
||||
};
|
||||
Ok(ir::Expr::new(value, range.clone()))
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
//! IR is an abstract representation of Orchid expressions that's impractical
|
||||
//! for all purposes except converting to and from other representations. Future
|
||||
//! innovations in the processing and execution of code will likely operate on
|
||||
//! this representation.
|
||||
|
||||
use std::fmt::{Debug, Write};
|
||||
use std::rc::Rc;
|
||||
|
||||
use super::location::Location;
|
||||
use crate::foreign::{Atom, ExFn};
|
||||
use crate::utils::string_from_charset;
|
||||
use crate::Sym;
|
||||
use crate::foreign::atom::AtomGenerator;
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::name::Sym;
|
||||
use crate::utils::string_from_charset::string_from_charset;
|
||||
|
||||
/// Indicates whether either side needs to be wrapped. Syntax whose end is
|
||||
/// ambiguous on that side must use parentheses, or forward the flag
|
||||
@@ -14,10 +19,14 @@ struct Wrap(bool, bool);
|
||||
#[derive(Clone)]
|
||||
pub struct Expr {
|
||||
pub value: Clause,
|
||||
pub location: Location,
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn new(value: Clause, location: SourceRange) -> Self {
|
||||
Self { value, location: CodeLocation::Source(location) }
|
||||
}
|
||||
|
||||
fn deep_fmt(
|
||||
&self,
|
||||
f: &mut std::fmt::Formatter<'_>,
|
||||
@@ -42,10 +51,8 @@ pub enum Clause {
|
||||
Lambda(Rc<Expr>),
|
||||
Constant(Sym),
|
||||
LambdaArg(usize),
|
||||
/// An opaque function, eg. an effectful function employing CPS
|
||||
ExternFn(ExFn),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
Atom(AtomGenerator),
|
||||
}
|
||||
|
||||
const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||
@@ -79,7 +86,6 @@ impl Clause {
|
||||
) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Atom(a) => write!(f, "{a:?}"),
|
||||
Self::ExternFn(fun) => write!(f, "{fun:?}"),
|
||||
Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr),
|
||||
Self::LambdaArg(skip) => {
|
||||
let lambda_depth = (depth - skip - 1).try_into().unwrap();
|
||||
30
src/intermediate/ir_to_nort.rs
Normal file
30
src/intermediate/ir_to_nort.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
//! Convert IR to the interpreter's NORT representation
|
||||
|
||||
use super::ir;
|
||||
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())
|
||||
}
|
||||
|
||||
fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause {
|
||||
match cls {
|
||||
ir::Clause::Constant(name) => nort::Clause::Constant(name.clone()),
|
||||
ir::Clause::Atom(a) => nort::Clause::Atom(a.run()),
|
||||
ir::Clause::LambdaArg(n) => {
|
||||
ctx.arg_logic(n);
|
||||
nort::Clause::LambdaArg
|
||||
},
|
||||
ir::Clause::Apply(f, x) => ctx.apply_logic(|c| expr(f, c), |c| expr(x, c)),
|
||||
ir::Clause::Lambda(body) => ctx.lambda_logic(&(), |c| expr(body, c)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr {
|
||||
let c = NortBuilder::new(&|count| {
|
||||
let mut count: usize = *count;
|
||||
Box::new(move |()| count.checked_sub(1).map(|v| count = v).is_none())
|
||||
});
|
||||
nort::ClauseInst::new(clause(&expr.value, c)).to_expr(expr.location.clone())
|
||||
}
|
||||
3
src/intermediate/mod.rs
Normal file
3
src/intermediate/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub(crate) mod ast_to_ir;
|
||||
pub(crate) mod ir;
|
||||
pub(crate) mod ir_to_nort;
|
||||
@@ -1,11 +0,0 @@
|
||||
//! A type-agnostic interner
|
||||
//!
|
||||
//! Can be used to deduplicate various structures for fast equality comparisons.
|
||||
//! The parser uses it to intern strings.
|
||||
mod monotype;
|
||||
mod multitype;
|
||||
mod token;
|
||||
|
||||
pub use monotype::TypedInterner;
|
||||
pub use multitype::Interner;
|
||||
pub use token::Tok;
|
||||
@@ -1,55 +0,0 @@
|
||||
use std::borrow::Borrow;
|
||||
use std::hash::{BuildHasher, Hash};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::token::Tok;
|
||||
|
||||
/// An interner for any type that implements [Borrow]. This is inspired by
|
||||
/// Lasso but much simpler, in part because not much can be known about the
|
||||
/// type.
|
||||
pub struct TypedInterner<T: 'static + Eq + Hash + Clone> {
|
||||
tokens: RwLock<HashMap<Arc<T>, Tok<T>>>,
|
||||
}
|
||||
impl<T: Eq + Hash + Clone> TypedInterner<T> {
|
||||
/// Create a fresh interner instance
|
||||
#[must_use]
|
||||
pub fn new() -> Arc<Self> {
|
||||
Arc::new(Self { tokens: RwLock::new(HashMap::new()) })
|
||||
}
|
||||
|
||||
/// Intern an object, returning a token
|
||||
#[must_use]
|
||||
pub fn i<Q: ?Sized + Eq + Hash + ToOwned<Owned = T>>(
|
||||
self: &Arc<Self>,
|
||||
q: &Q,
|
||||
) -> Tok<T>
|
||||
where
|
||||
T: Borrow<Q>,
|
||||
{
|
||||
let mut tokens = self.tokens.write().unwrap();
|
||||
let hash = compute_hash(tokens.hasher(), q);
|
||||
let raw_entry = tokens
|
||||
.raw_entry_mut()
|
||||
.from_hash(hash, |k| <T as Borrow<Q>>::borrow(k) == q);
|
||||
let kv = raw_entry.or_insert_with(|| {
|
||||
let keyrc = Arc::new(q.to_owned());
|
||||
let token = Tok::<T>::new(keyrc.clone(), Arc::downgrade(self));
|
||||
(keyrc, token)
|
||||
});
|
||||
kv.1.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to compute hashes outside a hashmap
|
||||
#[must_use]
|
||||
fn compute_hash(
|
||||
hash_builder: &impl BuildHasher,
|
||||
key: &(impl Hash + ?Sized),
|
||||
) -> u64 {
|
||||
use core::hash::Hasher;
|
||||
let mut state = hash_builder.build_hasher();
|
||||
key.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::borrow::Borrow;
|
||||
use std::cell::{RefCell, RefMut};
|
||||
use std::hash::Hash;
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::monotype::TypedInterner;
|
||||
use super::token::Tok;
|
||||
|
||||
/// A collection of interners based on their type. Allows to intern any object
|
||||
/// that implements [ToOwned]. Objects of the same type are stored together in a
|
||||
/// [TypedInterner].
|
||||
pub struct Interner {
|
||||
interners: RefCell<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
|
||||
}
|
||||
impl Interner {
|
||||
/// Create a new interner
|
||||
#[must_use]
|
||||
pub fn new() -> Self { Self { interners: RefCell::new(HashMap::new()) } }
|
||||
|
||||
/// Intern something
|
||||
#[must_use]
|
||||
pub fn i<Q: ?Sized + Eq + Hash + ToOwned>(&self, q: &Q) -> Tok<Q::Owned>
|
||||
where
|
||||
Q::Owned: 'static + Eq + Hash + Clone + Borrow<Q> + Send + Sync,
|
||||
{
|
||||
let mut interners = self.interners.borrow_mut();
|
||||
let interner = get_interner(&mut interners);
|
||||
interner.i(q)
|
||||
}
|
||||
|
||||
/// Fully resolve a list of interned things.
|
||||
#[must_use]
|
||||
pub fn extern_all<T: 'static + Eq + Hash + Clone>(s: &[Tok<T>]) -> Vec<T> {
|
||||
s.iter().map(|t| (**t).clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Interner {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
|
||||
/// Get or create an interner for a given type.
|
||||
#[must_use]
|
||||
fn get_interner<T: 'static + Eq + Hash + Clone + Send + Sync>(
|
||||
interners: &mut RefMut<HashMap<TypeId, Arc<dyn Any + Send + Sync>>>,
|
||||
) -> Arc<TypedInterner<T>> {
|
||||
let boxed = interners
|
||||
.raw_entry_mut()
|
||||
.from_key(&TypeId::of::<T>())
|
||||
.or_insert_with(|| (TypeId::of::<T>(), TypedInterner::<T>::new()))
|
||||
.1
|
||||
.clone();
|
||||
boxed.downcast().expect("the typeid is supposed to protect from this")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
pub fn test_string() {
|
||||
let interner = Interner::new();
|
||||
let key1 = interner.i("foo");
|
||||
let key2 = interner.i(&"foo".to_string());
|
||||
assert_eq!(key1, key2)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_slice() {
|
||||
let interner = Interner::new();
|
||||
let key1 = interner.i(&vec![1, 2, 3]);
|
||||
let key2 = interner.i(&[1, 2, 3][..]);
|
||||
assert_eq!(key1, key2);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
#[allow(unused)]
|
||||
pub fn test_str_slice() {
|
||||
let interner = Interner::new();
|
||||
let key1 =
|
||||
interner.i(&vec!["a".to_string(), "b".to_string(), "c".to_string()]);
|
||||
let key2 = interner.i(&["a", "b", "c"][..]);
|
||||
// assert_eq!(key1, key2);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
use std::cmp::PartialEq;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroUsize;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Weak};
|
||||
|
||||
use super::TypedInterner;
|
||||
|
||||
/// A number representing an object of type `T` stored in some interner. It is a
|
||||
/// logic error to compare tokens obtained from different interners, or to use a
|
||||
/// token with an interner other than the one that created it, but this is
|
||||
/// currently not enforced.
|
||||
#[derive(Clone)]
|
||||
pub struct Tok<T: Eq + Hash + Clone + 'static> {
|
||||
data: Arc<T>,
|
||||
interner: Weak<TypedInterner<T>>,
|
||||
}
|
||||
impl<T: Eq + Hash + Clone + 'static> Tok<T> {
|
||||
/// Create a new token. Used exclusively by the interner
|
||||
#[must_use]
|
||||
pub(crate) fn new(data: Arc<T>, interner: Weak<TypedInterner<T>>) -> Self {
|
||||
Self { data, interner }
|
||||
}
|
||||
/// Take the ID number out of a token
|
||||
#[must_use]
|
||||
pub fn id(&self) -> NonZeroUsize {
|
||||
((self.data.as_ref() as *const T as usize).try_into())
|
||||
.expect("Pointer can always be cast to nonzero")
|
||||
}
|
||||
/// Cast into usize
|
||||
#[must_use]
|
||||
pub fn usize(&self) -> usize { self.id().into() }
|
||||
///
|
||||
pub fn assert_comparable(&self, other: &Self) {
|
||||
let iref = self.interner.as_ptr() as usize;
|
||||
assert!(
|
||||
iref == other.interner.as_ptr() as usize,
|
||||
"Tokens must come from the same interner"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static> Tok<Vec<Tok<T>>> {
|
||||
/// Extern all elements of the vector in a new vector
|
||||
pub fn extern_vec(&self) -> Vec<T> {
|
||||
self.iter().map(|t| (**t).clone()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static> Deref for Tok<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target { self.data.as_ref() }
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static + Debug> Debug for Tok<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Token({} -> {:?})", self.id(), self.data.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + Display + 'static> Display for Tok<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", **self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static> Eq for Tok<T> {}
|
||||
impl<T: Eq + Hash + Clone + 'static> PartialEq for Tok<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.assert_comparable(other);
|
||||
self.id() == other.id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static> Ord for Tok<T> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.assert_comparable(other);
|
||||
self.id().cmp(&other.id())
|
||||
}
|
||||
}
|
||||
impl<T: Eq + Hash + Clone + 'static> PartialOrd for Tok<T> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Eq + Hash + Clone + 'static> Hash for Tok<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
state.write_usize(self.usize())
|
||||
}
|
||||
}
|
||||
@@ -1,113 +1,188 @@
|
||||
use super::context::Context;
|
||||
use super::error::RuntimeError;
|
||||
use super::Return;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use crate::representations::PathSet;
|
||||
use crate::utils::never::{unwrap_always, Always};
|
||||
use crate::utils::Side;
|
||||
use std::collections::VecDeque;
|
||||
use std::mem;
|
||||
|
||||
use never::Never;
|
||||
|
||||
use super::context::RunContext;
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, ClauseInst, Expr};
|
||||
use super::path_set::{PathSet, Step};
|
||||
use super::run::run;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
/// Information about a function call presented to an external function
|
||||
pub struct CallData<'a> {
|
||||
/// Location of the function expression
|
||||
pub location: CodeLocation,
|
||||
/// The argument the function was called on. Functions are curried
|
||||
pub arg: Expr,
|
||||
/// Information relating to this interpreter run
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// normalization step in the intermediate expressions.
|
||||
fn map_at<E>(
|
||||
path: &[Side],
|
||||
source: ExprInst,
|
||||
mapper: &mut impl FnMut(Clause) -> Result<Clause, E>,
|
||||
) -> Result<ExprInst, E> {
|
||||
source
|
||||
.try_update(|value, _loc| {
|
||||
// Pass right through lambdas
|
||||
if let Clause::Lambda { args, body } = value {
|
||||
return Ok((
|
||||
Clause::Lambda { args, body: map_at(path, body, mapper)? },
|
||||
(),
|
||||
));
|
||||
}
|
||||
// If the path ends here, process the next (non-lambda) node
|
||||
let (head, tail) = if let Some(sf) = path.split_first() {
|
||||
sf
|
||||
} else {
|
||||
return Ok((mapper(value)?, ()));
|
||||
};
|
||||
// If it's an Apply, execute the next step in the path
|
||||
if let Clause::Apply { f, x } = value {
|
||||
return Ok((
|
||||
match head {
|
||||
Side::Left => Clause::Apply { f: map_at(tail, f, mapper)?, x },
|
||||
Side::Right => Clause::Apply { f, x: map_at(tail, x, mapper)? },
|
||||
mut path: impl Iterator<Item = Step>,
|
||||
source: &Clause,
|
||||
mapper: &mut impl FnMut(&Clause) -> Result<Clause, E>,
|
||||
) -> Result<Clause, E> {
|
||||
// Pass through some unambiguous wrapper clauses
|
||||
match source {
|
||||
Clause::Identity(alt) => return map_at(path, &alt.cls(), 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(),
|
||||
location: b_loc.clone(),
|
||||
},
|
||||
(),
|
||||
));
|
||||
}),
|
||||
_ => (),
|
||||
}
|
||||
panic!("Invalid path")
|
||||
Ok(match (source, path.next()) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) =>
|
||||
unreachable!("Handled above"),
|
||||
// If the path ends and this isn't a lambda, process it
|
||||
(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.clause.cls(), mapper)?.to_expr(x.location()))
|
||||
};
|
||||
match head {
|
||||
None => Clause::Apply { f: proc(f)?, x: x.clone() },
|
||||
Some(n) => {
|
||||
let i = x.len() - n - 1;
|
||||
let mut argv = x.clone();
|
||||
argv[i] = proc(&x[i])?;
|
||||
Clause::Apply { f: f.clone(), x: argv }
|
||||
},
|
||||
}
|
||||
},
|
||||
(_, Some(_)) => panic!("Path leads into node that isn't Apply or Lambda"),
|
||||
})
|
||||
.map(|p| p.0)
|
||||
}
|
||||
|
||||
/// Replace the [Clause::LambdaArg] placeholders at the ends of the [PathSet]
|
||||
/// with the value in the body. Note that a path may point to multiple
|
||||
/// placeholders.
|
||||
#[must_use]
|
||||
fn substitute(paths: &PathSet, value: Clause, body: ExprInst) -> ExprInst {
|
||||
fn substitute(paths: &PathSet, value: ClauseInst, body: &Clause) -> Clause {
|
||||
let PathSet { steps, next } = paths;
|
||||
unwrap_always(map_at(steps, body, &mut |checkpoint| -> Always<Clause> {
|
||||
match (checkpoint, next) {
|
||||
(Clause::Lambda { .. }, _) => unreachable!("Handled by map_at"),
|
||||
(Clause::Apply { f, x }, Some((left, right))) => Ok(Clause::Apply {
|
||||
f: substitute(left, value.clone(), f),
|
||||
x: substitute(right, value.clone(), x),
|
||||
}),
|
||||
(Clause::LambdaArg, None) => Ok(value.clone()),
|
||||
(_, None) => {
|
||||
panic!("Substitution path ends in something other than LambdaArg")
|
||||
map_at(steps.iter().cloned(), body, &mut |chkpt| -> Result<Clause, Never> {
|
||||
match (chkpt, next) {
|
||||
(Clause::Lambda { .. } | Clause::Identity(_), _) => {
|
||||
unreachable!("Handled by map_at")
|
||||
},
|
||||
(_, Some(_)) => {
|
||||
panic!("Substitution path leads into something other than Apply")
|
||||
(Clause::Apply { f, x }, Some(conts)) => {
|
||||
let mut argv = x.clone();
|
||||
let f = match conts.get(&None) {
|
||||
None => f.clone(),
|
||||
Some(sp) => substitute(sp, value.clone(), &f.clause.cls())
|
||||
.to_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.clause.cls());
|
||||
*old = tmp.to_expr(old.location());
|
||||
}
|
||||
}
|
||||
Ok(Clause::Apply { f, x: argv })
|
||||
},
|
||||
(Clause::LambdaArg, None) => Ok(Clause::Identity(value.clone())),
|
||||
(_, None) => panic!("Argument path must point to LambdaArg"),
|
||||
(_, Some(_)) => panic!("Argument path can only fork at Apply"),
|
||||
}
|
||||
})
|
||||
.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 };
|
||||
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)?),
|
||||
_ => panic!("Not an atom"),
|
||||
},
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
/// Apply a function-like expression to a parameter.
|
||||
pub fn apply(
|
||||
f: ExprInst,
|
||||
x: ExprInst,
|
||||
ctx: Context,
|
||||
) -> Result<Return, RuntimeError> {
|
||||
let (state, (gas, inert)) = f.try_update(|clause, loc| match clause {
|
||||
pub(super) fn apply(
|
||||
mut f: Expr,
|
||||
mut argv: VecDeque<Expr>,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<(Option<usize>, Clause), RunError> {
|
||||
// allow looping but break on the main path so that `continue` functions as a
|
||||
// trampoline
|
||||
loop {
|
||||
if argv.is_empty() {
|
||||
return Ok((ctx.gas, f.clause.into_cls()));
|
||||
} else if ctx.gas == Some(0) {
|
||||
return Ok((Some(0), Clause::Apply { f, x: argv }));
|
||||
}
|
||||
let mut f_cls = f.clause.cls_mut();
|
||||
match &mut *f_cls {
|
||||
// apply an ExternFn or an internal function
|
||||
Clause::ExternFn(f) => {
|
||||
let clause = f.apply(x, ctx.clone()).map_err(RuntimeError::Extern)?;
|
||||
Ok((clause, (ctx.gas.map(|g| g - 1), false)))
|
||||
Clause::Atom(_) => {
|
||||
mem::drop(f_cls);
|
||||
// take a step in expanding atom
|
||||
let halt = run(f, ctx.clone())?;
|
||||
ctx.gas = halt.gas;
|
||||
if halt.inert && halt.state.clause.is_atom() {
|
||||
let arg = argv.pop_front().expect("checked above");
|
||||
let loc = halt.state.location();
|
||||
f = apply_as_atom(halt.state, arg, ctx.clone())?.to_expr(loc)
|
||||
} else {
|
||||
f = halt.state
|
||||
}
|
||||
},
|
||||
Clause::Lambda { args, body } => Ok(if let Some(args) = args {
|
||||
let x_cls = x.expr_val().clause;
|
||||
let result = substitute(&args, x_cls, body);
|
||||
Clause::Lambda { args, body } => {
|
||||
match args {
|
||||
None => *f_cls = body.clause.clone().into_cls(),
|
||||
Some(args) => {
|
||||
let arg = argv.pop_front().expect("checked above").clause.clone();
|
||||
let cls = substitute(args, arg, &body.clause.cls());
|
||||
// cost of substitution
|
||||
// XXX: should this be the number of occurrences instead?
|
||||
(result.expr_val().clause, (ctx.gas.map(|x| x - 1), false))
|
||||
} else {
|
||||
(body.expr_val().clause, (ctx.gas, false))
|
||||
}),
|
||||
Clause::Constant(name) =>
|
||||
if let Some(sym) = ctx.symbols.get(&name) {
|
||||
Ok((Clause::Apply { f: sym.clone(), x }, (ctx.gas, false)))
|
||||
} else {
|
||||
Err(RuntimeError::MissingSymbol(name.clone(), loc))
|
||||
ctx.use_gas(1);
|
||||
mem::drop(f_cls);
|
||||
f = cls.to_expr(f.location());
|
||||
},
|
||||
Clause::Atom(atom) => {
|
||||
// take a step in expanding atom
|
||||
let AtomicReturn { clause, gas, inert } = atom.run(ctx.clone())?;
|
||||
Ok((Clause::Apply { f: clause.wrap(), x }, (gas, inert)))
|
||||
}
|
||||
},
|
||||
Clause::Apply { f: fun, x: arg } => {
|
||||
// take a step in resolving pre-function
|
||||
let ret = apply(fun, arg, ctx.clone())?;
|
||||
let Return { state, inert, gas } = ret;
|
||||
Ok((Clause::Apply { f: state, x }, (gas, inert)))
|
||||
Clause::Constant(name) => {
|
||||
let name = name.clone();
|
||||
mem::drop(f_cls);
|
||||
f = (ctx.symbols.get(&name).cloned())
|
||||
.ok_or_else(|| RunError::MissingSymbol(name, f.location()))?;
|
||||
ctx.use_gas(1);
|
||||
},
|
||||
_ => Err(RuntimeError::NonFunctionApplication(loc)),
|
||||
})?;
|
||||
Ok(Return { state, gas, inert })
|
||||
Clause::Apply { f: fun, x } => {
|
||||
for item in x.drain(..).rev() {
|
||||
argv.push_front(item)
|
||||
}
|
||||
let tmp = fun.clone();
|
||||
mem::drop(f_cls);
|
||||
f = tmp;
|
||||
},
|
||||
Clause::Identity(f2) => {
|
||||
let tmp = f2.clone();
|
||||
mem::drop(f_cls);
|
||||
f.clause = tmp
|
||||
},
|
||||
Clause::Bottom(bottom) => return Err(bottom.clone()),
|
||||
Clause::LambdaArg => panic!("Leftover argument marker"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,38 @@
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use crate::interner::Interner;
|
||||
use crate::representations::interpreted::ExprInst;
|
||||
use crate::Sym;
|
||||
use super::nort::Expr;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// All the data associated with an interpreter run
|
||||
#[derive(Clone)]
|
||||
pub struct Context<'a> {
|
||||
pub struct RunContext<'a> {
|
||||
/// Table used to resolve constants
|
||||
pub symbols: &'a HashMap<Sym, ExprInst>,
|
||||
/// The interner used for strings internally, so external functions can
|
||||
/// deduce referenced constant names on the fly
|
||||
pub interner: &'a Interner,
|
||||
pub symbols: &'a HashMap<Sym, Expr>,
|
||||
/// The number of reduction steps the interpreter can take before returning
|
||||
pub gas: Option<usize>,
|
||||
}
|
||||
impl<'a> RunContext<'a> {
|
||||
/// Consume some gas if it is being counted
|
||||
pub fn use_gas(&mut self, amount: usize) {
|
||||
if let Some(g) = self.gas.as_mut() {
|
||||
*g = g.saturating_sub(amount)
|
||||
}
|
||||
}
|
||||
/// 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 Return {
|
||||
pub struct Halt {
|
||||
/// The new expression tree
|
||||
pub state: ExprInst,
|
||||
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 Return {
|
||||
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
|
||||
|
||||
@@ -1,37 +1,33 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::{Location, Sym};
|
||||
use crate::foreign::error::ExternError;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
use super::run::Interrupted;
|
||||
|
||||
/// Problems in the process of execution
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RuntimeError {
|
||||
pub enum RunError {
|
||||
/// A Rust function encountered an error
|
||||
Extern(Arc<dyn ExternError>),
|
||||
/// Primitive applied as function
|
||||
NonFunctionApplication(Location),
|
||||
/// Symbol not in context
|
||||
MissingSymbol(Sym, Location),
|
||||
MissingSymbol(Sym, CodeLocation),
|
||||
/// Ran out of gas
|
||||
Interrupted(Interrupted)
|
||||
}
|
||||
|
||||
impl From<Arc<dyn ExternError>> for RuntimeError {
|
||||
impl From<Arc<dyn ExternError>> for RunError {
|
||||
fn from(value: Arc<dyn ExternError>) -> Self { Self::Extern(value) }
|
||||
}
|
||||
|
||||
impl Display for RuntimeError {
|
||||
impl Display for RunError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Extern(e) => write!(f, "Error in external function: {e}"),
|
||||
Self::NonFunctionApplication(location) => {
|
||||
write!(f, "Primitive applied as function at {}", location)
|
||||
},
|
||||
Self::MissingSymbol(sym, loc) => {
|
||||
write!(
|
||||
f,
|
||||
"{}, called at {loc} is not loaded",
|
||||
sym.extern_vec().join("::")
|
||||
)
|
||||
write!(f, "{sym}, called at {loc} is not loaded")
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
126
src/interpreter/gen_nort.rs
Normal file
126
src/interpreter/gen_nort.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
//! Implementations of [Generable] for [super::nort]
|
||||
|
||||
use intern_all::i;
|
||||
|
||||
use super::nort_builder::NortBuilder;
|
||||
use crate::foreign::atom::Atom;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::gen::traits::Generable;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
|
||||
/// Context data for instantiating templated expressions as [super::nort].
|
||||
/// Instances of this type are created via [nort_gen]
|
||||
pub type NortGenCtx<'a> = (CodeLocation, NortBuilder<'a, str, str>);
|
||||
|
||||
/// Create [NortGenCtx] instances to generate interpreted expressions
|
||||
pub fn nort_gen<'a>(location: CodeLocation) -> NortGenCtx<'a> {
|
||||
(location, NortBuilder::new(&|l| Box::new(move |r| l == r)))
|
||||
}
|
||||
|
||||
impl Generable for Expr {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
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())
|
||||
}
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
Clause::arg(ctx.clone(), name).to_expr(ctx.0.clone())
|
||||
}
|
||||
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self {
|
||||
Clause::atom(ctx.clone(), a).to_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 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())
|
||||
}
|
||||
}
|
||||
|
||||
impl Generable for ClauseInst {
|
||||
type Ctx<'a> = NortGenCtx<'a>;
|
||||
fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self {
|
||||
Clause::arg(ctx, name).to_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 apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
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()),
|
||||
))
|
||||
.to_clsi(ctx.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant");
|
||||
Clause::Constant(sym)
|
||||
}
|
||||
fn apply(
|
||||
ctx: Self::Ctx<'_>,
|
||||
f: impl FnOnce(Self::Ctx<'_>) -> Self,
|
||||
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()),
|
||||
)
|
||||
}
|
||||
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()))
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,19 @@
|
||||
use std::any::{Any, TypeId};
|
||||
use std::sync::Arc;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::{run, Context, Return, RuntimeError};
|
||||
use crate::foreign::{Atom, Atomic, ExternError};
|
||||
use crate::interpreted::{Clause, Expr, ExprInst};
|
||||
use crate::utils::take_with_output;
|
||||
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 crate::foreign::to_clause::ToClause;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
trait_set! {
|
||||
trait Handler = FnMut(Box<dyn Any>) -> HandlerRes;
|
||||
trait Handler = for<'a> FnMut(&'a dyn Any, CodeLocation) -> Expr;
|
||||
}
|
||||
|
||||
/// A table of command handlers
|
||||
@@ -23,26 +26,37 @@ impl<'a> HandlerTable<'a> {
|
||||
#[must_use]
|
||||
pub fn new() -> Self { Self { handlers: HashMap::new() } }
|
||||
|
||||
/// Add a handler function to interpret a type of atom and decide what happens
|
||||
/// next. This function can be impure.
|
||||
pub fn register<T: 'static>(
|
||||
/// 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 FnMut(Box<T>) -> HandlerRes + 'a,
|
||||
mut f: impl for<'b> FnMut(&'b T) -> R + 'a,
|
||||
) {
|
||||
let cb = move |a: Box<dyn Any>| f(a.downcast().expect("found by TypeId"));
|
||||
let cb = move |a: &dyn Any, loc: CodeLocation| {
|
||||
f(a.downcast_ref().expect("found by TypeId")).to_expr(loc)
|
||||
};
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), 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 {
|
||||
self.register(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Find and execute the corresponding handler for this type
|
||||
pub fn dispatch(
|
||||
&mut self,
|
||||
arg: Box<dyn Atomic>,
|
||||
) -> Result<HandlerRes, Box<dyn Atomic>> {
|
||||
match self.handlers.get_mut(&arg.as_any_ref().type_id()) {
|
||||
Some(f) => Ok(f(arg.as_any())),
|
||||
None => Err(arg),
|
||||
}
|
||||
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))
|
||||
}
|
||||
|
||||
/// Combine two non-overlapping handler sets
|
||||
@@ -56,33 +70,27 @@ impl<'a> HandlerTable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Various possible outcomes of a [Handler] execution. Ok returns control to
|
||||
/// the interpreter. The meaning of Err is decided by the value in it.
|
||||
pub type HandlerRes = Result<ExprInst, Arc<dyn ExternError>>;
|
||||
|
||||
/// [run] orchid code, executing any commands it returns using the specified
|
||||
/// [Handler]s.
|
||||
pub fn run_handler(
|
||||
mut expr: ExprInst,
|
||||
mut state: Expr,
|
||||
handlers: &mut HandlerTable,
|
||||
mut ctx: Context,
|
||||
) -> Result<Return, RuntimeError> {
|
||||
RunContext { mut gas, symbols }: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
loop {
|
||||
let mut ret = run(expr, ctx.clone())?;
|
||||
let quit = take_with_output(&mut ret.state, |exi| match exi.expr_val() {
|
||||
Expr { clause: Clause::Atom(a), .. } => match handlers.dispatch(a.0) {
|
||||
Err(b) => (Clause::Atom(Atom(b)).wrap(), Ok(true)),
|
||||
Ok(e) => match e {
|
||||
Ok(expr) => (expr, Ok(false)),
|
||||
Err(e) => (Clause::Bottom.wrap(), Err(e)),
|
||||
},
|
||||
},
|
||||
expr => (ExprInst::new(expr), Ok(true)),
|
||||
})?;
|
||||
if quit | ret.gas.map_or(false, |g| g == 0) {
|
||||
return Ok(ret);
|
||||
let inert;
|
||||
Halt { gas, inert, state } = run(state, RunContext { gas, symbols })?;
|
||||
let state_cls = state.clause.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 inert || gas == Some(0) {
|
||||
drop(state_cls);
|
||||
break Ok(Halt { gas, inert, state });
|
||||
}
|
||||
ctx.gas = ret.gas;
|
||||
expr = ret.state;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
//! functions to interact with Orchid code
|
||||
mod apply;
|
||||
mod context;
|
||||
mod error;
|
||||
mod handler;
|
||||
mod run;
|
||||
|
||||
pub use context::{Context, Return, ReturnStatus};
|
||||
pub use error::RuntimeError;
|
||||
pub use handler::{run_handler, HandlerRes, HandlerTable};
|
||||
pub use run::run;
|
||||
pub mod apply;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod gen_nort;
|
||||
pub mod handler;
|
||||
pub mod nort_builder;
|
||||
pub mod nort;
|
||||
pub(crate) mod path_set;
|
||||
pub mod run;
|
||||
|
||||
334
src/interpreter/nort.rs
Normal file
334
src/interpreter/nort.rs
Normal file
@@ -0,0 +1,334 @@
|
||||
//! The NORT (Normal Order Referencing Tree) is the interpreter's runtime
|
||||
//! representation of Orchid programs.
|
||||
//!
|
||||
//! It uses a locator tree to find bound variables in lambda functions, which
|
||||
//! necessitates a normal reduction order because modifying the body by reducing
|
||||
//! expressions would invalidate any locators in enclosing lambdas.
|
||||
//!
|
||||
//! Clauses are held in a mutable `Arc<Mutex<_>>`, so that after substitution
|
||||
//! the instances of the argument remain linked and a reduction step applied to
|
||||
//! any instance transforms all of them.
|
||||
//!
|
||||
//! To improve locality and make the tree less deep and locators shorter,
|
||||
//! 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 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::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> {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = T> + '_;
|
||||
}
|
||||
|
||||
/// An expression with metadata
|
||||
#[derive(Clone)]
|
||||
pub struct Expr {
|
||||
/// The actual value
|
||||
pub clause: ClauseInst,
|
||||
/// Information about the code that produced this value
|
||||
pub location: CodeLocation,
|
||||
}
|
||||
impl Expr {
|
||||
/// Constructor
|
||||
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> {
|
||||
let Expr { mut clause, location } = self;
|
||||
loop {
|
||||
let cls_deref = clause.cls();
|
||||
match &*cls_deref {
|
||||
Clause::Identity(alt) => {
|
||||
let temp = alt.clone();
|
||||
drop(cls_deref);
|
||||
clause = temp;
|
||||
},
|
||||
_ => {
|
||||
drop(cls_deref);
|
||||
return T::from_expr(Expr { clause, location });
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Visit all expressions in the tree. The search can be exited early by
|
||||
/// returning [Some]
|
||||
///
|
||||
/// See also [parsed::Expr::search_all]
|
||||
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::Lambda { body, .. } => body.search_all(predicate),
|
||||
Clause::Constant(_)
|
||||
| Clause::LambdaArg
|
||||
| Clause::Atom(_)
|
||||
| Clause::Bottom(_) => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Expr {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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 AsDerefMut<Clause> for Expr {
|
||||
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)]
|
||||
pub struct ClauseInst(pub Arc<Mutex<Clause>>);
|
||||
impl ClauseInst {
|
||||
/// Wrap a [Clause] in a shared container so that normalization steps are
|
||||
/// applied to all references
|
||||
#[must_use]
|
||||
pub fn new(cls: Clause) -> Self { Self(Arc::new(Mutex::new(cls))) }
|
||||
|
||||
/// Take the [Clause] out of this container if it's the last reference to it,
|
||||
/// or return self.
|
||||
pub fn try_unwrap(self) -> Result<Clause, 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.
|
||||
pub fn try_normalize<T>(
|
||||
&self,
|
||||
mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>,
|
||||
) -> Result<(ClauseInst, T), RunError> {
|
||||
enum Report<T> {
|
||||
Nested(ClauseInst, T),
|
||||
Plain(T),
|
||||
}
|
||||
let ret = take_with_output(&mut *self.cls_mut(), |clause| match &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),
|
||||
})
|
||||
}
|
||||
|
||||
/// 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() {
|
||||
Clause::Identity(sub) => sub.inspect(predicate),
|
||||
x => predicate(x),
|
||||
}
|
||||
}
|
||||
|
||||
/// If this expression is an [Atomic], request an object of the given type.
|
||||
/// 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() {
|
||||
Clause::Atom(a) => request(&*a.0),
|
||||
Clause::Identity(alt) => alt.request(),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Associate a location with this clause
|
||||
pub fn to_expr(self, location: CodeLocation) -> Expr {
|
||||
Expr { clause: self.clone(), location: location.clone() }
|
||||
}
|
||||
/// Check ahead-of-time if this clause contains an atom. Calls
|
||||
/// [ClauseInst#cls] for read access.
|
||||
///
|
||||
/// 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(_)) }
|
||||
|
||||
/// 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
|
||||
/// [Clause::Identity].
|
||||
///
|
||||
/// 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() {
|
||||
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::LambdaArg => Clause::LambdaArg,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr:?}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for ClauseInst {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.0.try_lock() {
|
||||
Ok(expr) => write!(f, "{expr}"),
|
||||
Err(TryLockError::Poisoned(_)) => write!(f, "<poisoned>"),
|
||||
Err(TryLockError::WouldBlock) => write!(f, "<locked>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for ClauseInst {
|
||||
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),
|
||||
/// Indicates that this [ClauseInst] has the same value as the other
|
||||
/// [ClauseInst]. This has two benefits;
|
||||
///
|
||||
/// - [Clause] and therefore [Atomic] doesn't have to be [Clone] which saves
|
||||
/// many synchronization primitives and reference counters in usercode
|
||||
/// - it enforces on the type level that all copies are normalized together,
|
||||
/// so accidental inefficiency in the interpreter is rarer.
|
||||
///
|
||||
/// That being said, it's still arbitrary many indirections, so when possible
|
||||
/// APIs should be usable with a [ClauseInst] directly.
|
||||
Identity(ClauseInst),
|
||||
/// An opaque non-callable value, eg. a file handle
|
||||
Atom(Atom),
|
||||
/// A function application
|
||||
Apply {
|
||||
/// Function to be applied
|
||||
f: Expr,
|
||||
/// Argument to be substituted in the function
|
||||
x: VecDeque<Expr>,
|
||||
},
|
||||
/// A name to be looked up in the interpreter's symbol table
|
||||
Constant(Sym),
|
||||
/// A function
|
||||
Lambda {
|
||||
/// A collection of (zero or more) paths to placeholders belonging to this
|
||||
/// function
|
||||
args: Option<PathSet>,
|
||||
/// The tree produced by this function, with placeholders where the
|
||||
/// argument will go
|
||||
body: Expr,
|
||||
},
|
||||
/// A placeholder within a function that will be replaced upon application
|
||||
LambdaArg,
|
||||
}
|
||||
impl Clause {
|
||||
/// Wrap a clause in a refcounted lock
|
||||
pub fn to_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)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Clause {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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::Lambda { args, body } => match args {
|
||||
Some(path) => write!(f, "\\{path:?}.{body}"),
|
||||
None => write!(f, "\\_.{body}"),
|
||||
},
|
||||
Clause::Constant(t) => write!(f, "{t}"),
|
||||
Clause::Identity(other) => write!(f, "({other})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsDerefMut<Clause> for Clause {
|
||||
fn as_deref_mut(&mut self) -> impl DerefMut<Target = Clause> + '_ { self }
|
||||
}
|
||||
149
src/interpreter/nort_builder.rs
Normal file
149
src/interpreter/nort_builder.rs
Normal file
@@ -0,0 +1,149 @@
|
||||
use std::cell::RefCell;
|
||||
use std::mem;
|
||||
|
||||
use substack::Substack;
|
||||
|
||||
use super::nort::{AsDerefMut, Clause, Expr};
|
||||
use super::path_set::PathSet;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
enum IntGenData<'a, T: ?Sized> {
|
||||
Lambda(&'a T, &'a RefCell<Option<PathSet>>),
|
||||
/// Counts left steps within a chain of [Clause::Apply] for collapsing.
|
||||
Apply(&'a RefCell<usize>),
|
||||
/// Replaces [IntGenData::Apply] when stepping left into non-apply to record
|
||||
/// a [None] [super::path_set::Step].
|
||||
AppF,
|
||||
/// Replaces [IntGenData::Apply] when stepping right to freeze the value.
|
||||
AppArg(usize),
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> Copy for IntGenData<'a, T> {}
|
||||
impl<'a, T: ?Sized> Clone for IntGenData<'a, T> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
|
||||
struct ArgCollector(RefCell<Option<PathSet>>);
|
||||
impl ArgCollector {
|
||||
pub fn new() -> Self { Self(RefCell::new(None)) }
|
||||
pub fn into_path(self) -> Option<PathSet> { self.0.into_inner() }
|
||||
}
|
||||
|
||||
/// Strategy used to find the lambda corresponding to a given argument in the
|
||||
/// stack. The function is called on the data associated with the argument, then
|
||||
/// 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>;
|
||||
|
||||
/// Bundle of information passed down through recursive fnuctions to instantiate
|
||||
/// runtime [Expr], [super::nort::ClauseInst] or [Clause].
|
||||
///
|
||||
/// The context used by [crate::gen::traits::Gen] to convert templates is which
|
||||
/// includes this type is constructed with [super::gen_nort::nort_gen].
|
||||
pub struct NortBuilder<'a, T: ?Sized, U: ?Sized> {
|
||||
stack: Substack<'a, IntGenData<'a, T>>,
|
||||
lambda_picker: LambdaPicker<'a, T, U>,
|
||||
}
|
||||
impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> {
|
||||
/// Create a new recursive [super::nort] builder from a location that will be
|
||||
pub fn new(lambda_picker: LambdaPicker<'a, T, U>) -> Self {
|
||||
Self { stack: Substack::Bottom, lambda_picker }
|
||||
}
|
||||
/// [Substack::pop] and clone the location
|
||||
fn pop<'b>(&'b self, count: usize) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = *self.stack.pop(count);
|
||||
new
|
||||
}
|
||||
/// [Substack::push] and clone the location
|
||||
fn push<'b>(&'b self, data: IntGenData<'a, T>) -> NortBuilder<'b, T, U>
|
||||
where 'a: 'b {
|
||||
let mut new = *self;
|
||||
new.stack = self.stack.push(data);
|
||||
new
|
||||
}
|
||||
fn non_app_step<V>(self, f: impl FnOnce(NortBuilder<T, U>) -> V) -> V {
|
||||
if let Some(IntGenData::Apply(_)) = self.stack.value() {
|
||||
let prev = self.pop(1);
|
||||
f(prev.push(IntGenData::AppF))
|
||||
} else {
|
||||
f(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Climb back through the stack and find a lambda associated with this
|
||||
/// argument, then record the path taken from the lambda to this argument in
|
||||
/// the lambda's mutable cell.
|
||||
pub fn arg_logic(self, name: &'a U) {
|
||||
let mut lambda_chk = (self.lambda_picker)(name);
|
||||
self.non_app_step(|ctx| {
|
||||
let opt = ctx.stack.rfold(None, |path, item| match item {
|
||||
IntGenData::Apply(_) => panic!("This is removed after handling"),
|
||||
IntGenData::Lambda(n, rc) =>
|
||||
lambda_chk(n).then(|| (vec![], *rc)).or(path),
|
||||
IntGenData::AppArg(n) => path.map(|(p, rc)| (pushed(p, Some(*n)), rc)),
|
||||
IntGenData::AppF => path.map(|(p, rc)| (pushed(p, None), rc)),
|
||||
});
|
||||
let (path, slot) = opt.expect("Argument not wrapped in matching lambda");
|
||||
match &mut *slot.borrow_mut() {
|
||||
slot @ None => *slot = Some(PathSet::end(path)),
|
||||
Some(slot) => take_mut::take(slot, |p| p.overlay(PathSet::end(path))),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
let coll = ArgCollector::new();
|
||||
let frame = IntGenData::Lambda(name, &coll.0);
|
||||
let body = self.non_app_step(|ctx| body(ctx.push(frame)));
|
||||
let args = coll.into_path();
|
||||
Clause::Lambda { args, body }
|
||||
}
|
||||
|
||||
/// Logic for collapsing Apply clauses. Different steps of the logic
|
||||
/// communicate via mutable variables on the stack
|
||||
pub fn apply_logic(
|
||||
self,
|
||||
f: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
x: impl FnOnce(NortBuilder<T, U>) -> Expr,
|
||||
) -> Clause {
|
||||
let mut fun: Expr;
|
||||
let arg: Expr;
|
||||
if let Some(IntGenData::Apply(rc)) = self.stack.value() {
|
||||
// argument side commits backidx
|
||||
arg = x(self.pop(1).push(IntGenData::AppArg(*rc.borrow())));
|
||||
// function side increments backidx
|
||||
*rc.borrow_mut() += 1;
|
||||
fun = f(self);
|
||||
} else {
|
||||
// function side starts from backidx 1
|
||||
fun = f(self.push(IntGenData::Apply(&RefCell::new(1))));
|
||||
// argument side commits 0
|
||||
arg = x(self.push(IntGenData::AppArg(0)));
|
||||
};
|
||||
let mut cls_lk = fun.as_deref_mut();
|
||||
if let Clause::Apply { x, f: _ } = &mut *cls_lk {
|
||||
x.push_back(arg);
|
||||
mem::drop(cls_lk);
|
||||
fun.clause.into_cls()
|
||||
} else {
|
||||
mem::drop(cls_lk);
|
||||
Clause::Apply { f: fun, x: [arg].into() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized, U: ?Sized> Copy for NortBuilder<'a, T, U> {}
|
||||
impl<'a, T: ?Sized, U: ?Sized> Clone for NortBuilder<'a, T, U> {
|
||||
fn clone(&self) -> Self { *self }
|
||||
}
|
||||
165
src/interpreter/path_set.rs
Normal file
165
src/interpreter/path_set.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::fmt;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::utils::join::join_maps;
|
||||
|
||||
/// A step into a [super::nort::Clause::Apply]. If [None], it steps to the
|
||||
/// 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() }
|
||||
}
|
||||
|
||||
/// A branching path selecting some placeholders (but at least one) in a Lambda
|
||||
/// expression
|
||||
#[derive(Clone)]
|
||||
pub struct PathSet {
|
||||
/// The single steps through [super::nort::Clause::Apply]
|
||||
pub steps: VecDeque<Step>,
|
||||
/// if Some, it splits at a [super::nort::Clause::Apply]. If None, it ends in
|
||||
/// a [super::nort::Clause::LambdaArg]
|
||||
pub next: Option<HashMap<Step, PathSet>>,
|
||||
}
|
||||
|
||||
impl PathSet {
|
||||
/// Create a path set for more than one target
|
||||
pub fn branch(
|
||||
steps: impl IntoIterator<Item = Step>,
|
||||
conts: impl IntoIterator<Item = (Step, Self)>,
|
||||
) -> Self {
|
||||
let conts = conts.into_iter().collect::<HashMap<_, _>>();
|
||||
assert!(1 < conts.len(), "Branching pathsets need multiple continuations");
|
||||
Self { steps: steps.into_iter().collect(), next: Some(conts) }
|
||||
}
|
||||
|
||||
/// Create a path set for one target
|
||||
pub fn end(steps: impl IntoIterator<Item = Step>) -> Self {
|
||||
Self { steps: steps.into_iter().collect(), next: None }
|
||||
}
|
||||
|
||||
/// Create a path set that points to a slot that is a direct
|
||||
/// child of the given lambda with no applications. In essence, this means
|
||||
/// that this argument will be picked as the value of the expression after an
|
||||
/// arbitrary amount of subsequent discarded parameters.
|
||||
pub fn pick() -> Self { Self { steps: VecDeque::new(), next: None } }
|
||||
|
||||
/// Merge two paths into one path that points to all targets of both. Only
|
||||
/// works if both paths select leaf nodes of the same partial tree.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// if either path selects a node the other path dissects
|
||||
pub fn overlay(self, other: Self) -> Self {
|
||||
let (mut short, mut long) = match self.steps.len() < other.steps.len() {
|
||||
true => (self, other),
|
||||
false => (other, self),
|
||||
};
|
||||
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();
|
||||
// fact: match_len <= short_len <= long_len
|
||||
if short_len == match_len && match_len == long_len {
|
||||
// implies match_len == short_len == long_len
|
||||
match (short.next, long.next) {
|
||||
(None, None) => Self::end(short.steps.iter().cloned()),
|
||||
(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)),
|
||||
),
|
||||
}
|
||||
} else if short_len == match_len {
|
||||
// implies match_len == short_len < long_len
|
||||
// long.steps[0..match_len] is in steps
|
||||
// long.steps[match_len] becomes the choice of branch below
|
||||
// long.steps[match_len + 1..] is in tail
|
||||
let mut conts = short.next.expect("One path ends inside the other");
|
||||
let tail_steps = long.steps.split_off(match_len + 1);
|
||||
let tail = match long.next {
|
||||
Some(n) => Self::branch(tail_steps, n),
|
||||
None => Self::end(tail_steps),
|
||||
};
|
||||
let branch = long.steps[match_len];
|
||||
let prev_c = conts.remove(&branch);
|
||||
let new_c = if let Some(x) = prev_c { x.overlay(tail) } else { tail };
|
||||
conts.insert(branch, new_c);
|
||||
Self::branch(short.steps, conts)
|
||||
} else {
|
||||
// implies match_len < short_len <= long_len
|
||||
// steps[0..match_len] is in shared
|
||||
// steps[match_len] become the branches below
|
||||
// steps[match_len + 1..] is in new_long and new_short
|
||||
let new_short_steps = short.steps.split_off(match_len + 1);
|
||||
let short_last = short.steps.pop_back().expect("split at n + 1");
|
||||
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),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
/// Prepend a step to a path. If it had previously started at a node that is
|
||||
/// at the specified step within an Apply clause, it now starts at the Apply.
|
||||
///
|
||||
/// This is only valid if the new Apply is **separate** from the previous
|
||||
/// root.
|
||||
pub fn prepend(&mut self, step: Step) { self.steps.push_front(step); }
|
||||
}
|
||||
|
||||
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("");
|
||||
match &self.next {
|
||||
Some(conts) => {
|
||||
let opts =
|
||||
conts.iter().map(|(h, t)| format!("{}{t}", print_step(*h))).join("|");
|
||||
write!(f, "{step_s}({opts})")
|
||||
},
|
||||
None => write!(f, "{step_s}x"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PathSet {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "PathSet({self})")
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_combine() {
|
||||
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)");
|
||||
}
|
||||
|
||||
fn extend_scaffold() -> PathSet {
|
||||
PathSet::branch([None, Some(1), None], [
|
||||
(None, PathSet::end([None, Some(1)])),
|
||||
(Some(1), PathSet::end([None, Some(2)])),
|
||||
])
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extend_noclone() {
|
||||
let mut ps = extend_scaffold();
|
||||
ps.prepend(Some(0));
|
||||
assert_eq!(format!("{ps}"), "0>f>1>f>(f>f>1|1>f>2)");
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,101 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use hashbrown::HashMap;
|
||||
|
||||
use super::apply::apply;
|
||||
use super::context::{Context, Return};
|
||||
use super::error::RuntimeError;
|
||||
use crate::foreign::AtomicReturn;
|
||||
use crate::representations::interpreted::{Clause, ExprInst};
|
||||
use super::context::{Halt, RunContext};
|
||||
use super::error::RunError;
|
||||
use super::nort::{Clause, Expr};
|
||||
use crate::foreign::atom::AtomicReturn;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::location::CodeLocation;
|
||||
use crate::name::Sym;
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
/// Information about a normalization run presented to an atom
|
||||
#[derive(Clone)]
|
||||
pub struct RunData<'a> {
|
||||
/// Location of the atom
|
||||
pub location: CodeLocation,
|
||||
/// Information about the execution
|
||||
pub ctx: RunContext<'a>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interrupted {
|
||||
stack: Vec<Expr>,
|
||||
}
|
||||
impl Interrupted {
|
||||
pub fn resume(self, ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(self.stack, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
/// Normalize an expression using beta reduction with memoization
|
||||
pub fn run(expr: ExprInst, mut ctx: Context) -> Result<Return, RuntimeError> {
|
||||
let (state, (gas, inert)) = expr.try_normalize(
|
||||
|mut cls, loc| -> Result<(Clause, _), RuntimeError> {
|
||||
while ctx.gas.map(|g| g > 0).unwrap_or(true) {
|
||||
match cls {
|
||||
Clause::Apply { f, x } => {
|
||||
let res = apply(f, x, ctx.clone())?;
|
||||
if res.inert {
|
||||
return Ok((res.state.expr_val().clause, (res.gas, true)));
|
||||
pub fn run(mut expr: Expr, mut ctx: RunContext) -> Result<Halt, RunError> {
|
||||
run_stack(vec![expr], ctx)
|
||||
}
|
||||
|
||||
fn run_stack(
|
||||
mut stack: Vec<Expr>,
|
||||
mut ctx: RunContext,
|
||||
) -> Result<Halt, RunError> {
|
||||
let mut expr = stack.pop().expect("Empty stack");
|
||||
loop {
|
||||
if ctx.no_gas() {
|
||||
return Err(RunError::Interrupted(Interrupted {
|
||||
stack: pushed(stack, expr),
|
||||
}));
|
||||
}
|
||||
ctx.gas = res.gas;
|
||||
cls = res.state.expr().clause.clone();
|
||||
let (next_clsi, inert) = expr.clause.try_normalize(|mut cls| {
|
||||
loop {
|
||||
if ctx.no_gas() {
|
||||
return Ok((cls, false));
|
||||
}
|
||||
match cls {
|
||||
cls @ Clause::Identity(_) => return Ok((cls, false)),
|
||||
// TODO:
|
||||
// - unfuck nested loop
|
||||
// - inline most of [apply] to eliminate recursion step
|
||||
Clause::Apply { f, x } => {
|
||||
if x.is_empty() {
|
||||
return Ok((f.clause.into_cls(), false));
|
||||
}
|
||||
let (gas, clause) = apply(f, x, ctx.clone())?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = gas;
|
||||
}
|
||||
cls = clause;
|
||||
},
|
||||
Clause::Atom(data) => {
|
||||
let AtomicReturn { clause, gas, inert } = data.run(ctx.clone())?;
|
||||
if inert {
|
||||
return Ok((clause, (gas, true)));
|
||||
let run = RunData { ctx: ctx.clone(), location: expr.location() };
|
||||
let atomic_ret = data.run(run)?;
|
||||
if ctx.gas.is_some() {
|
||||
ctx.gas = atomic_ret.gas;
|
||||
}
|
||||
ctx.gas = gas;
|
||||
cls = clause;
|
||||
if atomic_ret.inert {
|
||||
return Ok((atomic_ret.clause, true));
|
||||
}
|
||||
cls = atomic_ret.clause;
|
||||
},
|
||||
Clause::Constant(c) => {
|
||||
let symval = (ctx.symbols.get(&c)).ok_or_else(|| {
|
||||
RuntimeError::MissingSymbol(c.clone(), loc.clone())
|
||||
RunError::MissingSymbol(c.clone(), expr.location())
|
||||
})?;
|
||||
ctx.gas = ctx.gas.map(|g| g - 1); // cost of lookup
|
||||
cls = symval.expr().clause.clone();
|
||||
cls = Clause::Identity(symval.clause.clone());
|
||||
},
|
||||
// non-reducible
|
||||
_ => return Ok((cls, (ctx.gas, true))),
|
||||
c => return Ok((c, true)),
|
||||
};
|
||||
}
|
||||
})?;
|
||||
expr.clause = next_clsi;
|
||||
if inert {
|
||||
match stack.pop() {
|
||||
Some(e) => expr = e,
|
||||
None => return Ok(Halt { state: expr, gas: ctx.gas, inert }),
|
||||
}
|
||||
}
|
||||
}
|
||||
// out of gas
|
||||
Ok((cls, (ctx.gas, false)))
|
||||
},
|
||||
)?;
|
||||
Ok(Return { state, gas, inert })
|
||||
}
|
||||
|
||||
28
src/lib.rs
28
src/lib.rs
@@ -10,27 +10,15 @@
|
||||
pub mod error;
|
||||
pub mod facade;
|
||||
pub mod foreign;
|
||||
pub mod interner;
|
||||
pub mod gen;
|
||||
pub mod intermediate;
|
||||
pub mod interpreter;
|
||||
pub mod libs;
|
||||
pub mod location;
|
||||
pub mod name;
|
||||
pub mod parse;
|
||||
pub mod pipeline;
|
||||
mod representations;
|
||||
pub mod rule;
|
||||
pub mod systems;
|
||||
mod utils;
|
||||
|
||||
pub use interner::{Interner, Tok};
|
||||
pub use pipeline::file_loader::{mk_dir_cache, mk_embed_cache};
|
||||
pub use pipeline::parse_layer;
|
||||
/// Element of VName and a common occurrence in the API
|
||||
pub type Stok = Tok<String>;
|
||||
pub use representations::ast_to_interpreted::ast_to_interpreted;
|
||||
pub use representations::project::{
|
||||
collect_consts, collect_rules, vname_to_sym_tree, ProjectTree,
|
||||
};
|
||||
pub use representations::{
|
||||
ast, from_const_tree, interpreted, sourcefile, tree, ConstTree, Location,
|
||||
NameLike, OrcString, PathSet, Sym, VName,
|
||||
};
|
||||
pub use utils::substack::Substack;
|
||||
pub use utils::{ddispatch, take_with_output, thread_pool, IdMap, Side};
|
||||
pub mod tree;
|
||||
pub mod utils;
|
||||
pub mod virt_fs;
|
||||
|
||||
@@ -3,15 +3,10 @@ use std::sync::{Arc, Mutex};
|
||||
pub struct DeleteCell<T>(pub Arc<Mutex<Option<T>>>);
|
||||
impl<T> DeleteCell<T> {
|
||||
pub fn new(t: T) -> Self { Self(Arc::new(Mutex::new(Some(t)))) }
|
||||
|
||||
pub fn take(&self) -> Option<T> { self.0.lock().unwrap().take() }
|
||||
|
||||
pub fn clone_out(&self) -> Option<T>
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.0.lock().unwrap().clone()
|
||||
}
|
||||
}
|
||||
impl<T: Clone> DeleteCell<T> {
|
||||
pub fn clone_out(&self) -> Option<T> { self.0.lock().unwrap().clone() }
|
||||
}
|
||||
impl<T> Clone for DeleteCell<T> {
|
||||
fn clone(&self) -> Self { Self(self.0.clone()) }
|
||||
@@ -4,6 +4,6 @@
|
||||
//! beyond being general Rust functions.
|
||||
//! It also exposes timers.
|
||||
|
||||
mod system;
|
||||
|
||||
pub use system::{AsynchSystem, InfiniteBlock, MessagePort};
|
||||
pub mod poller;
|
||||
pub mod system;
|
||||
mod delete_cell;
|
||||
@@ -1,10 +1,12 @@
|
||||
//! Abstract implementation of the poller
|
||||
|
||||
use std::collections::BinaryHeap;
|
||||
use std::mem;
|
||||
use std::sync::mpsc::{channel, Receiver, RecvError, RecvTimeoutError, Sender};
|
||||
use std::thread::sleep;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::utils::DeleteCell;
|
||||
use super::delete_cell::DeleteCell;
|
||||
|
||||
enum TimerKind<TOnce, TRec> {
|
||||
Once(DeleteCell<TOnce>),
|
||||
@@ -27,8 +29,8 @@ impl<TOnce, TRec> Clone for TimerKind<TOnce, TRec> {
|
||||
/// [Ord] implemenetation of this struct is reversed; it can be intuitively
|
||||
/// thought of as ordering by urgency.
|
||||
struct Timer<TOnce, TRec> {
|
||||
pub expires: Instant,
|
||||
pub kind: TimerKind<TOnce, TRec>,
|
||||
expires: Instant,
|
||||
kind: TimerKind<TOnce, TRec>,
|
||||
}
|
||||
impl<TOnce, TRec> Clone for Timer<TOnce, TRec> {
|
||||
fn clone(&self) -> Self {
|
||||
@@ -50,42 +52,55 @@ impl<TOnce, TRec> Ord for Timer<TOnce, TRec> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Representation of a scheduled timer
|
||||
#[derive(Clone)]
|
||||
pub struct TimerHandle<T>(DeleteCell<T>);
|
||||
impl<T> TimerHandle<T> {
|
||||
/// Cancel the timer
|
||||
pub fn cancel(self) { mem::drop(self.0.take()) }
|
||||
}
|
||||
|
||||
/// The abstract event poller implementation used by the standard asynch
|
||||
/// subsystem.
|
||||
pub struct Poller<TEv, TOnce, TRec: Clone> {
|
||||
timers: BinaryHeap<Timer<TOnce, TRec>>,
|
||||
receiver: Receiver<TEv>,
|
||||
}
|
||||
|
||||
impl<TEv, TOnce, TRec: Clone + Send> Poller<TEv, TOnce, TRec> {
|
||||
impl<TEv, TOnce, TRec: Clone> Poller<TEv, TOnce, TRec> {
|
||||
/// Create an event poller and a [Sender] that can produce events on it.
|
||||
pub fn new() -> (Sender<TEv>, Self) {
|
||||
let (sender, receiver) = channel();
|
||||
let this = Self { receiver, timers: BinaryHeap::new() };
|
||||
(sender, this)
|
||||
}
|
||||
|
||||
/// Set a single-fire timer
|
||||
pub fn set_timeout(
|
||||
&mut self,
|
||||
duration: Duration,
|
||||
data: TOnce,
|
||||
) -> impl Fn() + Clone {
|
||||
) -> TimerHandle<TOnce> {
|
||||
let data_cell = DeleteCell::new(data);
|
||||
self.timers.push(Timer {
|
||||
kind: TimerKind::Once(data_cell.clone()),
|
||||
expires: Instant::now() + duration,
|
||||
});
|
||||
move || mem::drop(data_cell.take())
|
||||
TimerHandle(data_cell)
|
||||
}
|
||||
|
||||
/// Set a recurring timer
|
||||
pub fn set_interval(
|
||||
&mut self,
|
||||
period: Duration,
|
||||
data: TRec,
|
||||
) -> impl Fn() + Send + Clone {
|
||||
) -> TimerHandle<TRec> {
|
||||
let data_cell = DeleteCell::new(data);
|
||||
self.timers.push(Timer {
|
||||
expires: Instant::now() + period,
|
||||
kind: TimerKind::Recurring { period, data_cell: data_cell.clone() },
|
||||
});
|
||||
move || mem::drop(data_cell.take())
|
||||
TimerHandle(data_cell)
|
||||
}
|
||||
|
||||
/// Process a timer popped from the timers heap of this event loop.
|
||||
@@ -140,8 +155,12 @@ impl<TEv, TOnce, TRec: Clone + Send> Poller<TEv, TOnce, TRec> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Events produced by [Poller].
|
||||
pub enum PollEvent<TEv, TOnce, TRec> {
|
||||
/// An event was sent to the [Sender] associated with the [Poller].
|
||||
Event(TEv),
|
||||
/// A single-fire timer expired
|
||||
Once(TOnce),
|
||||
/// A recurring event fired
|
||||
Recurring(TRec),
|
||||
}
|
||||
@@ -1,3 +1,7 @@
|
||||
//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the
|
||||
//! I/O subsystem. Also many other systems depend on it, these take a mut ref to
|
||||
//! register themselves.
|
||||
|
||||
use std::any::{type_name, Any, TypeId};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::VecDeque;
|
||||
@@ -11,17 +15,23 @@ use hashbrown::HashMap;
|
||||
use ordered_float::NotNan;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::{init_cps, CPSBox};
|
||||
use crate::foreign::{xfn_2ary, Atomic, ExternError, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::pipeline::file_loader::embed_to_map;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::stl::Numeric;
|
||||
use crate::utils::poller::{PollEvent, Poller};
|
||||
use crate::utils::unwrap_or;
|
||||
use crate::{ConstTree, Interner};
|
||||
use super::poller::{PollEvent, Poller, TimerHandle};
|
||||
use crate::facade::system::{IntoSystem, System};
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::error::ExternError;
|
||||
use crate::foreign::fn_bridge::constructors::xfn_2ary;
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::handler::HandlerTable;
|
||||
use crate::interpreter::nort::Expr;
|
||||
use crate::libs::std::number::Numeric;
|
||||
use crate::location::{CodeGenInfo, CodeLocation};
|
||||
use crate::utils::unwrap_or::unwrap_or;
|
||||
use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Timer {
|
||||
@@ -29,28 +39,28 @@ struct Timer {
|
||||
delay: NotNan<f64>,
|
||||
}
|
||||
|
||||
pub fn set_timer(recurring: bool, delay: Numeric) -> XfnResult<Clause> {
|
||||
Ok(init_cps(2, Timer { recurring, delay: delay.as_float() }))
|
||||
fn set_timer(rec: Inert<bool>, delay: Numeric) -> CPSBox<Timer> {
|
||||
CPSBox::new(2, Timer { recurring: rec.0, delay: delay.as_float() })
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CancelTimer(Arc<Mutex<dyn Fn() + Send>>);
|
||||
impl CancelTimer {
|
||||
pub fn new(f: impl Fn() + Send + 'static) -> Self {
|
||||
Self(Arc::new(Mutex::new(f)))
|
||||
pub fn new<T: Send + Clone + 'static>(canceller: TimerHandle<T>) -> Self {
|
||||
Self(Arc::new(Mutex::new(move || canceller.clone().cancel())))
|
||||
}
|
||||
pub fn cancel(&self) { self.0.lock().unwrap()() }
|
||||
}
|
||||
impl Debug for CancelTimer {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "opaque cancel operation")
|
||||
f.debug_struct("CancelTimer").finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Yield;
|
||||
impl InertAtomic for Yield {
|
||||
fn type_str() -> &'static str { "a yield command" }
|
||||
impl InertPayload for Yield {
|
||||
const TYPE_STR: &'static str = "asynch::yield";
|
||||
}
|
||||
|
||||
/// Error indicating a yield command when all event producers and timers had
|
||||
@@ -76,17 +86,24 @@ impl MessagePort {
|
||||
}
|
||||
}
|
||||
|
||||
fn gen() -> CodeGenInfo { CodeGenInfo::no_details("asynch") }
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/systems/asynch"]
|
||||
#[prefix = "system/"]
|
||||
#[folder = "src/libs/asynch"]
|
||||
#[include = "*.orc"]
|
||||
struct AsynchEmbed;
|
||||
|
||||
type AnyHandler<'a> = Box<dyn FnMut(Box<dyn Any>) -> Vec<ExprInst> + 'a>;
|
||||
fn code() -> DeclTree {
|
||||
DeclTree::ns("system::async", [DeclTree::leaf(
|
||||
PrefixFS::new(EmbeddedFS::new::<AsynchEmbed>(".orc", gen()), "", "io").rc(),
|
||||
)])
|
||||
}
|
||||
|
||||
type AnyHandler<'a> = Box<dyn FnMut(Box<dyn Any>) -> Vec<Expr> + 'a>;
|
||||
|
||||
/// Datastructures the asynch system will eventually be constructed from.
|
||||
pub struct AsynchSystem<'a> {
|
||||
poller: Poller<Box<dyn Any + Send>, ExprInst, ExprInst>,
|
||||
poller: Poller<Box<dyn Any + Send>, Expr, Expr>,
|
||||
sender: Sender<Box<dyn Any + Send>>,
|
||||
handlers: HashMap<TypeId, AnyHandler<'a>>,
|
||||
}
|
||||
@@ -109,7 +126,7 @@ impl<'a> AsynchSystem<'a> {
|
||||
/// if the given type is already handled.
|
||||
pub fn register<T: 'static>(
|
||||
&mut self,
|
||||
mut f: impl FnMut(Box<T>) -> Vec<ExprInst> + 'a,
|
||||
mut f: impl FnMut(Box<T>) -> Vec<Expr> + 'a,
|
||||
) {
|
||||
let cb = move |a: Box<dyn Any>| f(a.downcast().expect("keyed by TypeId"));
|
||||
let prev = self.handlers.insert(TypeId::of::<T>(), Box::new(cb));
|
||||
@@ -132,39 +149,40 @@ impl<'a> Default for AsynchSystem<'a> {
|
||||
}
|
||||
|
||||
impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
fn into_system(self, i: &Interner) -> System<'a> {
|
||||
fn into_system(self) -> System<'a> {
|
||||
let Self { mut handlers, poller, .. } = self;
|
||||
let mut handler_table = HandlerTable::new();
|
||||
let polly = Rc::new(RefCell::new(poller));
|
||||
handler_table.register({
|
||||
let polly = polly.clone();
|
||||
move |t: Box<CPSBox<Timer>>| {
|
||||
move |t: &CPSBox<Timer>| {
|
||||
let mut polly = polly.borrow_mut();
|
||||
let (timeout, action, cont) = t.unpack2();
|
||||
let duration = Duration::from_secs_f64(*timeout.delay);
|
||||
let cancel_timer = match timeout.recurring {
|
||||
let (Timer { delay, recurring }, action, cont) = t.unpack2();
|
||||
let duration = Duration::from_secs_f64(**delay);
|
||||
let cancel_timer = match *recurring {
|
||||
true => CancelTimer::new(polly.set_interval(duration, action)),
|
||||
false => CancelTimer::new(polly.set_timeout(duration, action)),
|
||||
};
|
||||
Ok(call(cont, [init_cps(1, cancel_timer).wrap()]).wrap())
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel_timer)));
|
||||
tpl.template(nort_gen(cont.location()), [cont])
|
||||
}
|
||||
});
|
||||
handler_table.register(move |t: Box<CPSBox<CancelTimer>>| {
|
||||
handler_table.register(move |t: &CPSBox<CancelTimer>| {
|
||||
let (command, cont) = t.unpack1();
|
||||
command.cancel();
|
||||
Ok(cont)
|
||||
cont
|
||||
});
|
||||
handler_table.register({
|
||||
let polly = polly.clone();
|
||||
let mut microtasks = VecDeque::new();
|
||||
move |_: Box<Yield>| {
|
||||
move |_: &Inert<Yield>| {
|
||||
if let Some(expr) = microtasks.pop_front() {
|
||||
return Ok(expr);
|
||||
}
|
||||
let mut polly = polly.borrow_mut();
|
||||
loop {
|
||||
let next = unwrap_or!(polly.run();
|
||||
return Err(InfiniteBlock.into_extern())
|
||||
return Err(InfiniteBlock.rc())
|
||||
);
|
||||
match next {
|
||||
PollEvent::Once(expr) => return Ok(expr),
|
||||
@@ -179,7 +197,9 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
if !events.is_empty() {
|
||||
microtasks = VecDeque::from(events);
|
||||
// trampoline
|
||||
return Ok(Yield.atom_exi());
|
||||
let loc =
|
||||
CodeLocation::Gen(CodeGenInfo::no_details("system::asynch"));
|
||||
return Ok(Inert(Yield).atom_expr(loc));
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -187,18 +207,14 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> {
|
||||
}
|
||||
});
|
||||
System {
|
||||
name: vec!["system".to_string(), "asynch".to_string()],
|
||||
name: "system::asynch",
|
||||
lexer_plugins: vec![],
|
||||
line_parsers: vec![],
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("async")],
|
||||
ConstTree::tree([
|
||||
(i.i("set_timer"), ConstTree::xfn(xfn_2ary(set_timer))),
|
||||
(i.i("yield"), ConstTree::atom(Yield)),
|
||||
]),
|
||||
)
|
||||
.unwrap_tree(),
|
||||
code: embed_to_map::<AsynchEmbed>(".orc", i),
|
||||
constants: ConstTree::ns("system::async", [ConstTree::tree([
|
||||
("set_timer", atom_leaf(xfn_2ary(set_timer))),
|
||||
("yield", atom_leaf(Inert(Yield))),
|
||||
])]),
|
||||
code: code(),
|
||||
prelude: Vec::new(),
|
||||
handlers: handler_table,
|
||||
}
|
||||
201
src/libs/directfs/commands.rs
Normal file
201
src/libs/directfs/commands.rs
Normal file
@@ -0,0 +1,201 @@
|
||||
use std::ffi::OsString;
|
||||
use std::fs::File;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use super::osstring::os_string_lib;
|
||||
use crate::facade::system::{IntoSystem, System};
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary};
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::foreign::process::Unstable;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::gen::tree::{atom_ent, atom_leaf, ConstTree};
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::handler::HandlerTable;
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::libs::io::instances::io_error_handler;
|
||||
use crate::libs::io::{Sink, Source};
|
||||
use crate::libs::scheduler::system::{SeqScheduler, SharedHandle};
|
||||
use crate::libs::std::runtime_error::RuntimeError;
|
||||
use crate::utils::combine::Combine;
|
||||
use crate::virt_fs::DeclTree;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ReadFileCmd(OsString);
|
||||
impl InertPayload for ReadFileCmd {
|
||||
const TYPE_STR: &'static str = "readfile command";
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ReadDirCmd(OsString);
|
||||
impl InertPayload for ReadDirCmd {
|
||||
const TYPE_STR: &'static str = "readdir command";
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct WriteFile {
|
||||
name: OsString,
|
||||
append: bool,
|
||||
}
|
||||
impl InertPayload for WriteFile {
|
||||
const TYPE_STR: &'static str = "writefile command";
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn read_file(sched: &SeqScheduler, cmd: &CPSBox<ReadFileCmd>) -> Expr {
|
||||
let (ReadFileCmd(name), succ, fail, cont) = cmd.unpack3();
|
||||
let name = name.clone();
|
||||
let cancel = sched.run_orphan(
|
||||
move |_| File::open(name),
|
||||
|file, _| match file {
|
||||
Err(e) => vec![io_error_handler(e, fail)],
|
||||
Ok(f) => {
|
||||
let source_handle = SharedHandle::wrap(Source::new(Box::new(f)));
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(Inert(source_handle)));
|
||||
vec![tpl.template(nort_gen(succ.location()), [succ])]
|
||||
},
|
||||
},
|
||||
);
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel)));
|
||||
tpl.template(nort_gen(cont.location()), [cont])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn read_dir(sched: &SeqScheduler, cmd: &CPSBox<ReadDirCmd>) -> Expr {
|
||||
let (ReadDirCmd(name), succ, fail, cont) = cmd.unpack3();
|
||||
let name = name.clone();
|
||||
let cancel = sched.run_orphan(
|
||||
move |_| {
|
||||
Path::new(&name)
|
||||
.read_dir()?
|
||||
.map(|r| r.and_then(|e| Ok((e.file_name(), e.file_type()?.is_dir()))))
|
||||
.collect()
|
||||
},
|
||||
|items: std::io::Result<Vec<(OsString, bool)>>, _| match items {
|
||||
Err(e) => vec![io_error_handler(e, fail)],
|
||||
Ok(os_namev) => {
|
||||
let converted = (os_namev.into_iter())
|
||||
.map(|(n, d)| {
|
||||
Ok((
|
||||
Inert(n).atom_expr(succ.location()),
|
||||
Inert(d).atom_expr(succ.location()),
|
||||
))
|
||||
})
|
||||
.collect::<Result<Vec<_>, Clause>>();
|
||||
match converted {
|
||||
Err(e) => {
|
||||
let e = e.to_expr(fail.location());
|
||||
let tpl = tpl::A(tpl::Slot, tpl::Slot);
|
||||
vec![tpl.template(nort_gen(fail.location()), [fail, e])]
|
||||
},
|
||||
Ok(names) => {
|
||||
let names = names.to_expr(succ.location());
|
||||
let tpl = tpl::A(tpl::Slot, tpl::Slot);
|
||||
vec![tpl.template(nort_gen(succ.location()), [succ, names])]
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel)));
|
||||
tpl.template(nort_gen(cont.location()), [cont])
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn write_file(sched: &SeqScheduler, cmd: &CPSBox<WriteFile>) -> Expr {
|
||||
let (cmd, succ, fail, cont) = cmd.unpack3();
|
||||
let cmd = cmd.clone();
|
||||
let cancel = sched.run_orphan(
|
||||
move |_| File::options().write(true).append(cmd.append).open(&cmd.name),
|
||||
|file, _| match file {
|
||||
Err(e) => vec![io_error_handler(e, fail)],
|
||||
Ok(f) => {
|
||||
let sink_handle = SharedHandle::wrap(Box::new(f) as Sink);
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(Inert(sink_handle)));
|
||||
vec![tpl.template(nort_gen(succ.location()), [succ])]
|
||||
},
|
||||
},
|
||||
);
|
||||
let tpl = tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel)));
|
||||
tpl.template(nort_gen(cont.location()), [cont])
|
||||
}
|
||||
|
||||
fn open_file_read_cmd(name: OsString) -> CPSBox<ReadFileCmd> {
|
||||
CPSBox::new(3, ReadFileCmd(name))
|
||||
}
|
||||
|
||||
fn read_dir_cmd(name: OsString) -> CPSBox<ReadDirCmd> {
|
||||
CPSBox::new(3, ReadDirCmd(name))
|
||||
}
|
||||
|
||||
fn open_file_write_cmd(name: OsString) -> CPSBox<WriteFile> {
|
||||
CPSBox::new(3, WriteFile { name, append: false })
|
||||
}
|
||||
|
||||
fn open_file_append_cmd(name: OsString) -> CPSBox<WriteFile> {
|
||||
CPSBox::new(3, WriteFile { name, append: true })
|
||||
}
|
||||
|
||||
fn join_paths(root: OsString, sub: OsString) -> OsString {
|
||||
let mut path = PathBuf::from(root);
|
||||
path.push(sub);
|
||||
path.into_os_string()
|
||||
}
|
||||
|
||||
fn pop_path(
|
||||
path: Inert<OsString>,
|
||||
) -> Option<(Inert<OsString>, Inert<OsString>)> {
|
||||
let mut path = PathBuf::from(path.0);
|
||||
let sub = path.file_name()?.to_owned();
|
||||
debug_assert!(path.pop(), "file_name above returned Some");
|
||||
Some((Inert(path.into_os_string()), Inert(sub)))
|
||||
}
|
||||
|
||||
/// A rudimentary system to read and write files.
|
||||
#[derive(Clone)]
|
||||
pub struct DirectFS {
|
||||
scheduler: SeqScheduler,
|
||||
}
|
||||
impl DirectFS {
|
||||
/// Create a new instance of the system.
|
||||
pub fn new(scheduler: SeqScheduler) -> Self { Self { scheduler } }
|
||||
}
|
||||
|
||||
impl IntoSystem<'static> for DirectFS {
|
||||
fn into_system(self) -> System<'static> {
|
||||
let mut handlers = HandlerTable::new();
|
||||
let sched = self.scheduler.clone();
|
||||
handlers.register(move |cmd| read_file(&sched, cmd));
|
||||
let sched = self.scheduler.clone();
|
||||
handlers.register(move |cmd| read_dir(&sched, cmd));
|
||||
let sched = self.scheduler;
|
||||
handlers.register(move |cmd| write_file(&sched, cmd));
|
||||
System {
|
||||
name: "system::directfs",
|
||||
code: DeclTree::empty(),
|
||||
prelude: Vec::new(),
|
||||
lexer_plugins: vec![],
|
||||
line_parsers: vec![],
|
||||
constants: ConstTree::ns("system::fs", [ConstTree::tree([
|
||||
("read_file", atom_leaf(xfn_1ary(open_file_read_cmd))),
|
||||
("read_dir", atom_leaf(xfn_1ary(read_dir_cmd))),
|
||||
("write_file", atom_leaf(xfn_1ary(open_file_write_cmd))),
|
||||
("append_file", atom_leaf(xfn_1ary(open_file_append_cmd))),
|
||||
("join_paths", atom_leaf(xfn_2ary(join_paths))),
|
||||
("pop_path", atom_leaf(xfn_1ary(pop_path))),
|
||||
atom_ent("cwd", [Unstable::new(|_| -> ExternResult<_> {
|
||||
let path = std::env::current_dir()
|
||||
.map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?;
|
||||
Ok(Inert(path.into_os_string()))
|
||||
})]),
|
||||
])])
|
||||
.combine(os_string_lib())
|
||||
.expect("os_string library and directfs conflict"),
|
||||
handlers,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
//! A rudimentary system exposing methods for Orchid to interact with the file
|
||||
//! system. All paths are strings.
|
||||
//!
|
||||
//! The system depends on [crate::libs::scheduler] for scheduling blocking I/O
|
||||
//! on a separate thread.
|
||||
mod commands;
|
||||
mod osstring;
|
||||
|
||||
44
src/libs/directfs/osstring.rs
Normal file
44
src/libs/directfs/osstring.rs
Normal file
@@ -0,0 +1,44 @@
|
||||
use std::ffi::OsString;
|
||||
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::nort::{Clause, Expr};
|
||||
use crate::libs::std::string::OrcString;
|
||||
use crate::location::CodeLocation;
|
||||
|
||||
impl InertPayload for OsString {
|
||||
const TYPE_STR: &'static str = "OsString";
|
||||
}
|
||||
impl TryFromExpr for OsString {
|
||||
fn from_expr(exi: Expr) -> ExternResult<Self> { Ok(Inert::from_expr(exi)?.0) }
|
||||
}
|
||||
impl ToClause for OsString {
|
||||
fn to_clause(self, _: CodeLocation) -> Clause { Inert(self).atom_cls() }
|
||||
}
|
||||
|
||||
pub fn os_to_string(
|
||||
os: Inert<OsString>,
|
||||
) -> Result<Inert<OrcString>, Inert<OsString>> {
|
||||
os.0.into_string().map(|s| Inert(s.into())).map_err(Inert)
|
||||
}
|
||||
|
||||
pub fn string_to_os(str: Inert<OrcString>) -> Inert<OsString> {
|
||||
Inert(str.0.get_string().into())
|
||||
}
|
||||
|
||||
pub fn os_print(os: Inert<OsString>) -> Inert<OrcString> {
|
||||
Inert(os.0.to_string_lossy().to_string().into())
|
||||
}
|
||||
|
||||
pub fn os_string_lib() -> ConstTree {
|
||||
ConstTree::tree([
|
||||
("os_to_string", atom_leaf(xfn_1ary(os_to_string))),
|
||||
("string_to_os", atom_leaf(xfn_1ary(string_to_os))),
|
||||
("os_print", atom_leaf(xfn_1ary(os_print))),
|
||||
])
|
||||
}
|
||||
80
src/libs/io/bindings.rs
Normal file
80
src/libs/io/bindings.rs
Normal file
@@ -0,0 +1,80 @@
|
||||
use super::flow::IOCmdHandlePack;
|
||||
use super::instances::{BRead, ReadCmd, SRead, WriteCmd};
|
||||
use super::service::{Sink, Source};
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary};
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::libs::scheduler::system::SharedHandle;
|
||||
use crate::libs::std::binary::Binary;
|
||||
use crate::libs::std::runtime_error::RuntimeError;
|
||||
use crate::libs::std::string::OrcString;
|
||||
use crate::utils::combine::Combine;
|
||||
|
||||
pub type WriteHandle = Inert<SharedHandle<Sink>>;
|
||||
pub type ReadHandle = Inert<SharedHandle<Source>>;
|
||||
|
||||
type ReadCmdPack = CPSBox<IOCmdHandlePack<ReadCmd>>;
|
||||
type WriteCmdPack = CPSBox<IOCmdHandlePack<WriteCmd>>;
|
||||
|
||||
pub fn read_string(Inert(handle): ReadHandle) -> ReadCmdPack {
|
||||
let cmd = ReadCmd::RStr(SRead::All);
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd })
|
||||
}
|
||||
pub fn read_line(Inert(handle): ReadHandle) -> ReadCmdPack {
|
||||
let cmd = ReadCmd::RStr(SRead::Line);
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd })
|
||||
}
|
||||
pub fn read_bin(Inert(handle): ReadHandle) -> ReadCmdPack {
|
||||
let cmd = ReadCmd::RBytes(BRead::All);
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd })
|
||||
}
|
||||
pub fn read_bytes(Inert(handle): ReadHandle, n: Inert<usize>) -> ReadCmdPack {
|
||||
let cmd = ReadCmd::RBytes(BRead::N(n.0));
|
||||
CPSBox::new(3, IOCmdHandlePack { cmd, handle })
|
||||
}
|
||||
pub fn read_until(
|
||||
Inert(handle): ReadHandle,
|
||||
Inert(pattern): Inert<usize>,
|
||||
) -> ExternResult<ReadCmdPack> {
|
||||
let pattern = pattern.try_into().map_err(|_| {
|
||||
let msg = format!("{pattern} doesn't fit into a byte");
|
||||
RuntimeError::ext(msg, "converting number to byte")
|
||||
})?;
|
||||
let cmd = ReadCmd::RBytes(BRead::Until(pattern));
|
||||
Ok(CPSBox::new(3, IOCmdHandlePack { handle, cmd }))
|
||||
}
|
||||
pub fn write_str(
|
||||
Inert(handle): WriteHandle,
|
||||
string: Inert<OrcString>,
|
||||
) -> WriteCmdPack {
|
||||
let cmd = WriteCmd::WStr(string.0.get_string());
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd })
|
||||
}
|
||||
pub fn write_bin(
|
||||
Inert(handle): WriteHandle,
|
||||
bytes: Inert<Binary>,
|
||||
) -> WriteCmdPack {
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::WBytes(bytes.0) })
|
||||
}
|
||||
pub fn flush(Inert(handle): WriteHandle) -> WriteCmdPack {
|
||||
CPSBox::new(3, IOCmdHandlePack { handle, cmd: WriteCmd::Flush })
|
||||
}
|
||||
|
||||
pub fn io_bindings<'a>(
|
||||
std_streams: impl IntoIterator<Item = (&'a str, ConstTree)>,
|
||||
) -> ConstTree {
|
||||
ConstTree::ns("system::io", [ConstTree::tree([
|
||||
("read_string", atom_leaf(xfn_1ary(read_string))),
|
||||
("read_line", atom_leaf(xfn_1ary(read_line))),
|
||||
("read_bin", atom_leaf(xfn_1ary(read_bin))),
|
||||
("read_n_bytes", atom_leaf(xfn_2ary(read_bytes))),
|
||||
("read_until", atom_leaf(xfn_2ary(read_until))),
|
||||
("write_str", atom_leaf(xfn_2ary(write_str))),
|
||||
("write_bin", atom_leaf(xfn_2ary(write_bin))),
|
||||
("flush", atom_leaf(xfn_1ary(flush))),
|
||||
])
|
||||
.combine(ConstTree::tree(std_streams))
|
||||
.expect("std_stream name clashing with io functions")])
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::systems::scheduler::Canceller;
|
||||
use crate::foreign::error::ExternError;
|
||||
use crate::libs::scheduler::cancel_flag::CancelFlag;
|
||||
|
||||
pub trait IOHandler<T> {
|
||||
type Product;
|
||||
@@ -25,7 +25,7 @@ pub trait IOCmd: Send {
|
||||
fn execute(
|
||||
self,
|
||||
stream: &mut Self::Stream,
|
||||
cancel: Canceller,
|
||||
cancel: CancelFlag,
|
||||
) -> Self::Result;
|
||||
}
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
use std::io::{self, BufRead, BufReader, Read, Write};
|
||||
use std::io::{self, BufRead, Read, Write};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::flow::IOCmd;
|
||||
use crate::foreign::Atomic;
|
||||
use crate::interpreted::ExprInst;
|
||||
use crate::systems::codegen::call;
|
||||
use crate::systems::scheduler::{Canceller, SharedHandle};
|
||||
use crate::systems::stl::Binary;
|
||||
use crate::OrcString;
|
||||
|
||||
/// Any type that we can read controlled amounts of data from
|
||||
pub type Source = BufReader<Box<dyn Read + Send>>;
|
||||
/// Any type that we can write data to
|
||||
pub type Sink = Box<dyn Write + Send>;
|
||||
use super::service::{Sink, Source};
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort::Expr;
|
||||
use crate::libs::scheduler::cancel_flag::CancelFlag;
|
||||
use crate::libs::scheduler::system::SharedHandle;
|
||||
use crate::libs::std::binary::Binary;
|
||||
use crate::libs::std::string::OrcString;
|
||||
use crate::location::{CodeGenInfo, CodeLocation};
|
||||
|
||||
/// String reading command
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SRead {
|
||||
pub(super) enum SRead {
|
||||
All,
|
||||
Line,
|
||||
}
|
||||
|
||||
/// Binary reading command
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum BRead {
|
||||
pub(super) enum BRead {
|
||||
All,
|
||||
N(usize),
|
||||
Until(u8),
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ReadCmd {
|
||||
pub(super) enum ReadCmd {
|
||||
RBytes(BRead),
|
||||
RStr(SRead),
|
||||
}
|
||||
@@ -45,7 +45,7 @@ impl IOCmd for ReadCmd {
|
||||
fn execute(
|
||||
self,
|
||||
stream: &mut Self::Stream,
|
||||
_cancel: Canceller,
|
||||
_cancel: CancelFlag,
|
||||
) -> Self::Result {
|
||||
match self {
|
||||
Self::RBytes(bread) => {
|
||||
@@ -77,33 +77,34 @@ impl IOCmd for ReadCmd {
|
||||
}
|
||||
|
||||
/// Reading command (string or binary)
|
||||
pub enum ReadResult {
|
||||
pub(super) enum ReadResult {
|
||||
RStr(SRead, io::Result<String>),
|
||||
RBin(BRead, io::Result<Vec<u8>>),
|
||||
}
|
||||
impl ReadResult {
|
||||
pub fn dispatch(self, succ: ExprInst, fail: ExprInst) -> Vec<ExprInst> {
|
||||
match self {
|
||||
ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) => {
|
||||
vec![call(fail, [wrap_io_error(e)]).wrap()]
|
||||
},
|
||||
ReadResult::RBin(_, Ok(bytes)) => {
|
||||
let arg = Binary(Arc::new(bytes)).atom_cls().wrap();
|
||||
vec![call(succ, [arg]).wrap()]
|
||||
},
|
||||
ReadResult::RStr(_, Ok(text)) => {
|
||||
vec![call(succ, [OrcString::from(text).atom_exi()]).wrap()]
|
||||
},
|
||||
}
|
||||
pub fn dispatch(self, succ: Expr, fail: Expr) -> Vec<Expr> {
|
||||
vec![match self {
|
||||
ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) =>
|
||||
io_error_handler(e, fail),
|
||||
ReadResult::RBin(_, Ok(bytes)) =>
|
||||
tpl::A(tpl::Slot, tpl::V(Inert(Binary(Arc::new(bytes)))))
|
||||
.template(nort_gen(succ.location()), [succ]),
|
||||
ReadResult::RStr(_, Ok(text)) =>
|
||||
tpl::A(tpl::Slot, tpl::V(Inert(OrcString::from(text))))
|
||||
.template(nort_gen(succ.location()), [succ]),
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
/// Function to convert [io::Error] to Orchid data
|
||||
pub fn wrap_io_error(_e: io::Error) -> ExprInst { 0usize.atom_exi() }
|
||||
pub(crate) fn io_error_handler(_e: io::Error, handler: Expr) -> Expr {
|
||||
let ctx = nort_gen(CodeLocation::Gen(CodeGenInfo::no_details("io_error")));
|
||||
tpl::A(tpl::Slot, tpl::V(Inert(0usize))).template(ctx, [handler])
|
||||
}
|
||||
|
||||
/// Writing command (string or binary)
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum WriteCmd {
|
||||
pub(super) enum WriteCmd {
|
||||
WBytes(Binary),
|
||||
WStr(String),
|
||||
Flush,
|
||||
@@ -117,7 +118,7 @@ impl IOCmd for WriteCmd {
|
||||
fn execute(
|
||||
self,
|
||||
stream: &mut Self::Stream,
|
||||
_cancel: Canceller,
|
||||
_cancel: CancelFlag,
|
||||
) -> Self::Result {
|
||||
let result = match &self {
|
||||
Self::Flush => stream.flush(),
|
||||
@@ -128,15 +129,13 @@ impl IOCmd for WriteCmd {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WriteResult {
|
||||
pub(super) struct WriteResult {
|
||||
#[allow(unused)]
|
||||
pub cmd: WriteCmd,
|
||||
pub result: io::Result<()>,
|
||||
}
|
||||
impl WriteResult {
|
||||
pub fn dispatch(self, succ: ExprInst, fail: ExprInst) -> Vec<ExprInst> {
|
||||
match self.result {
|
||||
Ok(_) => vec![succ],
|
||||
Err(e) => vec![call(fail, vec![wrap_io_error(e)]).wrap()],
|
||||
}
|
||||
pub fn dispatch(self, succ: Expr, fail: Expr) -> Vec<Expr> {
|
||||
vec![self.result.map_or_else(|e| io_error_handler(e, fail), |()| succ)]
|
||||
}
|
||||
}
|
||||
38
src/libs/io/mod.rs
Normal file
38
src/libs/io/mod.rs
Normal file
@@ -0,0 +1,38 @@
|
||||
//! System that allows Orchid to interact with trait objects of Rust's `Writer`
|
||||
//! and with `BufReader`s of `Reader` trait objects.
|
||||
//!
|
||||
//! You can pass standard streams during initialization, the stllib expects
|
||||
//! `stdin`, `stdout` and `stderr`. This system depends on
|
||||
//! [crate::libs::scheduler] to run blocking I/O operations off-thread, which in
|
||||
//! turn depends on [crate::libs::asynch] to process results on the main thread,
|
||||
//! and [crate::libs::std] for `std::panic`.
|
||||
//!
|
||||
//! ```
|
||||
//! use orchidlang::libs::asynch::system::AsynchSystem;
|
||||
//! use orchidlang::libs::scheduler::system::SeqScheduler;
|
||||
//! use orchidlang::libs::std::std_system::StdConfig;
|
||||
//! use orchidlang::libs::io::{IOService, Stream};
|
||||
//! use orchidlang::facade::loader::Loader;
|
||||
//! use std::io::BufReader;
|
||||
//!
|
||||
//!
|
||||
//! 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: false })
|
||||
//! .add_system(asynch)
|
||||
//! .add_system(scheduler.clone())
|
||||
//! .add_system(IOService::new(scheduler.clone(), std_streams));
|
||||
//! ```
|
||||
|
||||
mod bindings;
|
||||
mod flow;
|
||||
pub(super) mod instances;
|
||||
mod service;
|
||||
|
||||
pub use service::{IOService, Sink, Source, Stream};
|
||||
138
src/libs/io/service.rs
Normal file
138
src/libs/io/service.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the
|
||||
//! I/O subsystem
|
||||
|
||||
use std::io::{BufReader, Read, Write};
|
||||
|
||||
use rust_embed::RustEmbed;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::bindings::io_bindings;
|
||||
use super::flow::{IOCmd, IOCmdHandlePack};
|
||||
use super::instances::{ReadCmd, WriteCmd};
|
||||
use crate::facade::system::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::gen::tree::leaf;
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::handler::HandlerTable;
|
||||
use crate::libs::scheduler::system::{SeqScheduler, SharedHandle};
|
||||
use crate::location::CodeGenInfo;
|
||||
use crate::name::VName;
|
||||
use crate::pipeline::load_solution::Prelude;
|
||||
use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS};
|
||||
|
||||
/// Any type that we can read controlled amounts of data from
|
||||
pub type Source = BufReader<Box<dyn Read + Send>>;
|
||||
/// Any type that we can write data to
|
||||
pub type Sink = Box<dyn Write + Send>;
|
||||
|
||||
/// A shared type for sinks and sources
|
||||
pub enum Stream {
|
||||
/// A Source, aka. a BufReader
|
||||
Source(Source),
|
||||
/// A Sink, aka. a Writer
|
||||
Sink(Sink),
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
/// The table of default streams to be overlain on the I/O module, typicially
|
||||
/// stdin, stdout, stderr.
|
||||
pub(super) trait StreamTable<'a> = IntoIterator<Item = (&'a str, Stream)>
|
||||
}
|
||||
|
||||
fn gen() -> CodeGenInfo { CodeGenInfo::no_details("system::io") }
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/libs/io"]
|
||||
#[include = "*.orc"]
|
||||
struct IOEmbed;
|
||||
|
||||
fn code() -> DeclTree {
|
||||
DeclTree::ns("system::io", [DeclTree::leaf(
|
||||
PrefixFS::new(EmbeddedFS::new::<IOEmbed>(".orc", gen()), "", "io").rc(),
|
||||
)])
|
||||
}
|
||||
|
||||
/// A streaming I/O service for interacting with Rust's [std::io::Write] and
|
||||
/// [std::io::Read] traits.
|
||||
pub struct IOService<'a, ST: IntoIterator<Item = (&'a str, Stream)>> {
|
||||
scheduler: SeqScheduler,
|
||||
global_streams: ST,
|
||||
}
|
||||
impl<'a, ST: IntoIterator<Item = (&'a str, Stream)>> IOService<'a, ST> {
|
||||
/// Construct a new instance of the service
|
||||
pub fn new(scheduler: SeqScheduler, global_streams: ST) -> Self {
|
||||
Self { scheduler, global_streams }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, ST: IntoIterator<Item = (&'a str, Stream)>> IntoSystem<'static>
|
||||
for IOService<'a, ST>
|
||||
{
|
||||
fn into_system(self) -> System<'static> {
|
||||
let scheduler = self.scheduler.clone();
|
||||
let mut handlers = HandlerTable::new();
|
||||
handlers.register(move |cps: &CPSBox<IOCmdHandlePack<ReadCmd>>| {
|
||||
let (IOCmdHandlePack { cmd, handle }, succ, fail, cont) = cps.unpack3();
|
||||
let (cmd, fail1) = (*cmd, fail.clone());
|
||||
let result = scheduler.schedule(
|
||||
handle.clone(),
|
||||
move |mut stream, cancel| {
|
||||
let ret = cmd.execute(&mut stream, cancel);
|
||||
(stream, ret)
|
||||
},
|
||||
move |stream, res, _cancel| (stream, res.dispatch(succ, fail1)),
|
||||
|stream| (stream, Vec::new()),
|
||||
);
|
||||
match result {
|
||||
Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel)))
|
||||
.template(nort_gen(cont.location()), [cont]),
|
||||
Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e)))
|
||||
.template(nort_gen(fail.location()), [fail]),
|
||||
}
|
||||
});
|
||||
let scheduler = self.scheduler.clone();
|
||||
handlers.register(move |cps: &CPSBox<IOCmdHandlePack<WriteCmd>>| {
|
||||
let (IOCmdHandlePack { cmd, handle }, succ, fail, cont) = cps.unpack3();
|
||||
let (succ1, fail1, cmd) = (succ, fail.clone(), cmd.clone());
|
||||
let result = scheduler.schedule(
|
||||
handle.clone(),
|
||||
move |mut stream, cancel| {
|
||||
let ret = cmd.execute(&mut stream, cancel);
|
||||
(stream, ret)
|
||||
},
|
||||
move |stream, res, _cancel| (stream, res.dispatch(succ1, fail1)),
|
||||
|stream| (stream, Vec::new()),
|
||||
);
|
||||
match result {
|
||||
Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel)))
|
||||
.template(nort_gen(cont.location()), [cont]),
|
||||
Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e)))
|
||||
.template(nort_gen(fail.location()), [fail]),
|
||||
}
|
||||
});
|
||||
let streams = self.global_streams.into_iter().map(|(n, stream)| {
|
||||
let handle = match stream {
|
||||
Stream::Sink(sink) => leaf(tpl::V(Inert(SharedHandle::wrap(sink)))),
|
||||
Stream::Source(source) =>
|
||||
leaf(tpl::V(Inert(SharedHandle::wrap(source)))),
|
||||
};
|
||||
(n, handle)
|
||||
});
|
||||
System {
|
||||
handlers,
|
||||
name: "system::io",
|
||||
constants: io_bindings(streams),
|
||||
code: code(),
|
||||
prelude: vec![Prelude {
|
||||
target: VName::literal("system::io::prelude"),
|
||||
exclude: VName::literal("system::io"),
|
||||
owner: gen(),
|
||||
}],
|
||||
lexer_plugins: vec![],
|
||||
line_parsers: vec![],
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
//! Constants exposed to usercode by the interpreter
|
||||
pub mod asynch;
|
||||
pub mod codegen;
|
||||
pub mod directfs;
|
||||
pub mod io;
|
||||
pub mod scheduler;
|
||||
pub mod stl;
|
||||
pub mod parse_custom_line;
|
||||
|
||||
pub mod scheduler;
|
||||
pub mod std;
|
||||
47
src/libs/parse_custom_line.rs
Normal file
47
src/libs/parse_custom_line.rs
Normal file
@@ -0,0 +1,47 @@
|
||||
//! A helper for defining custom lines. See [custom_line]
|
||||
use intern_all::Tok;
|
||||
|
||||
use crate::error::ProjectResult;
|
||||
use crate::location::SourceRange;
|
||||
use crate::parse::errors::ParseErrorKind;
|
||||
use crate::parse::frag::Frag;
|
||||
use crate::parse::lexer::Lexeme;
|
||||
use crate::parse::parse_plugin::ParsePluginReq;
|
||||
|
||||
/// An exported line with a name for which the line parser denies exports
|
||||
pub struct Unexportable(Lexeme);
|
||||
impl ParseErrorKind for Unexportable {
|
||||
const DESCRIPTION: &'static str = "this line type cannot be exported";
|
||||
fn message(&self) -> String { format!("{} cannot be exported", &self.0) }
|
||||
}
|
||||
|
||||
/// Parse a line identified by the specified leading keyword. Although not
|
||||
/// required, plugins are encouraged to prefix their lines with a globally
|
||||
/// unique keyword which makes or breaks their parsing, to avoid accidental
|
||||
/// failure to recognize
|
||||
pub fn custom_line<'a>(
|
||||
tail: Frag<'a>,
|
||||
keyword: Tok<String>,
|
||||
exportable: bool,
|
||||
req: &dyn ParsePluginReq,
|
||||
) -> Option<ProjectResult<(bool, Frag<'a>, SourceRange)>> {
|
||||
let line_loc = req.frag_loc(tail);
|
||||
let (fst, tail) = req.pop(tail).ok()?;
|
||||
let fst_name = req.expect_name(fst).ok()?;
|
||||
let (exported, n_ent, tail) = if fst_name == keyword {
|
||||
(false, fst, tail.trim())
|
||||
} else if fst_name.as_str() == "export" {
|
||||
let (snd, tail) = req.pop(tail).ok()?;
|
||||
req.expect(Lexeme::Name(keyword), snd).ok()?;
|
||||
(true, snd, tail.trim())
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some(match exported && !exportable {
|
||||
true => {
|
||||
let err = Unexportable(n_ent.lexeme.clone());
|
||||
Err(err.pack(req.range_loc(n_ent.range.clone())))
|
||||
},
|
||||
false => Ok((exported, tail, line_loc)),
|
||||
})
|
||||
}
|
||||
@@ -1,31 +1,30 @@
|
||||
use std::any::Any;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::Canceller;
|
||||
use crate::interpreted::ExprInst;
|
||||
use super::cancel_flag::CancelFlag;
|
||||
use crate::interpreter::nort::Expr;
|
||||
|
||||
pub type SyncResult<T> = (T, Box<dyn Any + Send>);
|
||||
/// Output from handlers contains the resource being processed and any Orchid
|
||||
/// handlers executed as a result of the operation
|
||||
pub type HandlerRes<T> = (T, Vec<ExprInst>);
|
||||
pub type HandlerRes<T> = (T, Vec<Expr>);
|
||||
pub type SyncOperation<T> =
|
||||
Box<dyn FnOnce(T, Canceller) -> SyncResult<T> + Send>;
|
||||
pub type SyncOpResultHandler<T> = Box<
|
||||
dyn FnOnce(T, Box<dyn Any + Send>, Canceller) -> (T, Vec<ExprInst>) + Send,
|
||||
>;
|
||||
Box<dyn FnOnce(T, CancelFlag) -> SyncResult<T> + Send>;
|
||||
pub type SyncOpResultHandler<T> =
|
||||
Box<dyn FnOnce(T, Box<dyn Any + Send>, CancelFlag) -> (T, Vec<Expr>) + Send>;
|
||||
|
||||
struct SyncQueueItem<T> {
|
||||
cancelled: Canceller,
|
||||
cancelled: CancelFlag,
|
||||
operation: SyncOperation<T>,
|
||||
handler: SyncOpResultHandler<T>,
|
||||
early_cancel: Box<dyn FnOnce(T) -> (T, Vec<ExprInst>) + Send>,
|
||||
early_cancel: Box<dyn FnOnce(T) -> (T, Vec<Expr>) + Send>,
|
||||
}
|
||||
|
||||
pub enum NextItemReportKind<T> {
|
||||
Free(T),
|
||||
Next {
|
||||
instance: T,
|
||||
cancelled: Canceller,
|
||||
cancelled: CancelFlag,
|
||||
operation: SyncOperation<T>,
|
||||
rest: BusyState<T>,
|
||||
},
|
||||
@@ -34,17 +33,17 @@ pub enum NextItemReportKind<T> {
|
||||
|
||||
pub struct NextItemReport<T> {
|
||||
pub kind: NextItemReportKind<T>,
|
||||
pub events: Vec<ExprInst>,
|
||||
pub events: Vec<Expr>,
|
||||
}
|
||||
|
||||
pub struct BusyState<T> {
|
||||
pub(super) struct BusyState<T> {
|
||||
handler: SyncOpResultHandler<T>,
|
||||
queue: VecDeque<SyncQueueItem<T>>,
|
||||
seal: Option<Box<dyn FnOnce(T) -> Vec<ExprInst> + Send>>,
|
||||
seal: Option<Box<dyn FnOnce(T) -> Vec<Expr> + Send>>,
|
||||
}
|
||||
impl<T> BusyState<T> {
|
||||
pub fn new<U: 'static + Send>(
|
||||
handler: impl FnOnce(T, U, Canceller) -> HandlerRes<T> + Send + 'static,
|
||||
handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes<T> + Send + 'static,
|
||||
) -> Self {
|
||||
BusyState {
|
||||
handler: Box::new(|t, payload, cancel| {
|
||||
@@ -62,14 +61,14 @@ impl<T> BusyState<T> {
|
||||
/// successfully enqueued and None if the queue is already sealed.
|
||||
pub fn enqueue<U: 'static + Send>(
|
||||
&mut self,
|
||||
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> HandlerRes<T> + Send + 'static,
|
||||
operation: impl FnOnce(T, CancelFlag) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes<T> + Send + 'static,
|
||||
early_cancel: impl FnOnce(T) -> HandlerRes<T> + Send + 'static,
|
||||
) -> Option<Canceller> {
|
||||
) -> Option<CancelFlag> {
|
||||
if self.seal.is_some() {
|
||||
return None;
|
||||
}
|
||||
let cancelled = Canceller::new();
|
||||
let cancelled = CancelFlag::new();
|
||||
self.queue.push_back(SyncQueueItem {
|
||||
cancelled: cancelled.clone(),
|
||||
early_cancel: Box::new(early_cancel),
|
||||
@@ -87,7 +86,7 @@ impl<T> BusyState<T> {
|
||||
|
||||
pub fn seal(
|
||||
&mut self,
|
||||
recipient: impl FnOnce(T) -> Vec<ExprInst> + Send + 'static,
|
||||
recipient: impl FnOnce(T) -> Vec<Expr> + Send + 'static,
|
||||
) {
|
||||
assert!(self.seal.is_none(), "Already sealed");
|
||||
self.seal = Some(Box::new(recipient))
|
||||
@@ -99,7 +98,7 @@ impl<T> BusyState<T> {
|
||||
mut self,
|
||||
instance: T,
|
||||
result: Box<dyn Any + Send>,
|
||||
cancelled: Canceller,
|
||||
cancelled: CancelFlag,
|
||||
) -> NextItemReport<T> {
|
||||
let (mut instance, mut events) =
|
||||
(self.handler)(instance, result, cancelled);
|
||||
@@ -1,13 +1,15 @@
|
||||
//! Flag for cancelling scheduled operations
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A single-fire thread-safe boolean flag with relaxed ordering
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Canceller(Arc<AtomicBool>);
|
||||
pub struct CancelFlag(Arc<AtomicBool>);
|
||||
|
||||
impl Canceller {
|
||||
impl CancelFlag {
|
||||
/// Create a new canceller
|
||||
pub fn new() -> Self { Canceller(Arc::new(AtomicBool::new(false))) }
|
||||
pub fn new() -> Self { CancelFlag(Arc::new(AtomicBool::new(false))) }
|
||||
|
||||
/// Check whether the operation has been cancelled
|
||||
pub fn is_cancelled(&self) -> bool { self.0.load(Ordering::Relaxed) }
|
||||
@@ -16,6 +18,6 @@ impl Canceller {
|
||||
pub fn cancel(&self) { self.0.store(true, Ordering::Relaxed) }
|
||||
}
|
||||
|
||||
impl Default for Canceller {
|
||||
impl Default for CancelFlag {
|
||||
fn default() -> Self { Self::new() }
|
||||
}
|
||||
@@ -16,9 +16,6 @@ impl<T> IdMap<T> {
|
||||
/// Create a new empty set
|
||||
pub fn new() -> Self { Self { next_id: 0, data: HashMap::new() } }
|
||||
|
||||
/// Obtain a reference to the underlying map for iteration
|
||||
pub fn map(&self) -> &HashMap<u64, T> { &self.data }
|
||||
|
||||
/// Insert an element with a new ID and return the ID
|
||||
pub fn insert(&mut self, t: T) -> u64 {
|
||||
let id = self.next_id;
|
||||
@@ -28,14 +25,6 @@ impl<T> IdMap<T> {
|
||||
id
|
||||
}
|
||||
|
||||
/// Obtain a reference to the element with the given ID
|
||||
pub fn get(&self, id: u64) -> Option<&T> { self.data.get(&id) }
|
||||
|
||||
/// Obtain a mutable reference to the element with the given ID
|
||||
pub fn get_mut(&mut self, id: u64) -> Option<&mut T> {
|
||||
self.data.get_mut(&id)
|
||||
}
|
||||
|
||||
/// Remove the element with the given ID from the set. The ID will not be
|
||||
/// reused.
|
||||
pub fn remove(&mut self, id: u64) -> Option<T> { self.data.remove(&id) }
|
||||
@@ -56,6 +45,6 @@ mod test {
|
||||
let b = map.insert(2);
|
||||
assert_eq!(map.remove(a), Some(1));
|
||||
assert_eq!(map.remove(a), None);
|
||||
assert_eq!(map.get(b), Some(&2));
|
||||
assert_eq!(map.data.get(&b), Some(&2));
|
||||
}
|
||||
}
|
||||
8
src/libs/scheduler/mod.rs
Normal file
8
src/libs/scheduler/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
//! A generic utility to sequence long blocking mutations that require a mutable
|
||||
//! reference to a shared resource.
|
||||
|
||||
mod busy;
|
||||
pub mod cancel_flag;
|
||||
mod id_map;
|
||||
pub mod system;
|
||||
pub mod thread_pool;
|
||||
@@ -1,28 +1,47 @@
|
||||
//! Object to pass to [crate::facade::loader::Loader::add_system] to enable the
|
||||
//! scheduling subsystem. Other systems also take clones as dependencies.
|
||||
//!
|
||||
//! ```
|
||||
//! use orchidlang::libs::asynch::system::AsynchSystem;
|
||||
//! use orchidlang::libs::scheduler::system::SeqScheduler;
|
||||
//! use orchidlang::libs::std::std_system::StdConfig;
|
||||
//! use orchidlang::facade::loader::Loader;
|
||||
//!
|
||||
//! let mut asynch = AsynchSystem::new();
|
||||
//! let scheduler = SeqScheduler::new(&mut asynch);
|
||||
//! let env = Loader::new()
|
||||
//! .add_system(StdConfig { impure: false })
|
||||
//! .add_system(asynch)
|
||||
//! .add_system(scheduler.clone());
|
||||
//! ```
|
||||
|
||||
use std::any::{type_name, Any};
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Debug;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Itertools;
|
||||
use trait_set::trait_set;
|
||||
|
||||
use super::busy::{BusyState, NextItemReportKind, SyncOperation};
|
||||
use super::{Canceller, HandlerRes};
|
||||
use crate::error::AssertionError;
|
||||
use crate::facade::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::{init_cps, CPSBox};
|
||||
use crate::foreign::{xfn_1ary, InertAtomic, XfnResult};
|
||||
use crate::interpreted::{Clause, ExprInst};
|
||||
use crate::interpreter::HandlerTable;
|
||||
use crate::systems::asynch::{AsynchSystem, MessagePort};
|
||||
use super::busy::{BusyState, HandlerRes, NextItemReportKind, SyncOperation};
|
||||
use super::cancel_flag::CancelFlag;
|
||||
use super::id_map::IdMap;
|
||||
use super::thread_pool::ThreadPool;
|
||||
use crate::facade::system::{IntoSystem, System};
|
||||
use crate::foreign::cps_box::CPSBox;
|
||||
use crate::foreign::error::{AssertionError, ExternResult};
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::handler::HandlerTable;
|
||||
use crate::interpreter::nort::Expr;
|
||||
use crate::libs::asynch::system::{AsynchSystem, MessagePort};
|
||||
use crate::utils::ddispatch::Request;
|
||||
use crate::utils::thread_pool::ThreadPool;
|
||||
use crate::utils::{take_with_output, unwrap_or, IdMap};
|
||||
use crate::{ConstTree, Location};
|
||||
use crate::utils::take_with_output::take_with_output;
|
||||
use crate::utils::unwrap_or::unwrap_or;
|
||||
use crate::virt_fs::DeclTree;
|
||||
|
||||
enum SharedResource<T> {
|
||||
pub(super) enum SharedResource<T> {
|
||||
Free(T),
|
||||
Busy(BusyState<T>),
|
||||
Taken,
|
||||
@@ -47,7 +66,7 @@ pub enum SharedState {
|
||||
|
||||
/// A shared handle for a resource of type `T` that can be used with a
|
||||
/// [SeqScheduler] to execute mutating operations one by one in worker threads.
|
||||
pub struct SharedHandle<T>(Arc<Mutex<SharedResource<T>>>);
|
||||
pub struct SharedHandle<T>(pub(super) Arc<Mutex<SharedResource<T>>>);
|
||||
|
||||
impl<T> SharedHandle<T> {
|
||||
/// Wrap a value to be accessible to a [SeqScheduler].
|
||||
@@ -97,8 +116,8 @@ impl<T> Debug for SharedHandle<T> {
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
impl<T: Send + 'static> InertAtomic for SharedHandle<T> {
|
||||
fn type_str() -> &'static str { "a SharedHandle" }
|
||||
impl<T: Send + 'static> InertPayload for SharedHandle<T> {
|
||||
const TYPE_STR: &'static str = "a SharedHandle";
|
||||
fn respond(&self, mut request: Request) {
|
||||
request.serve_with(|| {
|
||||
let this = self.clone();
|
||||
@@ -110,7 +129,7 @@ impl<T: Send + 'static> InertAtomic for SharedHandle<T> {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TakeCmd(pub Arc<dyn Fn(SeqScheduler) + Send + Sync>);
|
||||
struct TakeCmd(pub Arc<dyn Fn(SeqScheduler) + Send + Sync>);
|
||||
impl Debug for TakeCmd {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "A command to drop a shared resource")
|
||||
@@ -121,27 +140,25 @@ impl Debug for TakeCmd {
|
||||
/// which is either already sealed or taken.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SealedOrTaken;
|
||||
impl InertAtomic for SealedOrTaken {
|
||||
fn type_str() -> &'static str {
|
||||
"a sealed-or-taken error for a shared resource"
|
||||
impl InertPayload for SealedOrTaken {
|
||||
const TYPE_STR: &'static str = "SealedOrTaken";
|
||||
}
|
||||
|
||||
fn take_and_drop(x: Expr) -> ExternResult<CPSBox<TakeCmd>> {
|
||||
match x.clause.request() {
|
||||
Some(t) => Ok(CPSBox::<TakeCmd>::new(1, t)),
|
||||
None => AssertionError::fail(x.location(), "SharedHandle", format!("{x}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_and_drop(x: ExprInst) -> XfnResult<Clause> {
|
||||
match x.request() {
|
||||
Some(t) => Ok(init_cps::<TakeCmd>(1, t)),
|
||||
None => AssertionError::fail(Location::Unknown, "SharedHandle"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_taken_error(x: ExprInst) -> XfnResult<bool> {
|
||||
Ok(x.downcast::<SealedOrTaken>().is_ok())
|
||||
fn is_taken_error(x: Expr) -> Inert<bool> {
|
||||
Inert(x.downcast::<Inert<SealedOrTaken>>().is_ok())
|
||||
}
|
||||
|
||||
trait_set! {
|
||||
/// The part of processing a blocking I/O task that cannot be done on a remote
|
||||
/// thread, eg. because it accesses other systems or Orchid code.
|
||||
trait NonSendFn = FnOnce(Box<dyn Any + Send>, SeqScheduler) -> Vec<ExprInst>;
|
||||
trait NonSendFn = FnOnce(Box<dyn Any + Send>, SeqScheduler) -> Vec<Expr>;
|
||||
}
|
||||
|
||||
struct SyncReply {
|
||||
@@ -195,10 +212,10 @@ impl SeqScheduler {
|
||||
pub fn schedule<T: Send + 'static, U: Send + 'static>(
|
||||
&self,
|
||||
handle: SharedHandle<T>,
|
||||
operation: impl FnOnce(T, Canceller) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, Canceller) -> HandlerRes<T> + Send + 'static,
|
||||
operation: impl FnOnce(T, CancelFlag) -> (T, U) + Send + 'static,
|
||||
handler: impl FnOnce(T, U, CancelFlag) -> HandlerRes<T> + Send + 'static,
|
||||
early_cancel: impl FnOnce(T) -> HandlerRes<T> + Send + 'static,
|
||||
) -> Result<Canceller, SealedOrTaken> {
|
||||
) -> Result<CancelFlag, SealedOrTaken> {
|
||||
take_with_output(&mut *handle.0.lock().unwrap(), {
|
||||
let handle = handle.clone();
|
||||
|state| {
|
||||
@@ -211,7 +228,7 @@ impl SeqScheduler {
|
||||
}
|
||||
},
|
||||
SharedResource::Free(t) => {
|
||||
let cancelled = Canceller::new();
|
||||
let cancelled = CancelFlag::new();
|
||||
drop(early_cancel); // cannot possibly be useful
|
||||
let op_erased: SyncOperation<T> = Box::new(|t, c| {
|
||||
let (t, u) = operation(t, c);
|
||||
@@ -229,10 +246,10 @@ impl SeqScheduler {
|
||||
/// without queuing on any particular data.
|
||||
pub fn run_orphan<T: Send + 'static>(
|
||||
&self,
|
||||
operation: impl FnOnce(Canceller) -> T + Send + 'static,
|
||||
handler: impl FnOnce(T, Canceller) -> Vec<ExprInst> + 'static,
|
||||
) -> Canceller {
|
||||
let cancelled = Canceller::new();
|
||||
operation: impl FnOnce(CancelFlag) -> T + Send + 'static,
|
||||
handler: impl FnOnce(T, CancelFlag) -> Vec<Expr> + 'static,
|
||||
) -> CancelFlag {
|
||||
let cancelled = CancelFlag::new();
|
||||
let canc1 = cancelled.clone();
|
||||
let opid = self.0.pending.borrow_mut().insert(Box::new(
|
||||
|data: Box<dyn Any + Send>, _| {
|
||||
@@ -252,8 +269,8 @@ impl SeqScheduler {
|
||||
pub fn seal<T>(
|
||||
&self,
|
||||
handle: SharedHandle<T>,
|
||||
seal: impl FnOnce(T) -> Vec<ExprInst> + Sync + Send + 'static,
|
||||
) -> Result<Vec<ExprInst>, SealedOrTaken> {
|
||||
seal: impl FnOnce(T) -> Vec<Expr> + Sync + Send + 'static,
|
||||
) -> Result<Vec<Expr>, SealedOrTaken> {
|
||||
take_with_output(&mut *handle.0.lock().unwrap(), |state| match state {
|
||||
SharedResource::Busy(mut b) if !b.is_sealed() => {
|
||||
b.seal(seal);
|
||||
@@ -274,7 +291,7 @@ impl SeqScheduler {
|
||||
&self,
|
||||
t: T,
|
||||
handle: SharedHandle<T>,
|
||||
cancelled: Canceller,
|
||||
cancelled: CancelFlag,
|
||||
operation: SyncOperation<T>,
|
||||
) {
|
||||
// referenced by self until run, references handle
|
||||
@@ -315,33 +332,29 @@ impl SeqScheduler {
|
||||
}
|
||||
|
||||
impl IntoSystem<'static> for SeqScheduler {
|
||||
fn into_system(self, i: &crate::Interner) -> crate::facade::System<'static> {
|
||||
fn into_system(self) -> System<'static> {
|
||||
let mut handlers = HandlerTable::new();
|
||||
handlers.register(|cmd: Box<CPSBox<Canceller>>| {
|
||||
handlers.register(|cmd: &CPSBox<CancelFlag>| {
|
||||
let (canceller, cont) = cmd.unpack1();
|
||||
canceller.cancel();
|
||||
Ok(cont)
|
||||
cont
|
||||
});
|
||||
handlers.register(move |cmd: Box<CPSBox<TakeCmd>>| {
|
||||
handlers.register(move |cmd: &CPSBox<TakeCmd>| {
|
||||
let (TakeCmd(cb), cont) = cmd.unpack1();
|
||||
cb(self.clone());
|
||||
Ok(cont)
|
||||
cont
|
||||
});
|
||||
System {
|
||||
name: ["system", "scheduler"].into_iter().map_into().collect(),
|
||||
name: "system::scheduler",
|
||||
prelude: Vec::new(),
|
||||
code: HashMap::new(),
|
||||
code: DeclTree::empty(),
|
||||
handlers,
|
||||
lexer_plugins: vec![],
|
||||
line_parsers: vec![],
|
||||
constants: ConstTree::namespace(
|
||||
[i.i("system"), i.i("scheduler")],
|
||||
ConstTree::tree([
|
||||
(i.i("is_taken_error"), ConstTree::xfn(xfn_1ary(is_taken_error))),
|
||||
(i.i("take_and_drop"), ConstTree::xfn(xfn_1ary(take_and_drop))),
|
||||
]),
|
||||
)
|
||||
.unwrap_tree(),
|
||||
constants: ConstTree::ns("system::scheduler", [ConstTree::tree([
|
||||
("is_taken_error", atom_leaf(xfn_1ary(is_taken_error))),
|
||||
("take_and_drop", atom_leaf(xfn_1ary(take_and_drop))),
|
||||
])]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
//! A thread pool for executing tasks in parallel, spawning threads as workload
|
||||
//! increases and terminating them as tasks finish. This is not terribly
|
||||
//! efficient, its main design goal is to parallelize blocking I/O calls.
|
||||
//!
|
||||
//! This is the abstract implementation of the scheduler.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
||||
@@ -81,7 +83,7 @@ struct ThreadPoolData<T: Task> {
|
||||
/// arrive. To get rid of the last waiting thread, drop the thread pool.
|
||||
///
|
||||
/// ```
|
||||
/// use orchidlang::thread_pool::{Task, ThreadPool};
|
||||
/// use orchidlang::libs::scheduler::thread_pool::{Task, ThreadPool};
|
||||
///
|
||||
/// struct MyTask(&'static str);
|
||||
/// impl Task for MyTask {
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Error produced by numeric opperations
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use crate::foreign::ExternError;
|
||||
use crate::foreign::error::ExternError;
|
||||
|
||||
/// Various errors produced by arithmetic operations
|
||||
#[derive(Clone)]
|
||||
160
src/libs/std/binary.rs
Normal file
160
src/libs/std/binary.rs
Normal file
@@ -0,0 +1,160 @@
|
||||
//! `std::binary` Operations on binary buffers.
|
||||
|
||||
use std::fmt::Debug;
|
||||
use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use itertools::Itertools;
|
||||
|
||||
use super::runtime_error::RuntimeError;
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::fn_bridge::constructors::{
|
||||
xfn_1ary, xfn_2ary, xfn_3ary, xfn_4ary,
|
||||
};
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::nort::Clause;
|
||||
use crate::utils::iter_find::iter_find;
|
||||
use crate::utils::unwrap_or::unwrap_or;
|
||||
|
||||
const INT_BYTES: usize = usize::BITS as usize / 8;
|
||||
|
||||
/// A block of binary data
|
||||
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Binary(pub Arc<Vec<u8>>);
|
||||
impl InertPayload for Binary {
|
||||
const TYPE_STR: &'static str = "a binary blob";
|
||||
}
|
||||
|
||||
impl Deref for Binary {
|
||||
type Target = Vec<u8>;
|
||||
fn deref(&self) -> &Self::Target { &self.0 }
|
||||
}
|
||||
|
||||
impl Debug for Binary {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let mut iter = self.0.iter().copied();
|
||||
f.write_str("Binary")?;
|
||||
for mut chunk in iter.by_ref().take(32).chunks(4).into_iter() {
|
||||
let a = chunk.next().expect("Chunks cannot be empty");
|
||||
let b = unwrap_or!(chunk.next(); return write!(f, "{a:02x}"));
|
||||
let c = unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}"));
|
||||
let d =
|
||||
unwrap_or!(chunk.next(); return write!(f, "{a:02x}{b:02x}{c:02x}"));
|
||||
write!(f, "{a:02x}{b:02x}{c:02x}{d:02x}")?
|
||||
}
|
||||
if iter.next().is_some() { write!(f, "...") } else { Ok(()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// Append two binary data blocks
|
||||
pub fn concatenate(a: Inert<Binary>, b: Inert<Binary>) -> Inert<Binary> {
|
||||
let data = (*a).iter().chain(b.0.0.iter()).copied().collect();
|
||||
Inert(Binary(Arc::new(data)))
|
||||
}
|
||||
|
||||
/// Extract a subsection of the binary data
|
||||
pub fn slice(
|
||||
s: Inert<Binary>,
|
||||
i: Inert<usize>,
|
||||
len: Inert<usize>,
|
||||
) -> ExternResult<Inert<Binary>> {
|
||||
if i.0 + len.0 < s.0.0.len() {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"indexing binary",
|
||||
)?
|
||||
}
|
||||
Ok(Inert(Binary(Arc::new(s.0.0[i.0..i.0 + len.0].to_vec()))))
|
||||
}
|
||||
|
||||
/// Return the index where the first argument first contains the second, if any
|
||||
pub fn find(haystack: Inert<Binary>, needle: Inert<Binary>) -> Option<Clause> {
|
||||
let found = iter_find(haystack.0.0.iter(), needle.0.0.iter());
|
||||
found.map(|i| Inert(i).atom_cls())
|
||||
}
|
||||
|
||||
/// Split binary data block into two smaller blocks
|
||||
pub fn split(
|
||||
bin: Inert<Binary>,
|
||||
i: Inert<usize>,
|
||||
) -> ExternResult<(Inert<Binary>, Inert<Binary>)> {
|
||||
if bin.0.0.len() < i.0 {
|
||||
RuntimeError::fail(
|
||||
"Byte index out of bounds".to_string(),
|
||||
"splitting binary",
|
||||
)?
|
||||
}
|
||||
let (asl, bsl) = bin.0.0.split_at(i.0);
|
||||
Ok((
|
||||
Inert(Binary(Arc::new(asl.to_vec()))),
|
||||
Inert(Binary(Arc::new(bsl.to_vec()))),
|
||||
))
|
||||
}
|
||||
|
||||
/// Read a number from a binary blob
|
||||
pub fn get_num(
|
||||
buf: Inert<Binary>,
|
||||
loc: Inert<usize>,
|
||||
size: Inert<usize>,
|
||||
is_le: Inert<bool>,
|
||||
) -> ExternResult<Inert<usize>> {
|
||||
if buf.0.0.len() < (loc.0 + size.0) {
|
||||
RuntimeError::fail(
|
||||
"section out of range".to_string(),
|
||||
"reading number from binary data",
|
||||
)?
|
||||
}
|
||||
if INT_BYTES < size.0 {
|
||||
RuntimeError::fail(
|
||||
"more than std::bin::int_bytes bytes provided".to_string(),
|
||||
"reading number from binary data",
|
||||
)?
|
||||
}
|
||||
let mut data = [0u8; INT_BYTES];
|
||||
let section = &buf.0.0[loc.0..(loc.0 + size.0)];
|
||||
let num = if is_le.0 {
|
||||
data[0..size.0].copy_from_slice(section);
|
||||
usize::from_le_bytes(data)
|
||||
} else {
|
||||
data[INT_BYTES - size.0..].copy_from_slice(section);
|
||||
usize::from_be_bytes(data)
|
||||
};
|
||||
Ok(Inert(num))
|
||||
}
|
||||
|
||||
/// Convert a number into a blob
|
||||
pub fn from_num(
|
||||
size: Inert<usize>,
|
||||
is_le: Inert<bool>,
|
||||
data: Inert<usize>,
|
||||
) -> ExternResult<Inert<Binary>> {
|
||||
if INT_BYTES < size.0 {
|
||||
RuntimeError::fail(
|
||||
"more than std::bin::int_bytes bytes requested".to_string(),
|
||||
"converting number to binary",
|
||||
)?
|
||||
}
|
||||
let bytes = match is_le.0 {
|
||||
true => data.0.to_le_bytes()[0..size.0].to_vec(),
|
||||
false => data.0.to_be_bytes()[8 - size.0..].to_vec(),
|
||||
};
|
||||
Ok(Inert(Binary(Arc::new(bytes))))
|
||||
}
|
||||
|
||||
/// Detect the number of bytes in the blob
|
||||
pub fn size(b: Inert<Binary>) -> Inert<usize> { Inert(b.0.len()) }
|
||||
|
||||
pub(super) fn bin_lib() -> ConstTree {
|
||||
ConstTree::ns("std::binary", [ConstTree::tree([
|
||||
("concat", atom_leaf(xfn_2ary(concatenate))),
|
||||
("slice", atom_leaf(xfn_3ary(slice))),
|
||||
("find", atom_leaf(xfn_2ary(find))),
|
||||
("split", atom_leaf(xfn_2ary(split))),
|
||||
("get_num", atom_leaf(xfn_4ary(get_num))),
|
||||
("from_num", atom_leaf(xfn_3ary(from_num))),
|
||||
("size", atom_leaf(xfn_1ary(size))),
|
||||
("int_bytes", atom_leaf(Inert(INT_BYTES))),
|
||||
])])
|
||||
}
|
||||
46
src/libs/std/bool.orc
Normal file
46
src/libs/std/bool.orc
Normal file
@@ -0,0 +1,46 @@
|
||||
import std::(pmatch, inspect)
|
||||
|
||||
export ::(!=, ==)
|
||||
|
||||
export const not := \bool. if bool then false else true
|
||||
macro ...$a != ...$b =0x3p36=> (not (...$a == ...$b))
|
||||
macro ...$a == ...$b =0x3p36=> (equals (...$a) (...$b))
|
||||
export macro ...$a and ...$b =0x4p36=> (ifthenelse (...$a) (...$b) false)
|
||||
export macro ...$a or ...$b =0x4p36=> (ifthenelse (...$a) true (...$b))
|
||||
export macro if ...$cond then ...$true else ...$false:1 =0x1p84=> (
|
||||
ifthenelse (...$cond) (...$true) (...$false)
|
||||
)
|
||||
|
||||
(
|
||||
macro pmatch::request (== ...$other)
|
||||
=0x1p230=> pmatch::response (
|
||||
if pmatch::value == (...$other)
|
||||
then pmatch::pass
|
||||
else pmatch::fail
|
||||
)
|
||||
( pmatch::no_binds )
|
||||
)
|
||||
|
||||
(
|
||||
macro pmatch::request (!= ...$other)
|
||||
=0x1p230=> pmatch::response (
|
||||
if pmatch::value != (...$other)
|
||||
then pmatch::pass
|
||||
else pmatch::fail
|
||||
)
|
||||
( pmatch::no_binds )
|
||||
)
|
||||
|
||||
(
|
||||
macro pmatch::request (true)
|
||||
=0x1p230=> pmatch::response
|
||||
(if pmatch::value then pmatch::pass else pmatch::fail)
|
||||
( pmatch::no_binds )
|
||||
)
|
||||
|
||||
(
|
||||
macro pmatch::request (false)
|
||||
=0x1p230=> pmatch::response
|
||||
(if pmatch::value then pmatch::fail else pmatch::pass)
|
||||
( pmatch::no_binds )
|
||||
)
|
||||
53
src/libs/std/bool.rs
Normal file
53
src/libs/std/bool.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use super::number::Numeric;
|
||||
use super::string::OrcString;
|
||||
use crate::foreign::error::{AssertionError, ExternResult};
|
||||
use crate::foreign::fn_bridge::constructors::{xfn_1ary, xfn_2ary};
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::foreign::try_from_expr::WithLoc;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::{Gen, GenClause};
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort_builder::NortBuilder;
|
||||
use crate::interpreter::nort::Expr;
|
||||
|
||||
const fn left() -> impl GenClause { tpl::L("l", tpl::L("_", tpl::P("l"))) }
|
||||
const fn right() -> impl GenClause { tpl::L("_", tpl::L("r", tpl::P("r"))) }
|
||||
|
||||
/// Takes a boolean and two branches, runs the first if the bool is true, the
|
||||
/// second if it's false.
|
||||
// Even though it's a ternary function, IfThenElse is implemented as an unary
|
||||
// foreign function, as the rest of the logic can be defined in Orchid.
|
||||
pub fn if_then_else(WithLoc(loc, b): WithLoc<Inert<bool>>) -> Expr {
|
||||
let ctx = nort_gen(loc);
|
||||
if b.0 { left().template(ctx, []) } else { right().template(ctx, []) }
|
||||
}
|
||||
|
||||
/// Compares the inner values if
|
||||
///
|
||||
/// - both are string,
|
||||
/// - both are bool,
|
||||
/// - both are either uint or num
|
||||
pub fn equals(
|
||||
WithLoc(loc, a): WithLoc<Expr>,
|
||||
b: Expr,
|
||||
) -> ExternResult<Inert<bool>> {
|
||||
Ok(Inert(if let Ok(l) = a.clone().downcast::<Inert<OrcString>>() {
|
||||
b.downcast::<Inert<OrcString>>().is_ok_and(|r| *l == *r)
|
||||
} else if let Ok(l) = a.clone().downcast::<Inert<bool>>() {
|
||||
b.downcast::<Inert<bool>>().is_ok_and(|r| *l == *r)
|
||||
} else if let Some(l) = a.clause.request::<Numeric>() {
|
||||
b.clause.request::<Numeric>().is_some_and(|r| l.as_float() == r.as_float())
|
||||
} else {
|
||||
AssertionError::fail(loc, "string, bool or numeric", format!("{a}"))?
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn bool_lib() -> ConstTree {
|
||||
ConstTree::ns("std::bool", [ConstTree::tree([
|
||||
("ifthenelse", atom_leaf(xfn_1ary(if_then_else))),
|
||||
("equals", atom_leaf(xfn_2ary(equals))),
|
||||
("true", atom_leaf(Inert(true))),
|
||||
("false", atom_leaf(Inert(false))),
|
||||
])])
|
||||
}
|
||||
71
src/libs/std/conv.rs
Normal file
71
src/libs/std/conv.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use ordered_float::NotNan;
|
||||
|
||||
use super::number::Numeric;
|
||||
use super::protocol::{gen_resolv, Protocol};
|
||||
use super::string::OrcString;
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::error::{AssertionError, ExternResult};
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::inert::Inert;
|
||||
use crate::foreign::try_from_expr::WithLoc;
|
||||
use crate::gen::tpl;
|
||||
use crate::gen::traits::Gen;
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::gen_nort::nort_gen;
|
||||
use crate::interpreter::nort_builder::NortBuilder;
|
||||
use crate::interpreter::nort::{ClauseInst, Expr};
|
||||
use crate::parse::numeric::parse_num;
|
||||
|
||||
pub static TO_STRING: Lazy<Protocol> =
|
||||
Lazy::new(|| Protocol::new("to_string", []));
|
||||
|
||||
fn to_numeric(WithLoc(loc, a): WithLoc<ClauseInst>) -> ExternResult<Numeric> {
|
||||
if let Some(n) = a.request::<Numeric>() {
|
||||
return Ok(n);
|
||||
}
|
||||
if let Some(s) = a.request::<OrcString>() {
|
||||
return parse_num(s.as_str()).map_err(|e| {
|
||||
AssertionError::ext(loc, "number syntax", format!("{e:?}"))
|
||||
});
|
||||
}
|
||||
AssertionError::fail(loc, "string or number", format!("{a}"))
|
||||
}
|
||||
|
||||
/// parse a number. Accepts the same syntax Orchid does.
|
||||
pub fn to_float(a: WithLoc<ClauseInst>) -> ExternResult<Inert<NotNan<f64>>> {
|
||||
to_numeric(a).map(|n| Inert(n.as_float()))
|
||||
}
|
||||
|
||||
/// Parse an unsigned integer. Accepts the same formats Orchid does. If the
|
||||
/// input is a number, floors it.
|
||||
pub fn to_uint(a: WithLoc<ClauseInst>) -> ExternResult<Inert<usize>> {
|
||||
to_numeric(a).map(|n| match n {
|
||||
Numeric::Float(f) => Inert(f.floor() as usize),
|
||||
Numeric::Uint(i) => Inert(i),
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert a literal to a string using Rust's conversions for floats, chars and
|
||||
/// uints respectively
|
||||
pub fn to_string(WithLoc(loc, a): WithLoc<Expr>) -> Expr {
|
||||
match a.clone().downcast::<Inert<OrcString>>() {
|
||||
Ok(str) => str.atom_expr(loc),
|
||||
Err(_) => match a.clause.request::<OrcString>() {
|
||||
Some(str) => Inert(str).atom_expr(loc),
|
||||
None => tpl::a2(gen_resolv("std::to_string"), tpl::Slot, tpl::Slot)
|
||||
.template(nort_gen(loc), [a.clone(), a]),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn conv_lib() -> ConstTree {
|
||||
ConstTree::ns("std", [ConstTree::tree([
|
||||
TO_STRING.as_tree_ent([]),
|
||||
ConstTree::tree_ent("conv", [
|
||||
("to_float", atom_leaf(xfn_1ary(to_float))),
|
||||
("to_uint", atom_leaf(xfn_1ary(to_uint))),
|
||||
("to_string", atom_leaf(xfn_1ary(to_string))),
|
||||
]),
|
||||
])])
|
||||
}
|
||||
86
src/libs/std/cross_pipeline.rs
Normal file
86
src/libs/std/cross_pipeline.rs
Normal file
@@ -0,0 +1,86 @@
|
||||
use std::collections::VecDeque;
|
||||
use std::iter;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::foreign::atom::Atomic;
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::process::Unstable;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::foreign::try_from_expr::TryFromExpr;
|
||||
use crate::location::{CodeLocation, SourceRange};
|
||||
use crate::parse::parsed::{self, PType};
|
||||
use crate::utils::pure_seq::pushed;
|
||||
|
||||
pub trait DeferredRuntimeCallback<T, U, R: ToClause>:
|
||||
Fn(Vec<(T, U)>) -> R + Clone + Send + 'static
|
||||
{
|
||||
}
|
||||
impl<T, U, R: ToClause, F: Fn(Vec<(T, U)>) -> R + Clone + Send + 'static>
|
||||
DeferredRuntimeCallback<T, U, R> for F
|
||||
{
|
||||
}
|
||||
|
||||
/// Lazy-recursive function that takes the next value from the interpreter
|
||||
/// and acts upon it
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// If the list of remaining keys is empty
|
||||
fn table_receiver_rec<
|
||||
T: Clone + Send + 'static,
|
||||
U: TryFromExpr + Clone + Send + 'static,
|
||||
R: ToClause + 'static,
|
||||
>(
|
||||
range: SourceRange,
|
||||
results: Vec<(T, U)>,
|
||||
mut remaining_keys: VecDeque<T>,
|
||||
callback: impl DeferredRuntimeCallback<T, U, R>,
|
||||
) -> impl Atomic {
|
||||
let t = remaining_keys.pop_front().expect("empty handled elsewhere");
|
||||
xfn_1ary(move |u: U| {
|
||||
let results = pushed(results, (t, u));
|
||||
match remaining_keys.is_empty() {
|
||||
true => callback(results).to_clause(CodeLocation::Source(range)),
|
||||
false =>
|
||||
table_receiver_rec(range, results, remaining_keys, callback).atom_cls(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn table_receiver<
|
||||
T: Clone + Send + 'static,
|
||||
U: TryFromExpr + Clone + Send + 'static,
|
||||
R: ToClause + 'static,
|
||||
>(
|
||||
range: SourceRange,
|
||||
keys: VecDeque<T>,
|
||||
callback: impl DeferredRuntimeCallback<T, U, R>,
|
||||
) -> parsed::Clause {
|
||||
if keys.is_empty() {
|
||||
Unstable::new(move |_| callback(Vec::new())).ast_cls()
|
||||
} else {
|
||||
Unstable::new(move |_| {
|
||||
table_receiver_rec(range, Vec::new(), keys, callback).atom_cls()
|
||||
})
|
||||
.ast_cls()
|
||||
}
|
||||
}
|
||||
|
||||
/// Defers the execution of the callback to runtime, allowing it to depend on
|
||||
/// the result of Otchid expressions.
|
||||
pub fn defer_to_runtime<
|
||||
T: Clone + Send + 'static,
|
||||
U: TryFromExpr + Clone + Send + 'static,
|
||||
R: ToClause + 'static,
|
||||
>(
|
||||
range: SourceRange,
|
||||
pairs: impl IntoIterator<Item = (T, Vec<parsed::Expr>)>,
|
||||
callback: impl DeferredRuntimeCallback<T, U, R>,
|
||||
) -> parsed::Clause {
|
||||
let (keys, ast_values) =
|
||||
pairs.into_iter().unzip::<_, _, VecDeque<_>, Vec<_>>();
|
||||
let items = iter::once(table_receiver(range.clone(), keys, callback)).chain(
|
||||
ast_values.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v))),
|
||||
);
|
||||
parsed::Clause::s('(', items, range)
|
||||
}
|
||||
41
src/libs/std/exit_status.rs
Normal file
41
src/libs/std/exit_status.rs
Normal file
@@ -0,0 +1,41 @@
|
||||
//! `std::exit_status` Exit status of a program or effectful subprogram.
|
||||
//!
|
||||
//! There is no support for custom codes, and the success/failure state can be
|
||||
//! inspected.
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::inert::{Inert, InertPayload};
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
|
||||
/// An Orchid equivalent to Rust's binary exit status model
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum ExitStatus {
|
||||
/// unix exit code 0
|
||||
Success,
|
||||
/// unix exit code 1
|
||||
Failure,
|
||||
}
|
||||
impl ExitStatus {
|
||||
/// Convert to Rust-land [ExitCode]
|
||||
pub fn code(self) -> ExitCode {
|
||||
match self {
|
||||
Self::Success => ExitCode::SUCCESS,
|
||||
Self::Failure => ExitCode::FAILURE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InertPayload for ExitStatus {
|
||||
const TYPE_STR: &'static str = "ExitStatus";
|
||||
}
|
||||
|
||||
pub(super) fn exit_status_lib() -> ConstTree {
|
||||
let is_success = |es: Inert<ExitStatus>| Inert(es.0 == ExitStatus::Success);
|
||||
ConstTree::ns("std::exit_status", [ConstTree::tree([
|
||||
("success", atom_leaf(Inert(ExitStatus::Success))),
|
||||
("failure", atom_leaf(Inert(ExitStatus::Failure))),
|
||||
("is_success", atom_leaf(xfn_1ary(is_success))),
|
||||
])])
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import super::known::*
|
||||
import super::match::*
|
||||
import super::pmatch::*
|
||||
import super::macro
|
||||
import super::match::(match, =>)
|
||||
|
||||
--[ Do nothing. Especially useful as a passive cps operation ]--
|
||||
export const identity := \x.x
|
||||
39
src/libs/std/inspect.rs
Normal file
39
src/libs/std/inspect.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use crate::foreign::atom::{Atomic, AtomicResult, AtomicReturn};
|
||||
use crate::foreign::error::ExternResult;
|
||||
use crate::foreign::fn_bridge::constructors::xfn_1ary;
|
||||
use crate::foreign::to_clause::ToClause;
|
||||
use crate::gen::tree::{atom_leaf, ConstTree};
|
||||
use crate::interpreter::apply::CallData;
|
||||
use crate::interpreter::nort::{Clause, ClauseInst, Expr};
|
||||
use crate::interpreter::run::RunData;
|
||||
use crate::utils::ddispatch::Responder;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Inspect;
|
||||
impl Responder for Inspect {}
|
||||
impl Atomic for Inspect {
|
||||
fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> { self }
|
||||
fn as_any_ref(&self) -> &dyn std::any::Any { self }
|
||||
fn redirect(&mut self) -> Option<&mut ClauseInst> { None }
|
||||
fn run(self: Box<Self>, run: RunData) -> AtomicResult {
|
||||
AtomicReturn::inert(*self, run.ctx)
|
||||
}
|
||||
fn apply_ref(&self, call: CallData) -> ExternResult<Clause> {
|
||||
eprintln!("{}", call.arg);
|
||||
Ok(call.arg.to_clause(call.location))
|
||||
}
|
||||
}
|
||||
|
||||
fn tee(x: Expr) -> Expr {
|
||||
eprintln!("{x}");
|
||||
x
|
||||
}
|
||||
|
||||
pub fn inspect_lib() -> ConstTree {
|
||||
ConstTree::ns("std", [ConstTree::tree([
|
||||
("inspect", atom_leaf(Inspect)),
|
||||
("tee", atom_leaf(xfn_1ary(tee))),
|
||||
])])
|
||||
}
|
||||
137
src/libs/std/list.orc
Normal file
137
src/libs/std/list.orc
Normal file
@@ -0,0 +1,137 @@
|
||||
import super::(option, tuple, tuple::t, panic, pmatch, pmatch::=>, macro, tee)
|
||||
import super::(functional::*, procedural::*)
|
||||
import super::(loop::*, bool::*, known::*, number::*)
|
||||
|
||||
as_type list ()
|
||||
|
||||
export const cons := \hd. \tl. wrap (option::some t[hd, unwrap tl])
|
||||
export const end := wrap option::none
|
||||
export const pop := \list. \default. \f. (
|
||||
pmatch::match (unwrap list) {
|
||||
option::none => default;
|
||||
option::some t[hd, tl] => f hd (wrap tl);
|
||||
}
|
||||
)
|
||||
|
||||
-- Operators
|
||||
|
||||
--[ Fold each element into an accumulator using an `acc -> el -> acc`. #eager ]--
|
||||
export const fold := \list. \acc. \f. (
|
||||
loop_over (list, acc) {
|
||||
cps head, list = pop list acc;
|
||||
let acc = f acc head;
|
||||
}
|
||||
)
|
||||
|
||||
--[ Fold each element into an accumulator in reverse order. #eager-notail ]--
|
||||
export const rfold := \list. \acc. \f. (
|
||||
recursive r (list)
|
||||
pop list acc \head. \tail.
|
||||
f (r tail) head
|
||||
)
|
||||
|
||||
--[ Reverse a list. #eager ]--
|
||||
export const reverse := \list. fold list end \tl. \hd. cons hd tl
|
||||
|
||||
--[ Fold each element into a shared element with an `el -> el -> el`. #eager-notail ]--
|
||||
export const reduce := \list. \f. do{
|
||||
cps head, list = pop list option::none;
|
||||
option::some $ fold list head f
|
||||
}
|
||||
|
||||
--[
|
||||
Return a new list that contains only the elements from the input list
|
||||
for which the function returns true. #lazy
|
||||
]--
|
||||
export const filter := \list. \f. (
|
||||
pop list end \head. \tail.
|
||||
if (f head)
|
||||
then cons head (filter tail f)
|
||||
else filter tail f
|
||||
)
|
||||
|
||||
--[ Transform each element of the list with an `el -> any`. #lazy ]--
|
||||
export const map := \list. \f. (
|
||||
recursive r (list)
|
||||
pop list end \head. \tail.
|
||||
cons (f head) (r tail)
|
||||
)
|
||||
|
||||
--[ Skip `n` elements from the list and return the tail. #lazy ]--
|
||||
export const skip := \foo. \n. (
|
||||
loop_over (foo, n) {
|
||||
cps _head, foo = if n <= 0
|
||||
then return foo
|
||||
else pop foo end;
|
||||
let n = n - 1;
|
||||
}
|
||||
)
|
||||
|
||||
--[ Return `n` elements from the list and discard the rest. #lazy ]--
|
||||
export const take := \list. \n. (
|
||||
recursive r (list, n)
|
||||
if n == 0
|
||||
then end
|
||||
else pop list end \head. \tail.
|
||||
cons head $ r tail $ n - 1
|
||||
)
|
||||
|
||||
--[ Return the `n`th element from the list. #eager ]--
|
||||
export const get := \list. \n. (
|
||||
loop_over (list, n) {
|
||||
cps head, list = pop list option::none;
|
||||
cps if n == 0
|
||||
then return (option::some head)
|
||||
else identity;
|
||||
let n = n - 1;
|
||||
}
|
||||
)
|
||||
|
||||
--[ Map every element to a pair of the index and the original element. #lazy ]--
|
||||
export const enumerate := \list. (
|
||||
recursive r (list, n = 0)
|
||||
pop list end \head. \tail.
|
||||
cons t[n, head] $ r tail $ n + 1
|
||||
)
|
||||
|
||||
--[
|
||||
Turn a list of CPS commands into a sequence. This is achieved by calling every
|
||||
element on the return value of the next element with the tail passed to it.
|
||||
The continuation is passed to the very last argument. #lazy
|
||||
]--
|
||||
export const chain := \list. \cont. loop_over (list) {
|
||||
cps head, list = pop list cont;
|
||||
cps head;
|
||||
}
|
||||
|
||||
macro new[..$items] =0x2p84=> mk_list macro::comma_list (..$items)
|
||||
|
||||
macro mk_list ( macro::list_item $item $tail ) =0x1p254=> (cons $item mk_list $tail)
|
||||
macro mk_list macro::list_end =0x1p254=> end
|
||||
|
||||
export ::(new)
|
||||
|
||||
( macro pmatch::request (cons $head $tail)
|
||||
=0x1p230=> await_subpatterns
|
||||
(pmatch::request ($head))
|
||||
(pmatch::request ($tail))
|
||||
)
|
||||
( macro await_subpatterns
|
||||
(pmatch::response $h_expr ( $h_binds ))
|
||||
(pmatch::response $t_expr ( $t_binds ))
|
||||
=0x1p230=> pmatch::response (
|
||||
pop
|
||||
pmatch::value
|
||||
pmatch::fail
|
||||
\head. \tail. (
|
||||
(\pmatch::pass. (\pmatch::value. $h_expr) head)
|
||||
(pmatch::take_binds $h_binds (
|
||||
(\pmatch::pass. (\pmatch::value. $t_expr) tail)
|
||||
(pmatch::take_binds $t_binds (
|
||||
pmatch::give_binds pmatch::chain_binds $h_binds $t_binds pmatch::pass
|
||||
))
|
||||
))
|
||||
)
|
||||
)
|
||||
(pmatch::chain_binds $h_binds $t_binds)
|
||||
)
|
||||
@@ -37,7 +37,6 @@ macro parse_binds (...$item) =0x1p250=> (
|
||||
()
|
||||
)
|
||||
|
||||
|
||||
-- while loop
|
||||
export macro statement (
|
||||
while ..$condition (..$binds) {
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user