in midst of refactor

This commit is contained in:
2024-04-29 21:46:42 +02:00
parent ed0d64d52e
commit aa3f7e99ab
221 changed files with 5431 additions and 685 deletions

674
Cargo.lock generated
View File

@@ -14,92 +14,18 @@ dependencies = [
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anstream"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
@@ -109,22 +35,6 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bound"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6021ae095f16f54aaae093f4c723700430e71eab731d3b0a07fc8fe258fd5110"
[[package]]
name = "bstr"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
@@ -132,70 +42,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.1"
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.50",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[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",
]
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "cpufeatures"
@@ -206,31 +56,6 @@ dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
version = "0.1.6"
@@ -241,6 +66,65 @@ dependencies = [
"typenum",
]
[[package]]
name = "darling"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54e36fcd13ed84ffdfda6f5be89b31287cbb80c439841fe69e04841435464391"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c2cf1c23a687a1feeb728783b993c4e1ad83d99f351801977dd809b48d0a70f"
dependencies = [
"fnv",
"ident_case",
"proc-macro2 1.0.78",
"quote 1.0.35",
"strsim",
"syn 2.0.52",
]
[[package]]
name = "darling_macro"
version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a668eda54683121533a393014d8692171709ff57a7d61f187b6e782719f8933f"
dependencies = [
"darling_core",
"quote 1.0.35",
"syn 2.0.52",
]
[[package]]
name = "derive_destructure"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc38e2e2eefd927b5804046cac648ab2e0a46d3f038a4b82f1e0aa149a1cbccd"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"syn 0.15.44",
]
[[package]]
name = "derive_more"
version = "0.99.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
"proc-macro2 1.0.78",
"quote 1.0.35",
"rustc_version",
"syn 1.0.109",
]
[[package]]
name = "digest"
version = "0.10.7"
@@ -253,9 +137,9 @@ dependencies = [
[[package]]
name = "dyn-clone"
version = "1.0.16"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "either"
@@ -263,6 +147,12 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -273,19 +163,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
@@ -297,30 +174,10 @@ dependencies = [
]
[[package]]
name = "heck"
version = "0.4.1"
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "intern-all"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c9bf7d7b0572f7b4398fddc93ac1a200a92d1ba319a27dac04649b2223c0f6"
dependencies = [
"hashbrown",
"lazy_static",
"trait-set",
]
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "itertools"
@@ -331,16 +188,6 @@ 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"
@@ -353,18 +200,6 @@ version = "0.2.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "never"
version = "0.1.0"
@@ -380,12 +215,6 @@ dependencies = [
"autocfg",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
version = "1.19.0"
@@ -393,29 +222,74 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "orchidlang"
version = "0.3.0"
name = "orchid-api"
version = "0.1.0"
dependencies = [
"bound",
"clap",
"const_format",
"derive_more",
"orchid-api-derive",
"orchid-api-traits",
"ordered-float",
]
[[package]]
name = "orchid-api-derive"
version = "0.1.0"
dependencies = [
"darling",
"itertools",
"orchid-api-traits",
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 2.0.52",
]
[[package]]
name = "orchid-api-traits"
version = "0.1.0"
dependencies = [
"ordered-float",
]
[[package]]
name = "orchid-base"
version = "0.1.0"
dependencies = [
"derive_destructure",
"dyn-clone",
"hashbrown",
"intern-all",
"itertools",
"lazy_static",
"never",
"once_cell",
"orchid-api",
"orchid-api-derive",
"orchid-api-traits",
"ordered-float",
"paste",
"rayon",
"rust-embed",
"substack",
"take_mut",
"termsize",
"trait-set",
"unicode-segmentation",
]
[[package]]
name = "orchid-extension"
version = "0.1.0"
dependencies = [
"orchid-api",
"orchid-api-traits",
"orchid-base",
]
[[package]]
name = "orchid-host"
version = "0.1.0"
[[package]]
name = "orchid-std"
version = "0.1.0"
[[package]]
name = "orcx"
version = "0.1.0"
[[package]]
name = "ordered-float"
version = "4.2.0"
@@ -426,10 +300,13 @@ dependencies = [
]
[[package]]
name = "paste"
version = "1.0.14"
name = "proc-macro2"
version = "0.4.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
dependencies = [
"unicode-xid",
]
[[package]]
name = "proc-macro2"
@@ -440,72 +317,29 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "0.6.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
dependencies = [
"proc-macro2 0.4.30",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
"proc-macro2 1.0.78",
]
[[package]]
name = "rayon"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
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",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rust-embed"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
@@ -514,28 +348,36 @@ dependencies = [
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8"
dependencies = [
"proc-macro2",
"quote",
"proc-macro2 1.0.78",
"quote 1.0.35",
"rust-embed-utils",
"syn 2.0.50",
"syn 2.0.52",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
version = "8.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581"
dependencies = [
"globset",
"sha2",
"walkdir",
]
[[package]]
name = "rustc_version"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
"semver",
]
[[package]]
name = "same-file"
version = "1.0.6"
@@ -546,24 +388,10 @@ dependencies = [
]
[[package]]
name = "serde"
version = "1.0.197"
name = "semver"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
]
checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
[[package]]
name = "sha2"
@@ -576,11 +404,15 @@ dependencies = [
"digest",
]
[[package]]
name = "stdio-perftest"
version = "0.1.0"
[[package]]
name = "strsim"
version = "0.11.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "substack"
@@ -588,67 +420,47 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffccc3d80f0a489de67aa74ff31ab852abb973e1c6dacf3704889e00ca544e7f"
[[package]]
name = "syn"
version = "0.15.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
dependencies = [
"proc-macro2 0.4.30",
"quote 0.6.13",
"unicode-xid",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"proc-macro2 1.0.78",
"quote 1.0.35",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.50"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
"proc-macro2 1.0.78",
"quote 1.0.35",
"unicode-ident",
]
[[package]]
name = "take_mut"
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 = "trait-set"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625"
dependencies = [
"proc-macro2",
"quote",
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 1.0.109",
]
@@ -664,23 +476,11 @@ version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-xid"
version = "0.2.4"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
[[package]]
name = "version_check"
@@ -698,12 +498,6 @@ 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"
@@ -714,12 +508,6 @@ 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"
@@ -732,7 +520,7 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi 0.3.9",
"winapi",
]
[[package]]
@@ -741,72 +529,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
[[package]]
name = "windows_i686_gnu"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
[[package]]
name = "windows_i686_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
[[package]]
name = "zerocopy"
version = "0.7.32"
@@ -822,7 +544,7 @@ version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"proc-macro2 1.0.78",
"quote 1.0.35",
"syn 2.0.52",
]

View File

@@ -1,41 +1,9 @@
[package]
name = "orchidlang"
version = "0.3.0"
edition = "2021"
license = "GPL-3.0"
repository = "https://github.com/lbfalvy/orchid"
description = """
An embeddable pure functional scripting language
"""
authors = ["Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"]
[workspace]
resolver = "2"
[lib]
path = "src/lib.rs"
[[bin]]
name = "orcx"
path = "src/bin/orcx.rs"
doc = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hashbrown = "0.14"
ordered-float = "4.2"
itertools = "0.12"
dyn-clone = "1.0"
trait-set = "0.3"
paste = "1.0"
rust-embed = { version = "8.2", features = ["include-exclude"] }
take_mut = "0.2"
unicode-segmentation = "1.11"
never = "0.1"
substack = "1.1"
intern-all = "0.4.1"
once_cell = "1.19"
const_format = "0.2"
bound = "0.5"
# Dependencies of orcx
clap = { version = "4.5", features = ["derive"] }
rayon = "1.8"
termsize = "0.1"
members = [
# "orchidlang",
"orcx",
"orchid-api",
"orchid-std", "orchid-base", "orchid-api-derive", "orchid-api-traits", "stdio-perftest", "orchid-extension", "orchid-host",
]

View File

@@ -0,0 +1,17 @@
[package]
name = "orchid-api-derive"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
quote = "1.0.35"
syn = { version = "2.0.52" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
proc-macro2 = "1.0.78"
darling = "0.20.8"
itertools = "0.12.1"

View File

@@ -0,0 +1,30 @@
use proc_macro2 as pm2;
use quote::ToTokens;
use syn::spanned::Spanned;
pub fn add_trait_bounds(mut generics: syn::Generics, bound: syn::TypeParamBound) -> syn::Generics {
for param in &mut generics.params {
if let syn::GenericParam::Type(ref mut type_param) = *param {
type_param.bounds.push(bound.clone())
}
}
generics
}
pub fn destructure(fields: &syn::Fields) -> Option<pm2::TokenStream> {
match fields {
syn::Fields::Unit => None,
syn::Fields::Named(_) => {
let field_list = fields.iter().map(|f| f.ident.as_ref().unwrap());
Some(quote! { { #(#field_list),* } })
},
syn::Fields::Unnamed(un) => {
let field_list = (0..fields.len()).map(|i| pos_field_name(i, un.span()));
Some(quote! { ( #(#field_list),* ) })
},
}
}
pub fn pos_field_name(i: usize, span: pm2::Span) -> pm2::TokenStream {
syn::Ident::new(&format!("field_{i}"), span).to_token_stream()
}

View File

@@ -0,0 +1,56 @@
use proc_macro::TokenStream;
use proc_macro2 as pm2;
use crate::common::add_trait_bounds;
pub fn derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as syn::DeriveInput);
let generics = add_trait_bounds(input.generics, parse_quote!(orchid_api_traits::Decode));
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let name = input.ident;
let decode = decode_body(&input.data);
let expanded = quote! {
impl #impl_generics orchid_api_traits::Decode for #name #ty_generics #where_clause {
fn decode<R: std::io::Read>(read: &mut R) -> Self { #decode }
}
};
TokenStream::from(expanded)
}
fn decode_fields(fields: &syn::Fields) -> pm2::TokenStream {
match fields {
syn::Fields::Unit => pm2::TokenStream::new(),
syn::Fields::Named(_) => {
let names = fields.iter().map(|f| f.ident.as_ref().unwrap());
quote! { { #( #names: orchid_api_traits::Decode::decode(read), )* } }
},
syn::Fields::Unnamed(_) => {
let exprs = fields.iter().map(|_| quote! { orchid_api_traits::Decode::decode(read), });
quote! { ( #( #exprs )* ) }
},
}
}
fn decode_body(data: &syn::Data) -> proc_macro2::TokenStream {
match data {
syn::Data::Union(_) => panic!("Unions can't be deserialized"),
syn::Data::Struct(str) => {
let fields = decode_fields(&str.fields);
quote! { Self #fields }
},
syn::Data::Enum(en) => {
let opts = en.variants.iter().enumerate().map(|(i, v @ syn::Variant { ident, .. })| {
let fields = decode_fields(&v.fields);
let id = i as u8;
quote! { #id => Self::#ident #fields, }
});
quote! {
match <u8 as orchid_api_traits::Decode>::decode(read) {
#(#opts)*
x => panic!("Unrecognized enum kind {x}")
}
}
},
}
}

View File

@@ -0,0 +1,68 @@
use proc_macro::TokenStream;
use proc_macro2 as pm2;
use quote::ToTokens;
use syn::spanned::Spanned;
use crate::common::{add_trait_bounds, destructure, pos_field_name};
pub fn derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as syn::DeriveInput);
let e_generics = add_trait_bounds(input.generics, parse_quote!(orchid_api_traits::Decode));
let (e_impl_generics, e_ty_generics, e_where_clause) = e_generics.split_for_impl();
let name = input.ident;
let encode = encode_body(&input.data);
let expanded = quote! {
impl #e_impl_generics orchid_api_traits::Encode for #name #e_ty_generics #e_where_clause {
fn encode<W: std::io::Write>(&self, write: &mut W) { #encode }
}
};
TokenStream::from(expanded)
}
fn encode_body(data: &syn::Data) -> Option<pm2::TokenStream> {
match data {
syn::Data::Union(_) => panic!("Unions can't be deserialized"),
syn::Data::Struct(str) => {
let dest = destructure(&str.fields)?;
let body = encode_items(&str.fields);
Some(quote! {
let Self #dest = &self;
#body
})
},
syn::Data::Enum(en) => {
let options = en.variants.iter().enumerate().map(|(i, v @ syn::Variant { ident, .. })| {
let dest = destructure(&v.fields).unwrap_or_default();
let body = encode_items(&v.fields);
quote! {
Self::#ident #dest => {
(#i as u64).encode(write);
#body
}
}
});
Some(quote! {
match self {
#(#options)*
_ => unreachable!("Autogenerated encode impl for all possible variants"),
}
})
},
}
}
fn encode_names<T: ToTokens>(names: impl Iterator<Item = T>) -> pm2::TokenStream {
quote! { #( #names .encode(write); )* }
}
fn encode_items(fields: &syn::Fields) -> Option<pm2::TokenStream> {
match fields {
syn::Fields::Unit => None,
syn::Fields::Named(_) => Some(encode_names(fields.iter().map(|f| f.ident.as_ref().unwrap()))),
syn::Fields::Unnamed(un) =>
Some(encode_names((0..fields.len()).map(|i| pos_field_name(i, un.span())))),
}
}

View File

@@ -0,0 +1,127 @@
use std::iter;
use itertools::Itertools;
use pm2::TokenTree;
use proc_macro::TokenStream;
use proc_macro2 as pm2;
use syn::DeriveInput;
pub fn derive(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as syn::DeriveInput);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let name = &input.ident;
let extendable = is_extendable(&input);
let is_leaf_val = if extendable { quote!(TLFalse) } else { quote!(TLTrue) };
match get_ancestry(&input) {
None => TokenStream::from(quote! {
impl #impl_generics orchid_api_traits::InHierarchy for #name #ty_generics #where_clause {
type IsRoot = orchid_api_traits::TLTrue;
type IsLeaf = orchid_api_traits:: #is_leaf_val ;
}
}),
Some(ancestry) => {
let parent = ancestry[0].clone();
let casts = gen_casts(&ancestry[..], &quote!(#name));
TokenStream::from(quote! {
#casts
impl #impl_generics orchid_api_traits::InHierarchy for #name #ty_generics #where_clause {
type IsRoot = orchid_api_traits::TLFalse;
type IsLeaf = orchid_api_traits:: #is_leaf_val ;
}
impl #impl_generics orchid_api_traits::Extends for #name #ty_generics #where_clause {
type Parent = #parent;
}
})
},
}
}
fn gen_casts(ancestry: &[pm2::TokenStream], this: &pm2::TokenStream) -> pm2::TokenStream {
let from_impls = iter::once(this).chain(ancestry.iter()).tuple_windows().map(|(prev, cur)| {
quote! {
impl From<#this> for #cur {
fn from(value: #this) -> Self {
#cur::#prev(value.into())
}
}
}
});
let try_from_impls = (1..=ancestry.len()).map(|len| {
let (orig, inter) = ancestry[..len].split_last().unwrap();
fn gen_chk(r: &[pm2::TokenStream], last: &pm2::TokenStream) -> pm2::TokenStream {
match r.split_last() {
None => quote! { #last (_) => true },
Some((ty, tail)) => {
let sub = gen_chk(tail, last);
quote! {
#ty ( value ) => match value {
#ty:: #sub ,
_ => false
}
}
}
}
}
let chk = gen_chk(inter, this);
fn gen_unpk(r: &[pm2::TokenStream], last: &pm2::TokenStream) -> pm2::TokenStream {
match r.split_last() {
None => quote! { #last ( value ) => value },
Some((ty, tail)) => {
let sub = gen_unpk(tail, last);
quote! {
#ty ( value ) => match value {
#ty:: #sub ,
_ => unreachable!("Checked above!"),
}
}
}
}
}
let unpk = gen_unpk(inter, this);
quote! {
impl TryFrom<#orig> for #this {
type Error = #orig;
fn try_from(value: #orig) -> Result<Self, Self::Error> {
let can_cast = match &value {
#orig:: #chk ,
_ => false
};
if !can_cast { return Err(value) }
Ok ( match value {
#orig:: #unpk ,
_ => unreachable!("Checked above!")
} )
}
}
}
});
from_impls.chain(try_from_impls).flatten().collect()
}
fn get_ancestry(input: &DeriveInput) -> Option<Vec<pm2::TokenStream>> {
input.attrs.iter().find(|a| a.path().get_ident().is_some_and(|i| *i == "extends")).map(|attr| {
match &attr.meta {
syn::Meta::List(list) => (list.tokens.clone().into_iter())
.batching(|it| {
let grp: pm2::TokenStream = it
.take_while(|t| {
if let TokenTree::Punct(punct) = t { punct.as_char() != ',' } else { true }
})
.collect();
(!grp.is_empty()).then_some(grp)
})
.collect(),
_ => panic!("The correct format of the parent macro is #[parent(SomeParentType)]"),
}
})
}
fn is_extendable(input: &DeriveInput) -> bool {
input.attrs.iter().any(|a| a.path().get_ident().is_some_and(|i| *i == "extendable"))
}
#[test]
fn test_wtf() {
eprintln!("{}", gen_casts(&[quote!(ExtHostReq)], &quote!(BogusReq)))
}

View File

@@ -0,0 +1,27 @@
mod common;
mod decode;
mod encode;
mod hierarchy;
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
#[allow(unused)]
use orchid_api_traits::Coding;
use proc_macro::TokenStream;
#[proc_macro_derive(Decode)]
pub fn decode(input: TokenStream) -> TokenStream { decode::derive(input) }
#[proc_macro_derive(Encode)]
pub fn encode(input: TokenStream) -> TokenStream { encode::derive(input) }
#[proc_macro_derive(Hierarchy, attributes(extends, extendable))]
pub fn hierarchy(input: TokenStream) -> TokenStream { hierarchy::derive(input) }
#[proc_macro_derive(Coding)]
pub fn coding(input: TokenStream) -> TokenStream {
decode(input.clone()).into_iter().chain(encode(input)).collect()
}

View File

@@ -0,0 +1,9 @@
[package]
name = "orchid-api-traits"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ordered-float = "4.2"

View File

@@ -0,0 +1,266 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::io::{Read, Write};
use std::iter;
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use ordered_float::{FloatCore, NotNan};
use crate::encode_enum;
pub trait Decode {
/// Decode an instance from the beginning of the buffer. Return the decoded
/// data and the remaining buffer.
fn decode<R: Read>(read: &mut R) -> Self;
}
pub trait Encode {
/// Append an instance of the struct to the buffer
fn encode<W: Write>(&self, write: &mut W);
fn enc_vec(&self) -> Vec<u8> {
let mut vec = Vec::new();
self.encode(&mut vec);
vec
}
}
pub trait Coding: Encode + Decode {}
impl<T: Encode + Decode> Coding for T {}
macro_rules! num_impl {
($number:ty, $size:expr) => {
impl Decode for $number {
fn decode<R: Read>(read: &mut R) -> Self {
let mut bytes = [0u8; $size];
read.read_exact(&mut bytes).unwrap();
<$number>::from_be_bytes(bytes)
}
}
impl Encode for $number {
fn encode<W: Write>(&self, write: &mut W) {
write.write_all(&self.to_be_bytes()).expect("Could not write number")
}
}
};
($number:ty) => {
num_impl!($number, (<$number>::BITS / 8) as usize);
};
}
num_impl!(u128);
num_impl!(u64);
num_impl!(u32);
num_impl!(u16);
num_impl!(u8);
num_impl!(i128);
num_impl!(i64);
num_impl!(i32);
num_impl!(i16);
num_impl!(i8);
num_impl!(f64, 8);
num_impl!(f32, 4);
macro_rules! nonzero_impl {
($name:ty) => {
impl Decode for $name {
fn decode<R: Read>(read: &mut R) -> Self { Self::new(Decode::decode(read)).unwrap() }
}
impl Encode for $name {
fn encode<W: Write>(&self, write: &mut W) { self.get().encode(write) }
}
};
}
nonzero_impl!(std::num::NonZeroU8);
nonzero_impl!(std::num::NonZeroU16);
nonzero_impl!(std::num::NonZeroU32);
nonzero_impl!(std::num::NonZeroU64);
nonzero_impl!(std::num::NonZeroU128);
nonzero_impl!(std::num::NonZeroI8);
nonzero_impl!(std::num::NonZeroI16);
nonzero_impl!(std::num::NonZeroI32);
nonzero_impl!(std::num::NonZeroI64);
nonzero_impl!(std::num::NonZeroI128);
impl<'a, T: Encode> Encode for &'a T {
fn encode<W: Write>(&self, write: &mut W) { (**self).encode(write) }
}
impl<T: Decode + FloatCore> Decode for NotNan<T> {
fn decode<R: Read>(read: &mut R) -> Self { NotNan::new(T::decode(read)).expect("Float was NaN") }
}
impl<T: Encode + FloatCore> Encode for NotNan<T> {
fn encode<W: Write>(&self, write: &mut W) { self.as_ref().encode(write) }
}
impl Decode for String {
fn decode<R: Read>(read: &mut R) -> Self {
let len = u64::decode(read).try_into().unwrap();
let mut data = vec![0u8; len];
read.read_exact(&mut data).unwrap();
std::str::from_utf8(&data).expect("String invalid UTF-8").to_owned()
}
}
impl Encode for String {
fn encode<W: Write>(&self, write: &mut W) {
u64::try_from(self.len()).unwrap().encode(write);
write.write_all(self.as_bytes()).unwrap()
}
}
impl Encode for str {
fn encode<W: Write>(&self, write: &mut W) {
u64::try_from(self.len()).unwrap().encode(write);
write.write_all(self.as_bytes()).unwrap()
}
}
impl<T: Decode> Decode for Vec<T> {
fn decode<R: Read>(read: &mut R) -> Self {
let len = u64::decode(read).try_into().unwrap();
iter::repeat_with(|| T::decode(read)).take(len).collect()
}
}
impl<T: Encode> Encode for Vec<T> {
fn encode<W: Write>(&self, write: &mut W) {
u64::try_from(self.len()).unwrap().encode(write);
self.iter().for_each(|t| t.encode(write));
}
}
impl<T: Encode> Encode for [T] {
fn encode<W: Write>(&self, write: &mut W) {
u64::try_from(self.len()).unwrap().encode(write);
self.iter().for_each(|t| t.encode(write));
}
}
impl<T: Decode> Decode for Option<T> {
fn decode<R: Read>(read: &mut R) -> Self {
match u8::decode(read) {
0 => None,
1 => Some(T::decode(read)),
x => panic!("{x} is not a valid option value"),
}
}
}
impl<T: Encode> Encode for Option<T> {
fn encode<W: Write>(&self, write: &mut W) {
let t = if let Some(t) = self { t } else { return 0u8.encode(write) };
1u8.encode(write);
t.encode(write);
}
}
impl<T: Decode, E: Decode> Decode for Result<T, E> {
fn decode<R: Read>(read: &mut R) -> Self {
match u8::decode(read) {
0 => Self::Ok(T::decode(read)),
1 => Self::Err(E::decode(read)),
x => panic!("Invalid Result tag {x}"),
}
}
}
impl<T: Encode, E: Encode> Encode for Result<T, E> {
fn encode<W: Write>(&self, write: &mut W) {
match self {
Ok(t) => encode_enum(write, 0, |w| t.encode(w)),
Err(e) => encode_enum(write, 1, |w| e.encode(w)),
}
}
}
impl<K: Decode + Eq + Hash, V: Decode> Decode for HashMap<K, V> {
fn decode<R: Read>(read: &mut R) -> Self {
let len = u64::decode(read).try_into().unwrap();
iter::repeat_with(|| <(K, V)>::decode(read)).take(len).collect()
}
}
impl<K: Encode + Eq + Hash, V: Encode> Encode for HashMap<K, V> {
fn encode<W: Write>(&self, write: &mut W) {
u64::try_from(self.len()).unwrap().encode(write);
self.iter().for_each(|pair| pair.encode(write));
}
}
macro_rules! tuple {
(($($t:ident)*) ($($T:ident)*)) => {
impl<$($T: Decode),*> Decode for ($($T,)*) {
fn decode<R: Read>(read: &mut R) -> Self { ($($T::decode(read),)*) }
}
impl<$($T: Encode),*> Encode for ($($T,)*) {
fn encode<W: Write>(&self, write: &mut W) {
let ($($t,)*) = self;
$( $t.encode(write); )*
}
}
};
}
tuple!((t)(T));
tuple!((t u) (T U));
tuple!((t u v) (T U V));
tuple!((t u v x) (T U V X)); // 4
tuple!((t u v x y) (T U V X Y));
tuple!((t u v x y z) (T U V X Y Z));
tuple!((t u v x y z a) (T U V X Y Z A));
tuple!((t u v x y z a b) (T U V X Y Z A B)); // 8
tuple!((t u v x y z a b c) (T U V X Y Z A B C));
tuple!((t u v x y z a b c d) (T U V X Y Z A B C D));
tuple!((t u v x y z a b c d e) (T U V X Y Z A B C D E));
tuple!((t u v x y z a b c d e f) (T U V X Y Z A B C D E F)); // 12
tuple!((t u v x y z a b c d e f g) (T U V X Y Z A B C D E F G));
tuple!((t u v x y z a b c d e f g h) (T U V X Y Z A B C D E F G H));
tuple!((t u v x y z a b c d e f g h i) (T U V X Y Z A B C D E F G H I));
tuple!((t u v x y z a b c d e f g h i j) (T U V X Y Z A B C D E F G H I J)); // 16
impl Decode for () {
fn decode<R: Read>(_: &mut R) -> Self {}
}
impl Encode for () {
fn encode<W: Write>(&self, _: &mut W) {}
}
impl Decode for bool {
fn decode<R: Read>(read: &mut R) -> Self {
let mut buf = [0];
read.read_exact(&mut buf).unwrap();
buf[0] != 0
}
}
impl Encode for bool {
fn encode<W: Write>(&self, write: &mut W) {
write.write_all(&[if *self { 0xff } else { 0 }]).unwrap()
}
}
impl<T: Decode, const N: usize> Decode for [T; N] {
fn decode<R: Read>(read: &mut R) -> Self {
// TODO: figure out how to do this in safe rust on the stack
((0..N).map(|_| T::decode(read)).collect::<Vec<_>>().try_into())
.unwrap_or_else(|_| unreachable!("The length of this iterator is statically known"))
}
}
impl<T: Encode, const N: usize> Encode for [T; N] {
fn encode<W: Write>(&self, write: &mut W) { self.iter().for_each(|t| t.encode(write)) }
}
impl<T: Decode> Decode for Range<T> {
fn decode<R: Read>(read: &mut R) -> Self { T::decode(read)..T::decode(read) }
}
impl<T: Encode> Encode for Range<T> {
fn encode<W: Write>(&self, write: &mut W) {
self.start.encode(write);
self.end.encode(write);
}
}
macro_rules! smart_ptr {
($name:tt) => {
impl<T: Decode> Decode for $name<T> {
fn decode<R: Read>(read: &mut R) -> Self { $name::new(T::decode(read)) }
}
impl<T: Encode> Encode for $name<T> {
fn encode<W: Write>(&self, write: &mut W) { (**self).encode(write) }
}
};
}
smart_ptr!(Arc);
smart_ptr!(Rc);
smart_ptr!(Box);
impl Decode for char {
fn decode<R: Read>(read: &mut R) -> Self { char::from_u32(u32::decode(read)).unwrap() }
}
impl Encode for char {
fn encode<W: Write>(&self, write: &mut W) { (*self as u32).encode(write) }
}

View File

@@ -0,0 +1,18 @@
use std::io::{Read, Write};
use crate::Encode;
pub fn encode_enum<W: Write>(write: &mut W, id: u8, f: impl FnOnce(&mut W)) {
id.encode(write);
f(write)
}
pub fn write_exact(write: &mut impl Write, bytes: &'static [u8]) {
write.write_all(bytes).expect("Failed to write exact bytes")
}
pub fn read_exact(read: &mut impl Read, bytes: &'static [u8]) {
let mut data = vec![0u8; bytes.len()];
read.read_exact(&mut data).expect("Failed to read bytes");
assert_eq!(&data, bytes, "Wrong bytes")
}

View File

@@ -0,0 +1,30 @@
pub trait TBool {}
pub struct TTrue;
impl TBool for TTrue {}
pub struct TFalse;
impl TBool for TFalse {}
/// Implementation picker for a tree node
///
/// Note: This technically allows for the degenerate case
/// ```
/// struct MyType;
/// impl TreeRolePicker for MyType {
/// type IsLeaf = TTrue;
/// type IsRoot = TTrue;
/// }
/// ```
/// This isn't very useful because it describes a one element sealed hierarchy.
pub trait TreeRolePicker {
type IsLeaf: TBool;
type IsRoot: TBool;
}
pub trait Extends: TreeRolePicker<IsRoot = TFalse> {
type Parent: TreeRolePicker<IsLeaf = TFalse>;
}
pub trait Inherits<T> {}
// impl<T> Inherits<T, 0> for T {}
impl<T: Extends, This> Inherits<T::Parent> for This where This: Inherits<T> {}

View File

@@ -0,0 +1,80 @@
/// [Hierarchy] implementation key. The two implementors of this trait are
/// [Base] and [Subtype]. These types are assigned to [InHierarchy::Role] to
/// select the implementation of [Hierarchy].
pub trait HierarchyRole {}
/// A type-level boolean suitable to select conditional trait implementations.
/// Implementors are [True] and [False]
pub trait TLBool {}
/// [TLBool] value of `true`. The opposite is [False]
pub struct TLTrue;
impl TLBool for TLTrue {}
/// [TLBool] value of `false`. The opposite is [True]
pub struct TLFalse;
impl TLBool for TLFalse {}
/// Assign this type to [InHierarchy::Role] and implement [Descendant] to create
/// a subtype. These types can be upcast to their parent type, conditionally
/// downcast from it, and selected for [Descendant::Parent] by other types.
pub struct Subtype;
impl HierarchyRole for Subtype {}
/// Assign this type to [InHierarchy::Role] to create a base type. These types
/// are upcast only to themselves, but they can be selected in
/// [Descendant::Parent].
pub struct Base;
impl HierarchyRole for Base {}
/// A type that implements [Hierarchy]. Used to select implementations of traits
/// on the hierarchy
pub trait InHierarchy: Clone {
/// Indicates that this hierarchy element is a leaf. Leaves can never have
/// children
type IsLeaf: TLBool;
/// Indicates that this hierarchy element is a root. Roots can never have
/// parents
type IsRoot: TLBool;
}
/// A type that derives from a parent type.
pub trait Extends: InHierarchy<IsRoot = TLFalse> + Into<Self::Parent> {
/// Specify the immediate parent of this type. This guides the
type Parent: InHierarchy<IsLeaf = TLFalse>
+ TryInto<Self>
+ UnderRootImpl<<Self::Parent as InHierarchy>::IsRoot>;
}
pub trait UnderRootImpl<IsRoot: TLBool>: Sized {
type __Root: UnderRoot<IsRoot = TLTrue, Root = Self::__Root>;
fn __into_root(self) -> Self::__Root;
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root>;
}
pub trait UnderRoot: InHierarchy {
type Root: UnderRoot<IsRoot = TLTrue, Root = Self::Root>;
fn into_root(self) -> Self::Root;
fn try_from_root(root: Self::Root) -> Result<Self, Self::Root>;
}
impl<T: InHierarchy + UnderRootImpl<T::IsRoot>> UnderRoot for T {
type Root = <Self as UnderRootImpl<<Self as InHierarchy>::IsRoot>>::__Root;
fn into_root(self) -> Self::Root { self.__into_root() }
fn try_from_root(root: Self::Root) -> Result<Self, Self::Root> { Self::__try_from_root(root) }
}
impl<T: InHierarchy<IsRoot = TLTrue>> UnderRootImpl<TLTrue> for T {
type __Root = Self;
fn __into_root(self) -> Self::__Root { self }
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root> { Ok(root) }
}
impl<T: InHierarchy<IsRoot = TLFalse> + Extends> UnderRootImpl<TLFalse> for T {
type __Root = <<Self as Extends>::Parent as UnderRootImpl<
<<Self as Extends>::Parent as InHierarchy>::IsRoot,
>>::__Root;
fn __into_root(self) -> Self::__Root {
<Self as Into<<Self as Extends>::Parent>>::into(self).into_root()
}
fn __try_from_root(root: Self::__Root) -> Result<Self, Self::__Root> {
let parent = <Self as Extends>::Parent::try_from_root(root)?;
parent.clone().try_into().map_err(|_| parent.into_root())
}
}

View File

@@ -0,0 +1,12 @@
mod coding;
mod helpers;
mod hierarchy;
mod relations;
pub use coding::{Coding, Decode, Encode};
pub use helpers::{encode_enum, read_exact, write_exact};
pub use hierarchy::{
Base, Extends, HierarchyRole, InHierarchy, Subtype, TLBool, TLFalse, TLTrue, UnderRoot,
UnderRootImpl,
};
pub use relations::{MsgSet, Request};

View File

@@ -0,0 +1,13 @@
use super::coding::{Coding, Encode};
pub trait Request: Coding + Sized + Send + 'static {
type Response: Coding + Send + 'static;
fn respond(&self, rep: Self::Response) -> Vec<u8> { rep.enc_vec() }
}
pub trait MsgSet {
type InReq: Coding + Sized + Send + 'static;
type InNot: Coding + Sized + Send + 'static;
type OutReq: Coding + Sized + Send + 'static;
type OutNot: Coding + Sized + Send + 'static;
}

12
orchid-api/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "orchid-api"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ordered-float = "4.2.0"
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
derive_more = "0.99.17"

91
orchid-api/src/atom.rs Normal file
View File

@@ -0,0 +1,91 @@
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::expr::{Expr, ExprTicket};
use crate::proto::{ExtHostReq, HostExtNotif, HostExtReq};
use crate::system::SysId;
pub type AtomData = Vec<u8>;
/// An atom representation that can be serialized and sent around. Atoms
/// represent the smallest increment of work.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Atom {
/// Instance ID of the system that created the atom
pub owner: SysId,
/// Indicates whether the owner should be notified when this atom is dropped.
/// Construction is always explicit and atoms are never cloned.
///
/// Atoms with `drop == false` are also known as trivial, they can be
/// duplicated and stored with no regard to expression lifetimes. NOTICE
/// that this only applies to the atom. If it's referenced with an
/// [ExprTicket], the ticket itself can still expire.
///
/// Notice also that the atoms still expire when the system is dropped, and
/// are not portable across instances of the same system, so this doesn't
/// imply that the atom is serializable.
pub drop: bool,
/// Data stored in the atom. This could be a key into a map, or the raw data
/// of the atom if it isn't too big.
pub data: AtomData,
}
/// Attempt to apply an atom as a function to an expression
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(AtomReq, HostExtReq)]
pub struct CallRef(pub Atom, pub ExprTicket);
impl Request for CallRef {
type Response = Expr;
}
/// Attempt to apply an atom as a function, consuming the atom and enabling the
/// library to reuse its datastructures rather than duplicating them. This is an
/// optimization over [CallRef] followed by [AtomDrop].
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(AtomReq, HostExtReq)]
pub struct FinalCall(pub Atom, pub ExprTicket);
impl Request for FinalCall {
type Response = Expr;
}
/// Determine whether two atoms are identical for the purposes of macro
/// application. If a given atom is never generated by macros or this relation
/// is difficult to define, the module can return false
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(AtomReq, HostExtReq)]
pub struct AtomSame(pub Atom, pub Atom);
impl Request for AtomSame {
type Response = bool;
}
/// A request blindly routed to the system that provides an atom.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(AtomReq, HostExtReq)]
pub struct Fwded(pub Atom, pub Vec<u8>);
impl Request for Fwded {
type Response = Vec<u8>;
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(ExtHostReq)]
pub struct Fwd(pub Atom, pub Vec<u8>);
impl Request for Fwd {
type Response = Vec<u8>;
}
/// Notification that an atom is being dropped because its associated expression
/// isn't referenced anywhere. This should have no effect if the atom's `drop`
/// flag is false.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(HostExtNotif)]
pub struct AtomDrop(pub Atom);
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(HostExtReq)]
#[extendable]
pub enum AtomReq {
CallRef(CallRef),
FinalCall(FinalCall),
AtomSame(AtomSame),
Fwded(Fwded),
}

1
orchid-api/src/error.rs Normal file
View File

@@ -0,0 +1 @@
pub type ProjErrId = u16;

99
orchid-api/src/expr.rs Normal file
View File

@@ -0,0 +1,99 @@
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::atom::Atom;
use crate::intern::{TStr, TStrv};
use crate::location::Location;
use crate::proto::ExtHostReq;
use crate::system::SysId;
/// An arbitrary ID associated with an expression on the host side. Incoming
/// tickets always come with some lifetime guarantee, which can be extended with
/// [AcquireExpr].
///
/// The ID is globally unique within its lifetime, but may be reused.
pub type ExprTicket = u64;
/// Acquire a strong reference to an expression. This keeps it alive until a
/// corresponding [Release] is emitted. The number of times a system has
/// acquired an expression is counted, and it is the system's responsibility to
/// ensure that acquires and releases pair up. Behaviour in case of a
/// superfluous free is not defined.
///
/// Some contexts may specify that an ingress [ExprTicket] is owned, this means
/// that acquiring it is not necessary.
///
/// This can be called with a foreign system to signal that an owned reference
/// is being passed, though [Relocate] may be a better fit.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Acquire(pub SysId, pub ExprTicket);
/// Release a reference either previously acquired through either [Acquire]
/// or by receiving an owned reference. The number of times a system has
/// acquired an expression is counted, and it is the system's responsibility to
/// ensure that acquires and releases pair up. Behaviour in case of excessive
/// freeing is not defined.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Release(pub SysId, pub ExprTicket);
/// Decrement the reference count for one system and increment it for another,
/// to indicate passing an owned reference. Equivalent to [Acquire] followed by
/// [Release].
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Relocate {
pub dec: SysId,
pub inc: SysId,
pub expr: ExprTicket,
}
/// A description of a new expression. It is used as the return value of
/// [crate::atom::Call] or [crate::atom::CallRef], or a constant in the
/// [crate::tree::Tree].
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub enum Clause {
/// Apply the lhs as a function to the rhs
Call(Box<Expr>, Box<Expr>),
/// Lambda function. The number operates as an argument name
Lambda(TStr, Box<Expr>),
/// Binds the argument passed to the lambda with the same ID in the same
/// template
Arg(TStr),
/// Insert the specified host-expression in the template here. When the clause
/// is used in the const tree, this variant is forbidden.
Slot(ExprTicket),
/// The lhs must be fully processed before the rhs can be processed.
/// Equivalent to Haskell's function of the same name
Seq(Box<Expr>, Box<Expr>),
/// Insert an atom in the tree. When the clause is used in the const tree, the
/// atom must be trivial.
Atom(Atom),
/// A reference to a constant
Const(TStrv),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Expr {
pub clause: Clause,
pub location: Location
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(ExprReq, ExtHostReq)]
pub struct Inspect(pub ExprTicket);
impl Request for Inspect {
type Response = Clause;
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(ExtHostReq)]
#[extendable]
pub enum ExprReq {
Inspect(Inspect),
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding)]
pub enum ExprNotif {
Acquire(Acquire),
Release(Release),
Relocate(Relocate),
}

16
orchid-api/src/fs.rs Normal file
View File

@@ -0,0 +1,16 @@
use orchid_api_derive::Coding;
use orchid_api_traits::Request;
pub type FsId = u16;
#[derive(Clone, Debug, Coding)]
pub enum Loaded {
Code(String),
Collection(Vec<String>),
}
#[derive(Clone, Debug, Coding)]
pub struct FsRead(pub Vec<String>);
impl Request for FsRead {
type Response = Result<Loaded, ()>;
}

91
orchid-api/src/intern.rs Normal file
View File

@@ -0,0 +1,91 @@
use std::num::NonZeroU64;
use std::sync::Arc;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::proto::{ExtHostReq, HostExtReq};
/// Intern requests sent by the replica to the master. These requests are
/// repeatable.
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(ExtHostReq)]
#[extendable]
pub enum IntReq {
InternStr(InternStr),
InternStrv(InternStrv),
ExternStr(ExternStr),
ExternStrv(ExternStrv),
}
/// replica -> master to intern a string on the master.
///
/// Repeatable.
///
/// See [IntReq]
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(IntReq, ExtHostReq)]
pub struct InternStr(pub Arc<String>);
impl Request for InternStr {
type Response = TStr;
}
/// replica -> master to find the interned string corresponding to a key.
///
/// Repeatable.
///
/// See [IntReq]
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(IntReq, ExtHostReq)]
pub struct ExternStr(pub TStr);
impl Request for ExternStr {
type Response = Arc<String>;
}
/// replica -> master to intern a vector of interned strings
///
/// Repeatable.
///
/// See [IntReq]
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(IntReq, ExtHostReq)]
pub struct InternStrv(pub Arc<Vec<TStr>>);
impl Request for InternStrv {
type Response = TStrv;
}
/// replica -> master to find the vector of interned strings corresponding to a
/// token
///
/// Repeatable.
///
/// See [IntReq]
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(IntReq, ExtHostReq)]
pub struct ExternStrv(pub TStrv);
impl Request for ExternStrv {
type Response = Arc<Vec<TStr>>;
}
/// A substitute for an interned string in serialized datastructures.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
pub struct TStr(pub NonZeroU64);
/// A substitute for an interned string sequence in serialized datastructures.
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
pub struct TStrv(pub NonZeroU64);
/// A request to sweep the replica. The master will not be sweeped until all
/// replicas respond, as it must retain everything the replicas retained
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(HostExtReq)]
pub struct Sweep;
impl Request for Sweep {
type Response = Retained;
}
/// List of keys in this replica that couldn't be sweeped because local
/// datastructures reference their value.
#[derive(Clone, Debug, Coding)]
pub struct Retained {
pub strings: Vec<TStr>,
pub vecs: Vec<TStrv>,
}

10
orchid-api/src/lib.rs Normal file
View File

@@ -0,0 +1,10 @@
pub mod atom;
pub mod error;
pub mod expr;
pub mod fs;
pub mod intern;
pub mod location;
pub mod proto;
pub mod system;
pub mod tree;
pub mod parser;

View File

@@ -0,0 +1,24 @@
use std::ops::Range;
use orchid_api_derive::Coding;
use crate::intern::TStrv;
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub enum Location {
None,
Gen(CodeGenInfo),
Range(SourceRange),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct SourceRange {
pub path: TStrv,
pub range: Range<u32>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct CodeGenInfo {
pub generator: String,
pub details: String,
}

55
orchid-api/src/parser.rs Normal file
View File

@@ -0,0 +1,55 @@
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use crate::intern::TStr;
use crate::proto::{ExtHostReq, HostExtReq};
use crate::system::SysId;
use crate::tree::TokenTree;
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(HostExtReq)]
#[extendable]
pub enum ParserReq {
MkLexer(MkLexer),
Lex(Lex),
}
pub type LexerId = u16;
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(ParserReq, HostExtReq)]
pub struct MkLexer(pub SysId, pub TStr);
impl Request for MkLexer {
type Response = LexerId;
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(ParserReq, HostExtReq)]
pub struct Lex {
pub parser: LexerId,
pub next: char,
pub pos: u32,
}
impl Request for Lex {
type Response = Option<LexResult>;
}
#[derive(Clone, Debug, Coding)]
pub struct LexResult {
pub consumed: u32,
pub data: TokenTree,
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(ExtHostReq)]
pub struct SubLex {
pub lexer: LexerId,
pub pos: u32,
}
impl Request for SubLex {
type Response = SubLex;
}
pub struct ParseLine {
}

125
orchid-api/src/proto.rs Normal file
View File

@@ -0,0 +1,125 @@
//! Basic messages of the Orchid extension API.
//!
//! The protocol is defined over a byte stream, normally the stdin/stdout of the
//! extension. The implementations of [Coding] in this library are considered
//! normative. Any breaking change here or in the default implementations of
//! [Coding] must also increment the version number in the intro strings.
//!
//! 3 different kinds of messages are recognized; request, response, and
//! notification. There are no general ordering guarantees about these, multiple
//! requests, even requests of the same type may be sent concurrently, unless
//! otherwise specified in the request's definition.
//!
//! Each message begins with a u32 length, followed by that many bytes of
//! message content. The first byte of the content is a u64 combined request ID
//! and discriminator, D.
//!
//! - If D = 0, the rest of the content is a notification.
//! - If 0 < D < 2^63, it is a request with identifier D.
//! - If 2^63 <= D, it is a response to request identifier !D.
//!
//! The order of both notifications and requests sent from the same thread must
//! be preserved. Toolkits must ensure that the client code is able to observe
//! the ordering of messages.
use std::io::{Read, Write};
use derive_more::{From, TryInto};
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::{read_exact, write_exact, Decode, Encode, MsgSet, Request};
use crate::{atom, expr, intern, parser, system, tree};
static HOST_INTRO: &[u8] = b"Orchid host, binary API v0\n";
pub struct HostHeader;
impl Decode for HostHeader {
fn decode<R: Read>(read: &mut R) -> Self {
read_exact(read, HOST_INTRO);
Self
}
}
impl Encode for HostHeader {
fn encode<W: Write>(&self, write: &mut W) { write_exact(write, HOST_INTRO) }
}
static EXT_INTRO: &[u8] = b"Orchid extension, binary API v0\n";
pub struct ExtensionHeader {
pub systems: Vec<system::SystemDecl>,
}
impl Decode for ExtensionHeader {
fn decode<R: Read>(read: &mut R) -> Self {
read_exact(read, EXT_INTRO);
Self { systems: Vec::decode(read) }
}
}
impl Encode for ExtensionHeader {
fn encode<W: Write>(&self, write: &mut W) {
write_exact(write, EXT_INTRO);
self.systems.encode(write)
}
}
#[derive(Clone, Debug, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Coding)]
pub struct Ping;
impl Request for Ping {
type Response = ();
}
/// Requests running from the extension to the host
#[derive(Clone, Coding, Hierarchy)]
#[extendable]
pub enum ExtHostReq {
Ping(Ping),
IntReq(intern::IntReq),
Fwd(atom::Fwd),
ExprReq(expr::ExprReq),
SubLex(parser::SubLex),
}
/// Notifications sent from the extension to the host
#[derive(Coding, From, TryInto)]
#[allow(clippy::enum_variant_names)]
pub enum ExtHostNotif {
Expr(expr::ExprNotif),
}
/// Requests running from the host to the extension
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable]
pub enum HostExtReq {
Ping(Ping),
NewSystem(system::NewSystem),
Sweep(intern::Sweep),
AtomReq(atom::AtomReq),
ParserReq(parser::ParserReq),
GetConstTree(tree::GetConstTree),
}
/// Notifications sent from the host to the extension
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extendable]
pub enum HostExtNotif {
SystemDrop(system::SystemDrop),
AtomDrop(atom::AtomDrop),
/// The host can assume that after this notif is sent, a correctly written
/// extension will eventually exit.
Exit,
}
/// Message set viewed from the extension's perspective
pub struct ExtMsgSet;
impl MsgSet for ExtMsgSet {
type InNot = HostExtNotif;
type InReq = HostExtReq;
type OutNot = ExtHostNotif;
type OutReq = ExtHostReq;
}
/// Message Set viewed from the host's perspective
pub struct HostMsgSet;
impl MsgSet for HostMsgSet {
type InNot = ExtHostNotif;
type InReq = ExtHostReq;
type OutNot = HostExtNotif;
type OutReq = HostExtReq;
}

54
orchid-api/src/system.rs Normal file
View File

@@ -0,0 +1,54 @@
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use ordered_float::NotNan;
use crate::proto::{HostExtNotif, HostExtReq};
/// ID of a system type
pub type SysDeclId = u16;
/// ID of a system instance
pub type SysId = u16;
/// Details about a system provided by this library
#[derive(Coding)]
pub struct SystemDecl {
/// ID of the system, unique within the library
pub id: SysDeclId,
/// This can be depended upon. Exactly one of each kind will be loaded
pub name: String,
/// If multiple instances of a system are found, the highest priority will be
/// used. This can be used for version counting, but also for fallbacks if a
/// negative number is found.
///
/// Systems cannot depend on specific versions and older versions of systems
/// are never loaded. Compatibility can be determined on a per-system basis
/// through an algorithm chosen by the provider.
pub priority: NotNan<f64>,
/// List of systems needed for this one to work correctly. These will be
/// looked up, and an error produced if they aren't found.
pub depends: Vec<String>,
}
/// Host -> extension; instantiate a system according to its [SystemDecl].
/// Multiple instances of a system may exist in the same address space, so it's
/// essential that any resource associated with a system finds its system by the
/// ID in a global map.
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(HostExtReq)]
pub struct NewSystem {
/// ID of the system
pub system: SysDeclId,
/// ID of the system instance, unique for the host
pub id: SysId,
/// Instance IDs for dependencies, in the order that the names appear in the
/// declaration
pub depends: Vec<SysId>,
}
impl Request for NewSystem {
type Response = ();
}
#[derive(Clone, Debug, Coding, Hierarchy)]
#[extends(HostExtNotif)]
pub struct SystemDrop(pub SysId);

76
orchid-api/src/tree.rs Normal file
View File

@@ -0,0 +1,76 @@
use std::collections::HashMap;
use orchid_api_derive::{Coding, Hierarchy};
use orchid_api_traits::Request;
use ordered_float::NotNan;
use crate::atom::Atom;
use crate::expr::Expr;
use crate::intern::TStr;
use crate::location::SourceRange;
use crate::proto::HostExtReq;
use crate::system::SysId;
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct TokenTree {
token: Token,
location: SourceRange,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub enum Token {
/// Lambda function. The number operates as an argument name
Lambda(TStr, Vec<TokenTree>),
Name(Vec<TStr>),
S(Paren, Vec<TokenTree>),
/// A placeholder in a macro. This variant is forbidden everywhere outside
/// line parser output
Ph(Placeholder),
Atom(Atom),
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub struct Placeholder {
name: TStr,
kind: PlaceholderKind,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub enum PlaceholderKind {
Scalar,
Name,
Vector { nonzero: bool, priority: u8 },
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)]
pub enum Paren {
Round,
Square,
Curly,
}
#[derive(Clone, Debug, Coding)]
pub struct MacroRule {
pub pattern: Vec<TokenTree>,
pub priority: NotNan<f64>,
pub template: Vec<TokenTree>,
}
#[derive(Clone, Debug, Coding)]
pub enum Tree {
Const(Expr),
Mod(TreeModule),
Rule(MacroRule),
}
#[derive(Clone, Debug, Coding)]
pub struct TreeModule {
pub children: HashMap<String, Tree>,
}
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)]
#[extends(HostExtReq)]
pub struct GetConstTree(pub SysId);
impl Request for GetConstTree {
type Response = TreeModule;
}

21
orchid-base/Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "orchid-base"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
derive_destructure = "1.0.0"
dyn-clone = "1.0.17"
hashbrown = "0.14.3"
itertools = "0.12.1"
lazy_static = "1.4.0"
never = "0.1.0"
orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-derive = { version = "0.1.0", path = "../orchid-api-derive" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
ordered-float = "4.2.0"
rust-embed = "8.3.0"
substack = "1.1.0"
trait-set = "0.3.0"

View File

44
orchid-base/src/child.rs Normal file
View File

@@ -0,0 +1,44 @@
use std::io::{self, Read, Write};
use std::sync::Mutex;
use std::{mem, process};
pub struct SharedChild {
child: process::Child,
stdin: Mutex<process::ChildStdin>,
stdout: Mutex<process::ChildStdout>,
}
impl SharedChild {
pub fn new(cmd: &mut process::Command) -> io::Result<Self> {
let mut child = cmd.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()).spawn()?;
let stdin = Mutex::new(child.stdin.take().expect("Piped stdin above"));
let stdout = Mutex::new(child.stdout.take().expect("Piped stdout above"));
Ok(Self { stdin, stdout, child })
}
pub fn send_msg(&self, msg: &[u8]) -> io::Result<()> {
send_msg(&mut *self.stdin.lock().unwrap(), msg)
}
pub fn recv_msg(&self) -> io::Result<Vec<u8>> { recv_msg(&mut *self.stdout.lock().unwrap()) }
}
impl Drop for SharedChild {
fn drop(&mut self) { mem::drop(self.child.kill()) }
}
pub fn send_msg(write: &mut impl Write, msg: &[u8]) -> io::Result<()> {
write.write_all(&(u32::try_from(msg.len()).unwrap()).to_be_bytes())?;
write.write_all(msg)?;
write.flush()
}
pub fn recv_msg(read: &mut impl Read) -> io::Result<Vec<u8>> {
let mut len = [0u8; 4];
read.read_exact(&mut len)?;
let len = u32::from_be_bytes(len);
let mut msg = vec![0u8; len as usize];
read.read_exact(&mut msg)?;
Ok(msg)
}
pub fn send_parent_msg(msg: &[u8]) -> io::Result<()> { send_msg(&mut io::stdout().lock(), msg) }
pub fn recv_parent_msg() -> io::Result<Vec<u8>> { recv_msg(&mut io::stdin().lock()) }

9
orchid-base/src/clone.rs Normal file
View File

@@ -0,0 +1,9 @@
#[macro_export]
macro_rules! clone {
($($n:ident),+; $body:expr) => (
{
$( let $n = $n.clone(); )+
$body
}
);
}

63
orchid-base/src/event.rs Normal file
View File

@@ -0,0 +1,63 @@
//! Multiple-listener-single-delivery event system.
use std::mem;
use std::sync::mpsc::{self, sync_channel};
use std::sync::Mutex;
struct Reply<T, U> {
resub: bool,
outcome: Result<U, T>,
}
struct Listener<T, E> {
sink: mpsc::SyncSender<T>,
source: mpsc::Receiver<Reply<T, E>>,
}
pub struct Event<T, U> {
listeners: Mutex<Vec<Listener<T, U>>>,
}
impl<T, U> Event<T, U> {
pub const fn new() -> Self { Self { listeners: Mutex::new(Vec::new()) } }
pub fn dispatch(&self, mut ev: T) -> Option<U> {
let mut listeners = self.listeners.lock().unwrap();
let mut alt_list = Vec::with_capacity(listeners.len());
mem::swap(&mut *listeners, &mut alt_list);
let mut items = alt_list.into_iter();
while let Some(l) = items.next() {
l.sink.send(ev).unwrap();
let Reply { resub, outcome } = l.source.recv().unwrap();
if resub {
listeners.push(l);
}
match outcome {
Ok(res) => {
listeners.extend(items);
return Some(res);
},
Err(next) => {
ev = next;
},
}
}
None
}
pub fn get_one<V>(&self, mut filter: impl FnMut(&T) -> bool, f: impl FnOnce(T) -> (U, V)) -> V {
let mut listeners = self.listeners.lock().unwrap();
let (sink, request) = sync_channel(0);
let (response, source) = sync_channel(0);
listeners.push(Listener { sink, source });
mem::drop(listeners);
loop {
let t = request.recv().unwrap();
if filter(&t) {
let (u, v) = f(t);
response.send(Reply { resub: false, outcome: Ok(u) }).unwrap();
return v;
}
response.send(Reply { resub: true, outcome: Err(t) }).unwrap();
}
}
}

View File

@@ -0,0 +1,67 @@
use std::any::TypeId;
use itertools::Itertools;
use orchid_api::expr::{Clause, Expr};
use orchid_api::location::Location;
use super::traits::{GenClause, Generable};
use crate::expr::RtExpr;
use crate::host::AtomHand;
use crate::intern::{deintern, intern};
fn safely_reinterpret<In: 'static, Out: 'static>(x: In) -> Result<Out, In> {
if TypeId::of::<In>() != TypeId::of::<Out>() {
return Err(x);
}
let bx = Box::new(x);
// SAFETY: type sameness asserted above, from_raw and into_raw pair up
Ok(*unsafe { Box::from_raw(Box::into_raw(bx) as *mut Out) })
}
/// impls of the gen traits for external types
impl GenClause for Expr {
fn generate<T: super::traits::Generable>(&self, ctx: T::Ctx<'_>, pop: &impl Fn() -> T) -> T {
match &self.clause {
Clause::Arg(arg) => T::arg(ctx, deintern(*arg).as_str()),
Clause::Atom(atom) => T::atom(ctx, AtomHand::from_api(atom.clone())),
Clause::Call(f, x) => T::apply(ctx, |c| f.generate(c, pop), |c| x.generate(c, pop)),
Clause::Lambda(arg, b) => T::lambda(ctx, deintern(*arg).as_str(), |ctx| b.generate(ctx, pop)),
Clause::Seq(n1, n2) => T::seq(ctx, |c| n1.generate(c, pop), |c| n2.generate(c, pop)),
Clause::Const(int) => T::constant(ctx, deintern(*int).iter().map(|t| t.as_str())),
Clause::Slot(expr) => {
let rte = RtExpr::resolve(*expr).expect("expired ticket");
safely_reinterpret(rte).expect("ticket slots make no sense for anything other than rte")
},
}
}
}
fn to_expr(clause: Clause) -> Expr { Expr { clause, location: Location::None } }
impl Generable for Expr {
type Ctx<'a> = ();
fn arg(_ctx: Self::Ctx<'_>, name: &str) -> Self { to_expr(Clause::Arg(intern(name).marker())) }
fn apply(
ctx: Self::Ctx<'_>,
f: impl FnOnce(Self::Ctx<'_>) -> Self,
x: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
to_expr(Clause::Call(Box::new(f(ctx)), Box::new(x(ctx))))
}
fn atom(_ctx: Self::Ctx<'_>, a: crate::host::AtomHand) -> Self { to_expr(Clause::Atom(a.api_ref())) }
fn constant<'a>(_ctx: Self::Ctx<'_>, name: impl IntoIterator<Item = &'a str>) -> Self {
to_expr(Clause::Const(intern(&name.into_iter().map(intern).collect_vec()[..]).marker()))
}
fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self {
to_expr(Clause::Lambda(intern(name).marker(), Box::new(body(ctx))))
}
fn seq(
ctx: Self::Ctx<'_>,
a: impl FnOnce(Self::Ctx<'_>) -> Self,
b: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self {
to_expr(Clause::Seq(Box::new(a(ctx)), Box::new(b(ctx))))
}
}

View File

@@ -5,3 +5,4 @@
pub mod tpl;
pub mod traits;
pub mod tree;
pub mod impls;

View File

@@ -2,23 +2,14 @@
//! [GenClause].
use super::traits::{GenClause, Generable};
use crate::foreign::atom::{Atom, AtomGenerator, Atomic};
use crate::host::AtomHand;
/// 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> {
/// A trivial atom
#[derive(Clone, Debug)]
pub struct SysAtom(pub AtomHand);
impl GenClause for SysAtom {
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())
T::atom(ctx, self.0.clone())
}
}

View File

@@ -5,15 +5,15 @@ use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt;
use crate::foreign::atom::Atom;
use crate::host::AtomHand;
/// Representations of the Orchid expression tree that can describe basic
/// language elements.
pub trait Generable: Sized {
pub trait Generable: Sized + 'static {
/// Context information defined by parents. Generators just forward this.
type Ctx<'a>: Sized;
/// Wrap external data.
fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self;
fn atom(ctx: Self::Ctx<'_>, a: AtomHand) -> 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
@@ -22,9 +22,16 @@ pub trait Generable: Sized {
f: impl FnOnce(Self::Ctx<'_>) -> Self,
x: impl FnOnce(Self::Ctx<'_>) -> Self,
) -> Self;
/// Generate a dependency between the top clause and the bottom clause
fn seq(
ctx: Self::Ctx<'_>,
a: impl FnOnce(Self::Ctx<'_>) -> Self,
b: 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;

View File

@@ -0,0 +1,79 @@
//! Components to build in-memory module trees that in Orchid. These modules
//! can only contain constants and other modules.
use std::fmt;
use dyn_clone::{clone_box, DynClone};
use orchid_api::expr::Expr;
use trait_set::trait_set;
use super::tpl;
use super::traits::{Gen, GenClause};
use crate::combine::Combine;
use crate::host::AtomHand;
use crate::tree::{ModEntry, ModMember, TreeConflict};
trait_set! {
trait TreeLeaf = Gen<Expr, [Expr; 0]> + DynClone + Send;
}
/// A leaf in the [ConstTree]
pub struct GenConst(Box<dyn TreeLeaf>);
impl GenConst {
fn c(data: impl GenClause + Send + Clone + 'static) -> Self { Self(Box::new(data)) }
}
impl fmt::Debug for GenConst {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.0) }
}
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 [Combine]
/// for merging libraries.
pub type ConstTree = ModEntry<GenConst, (), ()>;
/// Describe a constant
#[must_use]
pub fn leaf(value: impl GenClause + Clone + Send + 'static) -> ConstTree {
ModEntry::wrap(ModMember::Item(GenConst::c(value)))
}
/// Describe a constant which appears in [ConstTree::tree].
///
/// The unarray tricks rustfmt into keeping this call as a single line even if
/// it chooses to break the argument into a block.
pub fn ent<K: AsRef<str>>(
key: K,
[g]: [impl GenClause + Clone + Send + 'static; 1],
) -> (K, ConstTree) {
(key, leaf(g))
}
/// Describe an [Atomic]
#[must_use]
pub fn atom_leaf(atom: AtomHand) -> ConstTree { leaf(tpl::SysAtom(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]: [AtomHand; 1]) -> (K, ConstTree) {
(key, atom_leaf(atom))
}
/// Errors produced duriung the merger of constant trees
pub type ConstCombineErr = TreeConflict<GenConst, (), ()>;

303
orchid-base/src/intern.rs Normal file
View File

@@ -0,0 +1,303 @@
use std::borrow::Borrow;
use std::hash::BuildHasher as _;
use std::num::NonZeroU64;
use std::ops::{Deref, DerefMut};
use std::sync::{atomic, Arc, Mutex, MutexGuard};
use std::{fmt, hash};
use hashbrown::{HashMap, HashSet};
use itertools::Itertools as _;
use orchid_api::intern::{
ExternStr, ExternStrv, IntReq, InternStr, InternStrv, Retained, TStr, TStrv,
};
use orchid_api_traits::Request;
use crate::reqnot::{DynRequester, Requester};
#[derive(Clone)]
pub struct Token<T: ?Sized + Interned> {
data: Arc<T>,
marker: T::Marker,
}
impl<T: Interned + ?Sized> Token<T> {
pub fn marker(&self) -> T::Marker { self.marker }
pub fn arc(&self) -> Arc<T> { self.data.clone() }
}
impl<T: Interned + ?Sized> Deref for Token<T> {
type Target = T;
fn deref(&self) -> &Self::Target { self.data.as_ref() }
}
impl<T: Interned + ?Sized> Ord for Token<T> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.marker().cmp(&other.marker()) }
}
impl<T: Interned + ?Sized> PartialOrd for Token<T> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { Some(self.cmp(other)) }
}
impl<T: Interned + ?Sized> Eq for Token<T> {}
impl<T: Interned + ?Sized> PartialEq for Token<T> {
fn eq(&self, other: &Self) -> bool { self.cmp(other).is_eq() }
}
impl<T: Interned + ?Sized> hash::Hash for Token<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) { self.marker().hash(state) }
}
impl<T: Interned + ?Sized + fmt::Display> fmt::Display for Token<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &*self.data)
}
}
impl<T: Interned + ?Sized + fmt::Debug> fmt::Debug for Token<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Token({} -> {:?})", self.marker().get_id(), self.data.as_ref())
}
}
pub trait Interned: Eq + hash::Hash + Clone {
type Marker: InternMarker<Interned = Self>;
fn intern(self: Arc<Self>, req: &(impl DynRequester<Transfer = IntReq> + ?Sized))
-> Self::Marker;
fn bimap(interner: &mut Interner) -> &mut Bimap<Self>;
}
pub trait Internable {
type Interned: Interned;
fn get_owned(&self) -> Arc<Self::Interned>;
}
pub trait InternMarker: Copy + PartialEq + Eq + PartialOrd + Ord + hash::Hash {
type Interned: Interned<Marker = Self>;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Token<Self::Interned>;
fn get_id(self) -> NonZeroU64;
fn from_id(id: NonZeroU64) -> Self;
}
impl Interned for String {
type Marker = TStr;
fn intern(
self: Arc<Self>,
req: &(impl DynRequester<Transfer = IntReq> + ?Sized),
) -> Self::Marker {
req.request(InternStr(self))
}
fn bimap(interner: &mut Interner) -> &mut Bimap<Self> { &mut interner.strings }
}
impl InternMarker for TStr {
type Interned = String;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Token<Self::Interned> {
Token { marker: self, data: req.request(ExternStr(self)) }
}
fn get_id(self) -> NonZeroU64 { self.0 }
fn from_id(id: NonZeroU64) -> Self { Self(id) }
}
impl Internable for str {
type Interned = String;
fn get_owned(&self) -> Arc<Self::Interned> { Arc::new(self.to_string()) }
}
impl Internable for String {
type Interned = String;
fn get_owned(&self) -> Arc<Self::Interned> { Arc::new(self.to_string()) }
}
impl Interned for Vec<Token<String>> {
type Marker = TStrv;
fn intern(
self: Arc<Self>,
req: &(impl DynRequester<Transfer = IntReq> + ?Sized),
) -> Self::Marker {
req.request(InternStrv(Arc::new(self.iter().map(|t| t.marker()).collect())))
}
fn bimap(interner: &mut Interner) -> &mut Bimap<Self> { &mut interner.vecs }
}
impl InternMarker for TStrv {
type Interned = Vec<Token<String>>;
fn resolve(self, req: &(impl DynRequester<Transfer = IntReq> + ?Sized)) -> Token<Self::Interned> {
let data = Arc::new(req.request(ExternStrv(self)).iter().map(|m| deintern(*m)).collect_vec());
Token { marker: self, data }
}
fn get_id(self) -> NonZeroU64 { self.0 }
fn from_id(id: NonZeroU64) -> Self { Self(id) }
}
impl Internable for [Token<String>] {
type Interned = Vec<Token<String>>;
fn get_owned(&self) -> Arc<Self::Interned> { Arc::new(self.to_vec()) }
}
impl Internable for Vec<Token<String>> {
type Interned = Vec<Token<String>>;
fn get_owned(&self) -> Arc<Self::Interned> { Arc::new(self.to_vec()) }
}
impl Internable for Vec<TStr> {
type Interned = Vec<Token<String>>;
fn get_owned(&self) -> Arc<Self::Interned> {
Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
}
}
impl Internable for [TStr] {
type Interned = Vec<Token<String>>;
fn get_owned(&self) -> Arc<Self::Interned> {
Arc::new(self.iter().map(|ts| deintern(*ts)).collect())
}
}
/// The number of references held to any token by the interner.
const BASE_RC: usize = 3;
#[test]
fn base_rc_correct() {
let tok = Token { marker: TStr(1.try_into().unwrap()), data: Arc::new("foo".to_string()) };
let mut bimap = Bimap::default();
bimap.insert(tok.clone());
assert_eq!(Arc::strong_count(&tok.data), BASE_RC + 1, "the bimap plus the current instance");
}
pub struct Bimap<T: Interned + ?Sized> {
intern: HashMap<Arc<T>, Token<T>>,
by_id: HashMap<T::Marker, Token<T>>,
}
impl<T: Interned + ?Sized> Bimap<T> {
pub fn insert(&mut self, token: Token<T>) {
self.intern.insert(token.data.clone(), token.clone());
self.by_id.insert(token.marker(), token);
}
pub fn by_marker(&self, marker: T::Marker) -> Option<Token<T>> {
self.by_id.get(&marker).cloned()
}
pub fn by_value<Q: Eq + hash::Hash>(&self, q: &Q) -> Option<Token<T>>
where T: Borrow<Q> {
(self.intern.raw_entry())
.from_hash(self.intern.hasher().hash_one(q), |k| k.as_ref().borrow() == q)
.map(|p| p.1.clone())
}
pub fn sweep_replica(&mut self) -> Vec<T::Marker> {
(self.intern)
.extract_if(|k, _| Arc::strong_count(k) == BASE_RC)
.map(|(_, v)| {
self.by_id.remove(&v.marker());
v.marker()
})
.collect()
}
pub fn sweep_master(&mut self, retained: HashSet<T::Marker>) {
self.intern.retain(|k, v| BASE_RC < Arc::strong_count(k) || retained.contains(&v.marker()))
}
}
impl<T: Interned + ?Sized> Default for Bimap<T> {
fn default() -> Self { Self { by_id: HashMap::new(), intern: HashMap::new() } }
}
pub trait UpComm {
fn up<R: Request>(&self, req: R) -> R::Response;
}
#[derive(Default)]
pub struct Interner {
strings: Bimap<String>,
vecs: Bimap<Vec<Token<String>>>,
master: Option<Box<dyn DynRequester<Transfer = IntReq>>>,
}
static ID: atomic::AtomicU64 = atomic::AtomicU64::new(1);
static INTERNER: Mutex<Option<Interner>> = Mutex::new(None);
pub fn interner() -> impl DerefMut<Target = Interner> {
struct G(MutexGuard<'static, Option<Interner>>);
impl Deref for G {
type Target = Interner;
fn deref(&self) -> &Self::Target { self.0.as_ref().expect("Guard pre-initialized") }
}
impl DerefMut for G {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0.as_mut().expect("Guard pre-iniitialized")
}
}
let mut g = INTERNER.lock().unwrap();
g.get_or_insert_with(Interner::default);
G(g)
}
pub fn init_replica(req: impl DynRequester<Transfer = IntReq> + 'static) {
let mut g = INTERNER.lock().unwrap();
assert!(g.is_none(), "Attempted to initialize replica interner after first use");
*g = Some(Interner {
strings: Bimap::default(),
vecs: Bimap::default(),
master: Some(Box::new(req)),
})
}
pub fn intern<T: Interned>(t: &(impl Internable<Interned = T> + ?Sized)) -> Token<T> {
let mut g = interner();
let data = t.get_owned();
let marker = (g.master.as_mut()).map_or_else(
|| T::Marker::from_id(NonZeroU64::new(ID.fetch_add(1, atomic::Ordering::Relaxed)).unwrap()),
|c| data.clone().intern(&**c),
);
let tok = Token { marker, data };
T::bimap(&mut g).insert(tok.clone());
tok
}
pub fn deintern<M: InternMarker>(marker: M) -> Token<M::Interned> {
let mut g = interner();
if let Some(tok) = M::Interned::bimap(&mut g).by_marker(marker) {
return tok;
}
let master = g.master.as_mut().expect("ID not in local interner and this is master");
let token = marker.resolve(&**master);
M::Interned::bimap(&mut g).insert(token.clone());
token
}
pub fn merge_retained(into: &mut Retained, from: &Retained) {
into.strings = into.strings.iter().chain(&from.strings).copied().unique().collect();
into.vecs = into.vecs.iter().chain(&from.vecs).copied().unique().collect();
}
pub fn sweep_replica() -> Retained {
let mut g = interner();
assert!(g.master.is_some(), "Not a replica");
Retained { strings: g.strings.sweep_replica(), vecs: g.vecs.sweep_replica() }
}
pub fn sweep_master(retained: Retained) {
let mut g = interner();
assert!(g.master.is_none(), "Not master");
g.strings.sweep_master(retained.strings.into_iter().collect());
g.vecs.sweep_master(retained.vecs.into_iter().collect());
}
/// Create a thread-local token instance and copy it. This ensures that the
/// interner will only be called the first time the expresion is executed,
/// and subsequent calls will just copy the token. Accepts a single static
/// expression (i.e. a literal).
#[macro_export]
macro_rules! intern {
($ty:ty : $expr:expr) => {{
thread_local! {
static VALUE: $crate::intern::Token<<$ty as $crate::intern::Internable>::Interned>
= $crate::intern::intern($expr as &$ty);
}
VALUE.with(|v| v.clone())
}};
}
#[allow(unused)]
fn test_i() {
let _: Token<String> = intern!(str: "foo");
let _: Token<Vec<Token<String>>> = intern!([Token<String>]: &[
intern!(str: "bar"),
intern!(str: "baz")
]);
}

17
orchid-base/src/lib.rs Normal file
View File

@@ -0,0 +1,17 @@
pub mod boxed_iter;
pub mod child;
pub mod clone;
pub mod combine;
pub mod event;
pub mod expr;
pub mod gen;
pub mod intern;
pub mod location;
pub mod name;
pub mod proj_error;
pub mod reqnot;
pub mod tree;
pub mod virt_fs;
pub mod join;
pub mod sequence;
pub mod api_utils;

View File

@@ -7,7 +7,7 @@ use std::sync::Arc;
use itertools::Itertools;
use crate::name::{NameLike, Sym};
use crate::name::Sym;
use crate::sym;
/// A full source code unit, such as a source file
@@ -50,12 +50,11 @@ pub struct SourceRange {
pub(crate) range: Range<usize>,
}
impl SourceRange {
/// Create a new instance.
pub fn new(range: Range<usize>, code: SourceCode) -> Self { Self { code, range } }
/// Create a dud [SourceRange] for testing. Its value is unspecified and
/// volatile.
pub fn mock() -> Self {
let code = SourceCode { path: sym!(test), text: Arc::new(String::new()) };
SourceRange { range: 0..1, code }
}
pub fn mock() -> Self { Self::new(0..1, SourceCode::new(sym!(test), Arc::new(String::new()))) }
/// Source code
pub fn code(&self) -> SourceCode { self.code.clone() }
/// Source text
@@ -64,11 +63,15 @@ impl SourceRange {
pub fn path(&self) -> Sym { self.code.path.clone() }
/// Byte range
pub fn range(&self) -> Range<usize> { self.range.clone() }
/// 0-based index of first byte
pub fn start(&self) -> usize { self.range.start }
/// 0-based index of last byte + 1
pub fn end(&self) -> usize { self.range.end }
/// Syntactic location
pub fn origin(&self) -> CodeOrigin { CodeOrigin::Source(self.clone()) }
/// Transform the numeric byte range
pub fn map_range(&self, map: impl FnOnce(Range<usize>) -> Range<usize>) -> Self {
Self { code: self.code(), range: map(self.range()) }
Self::new(map(self.range()), self.code())
}
}
impl fmt::Debug for SourceRange {

488
orchid-base/src/name.rs Normal file
View File

@@ -0,0 +1,488 @@
//! Various datatypes that all represent namespaced names.
use std::borrow::Borrow;
use std::hash::Hash;
use std::iter::Cloned;
use std::num::{NonZeroU64, NonZeroUsize};
use std::ops::{Deref, Index};
use std::path::Path;
use std::{fmt, slice, vec};
use itertools::Itertools;
use trait_set::trait_set;
use crate::intern::{intern, InternMarker, Token};
trait_set! {
/// Traits that all name iterators should implement
pub trait NameIter = Iterator<Item = Token<String>> + DoubleEndedIterator + ExactSizeIterator;
}
/// A borrowed name fragment which can be empty. See [VPath] for the owned
/// variant.
#[derive(Hash, PartialEq, Eq)]
#[repr(transparent)]
pub struct PathSlice([Token<String>]);
impl PathSlice {
/// Create a new [PathSlice]
pub fn new(slice: &[Token<String>]) -> &PathSlice {
// SAFETY: This is ok because PathSlice is #[repr(transparent)]
unsafe { &*(slice as *const [Token<String>] as *const PathSlice) }
}
/// Convert to an owned name fragment
pub fn to_vpath(&self) -> VPath { VPath(self.0.to_vec()) }
/// Iterate over the tokens
pub fn iter(&self) -> impl NameIter + '_ { self.into_iter() }
/// Iterate over the segments
pub fn str_iter(&self) -> impl Iterator<Item = &'_ str> {
Box::new(self.0.iter().map(|s| s.as_str()))
}
/// Find the longest shared prefix of this name and another sequence
pub fn coprefix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice {
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()]
}
/// Find the longest shared suffix of this name and another sequence
pub fn cosuffix<'a>(&'a self, other: &PathSlice) -> &'a PathSlice {
&self[0..self.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()]
}
/// Remove another
pub fn strip_prefix<'a>(&'a self, other: &PathSlice) -> Option<&'a PathSlice> {
let shared = self.coprefix(other).len();
(shared == other.len()).then_some(PathSlice::new(&self[shared..]))
}
/// Number of path segments
pub fn len(&self) -> usize { self.0.len() }
/// Whether there are any path segments. In other words, whether this is a
/// valid name
pub fn is_empty(&self) -> bool { self.len() == 0 }
/// Obtain a reference to the held slice. With all indexing traits shadowed,
/// this is better done explicitly
pub fn as_slice(&self) -> &[Token<String>] { self }
/// Global empty path slice
pub fn empty() -> &'static Self { PathSlice::new(&[]) }
}
impl fmt::Debug for PathSlice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") }
}
impl fmt::Display for PathSlice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.str_iter().join("::"))
}
}
impl Borrow<[Token<String>]> for PathSlice {
fn borrow(&self) -> &[Token<String>] { &self.0 }
}
impl<'a> IntoIterator for &'a PathSlice {
type IntoIter = Cloned<slice::Iter<'a, Token<String>>>;
type Item = Token<String>;
fn into_iter(self) -> Self::IntoIter { self.0.iter().cloned() }
}
mod idx_impls {
use std::ops;
use super::PathSlice;
use crate::intern::Token;
impl ops::Index<usize> for PathSlice {
type Output = Token<String>;
fn index(&self, index: usize) -> &Self::Output { &self.0[index] }
}
macro_rules! impl_range_index_for_pathslice {
($range:ty) => {
impl ops::Index<$range> for PathSlice {
type Output = Self;
fn index(&self, index: $range) -> &Self::Output { Self::new(&self.0[index]) }
}
};
}
impl_range_index_for_pathslice!(ops::RangeFull);
impl_range_index_for_pathslice!(ops::RangeFrom<usize>);
impl_range_index_for_pathslice!(ops::RangeTo<usize>);
impl_range_index_for_pathslice!(ops::Range<usize>);
impl_range_index_for_pathslice!(ops::RangeInclusive<usize>);
impl_range_index_for_pathslice!(ops::RangeToInclusive<usize>);
}
impl Deref for PathSlice {
type Target = [Token<String>];
fn deref(&self) -> &Self::Target { &self.0 }
}
impl Borrow<PathSlice> for [Token<String>] {
fn borrow(&self) -> &PathSlice { PathSlice::new(self) }
}
impl<const N: usize> Borrow<PathSlice> for [Token<String>; N] {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) }
}
impl Borrow<PathSlice> for Vec<Token<String>> {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) }
}
/// A token path which may be empty. [VName] is the non-empty,
/// [PathSlice] is the borrowed version
#[derive(Clone, Default, Hash, PartialEq, Eq)]
pub struct VPath(pub Vec<Token<String>>);
impl VPath {
/// Collect segments into a vector
pub fn new(items: impl IntoIterator<Item = Token<String>>) -> Self {
Self(items.into_iter().collect())
}
/// Number of path segments
pub fn len(&self) -> usize { self.0.len() }
/// Whether there are any path segments. In other words, whether this is a
/// valid name
pub fn is_empty(&self) -> bool { self.len() == 0 }
/// Prepend some tokens to the path
pub fn prefix(self, items: impl IntoIterator<Item = Token<String>>) -> Self {
Self(items.into_iter().chain(self.0).collect())
}
/// Append some tokens to the path
pub fn suffix(self, items: impl IntoIterator<Item = Token<String>>) -> Self {
Self(self.0.into_iter().chain(items).collect())
}
/// Partition the string by `::` namespace separators
pub fn parse(s: &str) -> Self {
Self(if s.is_empty() { vec![] } else { s.split("::").map(intern).collect() })
}
/// Walk over the segments
pub fn str_iter(&self) -> impl Iterator<Item = &'_ str> {
Box::new(self.0.iter().map(|s| s.as_str()))
}
/// Try to convert into non-empty version
pub fn into_name(self) -> Result<VName, EmptyNameError> { VName::new(self.0) }
/// Add a token to the path. Since now we know that it can't be empty, turn it
/// into a name.
pub fn name_with_prefix(self, name: Token<String>) -> VName {
VName(self.into_iter().chain([name]).collect())
}
/// Add a token to the beginning of the. Since now we know that it can't be
/// empty, turn it into a name.
pub fn name_with_suffix(self, name: Token<String>) -> VName {
VName([name].into_iter().chain(self).collect())
}
/// Convert a fs path to a vpath
pub fn from_path(path: &Path) -> Option<(Self, bool)> {
let to_vpath =
|p: &Path| p.iter().map(|c| c.to_str().map(intern)).collect::<Option<_>>().map(VPath);
match path.extension().map(|s| s.to_str()) {
Some(Some("orc")) => Some((to_vpath(&path.with_extension(""))?, true)),
None => Some((to_vpath(path)?, false)),
Some(_) => None,
}
}
}
impl fmt::Debug for VPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") }
}
impl fmt::Display for VPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.str_iter().join("::"))
}
}
impl FromIterator<Token<String>> for VPath {
fn from_iter<T: IntoIterator<Item = Token<String>>>(iter: T) -> Self {
Self(iter.into_iter().collect())
}
}
impl IntoIterator for VPath {
type Item = Token<String>;
type IntoIter = vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
impl Borrow<[Token<String>]> for VPath {
fn borrow(&self) -> &[Token<String>] { self.0.borrow() }
}
impl Borrow<PathSlice> for VPath {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) }
}
impl Deref for VPath {
type Target = PathSlice;
fn deref(&self) -> &Self::Target { self.borrow() }
}
impl<T> Index<T> for VPath
where PathSlice: Index<T>
{
type Output = <PathSlice as Index<T>>::Output;
fn index(&self, index: T) -> &Self::Output { &Borrow::<PathSlice>::borrow(self)[index] }
}
/// A mutable representation of a namespaced identifier of at least one segment.
///
/// These names may be relative or otherwise partially processed.
///
/// See also [Sym] for the immutable representation, and [VPath] for possibly
/// empty values
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct VName(Vec<Token<String>>);
impl VName {
/// Assert that the sequence isn't empty and wrap it in [VName] to represent
/// this invariant
pub fn new(items: impl IntoIterator<Item = Token<String>>) -> Result<Self, EmptyNameError> {
let data: Vec<_> = items.into_iter().collect();
if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) }
}
/// Unwrap the enclosed vector
pub fn into_vec(self) -> Vec<Token<String>> { self.0 }
/// Get a reference to the enclosed vector
pub fn vec(&self) -> &Vec<Token<String>> { &self.0 }
/// Mutable access to the underlying vector. To ensure correct results, this
/// must never be empty.
pub fn vec_mut(&mut self) -> &mut Vec<Token<String>> { &mut self.0 }
/// Intern the name and return a [Sym]
pub fn to_sym(&self) -> Sym { Sym(intern(&self.0[..])) }
/// If this name has only one segment, return it
pub fn as_root(&self) -> Option<Token<String>> { self.0.iter().exactly_one().ok().cloned() }
/// Prepend the segments to this name
#[must_use = "This is a pure function"]
pub fn prefix(self, items: impl IntoIterator<Item = Token<String>>) -> Self {
Self(items.into_iter().chain(self.0).collect())
}
/// Append the segments to this name
#[must_use = "This is a pure function"]
pub fn suffix(self, items: impl IntoIterator<Item = Token<String>>) -> Self {
Self(self.0.into_iter().chain(items).collect())
}
/// Read a `::` separated namespaced name
pub fn parse(s: &str) -> Result<Self, EmptyNameError> { Self::new(VPath::parse(s)) }
/// Obtain an iterator over the segments of the name
pub fn iter(&self) -> impl Iterator<Item = Token<String>> + '_ { self.0.iter().cloned() }
}
impl fmt::Debug for VName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") }
}
impl fmt::Display for VName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.str_iter().join("::"))
}
}
impl IntoIterator for VName {
type Item = Token<String>;
type IntoIter = vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter { self.0.into_iter() }
}
impl<T> Index<T> for VName
where PathSlice: Index<T>
{
type Output = <PathSlice as Index<T>>::Output;
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
}
impl Borrow<[Token<String>]> for VName {
fn borrow(&self) -> &[Token<String>] { self.0.borrow() }
}
impl Borrow<PathSlice> for VName {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) }
}
impl Deref for VName {
type Target = PathSlice;
fn deref(&self) -> &Self::Target { self.borrow() }
}
/// Error produced when a non-empty name [VName] or [Sym] is constructed with an
/// empty sequence
#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct EmptyNameError;
impl TryFrom<&[Token<String>]> for VName {
type Error = EmptyNameError;
fn try_from(value: &[Token<String>]) -> Result<Self, Self::Error> {
Self::new(value.iter().cloned())
}
}
/// An interned representation of a namespaced identifier.
///
/// These names are always absolute.
///
/// See also [VName]
#[derive(Clone, Hash, PartialEq, Eq)]
pub struct Sym(Token<Vec<Token<String>>>);
impl Sym {
/// Assert that the sequence isn't empty, intern it and wrap it in a [Sym] to
/// represent this invariant
pub fn new(v: impl IntoIterator<Item = Token<String>>) -> Result<Self, EmptyNameError> {
let items = v.into_iter().collect_vec();
Self::from_tok(intern(&items[..]))
}
/// Read a `::` separated namespaced name.
pub fn parse(s: &str) -> Result<Self, EmptyNameError> {
Ok(Sym(intern(&VName::parse(s)?.into_vec()[..])))
}
/// Assert that a token isn't empty, and wrap it in a [Sym]
pub fn from_tok(t: Token<Vec<Token<String>>>) -> Result<Self, EmptyNameError> {
if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) }
}
/// Grab the interner token
pub fn tok(&self) -> Token<Vec<Token<String>>> { self.0.clone() }
/// Get a number unique to this name suitable for arbitrary ordering.
pub fn id(&self) -> NonZeroU64 { self.0.marker().get_id() }
/// Extern the sym for editing
pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) }
}
impl fmt::Debug for Sym {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sym({self})") }
}
impl fmt::Display for Sym {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.str_iter().join("::"))
}
}
impl<T> Index<T> for Sym
where PathSlice: Index<T>
{
type Output = <PathSlice as Index<T>>::Output;
fn index(&self, index: T) -> &Self::Output { &self.deref()[index] }
}
impl Borrow<[Token<String>]> for Sym {
fn borrow(&self) -> &[Token<String>] { &self.0[..] }
}
impl Borrow<PathSlice> for Sym {
fn borrow(&self) -> &PathSlice { PathSlice::new(&self.0[..]) }
}
impl Deref for Sym {
type Target = PathSlice;
fn deref(&self) -> &Self::Target { self.borrow() }
}
/// An abstraction over tokenized vs non-tokenized names so that they can be
/// handled together in datastructures. The names can never be empty
#[allow(clippy::len_without_is_empty)] // never empty
pub trait NameLike:
'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow<PathSlice>
{
/// Convert into held slice
fn as_slice(&self) -> &[Token<String>] { Borrow::<PathSlice>::borrow(self) }
/// Get iterator over tokens
fn iter(&self) -> impl NameIter + '_ { self.as_slice().iter().cloned() }
/// Get iterator over string segments
fn str_iter(&self) -> impl Iterator<Item = &'_ str> + '_ {
self.as_slice().iter().map(|t| t.as_str())
}
/// Fully resolve the name for printing
#[must_use]
fn to_strv(&self) -> Vec<String> { self.iter().map(|s| s.to_string()).collect() }
/// Format the name as an approximate filename
fn as_src_path(&self) -> String { format!("{}.orc", self.iter().join("/")) }
/// Return the number of segments in the name
fn len(&self) -> NonZeroUsize {
NonZeroUsize::try_from(self.iter().count()).expect("NameLike never empty")
}
/// Like slice's `split_first` except we know that it always returns Some
fn split_first(&self) -> (Token<String>, &PathSlice) {
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
(foot.clone(), PathSlice::new(torso))
}
/// Like slice's `split_last` except we know that it always returns Some
fn split_last(&self) -> (Token<String>, &PathSlice) {
let (foot, torso) = self.as_slice().split_last().expect("NameLike never empty");
(foot.clone(), PathSlice::new(torso))
}
/// Get the first element
fn first(&self) -> Token<String> { self.split_first().0 }
/// Get the last element
fn last(&self) -> Token<String> { self.split_last().0 }
}
impl NameLike for Sym {}
impl NameLike for VName {}
/// Create a [Sym] literal.
///
/// Both the name and its components will be cached in a thread-local static so
/// that subsequent executions of the expression only incur an Arc-clone for
/// cloning the token.
#[macro_export]
macro_rules! sym {
($seg1:tt $( :: $seg:tt)*) => {
$crate::name::Sym::from_tok($crate::intern!([$crate::intern::Token<String>]: &[
$crate::intern!(str: stringify!($seg1))
$( , $crate::intern!(str: stringify!($seg)) )*
])).unwrap()
};
(@NAME $seg:tt) => {}
}
/// Create a [VName] literal.
///
/// The components are interned much like in [sym].
#[macro_export]
macro_rules! vname {
($seg1:tt $( :: $seg:tt)*) => {
$crate::name::VName::new([
$crate::intern!(str: stringify!($seg1))
$( , $crate::intern!(str: stringify!($seg)) )*
]).unwrap()
};
}
/// Create a [VPath] literal.
///
/// The components are interned much like in [sym].
#[macro_export]
macro_rules! vpath {
($seg1:tt $( :: $seg:tt)+) => {
$crate::name::VPath(vec![
$crate::intern!(str: stringify!($seg1))
$( , $crate::intern!(str: stringify!($seg)) )+
])
};
() => {
$crate::name::VPath(vec![])
}
}
/// Create a &[PathSlice] literal.
///
/// The components are interned much like in [sym]
#[macro_export]
macro_rules! path_slice {
($seg1:tt $( :: $seg:tt)+) => {
$crate::name::PathSlice::new(&[
$crate::intern!(str: stringify!($seg1))
$( , $crate::intern!(str: stringify!($seg)) )+
])
};
() => {
$crate::name::PathSlice::new(&[])
}
}
#[cfg(test)]
mod test {
use std::borrow::Borrow;
use super::{PathSlice, Sym, VName};
use crate::intern::{intern, Token};
use crate::name::VPath;
#[test]
fn recur() {
let myname = vname!(foo::bar);
let _borrowed_slice: &[Token<String>] = myname.borrow();
let _borrowed_pathslice: &PathSlice = myname.borrow();
let _deref_pathslice: &PathSlice = &myname;
let _as_slice_out: &[Token<String>] = myname.as_slice();
}
#[test]
fn literals() {
assert_eq!(
sym!(foo::bar::baz),
Sym::new([intern("foo"), intern("bar"), intern("baz")]).unwrap()
);
assert_eq!(
vname!(foo::bar::baz),
VName::new([intern("foo"), intern("bar"), intern("baz")]).unwrap()
);
assert_eq!(vpath!(foo::bar::baz), VPath::new([intern("foo"), intern("bar"), intern("baz")]));
assert_eq!(
path_slice!(foo::bar::baz),
PathSlice::new(&[intern("foo"), intern("bar"), intern("baz")])
);
}
}

View File

@@ -10,7 +10,7 @@ use dyn_clone::{clone_box, DynClone};
use itertools::Itertools;
use crate::location::CodeOrigin;
use crate::utils::boxed_iter::{box_once, BoxedIter};
use crate::boxed_iter::{box_once, BoxedIter};
#[allow(unused)] // for doc
use crate::virt_fs::CodeNotFound;

222
orchid-base/src/reqnot.rs Normal file
View File

@@ -0,0 +1,222 @@
use std::mem;
use std::ops::{BitAnd, Deref};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{sync_channel, SyncSender};
use std::sync::{Arc, Mutex};
use dyn_clone::{clone_box, DynClone};
use hashbrown::HashMap;
use orchid_api_traits::{Coding, Decode, Encode, MsgSet, Request};
use trait_set::trait_set;
trait_set! {
pub trait SendFn<T: MsgSet> = for<'a> FnMut(&'a [u8], ReqNot<T>) + DynClone + Send + 'static;
pub trait ReqFn<T: MsgSet> = FnMut(RequestHandle<T>) + Send + 'static;
pub trait NotifFn<T: MsgSet> = for<'a> FnMut(T::InNot, ReqNot<T>) + Send + Sync + 'static;
}
fn get_id(message: &[u8]) -> (u64, &[u8]) {
(u64::from_be_bytes(message[..8].to_vec().try_into().unwrap()), &message[8..])
}
pub struct RequestHandle<T: MsgSet> {
id: u64,
message: T::InReq,
send: Box<dyn SendFn<T>>,
parent: ReqNot<T>,
fulfilled: AtomicBool,
}
impl<MS: MsgSet> RequestHandle<MS> {
pub fn reqnot(&self) -> ReqNot<MS> { self.parent.clone() }
pub fn req(&self) -> &MS::InReq { &self.message }
fn respond(&self, response: &impl Encode) {
assert!(!self.fulfilled.swap(true, Ordering::Relaxed), "Already responded");
let mut buf = (!self.id).to_be_bytes().to_vec();
response.encode(&mut buf);
clone_box(&*self.send)(&buf, self.parent.clone());
}
pub fn handle<T: Request>(&self, _: &T, rep: &T::Response) { self.respond(rep) }
}
impl<MS: MsgSet> Drop for RequestHandle<MS> {
fn drop(&mut self) {
debug_assert!(self.fulfilled.load(Ordering::Relaxed), "Request dropped without response")
}
}
pub fn respond_with<R: Request>(r: &R, f: impl FnOnce(&R) -> R::Response) -> Vec<u8> {
r.respond(f(r))
}
pub struct ReqNotData<T: MsgSet> {
id: u64,
send: Box<dyn SendFn<T>>,
notif: Box<dyn NotifFn<T>>,
req: Box<dyn ReqFn<T>>,
responses: HashMap<u64, SyncSender<Vec<u8>>>,
}
pub struct RawReply(Vec<u8>);
impl Deref for RawReply {
type Target = [u8];
fn deref(&self) -> &Self::Target { get_id(&self.0[..]).1 }
}
pub struct ReqNot<T: MsgSet>(Arc<Mutex<ReqNotData<T>>>);
impl<T: MsgSet> ReqNot<T> {
pub fn new(send: impl SendFn<T>, notif: impl NotifFn<T>, req: impl ReqFn<T>) -> Self {
Self(Arc::new(Mutex::new(ReqNotData {
id: 1,
send: Box::new(send),
notif: Box::new(notif),
req: Box::new(req),
responses: HashMap::new(),
})))
}
/// Can be called from a polling thread or dispatched in any other way
pub fn receive(&self, message: Vec<u8>) {
let mut g = self.0.lock().unwrap();
let (id, payload) = get_id(&message[..]);
if id == 0 {
(g.notif)(T::InNot::decode(&mut &payload[..]), self.clone())
} else if 0 < id.bitand(1 << 63) {
let sender = g.responses.remove(&!id).expect("Received response for invalid message");
sender.send(message).unwrap();
} else {
let send = clone_box(&*g.send);
let message = T::InReq::decode(&mut &payload[..]);
(g.req)(RequestHandle { id, message, send, fulfilled: false.into(), parent: self.clone() })
}
}
pub fn notify<N: Coding + Into<T::OutNot>>(&self, notif: N) {
let mut send = clone_box(&*self.0.lock().unwrap().send);
let mut buf = vec![0; 8];
let msg: T::OutNot = notif.into();
msg.encode(&mut buf);
send(&buf, self.clone())
}
}
pub struct MappedRequester<'a, T>(Box<dyn Fn(T) -> RawReply + Send + Sync + 'a>);
impl<'a, T> MappedRequester<'a, T> {
fn new<U: DynRequester + 'a>(req: U) -> Self
where T: Into<U::Transfer> {
MappedRequester(Box::new(move |t| req.raw_request(t.into())))
}
}
impl<'a, T> DynRequester for MappedRequester<'a, T> {
type Transfer = T;
fn raw_request(&self, data: Self::Transfer) -> RawReply { self.0(data) }
}
impl<T: MsgSet> DynRequester for ReqNot<T> {
type Transfer = T::OutReq;
fn raw_request(&self, req: Self::Transfer) -> RawReply {
let mut g = self.0.lock().unwrap();
let id = g.id;
g.id += 1;
let mut buf = id.to_be_bytes().to_vec();
req.encode(&mut buf);
let (send, recv) = sync_channel(1);
g.responses.insert(id, send);
let mut send = clone_box(&*g.send);
mem::drop(g);
send(&buf, self.clone());
RawReply(recv.recv().unwrap())
}
}
pub trait DynRequester: Send + Sync {
type Transfer;
fn raw_request(&self, data: Self::Transfer) -> RawReply;
}
pub trait Requester: DynRequester {
#[must_use = "These types are subject to change with protocol versions. \
If you don't want to use the return value, At a minimum, force the type."]
fn request<R: Request + Into<Self::Transfer>>(&self, data: R) -> R::Response;
fn map<'a, U: Into<Self::Transfer>>(self) -> MappedRequester<'a, U>
where Self: Sized + 'a {
MappedRequester::new(self)
}
}
impl<'a, This: DynRequester + ?Sized + 'a> Requester for This {
fn request<R: Request + Into<Self::Transfer>>(&self, data: R) -> R::Response {
R::Response::decode(&mut &self.raw_request(data.into())[..])
}
}
impl<T: MsgSet> Clone for ReqNot<T> {
fn clone(&self) -> Self { Self(self.0.clone()) }
}
#[cfg(test)]
mod test {
use std::sync::{Arc, Mutex};
use orchid_api_derive::Coding;
use orchid_api_traits::Request;
use super::{MsgSet, ReqNot};
use crate::{clone, reqnot::Requester as _};
#[derive(Coding, Debug, PartialEq)]
pub struct TestReq(u8);
impl Request for TestReq {
type Response = u8;
}
pub struct TestMsgSet;
impl MsgSet for TestMsgSet {
type InNot = u8;
type InReq = TestReq;
type OutNot = u8;
type OutReq = TestReq;
}
#[test]
fn notification() {
let received = Arc::new(Mutex::new(None));
let receiver = ReqNot::<TestMsgSet>::new(
|_, _| panic!("Should not send anything"),
clone!(received; move |notif, _| *received.lock().unwrap() = Some(notif)),
|_| panic!("Not receiving a request"),
);
let sender = ReqNot::<TestMsgSet>::new(
clone!(receiver; move |d, _| receiver.receive(d.to_vec())),
|_, _| panic!("Should not receive notif"),
|_| panic!("Should not receive request"),
);
sender.notify(3);
assert_eq!(*received.lock().unwrap(), Some(3));
sender.notify(4);
assert_eq!(*received.lock().unwrap(), Some(4));
}
#[test]
fn request() {
let receiver = Arc::new(Mutex::<Option<ReqNot<TestMsgSet>>>::new(None));
let sender = Arc::new(ReqNot::<TestMsgSet>::new(
{
let receiver = receiver.clone();
move |d, _| receiver.lock().unwrap().as_ref().unwrap().receive(d.to_vec())
},
|_, _| panic!("Should not receive notif"),
|_| panic!("Should not receive request"),
));
*receiver.lock().unwrap() = Some(ReqNot::new(
{
let sender = sender.clone();
move |d, _| sender.receive(d.to_vec())
},
|_, _| panic!("Not receiving notifs"),
|req| {
assert_eq!(req.req(), &TestReq(5));
req.respond(&6u8);
},
));
let response = sender.request(TestReq(5));
assert_eq!(response, 6);
}
}

View File

@@ -45,7 +45,7 @@ impl<'a> System<'a> {
/// An error raised when a system fails to load a path. This usually means that
/// another system the current one depends on did not get loaded
#[derive(Debug)]
pub struct MissingSystemCode {
struct MissingSystemCode {
path: VName,
system: Vec<String>,
referrer: VName,

630
orchid-base/src/tree.rs Normal file
View File

@@ -0,0 +1,630 @@
//! Generic module tree structure
//!
//! Used by various stages of the pipeline with different parameters
use std::fmt;
use hashbrown::HashMap;
use itertools::Itertools as _;
use never::Never;
use substack::Substack;
use trait_set::trait_set;
use crate::boxed_iter::BoxedIter;
use crate::combine::Combine;
use crate::intern::{intern, Token};
use crate::join::try_join_maps;
use crate::location::CodeOrigin;
use crate::name::{VName, VPath};
use crate::proj_error::{ProjectError, ProjectErrorObj};
use crate::sequence::Sequence;
/// An umbrella trait for operations you can carry out on any part of the tree
/// structure
pub trait TreeTransforms: Sized {
/// Data held at the leaves of the tree
type Item;
/// Data associated with modules
type XMod;
/// Data associated with entries inside modules
type XEnt;
/// Recursive type to enable [TreeTransforms::map_data] to transform the whole
/// tree
type SelfType<T, U, V>: TreeTransforms<Item = T, XMod = U, XEnt = V>;
/// Implementation for [TreeTransforms::map_data]
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Token<String>>, Self::Item) -> T,
module: &mut impl FnMut(Substack<Token<String>>, Self::XMod) -> U,
entry: &mut impl FnMut(Substack<Token<String>>, Self::XEnt) -> V,
path: Substack<Token<String>>,
) -> Self::SelfType<T, U, V>;
/// Transform all the data in the tree without changing its structure
fn map_data<T, U, V>(
self,
mut item: impl FnMut(Substack<Token<String>>, Self::Item) -> T,
mut module: impl FnMut(Substack<Token<String>>, Self::XMod) -> U,
mut entry: impl FnMut(Substack<Token<String>>, Self::XEnt) -> V,
) -> Self::SelfType<T, U, V> {
self.map_data_rec(&mut item, &mut module, &mut entry, Substack::Bottom)
}
/// Visit all elements in the tree. This is like [TreeTransforms::search] but
/// without the early exit
///
/// * init - can be used for reduce, otherwise pass `()`
/// * callback - a callback applied on every module.
/// * [`Substack<Token<String>>`] - the walked path
/// * [Module] - the current module
/// * `T` - data for reduce.
fn search_all<'a, T>(
&'a self,
init: T,
mut callback: impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> T,
) -> T {
let res =
self.search(init, |stack, member, state| Ok::<T, Never>(callback(stack, member, state)));
res.unwrap_or_else(|e| match e {})
}
/// Visit elements in the tree depth first with the provided function
///
/// * init - can be used for reduce, otherwise pass `()`
/// * callback - a callback applied on every module. Can return [Err] to
/// short-circuit the walk
/// * [`Substack<Token<String>>`] - the walked path
/// * [Module] - the current module
/// * `T` - data for reduce.
fn search<'a, T, E>(
&'a self,
init: T,
mut callback: impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
self.search_rec(init, Substack::Bottom, &mut callback)
}
/// Internal version of [TreeTransforms::search_all]
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Token<String>>,
callback: &mut impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E>;
}
/// The member in a [ModEntry] which is associated with a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModMember<Item, XMod, XEnt> {
/// Arbitrary data
Item(Item),
/// A child module
Sub(Module<Item, XMod, XEnt>),
}
impl<Item, XMod, XEnt> TreeTransforms for ModMember<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = ModMember<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Token<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Token<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Token<String>>, XEnt) -> V,
path: Substack<Token<String>>,
) -> Self::SelfType<T, U, V> {
match self {
Self::Item(it) => ModMember::Item(item(path, it)),
Self::Sub(sub) => ModMember::Sub(sub.map_data_rec(item, module, entry, path)),
}
}
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Token<String>>,
callback: &mut impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
match self {
Self::Item(it) => callback(stack, ModMemberRef::Item(it), state),
Self::Sub(m) => m.search_rec(state, stack, callback),
}
}
}
/// Reasons why merging trees might fail
pub enum ConflictKind<Item: Combine, XMod: Combine, XEnt: Combine> {
/// Error during the merging of items
Item(Item::Error),
/// Error during the merging of module metadata
Module(XMod::Error),
/// Error during the merging of entry metadata
XEnt(XEnt::Error),
/// An item appeared in one tree where the other contained a submodule
ItemModule,
}
macro_rules! impl_for_conflict {
($target:ty, ($($deps:tt)*), $for:ty, $body:tt) => {
impl<Item: Combine, XMod: Combine, XEnt: Combine> $target
for $for
where
Item::Error: $($deps)*,
XMod::Error: $($deps)*,
XEnt::Error: $($deps)*,
$body
};
}
impl_for_conflict!(Clone, (Clone), ConflictKind<Item, XMod, XEnt>, {
fn clone(&self) -> Self {
match self {
ConflictKind::Item(it_e) => ConflictKind::Item(it_e.clone()),
ConflictKind::Module(mod_e) => ConflictKind::Module(mod_e.clone()),
ConflictKind::XEnt(ent_e) => ConflictKind::XEnt(ent_e.clone()),
ConflictKind::ItemModule => ConflictKind::ItemModule,
}
}
});
impl_for_conflict!(fmt::Debug, (fmt::Debug), ConflictKind<Item, XMod, XEnt>, {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ConflictKind::Item(it_e) =>
f.debug_tuple("TreeCombineErr::Item").field(it_e).finish(),
ConflictKind::Module(mod_e) =>
f.debug_tuple("TreeCombineErr::Module").field(mod_e).finish(),
ConflictKind::XEnt(ent_e) =>
f.debug_tuple("TreeCombineErr::XEnt").field(ent_e).finish(),
ConflictKind::ItemModule => write!(f, "TreeCombineErr::Item2Module"),
}
}
});
/// Error produced when two trees cannot be merged
pub struct TreeConflict<Item: Combine, XMod: Combine, XEnt: Combine> {
/// Which subtree caused the failure
pub path: VPath,
/// What type of failure occurred
pub kind: ConflictKind<Item, XMod, XEnt>,
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> TreeConflict<Item, XMod, XEnt> {
fn new(kind: ConflictKind<Item, XMod, XEnt>) -> Self { Self { path: VPath::new([]), kind } }
fn push(self, seg: Token<String>) -> Self {
Self { path: self.path.prefix([seg]), kind: self.kind }
}
}
impl_for_conflict!(Clone, (Clone), TreeConflict<Item, XMod, XEnt>, {
fn clone(&self) -> Self {
Self { path: self.path.clone(), kind: self.kind.clone() }
}
});
impl_for_conflict!(fmt::Debug, (fmt::Debug), TreeConflict<Item, XMod, XEnt>, {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TreeConflict")
.field("path", &self.path)
.field("kind", &self.kind)
.finish()
}
});
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for ModMember<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, other: Self) -> Result<Self, Self::Error> {
match (self, other) {
(Self::Item(i1), Self::Item(i2)) => match i1.combine(i2) {
Ok(i) => Ok(Self::Item(i)),
Err(e) => Err(TreeConflict::new(ConflictKind::Item(e))),
},
(Self::Sub(m1), Self::Sub(m2)) => m1.combine(m2).map(Self::Sub),
(..) => Err(TreeConflict::new(ConflictKind::ItemModule)),
}
}
}
/// Data about a name in a [Module]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ModEntry<Item, XMod, XEnt> {
/// The submodule or item
pub member: ModMember<Item, XMod, XEnt>,
/// Additional fields
pub x: XEnt,
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for ModEntry<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, other: Self) -> Result<Self, Self::Error> {
match self.x.combine(other.x) {
Err(e) => Err(TreeConflict::new(ConflictKind::XEnt(e))),
Ok(x) => Ok(Self { x, member: self.member.combine(other.member)? }),
}
}
}
impl<Item, XMod, XEnt> ModEntry<Item, XMod, XEnt> {
/// Returns the item in this entry if it contains one.
#[must_use]
pub fn item(&self) -> Option<&Item> {
match &self.member {
ModMember::Item(it) => Some(it),
ModMember::Sub(_) => None,
}
}
}
impl<Item, XMod, XEnt> TreeTransforms for ModEntry<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = ModEntry<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Token<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Token<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Token<String>>, XEnt) -> V,
path: Substack<Token<String>>,
) -> Self::SelfType<T, U, V> {
ModEntry {
member: self.member.map_data_rec(item, module, entry, path.clone()),
x: entry(path, self.x),
}
}
fn search_rec<'a, T, E>(
&'a self,
state: T,
stack: Substack<Token<String>>,
callback: &mut impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
self.member.search_rec(state, stack, callback)
}
}
impl<Item, XMod, XEnt: Default> ModEntry<Item, XMod, XEnt> {
/// Wrap a member directly with trivial metadata
pub fn wrap(member: ModMember<Item, XMod, XEnt>) -> Self { Self { member, x: XEnt::default() } }
/// Wrap an item directly with trivial metadata
pub fn leaf(item: Item) -> Self { Self::wrap(ModMember::Item(item)) }
}
impl<Item, XMod: Default, XEnt: Default> ModEntry<Item, XMod, XEnt> {
/// Create an empty submodule
pub fn empty() -> Self { Self::wrap(ModMember::Sub(Module::wrap([]))) }
/// Create a module
#[must_use]
pub fn tree<K: AsRef<str>>(arr: impl IntoIterator<Item = (K, Self)>) -> Self {
Self::wrap(ModMember::Sub(Module::wrap(arr.into_iter().map(|(k, v)| (intern(k.as_ref()), v)))))
}
/// Create a record in the list passed to [ModEntry#tree] which describes a
/// submodule. This mostly exists to deal with strange rustfmt block
/// breaking behaviour
pub fn tree_ent<K: AsRef<str>>(key: K, arr: impl IntoIterator<Item = (K, Self)>) -> (K, Self) {
(key, Self::tree(arr))
}
/// Namespace the tree with the list of names
///
/// The unarray is used to trick rustfmt into breaking the sub-item
/// into a block without breaking anything else.
#[must_use]
pub fn ns(name: impl AsRef<str>, [mut end]: [Self; 1]) -> Self {
let elements = name.as_ref().split("::").collect::<Vec<_>>();
for name in elements.into_iter().rev() {
end = Self::tree([(name, end)]);
}
end
}
fn not_mod_panic<T>() -> T { panic!("Expected module but found leaf") }
/// Return the wrapped module. Panic if the entry wraps an item
pub fn unwrap_mod(self) -> Module<Item, XMod, XEnt> {
if let ModMember::Sub(m) = self.member { m } else { Self::not_mod_panic() }
}
/// Return the wrapped module. Panic if the entry wraps an item
pub fn unwrap_mod_ref(&self) -> &Module<Item, XMod, XEnt> {
if let ModMember::Sub(m) = &self.member { m } else { Self::not_mod_panic() }
}
}
/// A module, containing imports,
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Module<Item, XMod, XEnt> {
/// Submodules and items by name
pub entries: HashMap<Token<String>, ModEntry<Item, XMod, XEnt>>,
/// Additional fields
pub x: XMod,
}
trait_set! {
/// A filter applied to a module tree
pub trait Filter<'a, Item, XMod, XEnt> =
for<'b> Fn(&'b ModEntry<Item, XMod, XEnt>) -> bool + Clone + 'a
}
/// A line in a [Module]
pub type Record<Item, XMod, XEnt> = (Token<String>, ModEntry<Item, XMod, XEnt>);
impl<Item, XMod, XEnt> Module<Item, XMod, XEnt> {
/// Returns child names for which the value matches a filter
#[must_use]
pub fn keys<'a>(
&'a self,
filter: impl for<'b> Fn(&'b ModEntry<Item, XMod, XEnt>) -> bool + 'a,
) -> BoxedIter<Token<String>> {
Box::new((self.entries.iter()).filter(move |(_, v)| filter(v)).map(|(k, _)| k.clone()))
}
/// Return the module at the end of the given path
pub fn walk_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Token<String>],
path: &'b [Token<String>],
filter: impl Filter<'b, Item, XMod, XEnt>,
) -> Result<&'a Self, WalkError<'b>> {
let mut module = self;
for (pos, step) in path.iter().enumerate() {
let kind = match module.entries.get(step) {
None => ErrKind::Missing,
Some(ent) if !filter(ent) => ErrKind::Filtered,
Some(ModEntry { member: ModMember::Item(_), .. }) => ErrKind::NotModule,
Some(ModEntry { member: ModMember::Sub(next), .. }) => {
module = next;
continue;
},
};
let options = Sequence::new(move || module.keys(filter.clone()));
return Err(WalkError { kind, prefix, path, pos, options });
}
Ok(module)
}
/// Return the member at the end of the given path
///
/// # Panics
///
/// if path is empty, since the reference cannot be forwarded that way
pub fn walk1_ref<'a: 'b, 'b>(
&'a self,
prefix: &'b [Token<String>],
path: &'b [Token<String>],
filter: impl Filter<'b, Item, XMod, XEnt>,
) -> Result<(&'a ModEntry<Item, XMod, XEnt>, &'a Self), WalkError<'b>> {
let (last, parent) = path.split_last().expect("Path cannot be empty");
let pos = path.len() - 1;
let module = self.walk_ref(prefix, parent, filter.clone())?;
let err_kind = match &module.entries.get(last) {
Some(entry) if filter(entry) => return Ok((entry, module)),
Some(_) => ErrKind::Filtered,
None => ErrKind::Missing,
};
let options = Sequence::new(move || module.keys(filter.clone()));
Err(WalkError { kind: err_kind, options, prefix, path, pos })
}
/// Walk from one node to another in a tree, asserting that the origin can see
/// the target.
///
/// # Panics
///
/// If the target is the root node
pub fn inner_walk<'a: 'b, 'b>(
&'a self,
origin: &[Token<String>],
target: &'b [Token<String>],
is_exported: impl for<'c> Fn(&'c ModEntry<Item, XMod, XEnt>) -> bool + Clone + 'b,
) -> Result<(&'a ModEntry<Item, XMod, XEnt>, &'a Self), WalkError<'b>> {
let ignore_vis_len = 1 + origin.iter().zip(target).take_while(|(a, b)| a == b).count();
if target.len() <= ignore_vis_len {
return self.walk1_ref(&[], target, |_| true);
}
let (ignore_vis_path, hidden_path) = target.split_at(ignore_vis_len);
let first_divergence = self.walk_ref(&[], ignore_vis_path, |_| true)?;
first_divergence.walk1_ref(ignore_vis_path, hidden_path, is_exported)
}
/// Wrap entry table in a module with trivial metadata
pub fn wrap(entries: impl IntoIterator<Item = Record<Item, XMod, XEnt>>) -> Self
where XMod: Default {
Self { entries: entries.into_iter().collect(), x: XMod::default() }
}
}
impl<Item, XMod, XEnt> TreeTransforms for Module<Item, XMod, XEnt> {
type Item = Item;
type XEnt = XEnt;
type XMod = XMod;
type SelfType<T, U, V> = Module<T, U, V>;
fn map_data_rec<T, U, V>(
self,
item: &mut impl FnMut(Substack<Token<String>>, Item) -> T,
module: &mut impl FnMut(Substack<Token<String>>, XMod) -> U,
entry: &mut impl FnMut(Substack<Token<String>>, XEnt) -> V,
path: Substack<Token<String>>,
) -> Self::SelfType<T, U, V> {
Module {
x: module(path.clone(), self.x),
entries: (self.entries.into_iter())
.map(|(k, e)| (k.clone(), e.map_data_rec(item, module, entry, path.push(k))))
.collect(),
}
}
fn search_rec<'a, T, E>(
&'a self,
mut state: T,
stack: Substack<Token<String>>,
callback: &mut impl FnMut(
Substack<Token<String>>,
ModMemberRef<'a, Item, XMod, XEnt>,
T,
) -> Result<T, E>,
) -> Result<T, E> {
state = callback(stack.clone(), ModMemberRef::Mod(self), state)?;
for (key, value) in &self.entries {
state = value.search_rec(state, stack.push(key.clone()), callback)?;
}
Ok(state)
}
}
impl<Item: Combine, XMod: Combine, XEnt: Combine> Combine for Module<Item, XMod, XEnt> {
type Error = TreeConflict<Item, XMod, XEnt>;
fn combine(self, Self { entries, x }: Self) -> Result<Self, Self::Error> {
let entries =
try_join_maps(self.entries, entries, |k, l, r| l.combine(r).map_err(|e| e.push(k.clone())))?;
let x = (self.x.combine(x)).map_err(|e| TreeConflict::new(ConflictKind::Module(e)))?;
Ok(Self { x, entries })
}
}
impl<Item: fmt::Display, TExt: fmt::Display, XEnt: fmt::Display> fmt::Display
for Module<Item, TExt, XEnt>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "module {{")?;
for (name, ModEntry { member, x: extra }) in &self.entries {
match member {
ModMember::Sub(module) => write!(f, "\n{name} {extra} = {module}"),
ModMember::Item(item) => write!(f, "\n{name} {extra} = {item}"),
}?;
}
write!(f, "\n\n{}\n}}", &self.x)
}
}
/// A non-owning version of [ModMember]. Either an item-ref or a module-ref.
pub enum ModMemberRef<'a, Item, XMod, XEnt> {
/// Leaf
Item(&'a Item),
/// Node
Mod(&'a Module<Item, XMod, XEnt>),
}
/// Possible causes why the path could not be walked
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ErrKind {
/// `require_exported` was set to `true` and a module wasn't exported
Filtered,
/// A module was not found
Missing,
/// The path leads into a leaf node
NotModule,
}
#[derive(Clone)]
/// All details about a failed tree-walk
pub struct WalkError<'a> {
/// Failure mode
kind: ErrKind,
/// Path to the module where the walk started
prefix: &'a [Token<String>],
/// Planned walk path
path: &'a [Token<String>],
/// Index into walked path where the error occurred
pos: usize,
/// Alternatives to the failed steps
options: Sequence<'a, Token<String>>,
}
impl<'a> WalkError<'a> {
/// Total length of the path represented by this error
#[must_use]
pub fn depth(&self) -> usize { self.prefix.len() + self.pos + 1 }
/// Attach a location to the error and convert into trait object for reporting
#[must_use]
pub fn at(self, origin: &CodeOrigin) -> ProjectErrorObj {
let details = WalkErrorDetails {
origin: origin.clone(),
path: VName::new((self.prefix.iter()).chain(self.path.iter().take(self.pos + 1)).cloned())
.expect("empty paths don't cause an error"),
options: self.options.iter().collect(),
};
match self.kind {
ErrKind::Filtered => FilteredError(details).pack(),
ErrKind::Missing => MissingError(details).pack(),
ErrKind::NotModule => NotModuleError(details).pack(),
}
}
/// Construct an error for the very last item in a slice. This is often done
/// outside [super::tree] so it gets a function rather than exposing the
/// fields of [WalkError]
pub fn last(
path: &'a [Token<String>],
kind: ErrKind,
options: Sequence<'a, Token<String>>,
) -> Self {
WalkError { kind, path, options, pos: path.len() - 1, prefix: &[] }
}
}
impl<'a> fmt::Debug for WalkError<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("WalkError")
.field("kind", &self.kind)
.field("prefix", &self.prefix)
.field("path", &self.path)
.field("pos", &self.pos)
.finish_non_exhaustive()
}
}
struct WalkErrorDetails {
path: VName,
options: Vec<Token<String>>,
origin: CodeOrigin,
}
impl WalkErrorDetails {
fn print_options(&self) -> String { format!("options are {}", self.options.iter().join(", ")) }
}
struct FilteredError(WalkErrorDetails);
impl ProjectError for FilteredError {
const DESCRIPTION: &'static str = "The path leads into a private module";
fn one_position(&self) -> CodeOrigin { self.0.origin.clone() }
fn message(&self) -> String { format!("{} is private, {}", self.0.path, self.0.print_options()) }
}
struct MissingError(WalkErrorDetails);
impl ProjectError for MissingError {
const DESCRIPTION: &'static str = "Nonexistent path";
fn one_position(&self) -> CodeOrigin { self.0.origin.clone() }
fn message(&self) -> String {
format!("{} does not exist, {}", self.0.path, self.0.print_options())
}
}
struct NotModuleError(WalkErrorDetails);
impl ProjectError for NotModuleError {
const DESCRIPTION: &'static str = "The path leads into a leaf";
fn one_position(&self) -> CodeOrigin { self.0.origin.clone() }
fn message(&self) -> String {
format!("{} is not a module, {}", self.0.path, self.0.print_options())
}
}

View File

@@ -0,0 +1,95 @@
use std::rc::Rc;
use std::sync::Arc;
use crate::proj_error::{ErrorSansOrigin, ErrorSansOriginObj};
use crate::intern::Token;
use crate::name::{PathSlice, VPath};
/// Represents the result of loading code from a string-tree form such
/// as the file system. Cheap to clone.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Loaded {
/// Conceptually equivalent to a sourcefile
Code(Arc<String>),
/// Conceptually equivalent to the list of *.orc files in a folder, without
/// the extension
Collection(Arc<Vec<Token<String>>>),
}
impl Loaded {
/// Is the loaded item source code (not a collection)?
pub fn is_code(&self) -> bool { matches!(self, Loaded::Code(_)) }
/// Collect the elements in a collection rreport
pub fn collection(items: impl IntoIterator<Item = Token<String>>) -> Self {
Self::Collection(Arc::new(items.into_iter().collect()))
}
}
/// Returned by any source loading callback
pub type FSResult = Result<Loaded, ErrorSansOriginObj>;
/// Type that indicates the type of an entry without reading the contents
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum FSKind {
/// Invalid path or read error
None,
/// Source code
Code,
/// Internal tree node
Collection,
}
/// Distinguished error for missing code
#[derive(Clone, PartialEq, Eq)]
pub struct CodeNotFound(pub VPath);
impl CodeNotFound {
/// Instantiate error
pub fn new(path: VPath) -> Self { Self(path) }
}
impl ErrorSansOrigin for CodeNotFound {
const DESCRIPTION: &'static str = "No source code for path";
fn message(&self) -> String { format!("{} not found", self.0) }
}
/// A simplified view of a file system for the purposes of source code loading.
/// This includes the real FS and source code, but also various in-memory
/// formats and other sources for libraries and dependencies.
pub trait VirtFS {
/// Implementation of [VirtFS::read]
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult;
/// Discover information about a path without reading it.
///
/// Implement this if your vfs backend can do expensive operations
fn kind(&self, path: &PathSlice) -> FSKind {
match self.read(path) {
Err(_) => FSKind::None,
Ok(Loaded::Code(_)) => FSKind::Code,
Ok(Loaded::Collection(_)) => FSKind::Collection,
}
}
/// Convert a path into a human-readable string that is meaningful in the
/// target context.
fn display(&self, path: &[Token<String>]) -> Option<String>;
/// Convert the FS handler into a type-erased version of itself for packing in
/// a tree.
fn rc(self) -> Rc<dyn VirtFS>
where Self: Sized + 'static {
Rc::new(self)
}
/// Read a path, returning either a text file, a directory listing or an
/// error. Wrapper for [VirtFS::get]
fn read(&self, path: &PathSlice) -> FSResult { self.get(path, path) }
}
impl VirtFS for &dyn VirtFS {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
(*self).get(path, full_path)
}
fn display(&self, path: &[Token<String>]) -> Option<String> { (*self).display(path) }
}
impl<T: VirtFS + ?Sized> VirtFS for Rc<T> {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
(**self).get(path, full_path)
}
fn display(&self, path: &[Token<String>]) -> Option<String> { (**self).display(path) }
}

View File

@@ -0,0 +1,73 @@
use std::rc::Rc;
use std::sync::Arc;
use super::common::CodeNotFound;
use super::{FSResult, Loaded, VirtFS};
use crate::intern::Token;
use crate::proj_error::ErrorSansOrigin;
use crate::name::PathSlice;
use crate::tree::{ModEntry, ModMember};
use crate::combine::Combine;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConflictingTrees;
impl Combine for Rc<dyn VirtFS> {
type Error = ConflictingTrees;
fn combine(self, _: Self) -> Result<Self, Self::Error> { Err(ConflictingTrees) }
}
impl Combine for Arc<dyn VirtFS> {
type Error = ConflictingTrees;
fn combine(self, _: Self) -> Result<Self, Self::Error> { Err(ConflictingTrees) }
}
impl<'a> Combine for &'a dyn VirtFS {
type Error = ConflictingTrees;
fn combine(self, _: Self) -> Result<Self, Self::Error> { Err(ConflictingTrees) }
}
/// A declarative in-memory tree with [VirtFS] objects for leaves. Paths are
/// followed to a leaf and the leftover handled by it.
pub type DeclTree = ModEntry<Rc<dyn VirtFS>, (), ()>;
impl VirtFS for DeclTree {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
match &self.member {
ModMember::Item(it) => it.get(path, full_path),
ModMember::Sub(module) => match path.split_first() {
None => Ok(Loaded::collection(module.keys(|_| true))),
Some((head, tail)) => (module.entries.get(head))
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
.and_then(|ent| ent.get(tail, full_path)),
},
}
}
fn display(&self, path: &[Token<String>]) -> Option<String> {
let (head, tail) = path.split_first()?;
match &self.member {
ModMember::Item(it) => it.display(path),
ModMember::Sub(module) => module.entries.get(head)?.display(tail),
}
}
}
impl VirtFS for String {
fn display(&self, _: &[Token<String>]) -> Option<String> { None }
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
(path.is_empty().then(|| Loaded::Code(Arc::new(self.as_str().to_string()))))
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
}
}
impl<'a> VirtFS for &'a str {
fn display(&self, _: &[Token<String>]) -> Option<String> { None }
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
(path.is_empty().then(|| Loaded::Code(Arc::new(self.to_string()))))
.ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())
}
}
/// Insert a file by cleartext contents in the [DeclTree].
pub fn decl_file(s: &str) -> DeclTree { DeclTree::leaf(Rc::new(s.to_string())) }

View File

@@ -0,0 +1,121 @@
use std::cell::RefCell;
use std::fs::File;
use std::io;
use std::io::{ErrorKind, Read};
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use hashbrown::HashMap;
use super::common::CodeNotFound;
use super::{FSResult, Loaded, VirtFS};
use crate::intern::{intern, Token};
use crate::proj_error::{ErrorSansOrigin, ErrorSansOriginObj};
use crate::name::PathSlice;
#[derive(Clone)]
struct OpenError {
file: Arc<Mutex<io::Error>>,
dir: Arc<Mutex<io::Error>>,
}
impl OpenError {
pub fn wrap(file: io::Error, dir: io::Error) -> ErrorSansOriginObj {
Self { dir: Arc::new(Mutex::new(dir)), file: Arc::new(Mutex::new(file)) }.pack()
}
}
impl ErrorSansOrigin for OpenError {
const DESCRIPTION: &'static str = "A file system error occurred";
fn message(&self) -> String {
let Self { dir, file } = self;
format!(
"File system errors other than not found occurred\n\
as a file: {}\nas a directory: {}",
file.lock().unwrap(),
dir.lock().unwrap()
)
}
}
#[derive(Clone)]
struct IOError(Arc<Mutex<io::Error>>);
impl IOError {
pub fn wrap(inner: io::Error) -> ErrorSansOriginObj { Self(Arc::new(Mutex::new(inner))).pack() }
}
impl ErrorSansOrigin for IOError {
const DESCRIPTION: &'static str = "an I/O error occured";
fn message(&self) -> String { format!("File read error: {}", self.0.lock().unwrap()) }
}
#[derive(Clone)]
struct NotUtf8(PathBuf);
impl NotUtf8 {
pub fn wrap(path: &Path) -> ErrorSansOriginObj { Self(path.to_owned()).pack() }
}
impl ErrorSansOrigin for NotUtf8 {
const DESCRIPTION: &'static str = "Source files must be UTF-8";
fn message(&self) -> String {
format!("{} is a source file but contains invalid UTF-8", self.0.display())
}
}
/// A real file system directory linked into the virtual FS
pub struct DirNode {
cached: RefCell<HashMap<PathBuf, FSResult>>,
root: PathBuf,
suffix: &'static str,
}
impl DirNode {
/// Reference a real file system directory in the virtual FS
pub fn new(root: PathBuf, suffix: &'static str) -> Self {
assert!(suffix.starts_with('.'), "Extension must begin with .");
Self { cached: RefCell::default(), root, suffix }
}
fn ext(&self) -> &str { self.suffix.strip_prefix('.').expect("Checked in constructor") }
fn load_file(&self, fpath: &Path, orig_path: &PathSlice) -> FSResult {
match fpath.read_dir() {
Err(dir_e) => {
let fpath = fpath.with_extension(self.ext());
let mut file =
File::open(&fpath).map_err(|file_e| match (dir_e.kind(), file_e.kind()) {
(ErrorKind::NotFound, ErrorKind::NotFound) =>
CodeNotFound::new(orig_path.to_vpath()).pack(),
_ => OpenError::wrap(file_e, dir_e),
})?;
let mut buf = Vec::new();
file.read_to_end(&mut buf).map_err(IOError::wrap)?;
let text = String::from_utf8(buf).map_err(|_| NotUtf8::wrap(&fpath))?;
Ok(Loaded::Code(Arc::new(text)))
},
Ok(dir) => Ok(Loaded::collection(dir.filter_map(|ent_r| {
let ent = ent_r.ok()?;
let name = ent.file_name().into_string().ok()?;
match ent.metadata().ok()?.is_dir() {
false => Some(intern(name.strip_suffix(self.suffix)?)),
true => Some(intern(&name)),
}
}))),
}
}
fn mk_pathbuf(&self, path: &[Token<String>]) -> PathBuf {
let mut fpath = self.root.clone();
path.iter().for_each(|seg| fpath.push(seg.as_str()));
fpath
}
}
impl VirtFS for DirNode {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
let fpath = self.mk_pathbuf(path);
let mut binding = self.cached.borrow_mut();
let (_, res) = (binding.raw_entry_mut().from_key(&fpath))
.or_insert_with(|| (fpath.clone(), self.load_file(&fpath, full_path)));
res.clone()
}
fn display(&self, path: &[Token<String>]) -> Option<String> {
let pathbuf = self.mk_pathbuf(path).with_extension(self.ext());
Some(pathbuf.to_string_lossy().to_string())
}
}

View File

@@ -0,0 +1,74 @@
use std::sync::Arc;
use itertools::Itertools as _;
use rust_embed::RustEmbed;
use super::common::CodeNotFound;
use super::{FSResult, Loaded, VirtFS};
use crate::intern::{intern, Token};
use crate::proj_error::ErrorSansOrigin;
use crate::location::CodeGenInfo;
use crate::name::PathSlice;
use crate::tree::{ModEntry, ModMember, Module};
/// An in-memory FS tree for libraries managed internally by the interpreter
pub struct EmbeddedFS {
tree: Module<Arc<String>, (), ()>,
suffix: &'static str,
gen: CodeGenInfo,
}
impl EmbeddedFS {
/// Expose a directory embedded in a binary wiht [RustEmbed] to the
/// interpreter
pub fn new<T: RustEmbed>(suffix: &'static str, gen: CodeGenInfo) -> Self {
let mut tree = Module::wrap([]);
for path in T::iter() {
let data_buf = T::get(&path).expect("path from iterator").data.to_vec();
let data = String::from_utf8(data_buf).expect("embed must be utf8");
let mut cur_node = &mut tree;
let path_no_suffix = path.strip_suffix(suffix).expect("embed filtered for suffix");
let mut segments = path_no_suffix.split('/').map(intern);
let mut cur_seg = segments.next().expect("Embed is a directory");
for next_seg in segments {
if !cur_node.entries.contains_key(&cur_seg) {
let ent = ModEntry::wrap(ModMember::Sub(Module::wrap([])));
cur_node.entries.insert(cur_seg.clone(), ent);
}
let ent = cur_node.entries.get_mut(&cur_seg).expect("just constructed");
match &mut ent.member {
ModMember::Sub(submod) => cur_node = submod,
_ => panic!("Aliased file and folder"),
};
cur_seg = next_seg;
}
let data_ent = ModEntry::wrap(ModMember::Item(Arc::new(data)));
let prev = cur_node.entries.insert(cur_seg, data_ent);
debug_assert!(prev.is_none(), "file name unique");
}
// if gen.generator == "std" {
// panic!(
// "{:?}",
// tree.map_data(&|_, s| (), &|_, x| x, &|_, x| x, Substack::Bottom)
// );
// };
Self { gen, suffix, tree }
}
}
impl VirtFS for EmbeddedFS {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> FSResult {
if path.is_empty() {
return Ok(Loaded::collection(self.tree.keys(|_| true)));
}
let entry = (self.tree.walk1_ref(&[], path, |_| true))
.map_err(|_| CodeNotFound::new(full_path.to_vpath()).pack())?;
Ok(match &entry.0.member {
ModMember::Item(text) => Loaded::Code(text.clone()),
ModMember::Sub(sub) => Loaded::collection(sub.keys(|_| true)),
})
}
fn display(&self, path: &[Token<String>]) -> Option<String> {
let Self { gen, suffix, .. } = self;
Some(format!("{}{suffix} in {gen}", path.iter().join("/")))
}
}

View File

@@ -0,0 +1,38 @@
use itertools::Itertools;
use super::common::CodeNotFound;
use super::VirtFS;
use crate::intern::Token;
use crate::name::{PathSlice, VPath};
use crate::proj_error::ErrorSansOrigin;
/// Modify the prefix of a nested file tree
pub struct PrefixFS<'a> {
remove: VPath,
add: VPath,
wrapped: Box<dyn VirtFS + 'a>,
}
impl<'a> PrefixFS<'a> {
/// Modify the prefix of a file tree
pub fn new(wrapped: impl VirtFS + 'a, remove: impl AsRef<str>, add: impl AsRef<str>) -> Self {
Self {
wrapped: Box::new(wrapped),
remove: VPath::parse(remove.as_ref()),
add: VPath::parse(add.as_ref()),
}
}
fn proc_path(&self, path: &[Token<String>]) -> Option<Vec<Token<String>>> {
let path = path.strip_prefix(self.remove.as_slice())?;
Some(self.add.0.iter().chain(path).cloned().collect_vec())
}
}
impl<'a> VirtFS for PrefixFS<'a> {
fn get(&self, path: &[Token<String>], full_path: &PathSlice) -> super::FSResult {
let path =
self.proc_path(path).ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?;
self.wrapped.get(&path, full_path)
}
fn display(&self, path: &[Token<String>]) -> Option<String> {
self.wrapped.display(&self.proc_path(path)?)
}
}

View File

@@ -0,0 +1,11 @@
[package]
name = "orchid-extension"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
orchid-api = { version = "0.1.0", path = "../orchid-api" }
orchid-api-traits = { version = "0.1.0", path = "../orchid-api-traits" }
orchid-base = { version = "0.1.0", path = "../orchid-base" }

View File

@@ -0,0 +1,35 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use orchid_api::proto::{ExtMsgSet, ExtensionHeader, HostExtNotif, HostExtReq, HostHeader};
use orchid_api_traits::{Decode, Encode};
use orchid_base::child::{recv_parent_msg, send_parent_msg};
use orchid_base::clone;
use orchid_base::intern::{init_replica, sweep_replica};
use orchid_base::reqnot::{ReqNot, Requester};
pub struct ExtensionData {}
pub fn main(data: &mut ExtensionData) {
HostHeader::decode(&mut &recv_parent_msg().unwrap()[..]);
let mut buf = Vec::new();
ExtensionHeader { systems: vec![] }.encode(&mut buf);
send_parent_msg(&buf).unwrap();
let exiting = Arc::new(AtomicBool::new(false));
let rn = ReqNot::<ExtMsgSet>::new(
|a, _| send_parent_msg(a).unwrap(),
clone!(exiting; move |n, _| match n {
HostExtNotif::Exit => exiting.store(true, Ordering::Relaxed),
_ => todo!(),
}),
|req| match req.req() {
HostExtReq::Ping(ping) => req.handle(ping, &()),
HostExtReq::Sweep(sweep) => req.handle(sweep, &sweep_replica()),
_ => todo!(),
},
);
init_replica(rn.clone().map());
while !exiting.load(Ordering::Relaxed) {
rn.receive(recv_parent_msg().unwrap())
}
}

View File

@@ -0,0 +1,30 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use orchid_api::proto::{ExtMsgSet, ExtensionHeader, HostExtNotif, HostExtReq, HostHeader};
use orchid_api_traits::{Decode, Encode};
use ordered_float::NotNan;
use crate::child::{recv_parent_msg, send_parent_msg};
use crate::clone;
use crate::intern::{init_replica, sweep_replica};
use crate::reqnot::{ReqNot, Requester as _};
pub struct SystemParams {
deps: Vec<SystemHandle>,
}
pub struct SystemCtor {
deps: Vec<String>,
make: Box<dyn FnMut(SystemParams) -> System>,
name: String,
prio: NotNan<f64>,
dependencies: Vec<String>,
}
pub struct ExtensionData {
systems: Vec<SystemCtor>
}

View File

@@ -0,0 +1 @@
pub mod entrypoint;

8
orchid-host/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "orchid-host"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

41
orchid-host/src/expr.rs Normal file
View File

@@ -0,0 +1,41 @@
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, RwLock};
use hashbrown::HashMap;
use orchid_api::expr::ExprTicket;
use crate::host::AtomHand;
use lazy_static::lazy_static;
#[derive(Clone, Debug)]
pub struct RtExpr {
is_canonical: Arc<AtomicBool>,
data: Arc<()>,
}
impl RtExpr {
pub fn as_atom(&self) -> Option<AtomHand> { todo!() }
pub fn strong_count(&self) -> usize { todo!() }
pub fn id(&self) -> u64 { self.data.as_ref() as *const () as usize as u64 }
pub fn canonicalize(&self) -> ExprTicket {
if !self.is_canonical.swap(true, Ordering::Relaxed) {
KNOWN_EXPRS.write().unwrap().entry(self.id()).or_insert_with(|| self.clone());
}
self.id()
}
pub fn resolve(tk: ExprTicket) -> Option<Self> { KNOWN_EXPRS.read().unwrap().get(&tk).cloned() }
}
impl Drop for RtExpr {
fn drop(&mut self) {
// If the only two references left are this and known, remove from known
if Arc::strong_count(&self.data) == 2 && self.is_canonical.load(Ordering::Relaxed) {
// if known is poisoned, a leak is preferable to a panicking destructor
if let Ok(mut w) = KNOWN_EXPRS.write() {
w.remove(&self.id());
}
}
}
}
lazy_static!{
static ref KNOWN_EXPRS: RwLock<HashMap<ExprTicket, RtExpr>> = RwLock::default();
}

234
orchid-host/src/lib.rs Normal file
View File

@@ -0,0 +1,234 @@
use std::io::Write as _;
use std::sync::atomic::{AtomicU16, AtomicU32, Ordering};
use std::sync::{Arc, Mutex, RwLock, Weak};
use std::{fmt, io, process, thread};
use derive_destructure::destructure;
use hashbrown::HashMap;
use itertools::Itertools;
use lazy_static::lazy_static;
use orchid_api::atom::{Atom, AtomDrop, AtomSame, CallRef, FinalCall, Fwd, Fwded};
use orchid_api::expr::{Acquire, Expr, ExprNotif, ExprTicket, Release, Relocate};
use orchid_api::intern::IntReq;
use orchid_api::proto::{
ExtHostNotif, ExtHostReq, ExtensionHeader, HostExtNotif, HostHeader, HostMsgSet,
};
use orchid_api::system::{NewSystem, SysDeclId, SysId, SystemDecl, SystemDrop};
use orchid_api::tree::{GetConstTree, TreeModule};
use orchid_api_traits::{Decode, Encode};
use ordered_float::NotNan;
use crate::clone;
use crate::expr::RtExpr;
use crate::intern::{deintern, intern};
use crate::reqnot::{ReqNot, Requester as _};
#[derive(Debug, destructure)]
pub struct AtomData {
owner: System,
drop: bool,
data: Vec<u8>,
}
impl AtomData {
fn api(self) -> Atom {
let (owner, drop, data) = self.destructure();
Atom { data, drop, owner: owner.0.id }
}
fn api_ref(&self) -> Atom {
Atom { data: self.data.clone(), drop: self.drop, owner: self.owner.0.id }
}
}
impl Drop for AtomData {
fn drop(&mut self) {
self.owner.0.ext.0.reqnot.notify(AtomDrop(Atom {
owner: self.owner.0.id,
data: self.data.clone(),
drop: true,
}))
}
}
#[derive(Clone, Debug)]
pub struct AtomHand(Arc<AtomData>);
impl AtomHand {
pub fn from_api(Atom { data, drop, owner }: Atom) -> Self {
let owner = System::resolve(owner).expect("Atom owned by non-existing system");
Self(Arc::new(AtomData { data, drop, owner }))
}
pub fn call(self, arg: RtExpr) -> Expr {
let owner_sys = self.0.owner.clone();
let ext = &owner_sys.0.ext;
let ticket = owner_sys.give_expr(arg.canonicalize(), || arg);
match Arc::try_unwrap(self.0) {
Ok(data) => ext.0.reqnot.request(FinalCall(data.api(), ticket)),
Err(hand) => ext.0.reqnot.request(CallRef(hand.api_ref(), ticket)),
}
}
pub fn same(&self, other: &AtomHand) -> bool {
let owner = self.0.owner.0.id;
if other.0.owner.0.id != owner {
return false;
}
self.0.owner.0.ext.0.reqnot.request(AtomSame(self.0.api_ref(), other.0.api_ref()))
}
pub fn req(&self, req: Vec<u8>) -> Vec<u8> {
self.0.owner.0.ext.0.reqnot.request(Fwded(self.0.api_ref(), req))
}
pub fn api_ref(&self) -> Atom { self.0.api_ref() }
}
/// Data held about an Extension. This is refcounted within [Extension]. It's
/// important to only ever access parts of this struct through the [Arc] because
/// the components reference each other through [Weak]s of it, and will panic if
/// upgrading fails.
#[derive(destructure)]
pub struct ExtensionData {
child: Mutex<process::Child>,
reqnot: ReqNot<HostMsgSet>,
systems: Vec<SystemCtor>,
}
impl Drop for ExtensionData {
fn drop(&mut self) { self.reqnot.notify(HostExtNotif::Exit) }
}
fn acq_expr(sys: SysId, extk: ExprTicket) {
(System::resolve(sys).expect("Expr acq'd by invalid system"))
.give_expr(extk, || RtExpr::resolve(extk).expect("Invalid expr acq'd"));
}
fn rel_expr(sys: SysId, extk: ExprTicket) {
let sys = System::resolve(sys).unwrap();
let mut exprs = sys.0.exprs.write().unwrap();
exprs.entry(extk).and_replace_entry_with(|_, (rc, rt)| {
(0 < rc.fetch_sub(1, Ordering::Relaxed)).then_some((rc, rt))
});
}
#[derive(Clone)]
pub struct Extension(Arc<ExtensionData>);
impl Extension {
pub fn new(mut cmd: process::Command) -> io::Result<Self> {
let mut child = cmd.stdin(process::Stdio::piped()).stdout(process::Stdio::piped()).spawn()?;
HostHeader.encode(child.stdin.as_mut().unwrap());
let eh = ExtensionHeader::decode(child.stdout.as_mut().unwrap());
Ok(Self(Arc::new_cyclic(|weak| ExtensionData {
child: Mutex::new(child),
reqnot: ReqNot::new(
clone!(weak; move |sfn, _| {
let arc: Arc<ExtensionData> = weak.upgrade().unwrap();
let mut g = arc.child.lock().unwrap();
g.stdin.as_mut().unwrap().write_all(sfn).unwrap();
}),
|notif, _| match notif {
ExtHostNotif::Expr(expr) => match expr {
ExprNotif::Acquire(Acquire(sys, extk)) => acq_expr(sys, extk),
ExprNotif::Release(Release(sys, extk)) => rel_expr(sys, extk),
ExprNotif::Relocate(Relocate { inc, dec, expr }) => {
acq_expr(inc, expr);
rel_expr(dec, expr);
},
},
},
|req| match req.req() {
ExtHostReq::Ping(ping) => req.handle(ping, &()),
ExtHostReq::IntReq(intreq) => match intreq {
IntReq::InternStr(s) => req.handle(s, &intern(&**s.0).marker()),
IntReq::InternStrv(v) => req.handle(v, &intern(&*v.0).marker()),
IntReq::ExternStr(si) => req.handle(si, &deintern(si.0).arc()),
IntReq::ExternStrv(vi) =>
req.handle(vi, &Arc::new(deintern(vi.0).iter().map(|t| t.marker()).collect_vec())),
},
ExtHostReq::Fwd(fw @ Fwd(atom, _body)) => {
let sys = System::resolve(atom.owner).unwrap();
thread::spawn(clone!(fw; move || {
req.handle(&fw, &sys.0.ext.0.reqnot.request(Fwded(fw.0.clone(), fw.1.clone())))
}));
},
_ => todo!(),
},
),
systems: eh.systems.into_iter().map(|decl| SystemCtor { decl, ext: weak.clone() }).collect(),
})))
}
pub fn systems(&self) -> impl Iterator<Item = &SystemCtor> { self.0.systems.iter() }
}
pub struct SystemCtor {
decl: SystemDecl,
ext: Weak<ExtensionData>,
}
impl SystemCtor {
pub fn name(&self) -> &str { &self.decl.name }
pub fn priority(&self) -> NotNan<f64> { self.decl.priority }
pub fn depends(&self) -> impl ExactSizeIterator<Item = &str> {
self.decl.depends.iter().map(|s| &**s)
}
pub fn run<'a>(&self, depends: impl IntoIterator<Item = &'a System>) -> System {
let mut inst_g = SYSTEM_INSTS.write().unwrap();
let depends = depends.into_iter().map(|si| si.0.id).collect_vec();
debug_assert_eq!(depends.len(), self.decl.depends.len(), "Wrong number of deps provided");
let ext = self.ext.upgrade().expect("SystemCtor should be freed before Extension");
static NEXT_ID: AtomicU16 = AtomicU16::new(0);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let () = ext.reqnot.request(NewSystem { depends, id, system: self.decl.id });
let data = System(Arc::new(SystemInstData {
decl_id: self.decl.id,
ext: Extension(ext),
exprs: RwLock::default(),
id,
}));
inst_g.insert(id, data.clone());
data
}
}
lazy_static! {
static ref SYSTEM_INSTS: RwLock<HashMap<u16, System>> = RwLock::default();
}
#[derive(destructure)]
pub struct SystemInstData {
exprs: RwLock<HashMap<ExprTicket, (AtomicU32, RtExpr)>>,
ext: Extension,
decl_id: SysDeclId,
id: u16,
}
impl Drop for SystemInstData {
fn drop(&mut self) {
self.ext.0.reqnot.notify(SystemDrop(self.id));
if let Ok(mut g) = SYSTEM_INSTS.write() {
g.remove(&self.id);
}
}
}
#[derive(Clone)]
pub struct System(Arc<SystemInstData>);
impl System {
fn resolve(id: u16) -> Option<System> { SYSTEM_INSTS.read().unwrap().get(&id).cloned() }
fn give_expr(&self, ticket: ExprTicket, get_expr: impl FnOnce() -> RtExpr) -> ExprTicket {
let mut exprs = self.0.exprs.write().unwrap();
exprs
.entry(ticket)
.and_modify(|(c, _)| {
c.fetch_add(1, Ordering::Relaxed);
})
.or_insert((AtomicU32::new(1), get_expr()));
ticket
}
pub fn const_tree(&self) -> TreeModule { self.0.ext.0.reqnot.request(GetConstTree(self.0.id)) }
}
impl fmt::Debug for System {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ctor = (self.0.ext.0.systems.iter().find(|c| c.decl.id == self.0.decl_id))
.expect("System instance with no associated constructor");
write!(f, "System({} @ {} #{}, ", ctor.decl.name, ctor.decl.priority, self.0.id)?;
match self.0.exprs.read() {
Err(_) => write!(f, "expressions unavailable"),
Ok(r) => {
let rc: u32 = r.values().map(|v| v.0.load(Ordering::Relaxed)).sum();
write!(f, "{rc} refs to {} exprs", r.len())
},
}
}
}

8
orchid-std/Cargo.toml Normal file
View File

@@ -0,0 +1,8 @@
[package]
name = "orchid-std"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

12
orchid-std/src/lib.rs Normal file
View File

@@ -0,0 +1,12 @@
pub fn add(left: usize, right: usize) -> usize { left + right }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

1
orchidlang/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
target

828
orchidlang/Cargo.lock generated Normal file
View File

@@ -0,0 +1,828 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "ahash"
version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "aho-corasick"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "anstream"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bound"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6021ae095f16f54aaae093f4c723700430e71eab731d3b0a07fc8fe258fd5110"
[[package]]
name = "bstr"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.50",
]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[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.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "dyn-clone"
version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d"
[[package]]
name = "either"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "intern-all"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20c9bf7d7b0572f7b4398fddc93ac1a200a92d1ba319a27dac04649b2223c0f6"
dependencies = [
"hashbrown",
"lazy_static",
"trait-set",
]
[[package]]
name = "itertools"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
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.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
[[package]]
name = "log"
version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]]
name = "memchr"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "never"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91"
[[package]]
name = "num-traits"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a"
dependencies = [
"autocfg",
]
[[package]]
name = "numtoa"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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.3.0"
dependencies = [
"bound",
"clap",
"const_format",
"dyn-clone",
"hashbrown",
"intern-all",
"itertools",
"never",
"once_cell",
"ordered-float",
"paste",
"rayon",
"rust-embed",
"substack",
"take_mut",
"termsize",
"trait-set",
"unicode-segmentation",
]
[[package]]
name = "ordered-float"
version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e"
dependencies = [
"num-traits",
]
[[package]]
name = "paste"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "proc-macro2"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rayon"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
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",
]
[[package]]
name = "redox_termios"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb"
[[package]]
name = "regex-automata"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rust-embed"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.50",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665"
dependencies = [
"globset",
"sha2",
"walkdir",
]
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "serde"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "strsim"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01"
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "take_mut"
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 = "trait-set"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
[[package]]
name = "windows_i686_gnu"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
[[package]]
name = "windows_i686_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
[[package]]
name = "zerocopy"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
]

41
orchidlang/Cargo.toml Normal file
View File

@@ -0,0 +1,41 @@
[package]
name = "orchidlang"
version = "0.3.0"
edition = "2021"
license = "GPL-3.0"
repository = "https://github.com/lbfalvy/orchid"
description = """
An embeddable pure functional scripting language
"""
authors = ["Lawrence Bethlenfalvy <lbfalvy@protonmail.com>"]
[lib]
path = "src/lib.rs"
[[bin]]
name = "orcx"
path = "src/bin/orcx.rs"
doc = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
hashbrown = "0.14"
ordered-float = "4.2"
itertools = "0.12"
dyn-clone = "1.0"
trait-set = "0.3"
paste = "1.0"
rust-embed = { version = "8.2", features = ["include-exclude"] }
take_mut = "0.2"
unicode-segmentation = "1.11"
never = "0.1"
substack = "1.1"
intern-all = "0.4.1"
once_cell = "1.19"
const_format = "0.2"
bound = "0.5"
# Dependencies of orcx
clap = { version = "4.5", features = ["derive"] }
rayon = "1.8"
termsize = "0.1"

View File

@@ -94,7 +94,7 @@ fn exprv_rec(
}};
let v_end = match v.back() {
None => return expr_rec(last, ctx),
Some(penultimate) => penultimate.range.range.end,
Some(penultimate) => penultimate.range.end(),
};
let f = exprv_rec(v, ctx.clone(), location.map_range(|r| r.start..v_end))?;
let x = expr_rec(last, ctx.clone())?;

Some files were not shown because too many files have changed in this diff Show More