diff --git a/Cargo.lock b/Cargo.lock index 6efbf84..fc42f65 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 0b9caac..4103f50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 "] +[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", +] diff --git a/orchid-api-derive/Cargo.toml b/orchid-api-derive/Cargo.toml new file mode 100644 index 0000000..1598721 --- /dev/null +++ b/orchid-api-derive/Cargo.toml @@ -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" diff --git a/orchid-api-derive/src/common.rs b/orchid-api-derive/src/common.rs new file mode 100644 index 0000000..8cf3c7f --- /dev/null +++ b/orchid-api-derive/src/common.rs @@ -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 { + 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() +} diff --git a/orchid-api-derive/src/decode.rs b/orchid-api-derive/src/decode.rs new file mode 100644 index 0000000..068bc1f --- /dev/null +++ b/orchid-api-derive/src/decode.rs @@ -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(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 ::decode(read) { + #(#opts)* + x => panic!("Unrecognized enum kind {x}") + } + } + }, + } +} diff --git a/orchid-api-derive/src/encode.rs b/orchid-api-derive/src/encode.rs new file mode 100644 index 0000000..5f408e8 --- /dev/null +++ b/orchid-api-derive/src/encode.rs @@ -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(&self, write: &mut W) { #encode } + } + }; + TokenStream::from(expanded) +} + +fn encode_body(data: &syn::Data) -> Option { + 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(names: impl Iterator) -> pm2::TokenStream { + quote! { #( #names .encode(write); )* } +} + +fn encode_items(fields: &syn::Fields) -> Option { + 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())))), + } +} + diff --git a/orchid-api-derive/src/hierarchy.rs b/orchid-api-derive/src/hierarchy.rs new file mode 100644 index 0000000..5b1c90b --- /dev/null +++ b/orchid-api-derive/src/hierarchy.rs @@ -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[..], "e!(#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 { + 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> { + 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)], "e!(BogusReq))) +} diff --git a/orchid-api-derive/src/lib.rs b/orchid-api-derive/src/lib.rs new file mode 100644 index 0000000..2a8f538 --- /dev/null +++ b/orchid-api-derive/src/lib.rs @@ -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() +} diff --git a/orchid-api-traits/Cargo.toml b/orchid-api-traits/Cargo.toml new file mode 100644 index 0000000..41ef46c --- /dev/null +++ b/orchid-api-traits/Cargo.toml @@ -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" diff --git a/orchid-api-traits/src/coding.rs b/orchid-api-traits/src/coding.rs new file mode 100644 index 0000000..6e2c2b5 --- /dev/null +++ b/orchid-api-traits/src/coding.rs @@ -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(read: &mut R) -> Self; +} +pub trait Encode { + /// Append an instance of the struct to the buffer + fn encode(&self, write: &mut W); + fn enc_vec(&self) -> Vec { + let mut vec = Vec::new(); + self.encode(&mut vec); + vec + } +} +pub trait Coding: Encode + Decode {} +impl Coding for T {} + +macro_rules! num_impl { + ($number:ty, $size:expr) => { + impl Decode for $number { + fn decode(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(&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(read: &mut R) -> Self { Self::new(Decode::decode(read)).unwrap() } + } + impl Encode for $name { + fn encode(&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(&self, write: &mut W) { (**self).encode(write) } +} +impl Decode for NotNan { + fn decode(read: &mut R) -> Self { NotNan::new(T::decode(read)).expect("Float was NaN") } +} +impl Encode for NotNan { + fn encode(&self, write: &mut W) { self.as_ref().encode(write) } +} +impl Decode for String { + fn decode(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(&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(&self, write: &mut W) { + u64::try_from(self.len()).unwrap().encode(write); + write.write_all(self.as_bytes()).unwrap() + } +} +impl Decode for Vec { + fn decode(read: &mut R) -> Self { + let len = u64::decode(read).try_into().unwrap(); + iter::repeat_with(|| T::decode(read)).take(len).collect() + } +} +impl Encode for Vec { + fn encode(&self, write: &mut W) { + u64::try_from(self.len()).unwrap().encode(write); + self.iter().for_each(|t| t.encode(write)); + } +} +impl Encode for [T] { + fn encode(&self, write: &mut W) { + u64::try_from(self.len()).unwrap().encode(write); + self.iter().for_each(|t| t.encode(write)); + } +} +impl Decode for Option { + fn decode(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 Encode for Option { + fn encode(&self, write: &mut W) { + let t = if let Some(t) = self { t } else { return 0u8.encode(write) }; + 1u8.encode(write); + t.encode(write); + } +} +impl Decode for Result { + fn decode(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 Encode for Result { + fn encode(&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 Decode for HashMap { + fn decode(read: &mut R) -> Self { + let len = u64::decode(read).try_into().unwrap(); + iter::repeat_with(|| <(K, V)>::decode(read)).take(len).collect() + } +} +impl Encode for HashMap { + fn encode(&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(read: &mut R) -> Self { ($($T::decode(read),)*) } + } + impl<$($T: Encode),*> Encode for ($($T,)*) { + fn encode(&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(_: &mut R) -> Self {} +} +impl Encode for () { + fn encode(&self, _: &mut W) {} +} +impl Decode for bool { + fn decode(read: &mut R) -> Self { + let mut buf = [0]; + read.read_exact(&mut buf).unwrap(); + buf[0] != 0 + } +} +impl Encode for bool { + fn encode(&self, write: &mut W) { + write.write_all(&[if *self { 0xff } else { 0 }]).unwrap() + } +} +impl Decode for [T; N] { + fn decode(read: &mut R) -> Self { + // TODO: figure out how to do this in safe rust on the stack + ((0..N).map(|_| T::decode(read)).collect::>().try_into()) + .unwrap_or_else(|_| unreachable!("The length of this iterator is statically known")) + } +} +impl Encode for [T; N] { + fn encode(&self, write: &mut W) { self.iter().for_each(|t| t.encode(write)) } +} +impl Decode for Range { + fn decode(read: &mut R) -> Self { T::decode(read)..T::decode(read) } +} +impl Encode for Range { + fn encode(&self, write: &mut W) { + self.start.encode(write); + self.end.encode(write); + } +} + +macro_rules! smart_ptr { + ($name:tt) => { + impl Decode for $name { + fn decode(read: &mut R) -> Self { $name::new(T::decode(read)) } + } + impl Encode for $name { + fn encode(&self, write: &mut W) { (**self).encode(write) } + } + }; +} + +smart_ptr!(Arc); +smart_ptr!(Rc); +smart_ptr!(Box); + +impl Decode for char { + fn decode(read: &mut R) -> Self { char::from_u32(u32::decode(read)).unwrap() } +} +impl Encode for char { + fn encode(&self, write: &mut W) { (*self as u32).encode(write) } +} diff --git a/orchid-api-traits/src/helpers.rs b/orchid-api-traits/src/helpers.rs new file mode 100644 index 0000000..11722bd --- /dev/null +++ b/orchid-api-traits/src/helpers.rs @@ -0,0 +1,18 @@ +use std::io::{Read, Write}; + +use crate::Encode; + +pub fn encode_enum(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") +} diff --git a/orchid-api-traits/src/hier2.rs b/orchid-api-traits/src/hier2.rs new file mode 100644 index 0000000..8188100 --- /dev/null +++ b/orchid-api-traits/src/hier2.rs @@ -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 { + type Parent: TreeRolePicker; +} + +pub trait Inherits {} + +// impl Inherits for T {} +impl Inherits for This where This: Inherits {} diff --git a/orchid-api-traits/src/hierarchy.rs b/orchid-api-traits/src/hierarchy.rs new file mode 100644 index 0000000..b749e02 --- /dev/null +++ b/orchid-api-traits/src/hierarchy.rs @@ -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 + Into { + /// Specify the immediate parent of this type. This guides the + type Parent: InHierarchy + + TryInto + + UnderRootImpl<::IsRoot>; +} + +pub trait UnderRootImpl: Sized { + type __Root: UnderRoot; + fn __into_root(self) -> Self::__Root; + fn __try_from_root(root: Self::__Root) -> Result; +} + +pub trait UnderRoot: InHierarchy { + type Root: UnderRoot; + fn into_root(self) -> Self::Root; + fn try_from_root(root: Self::Root) -> Result; +} + +impl> UnderRoot for T { + type Root = ::IsRoot>>::__Root; + fn into_root(self) -> Self::Root { self.__into_root() } + fn try_from_root(root: Self::Root) -> Result { Self::__try_from_root(root) } +} + +impl> UnderRootImpl for T { + type __Root = Self; + fn __into_root(self) -> Self::__Root { self } + fn __try_from_root(root: Self::__Root) -> Result { Ok(root) } +} + +impl + Extends> UnderRootImpl for T { + type __Root = <::Parent as UnderRootImpl< + <::Parent as InHierarchy>::IsRoot, + >>::__Root; + fn __into_root(self) -> Self::__Root { + ::Parent>>::into(self).into_root() + } + fn __try_from_root(root: Self::__Root) -> Result { + let parent = ::Parent::try_from_root(root)?; + parent.clone().try_into().map_err(|_| parent.into_root()) + } +} diff --git a/orchid-api-traits/src/lib.rs b/orchid-api-traits/src/lib.rs new file mode 100644 index 0000000..7b7d539 --- /dev/null +++ b/orchid-api-traits/src/lib.rs @@ -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}; diff --git a/orchid-api-traits/src/relations.rs b/orchid-api-traits/src/relations.rs new file mode 100644 index 0000000..b50595f --- /dev/null +++ b/orchid-api-traits/src/relations.rs @@ -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 { 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; +} diff --git a/orchid-api/Cargo.toml b/orchid-api/Cargo.toml new file mode 100644 index 0000000..7484e92 --- /dev/null +++ b/orchid-api/Cargo.toml @@ -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" diff --git a/orchid-api/src/atom.rs b/orchid-api/src/atom.rs new file mode 100644 index 0000000..1345d9b --- /dev/null +++ b/orchid-api/src/atom.rs @@ -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; + +/// 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); +impl Request for Fwded { + type Response = Vec; +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] +#[extends(ExtHostReq)] +pub struct Fwd(pub Atom, pub Vec); +impl Request for Fwd { + type Response = Vec; +} + +/// 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), +} diff --git a/orchid-api/src/error.rs b/orchid-api/src/error.rs new file mode 100644 index 0000000..4b90491 --- /dev/null +++ b/orchid-api/src/error.rs @@ -0,0 +1 @@ +pub type ProjErrId = u16; diff --git a/orchid-api/src/expr.rs b/orchid-api/src/expr.rs new file mode 100644 index 0000000..4055d55 --- /dev/null +++ b/orchid-api/src/expr.rs @@ -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, Box), + /// Lambda function. The number operates as an argument name + Lambda(TStr, Box), + /// 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, Box), + /// 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), +} diff --git a/orchid-api/src/fs.rs b/orchid-api/src/fs.rs new file mode 100644 index 0000000..748c687 --- /dev/null +++ b/orchid-api/src/fs.rs @@ -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), +} + +#[derive(Clone, Debug, Coding)] +pub struct FsRead(pub Vec); +impl Request for FsRead { + type Response = Result; +} diff --git a/orchid-api/src/intern.rs b/orchid-api/src/intern.rs new file mode 100644 index 0000000..4e0dc26 --- /dev/null +++ b/orchid-api/src/intern.rs @@ -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); +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; +} +/// 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>); +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>; +} + +/// 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, + pub vecs: Vec, +} diff --git a/orchid-api/src/lib.rs b/orchid-api/src/lib.rs new file mode 100644 index 0000000..21a19f5 --- /dev/null +++ b/orchid-api/src/lib.rs @@ -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; diff --git a/orchid-api/src/location.rs b/orchid-api/src/location.rs new file mode 100644 index 0000000..21731f3 --- /dev/null +++ b/orchid-api/src/location.rs @@ -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, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Coding)] +pub struct CodeGenInfo { + pub generator: String, + pub details: String, +} diff --git a/orchid-api/src/parser.rs b/orchid-api/src/parser.rs new file mode 100644 index 0000000..fbff40a --- /dev/null +++ b/orchid-api/src/parser.rs @@ -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; +} + +#[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 { + +} diff --git a/orchid-api/src/proto.rs b/orchid-api/src/proto.rs new file mode 100644 index 0000000..236d7e4 --- /dev/null +++ b/orchid-api/src/proto.rs @@ -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(read: &mut R) -> Self { + read_exact(read, HOST_INTRO); + Self + } +} +impl Encode for HostHeader { + fn encode(&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, +} +impl Decode for ExtensionHeader { + fn decode(read: &mut R) -> Self { + read_exact(read, EXT_INTRO); + Self { systems: Vec::decode(read) } + } +} +impl Encode for ExtensionHeader { + fn encode(&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; +} diff --git a/orchid-api/src/system.rs b/orchid-api/src/system.rs new file mode 100644 index 0000000..b29e1d1 --- /dev/null +++ b/orchid-api/src/system.rs @@ -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, + /// 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, +} + +/// 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, +} +impl Request for NewSystem { + type Response = (); +} + +#[derive(Clone, Debug, Coding, Hierarchy)] +#[extends(HostExtNotif)] +pub struct SystemDrop(pub SysId); diff --git a/orchid-api/src/tree.rs b/orchid-api/src/tree.rs new file mode 100644 index 0000000..cc7d866 --- /dev/null +++ b/orchid-api/src/tree.rs @@ -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), + Name(Vec), + S(Paren, Vec), + /// 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, + pub priority: NotNan, + pub template: Vec, +} + +#[derive(Clone, Debug, Coding)] +pub enum Tree { + Const(Expr), + Mod(TreeModule), + Rule(MacroRule), +} + +#[derive(Clone, Debug, Coding)] +pub struct TreeModule { + pub children: HashMap, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Coding, Hierarchy)] +#[extends(HostExtReq)] +pub struct GetConstTree(pub SysId); +impl Request for GetConstTree { + type Response = TreeModule; +} diff --git a/orchid-base/Cargo.toml b/orchid-base/Cargo.toml new file mode 100644 index 0000000..79a1440 --- /dev/null +++ b/orchid-base/Cargo.toml @@ -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" diff --git a/orchid-base/src/api_utils.rs b/orchid-base/src/api_utils.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/utils/boxed_iter.rs b/orchid-base/src/boxed_iter.rs similarity index 100% rename from src/utils/boxed_iter.rs rename to orchid-base/src/boxed_iter.rs diff --git a/orchid-base/src/child.rs b/orchid-base/src/child.rs new file mode 100644 index 0000000..319915c --- /dev/null +++ b/orchid-base/src/child.rs @@ -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, + stdout: Mutex, +} +impl SharedChild { + pub fn new(cmd: &mut process::Command) -> io::Result { + 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> { 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> { + 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> { recv_msg(&mut io::stdin().lock()) } diff --git a/orchid-base/src/clone.rs b/orchid-base/src/clone.rs new file mode 100644 index 0000000..0eb593d --- /dev/null +++ b/orchid-base/src/clone.rs @@ -0,0 +1,9 @@ +#[macro_export] +macro_rules! clone { + ($($n:ident),+; $body:expr) => ( + { + $( let $n = $n.clone(); )+ + $body + } + ); +} diff --git a/src/utils/combine.rs b/orchid-base/src/combine.rs similarity index 100% rename from src/utils/combine.rs rename to orchid-base/src/combine.rs diff --git a/orchid-base/src/event.rs b/orchid-base/src/event.rs new file mode 100644 index 0000000..6df8a8f --- /dev/null +++ b/orchid-base/src/event.rs @@ -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 { + resub: bool, + outcome: Result, +} + +struct Listener { + sink: mpsc::SyncSender, + source: mpsc::Receiver>, +} + +pub struct Event { + listeners: Mutex>>, +} +impl Event { + pub const fn new() -> Self { Self { listeners: Mutex::new(Vec::new()) } } + + pub fn dispatch(&self, mut ev: T) -> Option { + 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(&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(); + } + } +} diff --git a/orchid-base/src/gen/impls.rs b/orchid-base/src/gen/impls.rs new file mode 100644 index 0000000..9793130 --- /dev/null +++ b/orchid-base/src/gen/impls.rs @@ -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(x: In) -> Result { + if TypeId::of::() != TypeId::of::() { + 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(&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) -> 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)))) + } +} diff --git a/src/gen/mod.rs b/orchid-base/src/gen/mod.rs similarity index 95% rename from src/gen/mod.rs rename to orchid-base/src/gen/mod.rs index ddff714..cfa6d6b 100644 --- a/src/gen/mod.rs +++ b/orchid-base/src/gen/mod.rs @@ -5,3 +5,4 @@ pub mod tpl; pub mod traits; pub mod tree; +pub mod impls; diff --git a/src/gen/tpl.rs b/orchid-base/src/gen/tpl.rs similarity index 77% rename from src/gen/tpl.rs rename to orchid-base/src/gen/tpl.rs index 6cf7d81..69c362b 100644 --- a/src/gen/tpl.rs +++ b/orchid-base/src/gen/tpl.rs @@ -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(pub A); -impl GenClause for V { +/// A trivial atom +#[derive(Clone, Debug)] +pub struct SysAtom(pub AtomHand); +impl GenClause for SysAtom { fn generate(&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(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T { - T::atom(ctx, self.0.run()) + T::atom(ctx, self.0.clone()) } } diff --git a/src/gen/traits.rs b/orchid-base/src/gen/traits.rs similarity index 88% rename from src/gen/traits.rs rename to orchid-base/src/gen/traits.rs index 12a1028..bbb33c7 100644 --- a/src/gen/traits.rs +++ b/orchid-base/src/gen/traits.rs @@ -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) -> 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; diff --git a/orchid-base/src/gen/tree.rs b/orchid-base/src/gen/tree.rs new file mode 100644 index 0000000..a2aa3cd --- /dev/null +++ b/orchid-base/src/gen/tree.rs @@ -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 + DynClone + Send; +} + +/// A leaf in the [ConstTree] +pub struct GenConst(Box); +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 { 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; + +/// 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>( + 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>(key: K, [atom]: [AtomHand; 1]) -> (K, ConstTree) { + (key, atom_leaf(atom)) +} + +/// Errors produced duriung the merger of constant trees +pub type ConstCombineErr = TreeConflict; diff --git a/src/interpreter/handler.rs b/orchid-base/src/handler.rs similarity index 100% rename from src/interpreter/handler.rs rename to orchid-base/src/handler.rs diff --git a/orchid-base/src/intern.rs b/orchid-base/src/intern.rs new file mode 100644 index 0000000..052bc2a --- /dev/null +++ b/orchid-base/src/intern.rs @@ -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 { + data: Arc, + marker: T::Marker, +} +impl Token { + pub fn marker(&self) -> T::Marker { self.marker } + pub fn arc(&self) -> Arc { self.data.clone() } +} +impl Deref for Token { + type Target = T; + + fn deref(&self) -> &Self::Target { self.data.as_ref() } +} +impl Ord for Token { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.marker().cmp(&other.marker()) } +} +impl PartialOrd for Token { + fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } +} +impl Eq for Token {} +impl PartialEq for Token { + fn eq(&self, other: &Self) -> bool { self.cmp(other).is_eq() } +} +impl hash::Hash for Token { + fn hash(&self, state: &mut H) { self.marker().hash(state) } +} +impl fmt::Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", &*self.data) + } +} +impl fmt::Debug for Token { + 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; + fn intern(self: Arc, req: &(impl DynRequester + ?Sized)) + -> Self::Marker; + fn bimap(interner: &mut Interner) -> &mut Bimap; +} + +pub trait Internable { + type Interned: Interned; + fn get_owned(&self) -> Arc; +} + +pub trait InternMarker: Copy + PartialEq + Eq + PartialOrd + Ord + hash::Hash { + type Interned: Interned; + fn resolve(self, req: &(impl DynRequester + ?Sized)) -> Token; + fn get_id(self) -> NonZeroU64; + fn from_id(id: NonZeroU64) -> Self; +} + +impl Interned for String { + type Marker = TStr; + fn intern( + self: Arc, + req: &(impl DynRequester + ?Sized), + ) -> Self::Marker { + req.request(InternStr(self)) + } + fn bimap(interner: &mut Interner) -> &mut Bimap { &mut interner.strings } +} + +impl InternMarker for TStr { + type Interned = String; + fn resolve(self, req: &(impl DynRequester + ?Sized)) -> Token { + 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 { Arc::new(self.to_string()) } +} + +impl Internable for String { + type Interned = String; + fn get_owned(&self) -> Arc { Arc::new(self.to_string()) } +} + +impl Interned for Vec> { + type Marker = TStrv; + fn intern( + self: Arc, + req: &(impl DynRequester + ?Sized), + ) -> Self::Marker { + req.request(InternStrv(Arc::new(self.iter().map(|t| t.marker()).collect()))) + } + fn bimap(interner: &mut Interner) -> &mut Bimap { &mut interner.vecs } +} + +impl InternMarker for TStrv { + type Interned = Vec>; + fn resolve(self, req: &(impl DynRequester + ?Sized)) -> Token { + 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] { + type Interned = Vec>; + fn get_owned(&self) -> Arc { Arc::new(self.to_vec()) } +} + +impl Internable for Vec> { + type Interned = Vec>; + fn get_owned(&self) -> Arc { Arc::new(self.to_vec()) } +} + +impl Internable for Vec { + type Interned = Vec>; + fn get_owned(&self) -> Arc { + Arc::new(self.iter().map(|ts| deintern(*ts)).collect()) + } +} + +impl Internable for [TStr] { + type Interned = Vec>; + fn get_owned(&self) -> Arc { + 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 { + intern: HashMap, Token>, + by_id: HashMap>, +} +impl Bimap { + pub fn insert(&mut self, token: Token) { + self.intern.insert(token.data.clone(), token.clone()); + self.by_id.insert(token.marker(), token); + } + + pub fn by_marker(&self, marker: T::Marker) -> Option> { + self.by_id.get(&marker).cloned() + } + + pub fn by_value(&self, q: &Q) -> Option> + where T: Borrow { + (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 { + (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) { + self.intern.retain(|k, v| BASE_RC < Arc::strong_count(k) || retained.contains(&v.marker())) + } +} + +impl Default for Bimap { + fn default() -> Self { Self { by_id: HashMap::new(), intern: HashMap::new() } } +} + +pub trait UpComm { + fn up(&self, req: R) -> R::Response; +} + +#[derive(Default)] +pub struct Interner { + strings: Bimap, + vecs: Bimap>>, + master: Option>>, +} + +static ID: atomic::AtomicU64 = atomic::AtomicU64::new(1); +static INTERNER: Mutex> = Mutex::new(None); + +pub fn interner() -> impl DerefMut { + struct G(MutexGuard<'static, Option>); + 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 + '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: &(impl Internable + ?Sized)) -> Token { + 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(marker: M) -> Token { + 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 = intern!(str: "foo"); + let _: Token>> = intern!([Token]: &[ + intern!(str: "bar"), + intern!(str: "baz") + ]); +} diff --git a/src/utils/join.rs b/orchid-base/src/join.rs similarity index 100% rename from src/utils/join.rs rename to orchid-base/src/join.rs diff --git a/orchid-base/src/lib.rs b/orchid-base/src/lib.rs new file mode 100644 index 0000000..3765909 --- /dev/null +++ b/orchid-base/src/lib.rs @@ -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; diff --git a/src/location.rs b/orchid-base/src/location.rs similarity index 92% rename from src/location.rs rename to orchid-base/src/location.rs index dea74cf..e2a01f2 100644 --- a/src/location.rs +++ b/orchid-base/src/location.rs @@ -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, } impl SourceRange { + /// Create a new instance. + pub fn new(range: Range, 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 { 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) -> Range) -> Self { - Self { code: self.code(), range: map(self.range()) } + Self::new(map(self.range()), self.code()) } } impl fmt::Debug for SourceRange { diff --git a/orchid-base/src/name.rs b/orchid-base/src/name.rs new file mode 100644 index 0000000..07dea34 --- /dev/null +++ b/orchid-base/src/name.rs @@ -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> + 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]); +impl PathSlice { + /// Create a new [PathSlice] + pub fn new(slice: &[Token]) -> &PathSlice { + // SAFETY: This is ok because PathSlice is #[repr(transparent)] + unsafe { &*(slice as *const [Token] 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 { + 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] { 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]> for PathSlice { + fn borrow(&self) -> &[Token] { &self.0 } +} +impl<'a> IntoIterator for &'a PathSlice { + type IntoIter = Cloned>>; + type Item = Token; + 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 for PathSlice { + type Output = Token; + 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); + impl_range_index_for_pathslice!(ops::RangeTo); + impl_range_index_for_pathslice!(ops::Range); + impl_range_index_for_pathslice!(ops::RangeInclusive); + impl_range_index_for_pathslice!(ops::RangeToInclusive); +} + +impl Deref for PathSlice { + type Target = [Token]; + + fn deref(&self) -> &Self::Target { &self.0 } +} +impl Borrow for [Token] { + fn borrow(&self) -> &PathSlice { PathSlice::new(self) } +} +impl Borrow for [Token; N] { + fn borrow(&self) -> &PathSlice { PathSlice::new(&self[..]) } +} +impl Borrow for Vec> { + 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>); +impl VPath { + /// Collect segments into a vector + pub fn new(items: impl IntoIterator>) -> 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>) -> Self { + Self(items.into_iter().chain(self.0).collect()) + } + /// Append some tokens to the path + pub fn suffix(self, items: impl IntoIterator>) -> 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 { + Box::new(self.0.iter().map(|s| s.as_str())) + } + /// Try to convert into non-empty version + pub fn into_name(self) -> Result { 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) -> 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) -> 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::>().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> for VPath { + fn from_iter>>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} +impl IntoIterator for VPath { + type Item = Token; + type IntoIter = vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} +impl Borrow<[Token]> for VPath { + fn borrow(&self) -> &[Token] { self.0.borrow() } +} +impl Borrow 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 Index for VPath +where PathSlice: Index +{ + type Output = >::Output; + + fn index(&self, index: T) -> &Self::Output { &Borrow::::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>); +impl VName { + /// Assert that the sequence isn't empty and wrap it in [VName] to represent + /// this invariant + pub fn new(items: impl IntoIterator>) -> Result { + 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> { self.0 } + /// Get a reference to the enclosed vector + pub fn vec(&self) -> &Vec> { &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> { &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> { 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>) -> 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>) -> Self { + Self(self.0.into_iter().chain(items).collect()) + } + /// Read a `::` separated namespaced name + pub fn parse(s: &str) -> Result { Self::new(VPath::parse(s)) } + /// Obtain an iterator over the segments of the name + pub fn iter(&self) -> impl Iterator> + '_ { 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; + type IntoIter = vec::IntoIter; + fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } +} +impl Index for VName +where PathSlice: Index +{ + type Output = >::Output; + + fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } +} +impl Borrow<[Token]> for VName { + fn borrow(&self) -> &[Token] { self.0.borrow() } +} +impl Borrow 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]> for VName { + type Error = EmptyNameError; + fn try_from(value: &[Token]) -> Result { + 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>>); +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>) -> Result { + let items = v.into_iter().collect_vec(); + Self::from_tok(intern(&items[..])) + } + /// Read a `::` separated namespaced name. + pub fn parse(s: &str) -> Result { + 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>>) -> Result { + if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) } + } + /// Grab the interner token + pub fn tok(&self) -> Token>> { 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 Index for Sym +where PathSlice: Index +{ + type Output = >::Output; + + fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } +} +impl Borrow<[Token]> for Sym { + fn borrow(&self) -> &[Token] { &self.0[..] } +} +impl Borrow 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 +{ + /// Convert into held slice + fn as_slice(&self) -> &[Token] { Borrow::::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 + '_ { + self.as_slice().iter().map(|t| t.as_str()) + } + /// Fully resolve the name for printing + #[must_use] + fn to_strv(&self) -> Vec { 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, &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, &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 { self.split_first().0 } + /// Get the last element + fn last(&self) -> Token { 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]: &[ + $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] = myname.borrow(); + let _borrowed_pathslice: &PathSlice = myname.borrow(); + let _deref_pathslice: &PathSlice = &myname; + let _as_slice_out: &[Token] = 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")]) + ); + } +} diff --git a/src/interpreter/nort.rs b/orchid-base/src/nort.rs similarity index 100% rename from src/interpreter/nort.rs rename to orchid-base/src/nort.rs diff --git a/src/foreign/atom.rs b/orchid-base/src/old-atom.rs similarity index 100% rename from src/foreign/atom.rs rename to orchid-base/src/old-atom.rs diff --git a/src/error.rs b/orchid-base/src/proj_error.rs similarity index 99% rename from src/error.rs rename to orchid-base/src/proj_error.rs index 30f952f..e7ed23e 100644 --- a/src/error.rs +++ b/orchid-base/src/proj_error.rs @@ -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; diff --git a/orchid-base/src/reqnot.rs b/orchid-base/src/reqnot.rs new file mode 100644 index 0000000..ff360a7 --- /dev/null +++ b/orchid-base/src/reqnot.rs @@ -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 = for<'a> FnMut(&'a [u8], ReqNot) + DynClone + Send + 'static; + pub trait ReqFn = FnMut(RequestHandle) + Send + 'static; + pub trait NotifFn = for<'a> FnMut(T::InNot, ReqNot) + 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 { + id: u64, + message: T::InReq, + send: Box>, + parent: ReqNot, + fulfilled: AtomicBool, +} +impl RequestHandle { + pub fn reqnot(&self) -> ReqNot { 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(&self, _: &T, rep: &T::Response) { self.respond(rep) } +} +impl Drop for RequestHandle { + fn drop(&mut self) { + debug_assert!(self.fulfilled.load(Ordering::Relaxed), "Request dropped without response") + } +} + +pub fn respond_with(r: &R, f: impl FnOnce(&R) -> R::Response) -> Vec { + r.respond(f(r)) +} + +pub struct ReqNotData { + id: u64, + send: Box>, + notif: Box>, + req: Box>, + responses: HashMap>>, +} + +pub struct RawReply(Vec); +impl Deref for RawReply { + type Target = [u8]; + fn deref(&self) -> &Self::Target { get_id(&self.0[..]).1 } +} + +pub struct ReqNot(Arc>>); +impl ReqNot { + pub fn new(send: impl SendFn, notif: impl NotifFn, req: impl ReqFn) -> 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) { + 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>(&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 RawReply + Send + Sync + 'a>); +impl<'a, T> MappedRequester<'a, T> { + fn new(req: U) -> Self + where T: Into { + 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 DynRequester for ReqNot { + 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>(&self, data: R) -> R::Response; + fn map<'a, U: Into>(self) -> MappedRequester<'a, U> + where Self: Sized + 'a { + MappedRequester::new(self) + } +} +impl<'a, This: DynRequester + ?Sized + 'a> Requester for This { + fn request>(&self, data: R) -> R::Response { + R::Response::decode(&mut &self.raw_request(data.into())[..]) + } +} + +impl Clone for ReqNot { + 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::::new( + |_, _| panic!("Should not send anything"), + clone!(received; move |notif, _| *received.lock().unwrap() = Some(notif)), + |_| panic!("Not receiving a request"), + ); + let sender = ReqNot::::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::>>::new(None)); + let sender = Arc::new(ReqNot::::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); + } +} diff --git a/src/foreign/error.rs b/orchid-base/src/rt_error.rs similarity index 100% rename from src/foreign/error.rs rename to orchid-base/src/rt_error.rs diff --git a/src/interpreter/context.rs b/orchid-base/src/run_ctx.rs similarity index 100% rename from src/interpreter/context.rs rename to orchid-base/src/run_ctx.rs diff --git a/src/utils/sequence.rs b/orchid-base/src/sequence.rs similarity index 100% rename from src/utils/sequence.rs rename to orchid-base/src/sequence.rs diff --git a/src/facade/system.rs b/orchid-base/src/system.rs similarity index 98% rename from src/facade/system.rs rename to orchid-base/src/system.rs index 60143e9..5da089a 100644 --- a/src/facade/system.rs +++ b/orchid-base/src/system.rs @@ -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, referrer: VName, diff --git a/orchid-base/src/tree.rs b/orchid-base/src/tree.rs new file mode 100644 index 0000000..98093ee --- /dev/null +++ b/orchid-base/src/tree.rs @@ -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: TreeTransforms; + + /// Implementation for [TreeTransforms::map_data] + fn map_data_rec( + self, + item: &mut impl FnMut(Substack>, Self::Item) -> T, + module: &mut impl FnMut(Substack>, Self::XMod) -> U, + entry: &mut impl FnMut(Substack>, Self::XEnt) -> V, + path: Substack>, + ) -> Self::SelfType; + + /// Transform all the data in the tree without changing its structure + fn map_data( + self, + mut item: impl FnMut(Substack>, Self::Item) -> T, + mut module: impl FnMut(Substack>, Self::XMod) -> U, + mut entry: impl FnMut(Substack>, Self::XEnt) -> V, + ) -> Self::SelfType { + 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>`] - 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>, + ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, + T, + ) -> T, + ) -> T { + let res = + self.search(init, |stack, member, state| Ok::(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>`] - 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>, + ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, + T, + ) -> Result, + ) -> Result { + 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>, + callback: &mut impl FnMut( + Substack>, + ModMemberRef<'a, Self::Item, Self::XMod, Self::XEnt>, + T, + ) -> Result, + ) -> Result; +} + +/// The member in a [ModEntry] which is associated with a name in a [Module] +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ModMember { + /// Arbitrary data + Item(Item), + /// A child module + Sub(Module), +} + +impl TreeTransforms for ModMember { + type Item = Item; + type XEnt = XEnt; + type XMod = XMod; + type SelfType = ModMember; + + fn map_data_rec( + self, + item: &mut impl FnMut(Substack>, Item) -> T, + module: &mut impl FnMut(Substack>, XMod) -> U, + entry: &mut impl FnMut(Substack>, XEnt) -> V, + path: Substack>, + ) -> Self::SelfType { + 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>, + callback: &mut impl FnMut( + Substack>, + ModMemberRef<'a, Item, XMod, XEnt>, + T, + ) -> Result, + ) -> Result { + 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 { + /// 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 $target + for $for + where + Item::Error: $($deps)*, + XMod::Error: $($deps)*, + XEnt::Error: $($deps)*, + $body + }; +} + +impl_for_conflict!(Clone, (Clone), ConflictKind, { + 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, { + 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 { + /// Which subtree caused the failure + pub path: VPath, + /// What type of failure occurred + pub kind: ConflictKind, +} +impl TreeConflict { + fn new(kind: ConflictKind) -> Self { Self { path: VPath::new([]), kind } } + + fn push(self, seg: Token) -> Self { + Self { path: self.path.prefix([seg]), kind: self.kind } + } +} + +impl_for_conflict!(Clone, (Clone), TreeConflict, { + fn clone(&self) -> Self { + Self { path: self.path.clone(), kind: self.kind.clone() } + } +}); + +impl_for_conflict!(fmt::Debug, (fmt::Debug), TreeConflict, { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TreeConflict") + .field("path", &self.path) + .field("kind", &self.kind) + .finish() + } +}); + +impl Combine for ModMember { + type Error = TreeConflict; + + fn combine(self, other: Self) -> Result { + 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 { + /// The submodule or item + pub member: ModMember, + /// Additional fields + pub x: XEnt, +} +impl Combine for ModEntry { + type Error = TreeConflict; + fn combine(self, other: Self) -> Result { + 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 ModEntry { + /// 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 TreeTransforms for ModEntry { + type Item = Item; + type XEnt = XEnt; + type XMod = XMod; + type SelfType = ModEntry; + + fn map_data_rec( + self, + item: &mut impl FnMut(Substack>, Item) -> T, + module: &mut impl FnMut(Substack>, XMod) -> U, + entry: &mut impl FnMut(Substack>, XEnt) -> V, + path: Substack>, + ) -> Self::SelfType { + 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>, + callback: &mut impl FnMut( + Substack>, + ModMemberRef<'a, Item, XMod, XEnt>, + T, + ) -> Result, + ) -> Result { + self.member.search_rec(state, stack, callback) + } +} +impl ModEntry { + /// Wrap a member directly with trivial metadata + pub fn wrap(member: ModMember) -> 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 ModEntry { + /// Create an empty submodule + pub fn empty() -> Self { Self::wrap(ModMember::Sub(Module::wrap([]))) } + + /// Create a module + #[must_use] + pub fn tree>(arr: impl IntoIterator) -> 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>(key: K, arr: impl IntoIterator) -> (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, [mut end]: [Self; 1]) -> Self { + let elements = name.as_ref().split("::").collect::>(); + for name in elements.into_iter().rev() { + end = Self::tree([(name, end)]); + } + end + } + + fn not_mod_panic() -> T { panic!("Expected module but found leaf") } + + /// Return the wrapped module. Panic if the entry wraps an item + pub fn unwrap_mod(self) -> Module { + 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 { + 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 { + /// Submodules and items by name + pub entries: HashMap, ModEntry>, + /// 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) -> bool + Clone + 'a +} + +/// A line in a [Module] +pub type Record = (Token, ModEntry); + +impl Module { + /// 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) -> bool + 'a, + ) -> BoxedIter> { + 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], + path: &'b [Token], + 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], + path: &'b [Token], + filter: impl Filter<'b, Item, XMod, XEnt>, + ) -> Result<(&'a ModEntry, &'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], + target: &'b [Token], + is_exported: impl for<'c> Fn(&'c ModEntry) -> bool + Clone + 'b, + ) -> Result<(&'a ModEntry, &'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>) -> Self + where XMod: Default { + Self { entries: entries.into_iter().collect(), x: XMod::default() } + } +} + +impl TreeTransforms for Module { + type Item = Item; + type XEnt = XEnt; + type XMod = XMod; + type SelfType = Module; + + fn map_data_rec( + self, + item: &mut impl FnMut(Substack>, Item) -> T, + module: &mut impl FnMut(Substack>, XMod) -> U, + entry: &mut impl FnMut(Substack>, XEnt) -> V, + path: Substack>, + ) -> Self::SelfType { + 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>, + callback: &mut impl FnMut( + Substack>, + ModMemberRef<'a, Item, XMod, XEnt>, + T, + ) -> Result, + ) -> Result { + 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 Combine for Module { + type Error = TreeConflict; + fn combine(self, Self { entries, x }: Self) -> Result { + 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 fmt::Display + for Module +{ + 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), +} + +/// 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], + /// Planned walk path + path: &'a [Token], + /// Index into walked path where the error occurred + pos: usize, + /// Alternatives to the failed steps + options: Sequence<'a, Token>, +} +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], + kind: ErrKind, + options: Sequence<'a, Token>, + ) -> 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>, + 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()) + } +} diff --git a/src/foreign/try_from_expr.rs b/orchid-base/src/try_from_expr.rs similarity index 100% rename from src/foreign/try_from_expr.rs rename to orchid-base/src/try_from_expr.rs diff --git a/orchid-base/src/virt_fs/common.rs b/orchid-base/src/virt_fs/common.rs new file mode 100644 index 0000000..599f7dd --- /dev/null +++ b/orchid-base/src/virt_fs/common.rs @@ -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), + /// Conceptually equivalent to the list of *.orc files in a folder, without + /// the extension + Collection(Arc>>), +} +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>) -> Self { + Self::Collection(Arc::new(items.into_iter().collect())) + } +} + +/// Returned by any source loading callback +pub type FSResult = Result; + +/// 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], 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]) -> Option; + /// Convert the FS handler into a type-erased version of itself for packing in + /// a tree. + fn rc(self) -> Rc + 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], full_path: &PathSlice) -> FSResult { + (*self).get(path, full_path) + } + fn display(&self, path: &[Token]) -> Option { (*self).display(path) } +} + +impl VirtFS for Rc { + fn get(&self, path: &[Token], full_path: &PathSlice) -> FSResult { + (**self).get(path, full_path) + } + fn display(&self, path: &[Token]) -> Option { (**self).display(path) } +} diff --git a/orchid-base/src/virt_fs/decl.rs b/orchid-base/src/virt_fs/decl.rs new file mode 100644 index 0000000..8e1f105 --- /dev/null +++ b/orchid-base/src/virt_fs/decl.rs @@ -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 { + type Error = ConflictingTrees; + fn combine(self, _: Self) -> Result { Err(ConflictingTrees) } +} + +impl Combine for Arc { + type Error = ConflictingTrees; + fn combine(self, _: Self) -> Result { Err(ConflictingTrees) } +} + +impl<'a> Combine for &'a dyn VirtFS { + type Error = ConflictingTrees; + fn combine(self, _: Self) -> Result { 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, (), ()>; + +impl VirtFS for DeclTree { + fn get(&self, path: &[Token], 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]) -> Option { + 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]) -> Option { None } + fn get(&self, path: &[Token], 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]) -> Option { None } + fn get(&self, path: &[Token], 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())) } diff --git a/orchid-base/src/virt_fs/dir.rs b/orchid-base/src/virt_fs/dir.rs new file mode 100644 index 0000000..444b4bd --- /dev/null +++ b/orchid-base/src/virt_fs/dir.rs @@ -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>, + dir: Arc>, +} +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>); +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>, + 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]) -> 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], 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]) -> Option { + let pathbuf = self.mk_pathbuf(path).with_extension(self.ext()); + Some(pathbuf.to_string_lossy().to_string()) + } +} diff --git a/orchid-base/src/virt_fs/embed.rs b/orchid-base/src/virt_fs/embed.rs new file mode 100644 index 0000000..e25338c --- /dev/null +++ b/orchid-base/src/virt_fs/embed.rs @@ -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, (), ()>, + suffix: &'static str, + gen: CodeGenInfo, +} +impl EmbeddedFS { + /// Expose a directory embedded in a binary wiht [RustEmbed] to the + /// interpreter + pub fn new(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], 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]) -> Option { + let Self { gen, suffix, .. } = self; + Some(format!("{}{suffix} in {gen}", path.iter().join("/"))) + } +} diff --git a/src/virt_fs/mod.rs b/orchid-base/src/virt_fs/mod.rs similarity index 100% rename from src/virt_fs/mod.rs rename to orchid-base/src/virt_fs/mod.rs diff --git a/orchid-base/src/virt_fs/prefix.rs b/orchid-base/src/virt_fs/prefix.rs new file mode 100644 index 0000000..47efb41 --- /dev/null +++ b/orchid-base/src/virt_fs/prefix.rs @@ -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, +} +impl<'a> PrefixFS<'a> { + /// Modify the prefix of a file tree + pub fn new(wrapped: impl VirtFS + 'a, remove: impl AsRef, add: impl AsRef) -> Self { + Self { + wrapped: Box::new(wrapped), + remove: VPath::parse(remove.as_ref()), + add: VPath::parse(add.as_ref()), + } + } + fn proc_path(&self, path: &[Token]) -> Option>> { + 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], 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]) -> Option { + self.wrapped.display(&self.proc_path(path)?) + } +} diff --git a/orchid-extension/Cargo.toml b/orchid-extension/Cargo.toml new file mode 100644 index 0000000..e8fda3d --- /dev/null +++ b/orchid-extension/Cargo.toml @@ -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" } diff --git a/orchid-extension/src/entrypoint.rs b/orchid-extension/src/entrypoint.rs new file mode 100644 index 0000000..7cf6453 --- /dev/null +++ b/orchid-extension/src/entrypoint.rs @@ -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::::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()) + } +} diff --git a/orchid-extension/src/extension.rs b/orchid-extension/src/extension.rs new file mode 100644 index 0000000..a1b0244 --- /dev/null +++ b/orchid-extension/src/extension.rs @@ -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, + +} + +pub struct SystemCtor { + deps: Vec, + make: Box System>, + name: String, + prio: NotNan, + dependencies: Vec, +} + +pub struct ExtensionData { + systems: Vec +} + + diff --git a/orchid-extension/src/lib.rs b/orchid-extension/src/lib.rs new file mode 100644 index 0000000..f5ecc5a --- /dev/null +++ b/orchid-extension/src/lib.rs @@ -0,0 +1 @@ +pub mod entrypoint; diff --git a/orchid-host/Cargo.toml b/orchid-host/Cargo.toml new file mode 100644 index 0000000..1c3667f --- /dev/null +++ b/orchid-host/Cargo.toml @@ -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] diff --git a/orchid-host/src/expr.rs b/orchid-host/src/expr.rs new file mode 100644 index 0000000..5a272c1 --- /dev/null +++ b/orchid-host/src/expr.rs @@ -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, + data: Arc<()>, +} +impl RtExpr { + pub fn as_atom(&self) -> Option { 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 { 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> = RwLock::default(); +} diff --git a/orchid-host/src/lib.rs b/orchid-host/src/lib.rs new file mode 100644 index 0000000..1a9c018 --- /dev/null +++ b/orchid-host/src/lib.rs @@ -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, +} +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); +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) -> Vec { + 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, + reqnot: ReqNot, + systems: Vec, +} +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); +impl Extension { + pub fn new(mut cmd: process::Command) -> io::Result { + 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 = 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 { self.0.systems.iter() } +} + +pub struct SystemCtor { + decl: SystemDecl, + ext: Weak, +} +impl SystemCtor { + pub fn name(&self) -> &str { &self.decl.name } + pub fn priority(&self) -> NotNan { self.decl.priority } + pub fn depends(&self) -> impl ExactSizeIterator { + self.decl.depends.iter().map(|s| &**s) + } + pub fn run<'a>(&self, depends: impl IntoIterator) -> 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> = RwLock::default(); +} + +#[derive(destructure)] +pub struct SystemInstData { + exprs: RwLock>, + 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); +impl System { + fn resolve(id: u16) -> Option { 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()) + }, + } + } +} diff --git a/orchid-std/Cargo.toml b/orchid-std/Cargo.toml new file mode 100644 index 0000000..668c271 --- /dev/null +++ b/orchid-std/Cargo.toml @@ -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] diff --git a/orchid-std/src/lib.rs b/orchid-std/src/lib.rs new file mode 100644 index 0000000..522d1f2 --- /dev/null +++ b/orchid-std/src/lib.rs @@ -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); + } +} diff --git a/orchidlang/.gitignore b/orchidlang/.gitignore new file mode 100644 index 0000000..1de5659 --- /dev/null +++ b/orchidlang/.gitignore @@ -0,0 +1 @@ +target \ No newline at end of file diff --git a/orchidlang/Cargo.lock b/orchidlang/Cargo.lock new file mode 100644 index 0000000..6efbf84 --- /dev/null +++ b/orchidlang/Cargo.lock @@ -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", +] diff --git a/orchidlang/Cargo.toml b/orchidlang/Cargo.toml new file mode 100644 index 0000000..0b9caac --- /dev/null +++ b/orchidlang/Cargo.toml @@ -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 "] + +[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" diff --git a/src/bin/cli/mod.rs b/orchidlang/src/bin/cli/mod.rs similarity index 100% rename from src/bin/cli/mod.rs rename to orchidlang/src/bin/cli/mod.rs diff --git a/src/bin/cli/prompt.rs b/orchidlang/src/bin/cli/prompt.rs similarity index 100% rename from src/bin/cli/prompt.rs rename to orchidlang/src/bin/cli/prompt.rs diff --git a/src/bin/features/macro_debug.rs b/orchidlang/src/bin/features/macro_debug.rs similarity index 100% rename from src/bin/features/macro_debug.rs rename to orchidlang/src/bin/features/macro_debug.rs diff --git a/src/bin/features/mod.rs b/orchidlang/src/bin/features/mod.rs similarity index 100% rename from src/bin/features/mod.rs rename to orchidlang/src/bin/features/mod.rs diff --git a/src/bin/features/print_project.rs b/orchidlang/src/bin/features/print_project.rs similarity index 100% rename from src/bin/features/print_project.rs rename to orchidlang/src/bin/features/print_project.rs diff --git a/src/bin/features/shared.rs b/orchidlang/src/bin/features/shared.rs similarity index 100% rename from src/bin/features/shared.rs rename to orchidlang/src/bin/features/shared.rs diff --git a/src/bin/features/tests.rs b/orchidlang/src/bin/features/tests.rs similarity index 100% rename from src/bin/features/tests.rs rename to orchidlang/src/bin/features/tests.rs diff --git a/src/bin/orcx.rs b/orchidlang/src/bin/orcx.rs similarity index 100% rename from src/bin/orcx.rs rename to orchidlang/src/bin/orcx.rs diff --git a/src/facade/loader.rs b/orchidlang/src/facade/loader.rs similarity index 100% rename from src/facade/loader.rs rename to orchidlang/src/facade/loader.rs diff --git a/src/facade/macro_runner.rs b/orchidlang/src/facade/macro_runner.rs similarity index 100% rename from src/facade/macro_runner.rs rename to orchidlang/src/facade/macro_runner.rs diff --git a/src/facade/merge_trees.rs b/orchidlang/src/facade/merge_trees.rs similarity index 100% rename from src/facade/merge_trees.rs rename to orchidlang/src/facade/merge_trees.rs diff --git a/src/facade/mod.rs b/orchidlang/src/facade/mod.rs similarity index 100% rename from src/facade/mod.rs rename to orchidlang/src/facade/mod.rs diff --git a/src/facade/process.rs b/orchidlang/src/facade/process.rs similarity index 100% rename from src/facade/process.rs rename to orchidlang/src/facade/process.rs diff --git a/src/facade/unbound_ref.rs b/orchidlang/src/facade/unbound_ref.rs similarity index 100% rename from src/facade/unbound_ref.rs rename to orchidlang/src/facade/unbound_ref.rs diff --git a/src/foreign/cps_box.rs b/orchidlang/src/foreign/cps_box.rs similarity index 100% rename from src/foreign/cps_box.rs rename to orchidlang/src/foreign/cps_box.rs diff --git a/src/foreign/fn_bridge.rs b/orchidlang/src/foreign/fn_bridge.rs similarity index 100% rename from src/foreign/fn_bridge.rs rename to orchidlang/src/foreign/fn_bridge.rs diff --git a/src/foreign/inert.rs b/orchidlang/src/foreign/inert.rs similarity index 100% rename from src/foreign/inert.rs rename to orchidlang/src/foreign/inert.rs diff --git a/src/foreign/mod.rs b/orchidlang/src/foreign/mod.rs similarity index 100% rename from src/foreign/mod.rs rename to orchidlang/src/foreign/mod.rs diff --git a/src/foreign/process.rs b/orchidlang/src/foreign/process.rs similarity index 100% rename from src/foreign/process.rs rename to orchidlang/src/foreign/process.rs diff --git a/src/foreign/to_clause.rs b/orchidlang/src/foreign/to_clause.rs similarity index 100% rename from src/foreign/to_clause.rs rename to orchidlang/src/foreign/to_clause.rs diff --git a/src/intermediate/ast_to_ir.rs b/orchidlang/src/intermediate/ast_to_ir.rs similarity index 98% rename from src/intermediate/ast_to_ir.rs rename to orchidlang/src/intermediate/ast_to_ir.rs index ae6fa10..2de1bda 100644 --- a/src/intermediate/ast_to_ir.rs +++ b/orchidlang/src/intermediate/ast_to_ir.rs @@ -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())?; diff --git a/src/intermediate/ir.rs b/orchidlang/src/intermediate/ir.rs similarity index 100% rename from src/intermediate/ir.rs rename to orchidlang/src/intermediate/ir.rs diff --git a/src/intermediate/ir_to_nort.rs b/orchidlang/src/intermediate/ir_to_nort.rs similarity index 100% rename from src/intermediate/ir_to_nort.rs rename to orchidlang/src/intermediate/ir_to_nort.rs diff --git a/src/intermediate/mod.rs b/orchidlang/src/intermediate/mod.rs similarity index 100% rename from src/intermediate/mod.rs rename to orchidlang/src/intermediate/mod.rs diff --git a/src/interpreter/apply.rs b/orchidlang/src/interpreter/apply.rs similarity index 100% rename from src/interpreter/apply.rs rename to orchidlang/src/interpreter/apply.rs diff --git a/src/interpreter/error.rs b/orchidlang/src/interpreter/error.rs similarity index 100% rename from src/interpreter/error.rs rename to orchidlang/src/interpreter/error.rs diff --git a/src/interpreter/gen_nort.rs b/orchidlang/src/interpreter/gen_nort.rs similarity index 100% rename from src/interpreter/gen_nort.rs rename to orchidlang/src/interpreter/gen_nort.rs diff --git a/src/interpreter/mod.rs b/orchidlang/src/interpreter/mod.rs similarity index 100% rename from src/interpreter/mod.rs rename to orchidlang/src/interpreter/mod.rs diff --git a/src/interpreter/nort_builder.rs b/orchidlang/src/interpreter/nort_builder.rs similarity index 100% rename from src/interpreter/nort_builder.rs rename to orchidlang/src/interpreter/nort_builder.rs diff --git a/src/interpreter/path_set.rs b/orchidlang/src/interpreter/path_set.rs similarity index 100% rename from src/interpreter/path_set.rs rename to orchidlang/src/interpreter/path_set.rs diff --git a/src/interpreter/run.rs b/orchidlang/src/interpreter/run.rs similarity index 100% rename from src/interpreter/run.rs rename to orchidlang/src/interpreter/run.rs diff --git a/src/lib.rs b/orchidlang/src/lib.rs similarity index 100% rename from src/lib.rs rename to orchidlang/src/lib.rs diff --git a/src/libs/asynch/async.orc b/orchidlang/src/libs/asynch/async.orc similarity index 100% rename from src/libs/asynch/async.orc rename to orchidlang/src/libs/asynch/async.orc diff --git a/src/libs/asynch/delete_cell.rs b/orchidlang/src/libs/asynch/delete_cell.rs similarity index 100% rename from src/libs/asynch/delete_cell.rs rename to orchidlang/src/libs/asynch/delete_cell.rs diff --git a/src/libs/asynch/mod.rs b/orchidlang/src/libs/asynch/mod.rs similarity index 100% rename from src/libs/asynch/mod.rs rename to orchidlang/src/libs/asynch/mod.rs diff --git a/src/libs/asynch/poller.rs b/orchidlang/src/libs/asynch/poller.rs similarity index 100% rename from src/libs/asynch/poller.rs rename to orchidlang/src/libs/asynch/poller.rs diff --git a/src/libs/asynch/system.rs b/orchidlang/src/libs/asynch/system.rs similarity index 100% rename from src/libs/asynch/system.rs rename to orchidlang/src/libs/asynch/system.rs diff --git a/src/libs/directfs/commands.rs b/orchidlang/src/libs/directfs/commands.rs similarity index 100% rename from src/libs/directfs/commands.rs rename to orchidlang/src/libs/directfs/commands.rs diff --git a/src/libs/directfs/mod.rs b/orchidlang/src/libs/directfs/mod.rs similarity index 100% rename from src/libs/directfs/mod.rs rename to orchidlang/src/libs/directfs/mod.rs diff --git a/src/libs/directfs/osstring.rs b/orchidlang/src/libs/directfs/osstring.rs similarity index 100% rename from src/libs/directfs/osstring.rs rename to orchidlang/src/libs/directfs/osstring.rs diff --git a/src/libs/io/bindings.rs b/orchidlang/src/libs/io/bindings.rs similarity index 100% rename from src/libs/io/bindings.rs rename to orchidlang/src/libs/io/bindings.rs diff --git a/src/libs/io/flow.rs b/orchidlang/src/libs/io/flow.rs similarity index 100% rename from src/libs/io/flow.rs rename to orchidlang/src/libs/io/flow.rs diff --git a/src/libs/io/instances.rs b/orchidlang/src/libs/io/instances.rs similarity index 100% rename from src/libs/io/instances.rs rename to orchidlang/src/libs/io/instances.rs diff --git a/src/libs/io/io.orc b/orchidlang/src/libs/io/io.orc similarity index 100% rename from src/libs/io/io.orc rename to orchidlang/src/libs/io/io.orc diff --git a/src/libs/io/mod.rs b/orchidlang/src/libs/io/mod.rs similarity index 100% rename from src/libs/io/mod.rs rename to orchidlang/src/libs/io/mod.rs diff --git a/src/libs/io/service.rs b/orchidlang/src/libs/io/service.rs similarity index 100% rename from src/libs/io/service.rs rename to orchidlang/src/libs/io/service.rs diff --git a/src/libs/mod.rs b/orchidlang/src/libs/mod.rs similarity index 100% rename from src/libs/mod.rs rename to orchidlang/src/libs/mod.rs diff --git a/src/libs/parse_custom_line.rs b/orchidlang/src/libs/parse_custom_line.rs similarity index 100% rename from src/libs/parse_custom_line.rs rename to orchidlang/src/libs/parse_custom_line.rs diff --git a/src/libs/scheduler/busy.rs b/orchidlang/src/libs/scheduler/busy.rs similarity index 100% rename from src/libs/scheduler/busy.rs rename to orchidlang/src/libs/scheduler/busy.rs diff --git a/src/libs/scheduler/cancel_flag.rs b/orchidlang/src/libs/scheduler/cancel_flag.rs similarity index 100% rename from src/libs/scheduler/cancel_flag.rs rename to orchidlang/src/libs/scheduler/cancel_flag.rs diff --git a/src/libs/scheduler/id_map.rs b/orchidlang/src/libs/scheduler/id_map.rs similarity index 100% rename from src/libs/scheduler/id_map.rs rename to orchidlang/src/libs/scheduler/id_map.rs diff --git a/src/libs/scheduler/mod.rs b/orchidlang/src/libs/scheduler/mod.rs similarity index 100% rename from src/libs/scheduler/mod.rs rename to orchidlang/src/libs/scheduler/mod.rs diff --git a/src/libs/scheduler/system.rs b/orchidlang/src/libs/scheduler/system.rs similarity index 100% rename from src/libs/scheduler/system.rs rename to orchidlang/src/libs/scheduler/system.rs diff --git a/src/libs/scheduler/thread_pool.rs b/orchidlang/src/libs/scheduler/thread_pool.rs similarity index 100% rename from src/libs/scheduler/thread_pool.rs rename to orchidlang/src/libs/scheduler/thread_pool.rs diff --git a/src/libs/std/arithmetic_error.rs b/orchidlang/src/libs/std/arithmetic_error.rs similarity index 100% rename from src/libs/std/arithmetic_error.rs rename to orchidlang/src/libs/std/arithmetic_error.rs diff --git a/src/libs/std/binary.rs b/orchidlang/src/libs/std/binary.rs similarity index 100% rename from src/libs/std/binary.rs rename to orchidlang/src/libs/std/binary.rs diff --git a/src/libs/std/bool.orc b/orchidlang/src/libs/std/bool.orc similarity index 100% rename from src/libs/std/bool.orc rename to orchidlang/src/libs/std/bool.orc diff --git a/src/libs/std/bool.rs b/orchidlang/src/libs/std/bool.rs similarity index 100% rename from src/libs/std/bool.rs rename to orchidlang/src/libs/std/bool.rs diff --git a/src/libs/std/conv.rs b/orchidlang/src/libs/std/conv.rs similarity index 100% rename from src/libs/std/conv.rs rename to orchidlang/src/libs/std/conv.rs diff --git a/src/libs/std/cross_pipeline.rs b/orchidlang/src/libs/std/cross_pipeline.rs similarity index 100% rename from src/libs/std/cross_pipeline.rs rename to orchidlang/src/libs/std/cross_pipeline.rs diff --git a/src/libs/std/exit_status.rs b/orchidlang/src/libs/std/exit_status.rs similarity index 100% rename from src/libs/std/exit_status.rs rename to orchidlang/src/libs/std/exit_status.rs diff --git a/src/libs/std/fn.orc b/orchidlang/src/libs/std/fn.orc similarity index 100% rename from src/libs/std/fn.orc rename to orchidlang/src/libs/std/fn.orc diff --git a/src/libs/std/inspect.rs b/orchidlang/src/libs/std/inspect.rs similarity index 100% rename from src/libs/std/inspect.rs rename to orchidlang/src/libs/std/inspect.rs diff --git a/src/libs/std/known.orc b/orchidlang/src/libs/std/known.orc similarity index 100% rename from src/libs/std/known.orc rename to orchidlang/src/libs/std/known.orc diff --git a/src/libs/std/list.orc b/orchidlang/src/libs/std/list.orc similarity index 100% rename from src/libs/std/list.orc rename to orchidlang/src/libs/std/list.orc diff --git a/src/libs/std/loop.orc b/orchidlang/src/libs/std/loop.orc similarity index 100% rename from src/libs/std/loop.orc rename to orchidlang/src/libs/std/loop.orc diff --git a/src/libs/std/macro.orc b/orchidlang/src/libs/std/macro.orc similarity index 100% rename from src/libs/std/macro.orc rename to orchidlang/src/libs/std/macro.orc diff --git a/src/libs/std/map.orc b/orchidlang/src/libs/std/map.orc similarity index 100% rename from src/libs/std/map.orc rename to orchidlang/src/libs/std/map.orc diff --git a/src/libs/std/mod.rs b/orchidlang/src/libs/std/mod.rs similarity index 100% rename from src/libs/std/mod.rs rename to orchidlang/src/libs/std/mod.rs diff --git a/src/libs/std/number.orc b/orchidlang/src/libs/std/number.orc similarity index 100% rename from src/libs/std/number.orc rename to orchidlang/src/libs/std/number.orc diff --git a/src/libs/std/number.rs b/orchidlang/src/libs/std/number.rs similarity index 100% rename from src/libs/std/number.rs rename to orchidlang/src/libs/std/number.rs diff --git a/src/libs/std/option.orc b/orchidlang/src/libs/std/option.orc similarity index 100% rename from src/libs/std/option.orc rename to orchidlang/src/libs/std/option.orc diff --git a/src/libs/std/panic.rs b/orchidlang/src/libs/std/panic.rs similarity index 100% rename from src/libs/std/panic.rs rename to orchidlang/src/libs/std/panic.rs diff --git a/src/libs/std/pmatch.orc b/orchidlang/src/libs/std/pmatch.orc similarity index 100% rename from src/libs/std/pmatch.orc rename to orchidlang/src/libs/std/pmatch.orc diff --git a/src/libs/std/prelude.orc b/orchidlang/src/libs/std/prelude.orc similarity index 100% rename from src/libs/std/prelude.orc rename to orchidlang/src/libs/std/prelude.orc diff --git a/src/libs/std/procedural.orc b/orchidlang/src/libs/std/procedural.orc similarity index 100% rename from src/libs/std/procedural.orc rename to orchidlang/src/libs/std/procedural.orc diff --git a/src/libs/std/protocol.orc b/orchidlang/src/libs/std/protocol.orc similarity index 100% rename from src/libs/std/protocol.orc rename to orchidlang/src/libs/std/protocol.orc diff --git a/src/libs/std/protocol.rs b/orchidlang/src/libs/std/protocol.rs similarity index 100% rename from src/libs/std/protocol.rs rename to orchidlang/src/libs/std/protocol.rs diff --git a/src/libs/std/reflect.rs b/orchidlang/src/libs/std/reflect.rs similarity index 100% rename from src/libs/std/reflect.rs rename to orchidlang/src/libs/std/reflect.rs diff --git a/src/libs/std/result.orc b/orchidlang/src/libs/std/result.orc similarity index 100% rename from src/libs/std/result.orc rename to orchidlang/src/libs/std/result.orc diff --git a/src/libs/std/runtime_error.rs b/orchidlang/src/libs/std/runtime_error.rs similarity index 100% rename from src/libs/std/runtime_error.rs rename to orchidlang/src/libs/std/runtime_error.rs diff --git a/src/libs/std/state.rs b/orchidlang/src/libs/std/state.rs similarity index 100% rename from src/libs/std/state.rs rename to orchidlang/src/libs/std/state.rs diff --git a/src/libs/std/std_system.rs b/orchidlang/src/libs/std/std_system.rs similarity index 100% rename from src/libs/std/std_system.rs rename to orchidlang/src/libs/std/std_system.rs diff --git a/src/libs/std/string.orc b/orchidlang/src/libs/std/string.orc similarity index 100% rename from src/libs/std/string.orc rename to orchidlang/src/libs/std/string.orc diff --git a/src/libs/std/string.rs b/orchidlang/src/libs/std/string.rs similarity index 100% rename from src/libs/std/string.rs rename to orchidlang/src/libs/std/string.rs diff --git a/src/libs/std/tuple.orc b/orchidlang/src/libs/std/tuple.orc similarity index 100% rename from src/libs/std/tuple.orc rename to orchidlang/src/libs/std/tuple.orc diff --git a/src/libs/std/tuple.rs b/orchidlang/src/libs/std/tuple.rs similarity index 100% rename from src/libs/std/tuple.rs rename to orchidlang/src/libs/std/tuple.rs diff --git a/src/name.rs b/orchidlang/src/name.rs similarity index 100% rename from src/name.rs rename to orchidlang/src/name.rs diff --git a/src/parse/context.rs b/orchidlang/src/parse/context.rs similarity index 98% rename from src/parse/context.rs rename to orchidlang/src/parse/context.rs index b842132..136498e 100644 --- a/src/parse/context.rs +++ b/orchidlang/src/parse/context.rs @@ -135,8 +135,8 @@ impl<'a, C: ParseCtx + ?Sized> ParseCtx for FlatLocContext<'a, C> { fn pos(&self, _: &str) -> usize { 0 } fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { self.sub.lexers() } fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { self.sub.line_parsers() } - fn code_info(&self) -> SourceCode { self.range.code.clone() } - fn range(&self, _: usize, _: &str) -> Range { self.range.range.clone() } + fn code_info(&self) -> SourceCode { self.range.code() } + fn range(&self, _: usize, _: &str) -> Range { self.range.range() } } /// Context that forwards everything to a wrapped context except for error diff --git a/src/parse/errors.rs b/orchidlang/src/parse/errors.rs similarity index 100% rename from src/parse/errors.rs rename to orchidlang/src/parse/errors.rs diff --git a/src/parse/facade.rs b/orchidlang/src/parse/facade.rs similarity index 100% rename from src/parse/facade.rs rename to orchidlang/src/parse/facade.rs diff --git a/src/parse/frag.rs b/orchidlang/src/parse/frag.rs similarity index 100% rename from src/parse/frag.rs rename to orchidlang/src/parse/frag.rs diff --git a/src/parse/lex_plugin.rs b/orchidlang/src/parse/lex_plugin.rs similarity index 100% rename from src/parse/lex_plugin.rs rename to orchidlang/src/parse/lex_plugin.rs diff --git a/src/parse/lexer.rs b/orchidlang/src/parse/lexer.rs similarity index 100% rename from src/parse/lexer.rs rename to orchidlang/src/parse/lexer.rs diff --git a/src/parse/mod.rs b/orchidlang/src/parse/mod.rs similarity index 100% rename from src/parse/mod.rs rename to orchidlang/src/parse/mod.rs diff --git a/src/parse/multiname.rs b/orchidlang/src/parse/multiname.rs similarity index 100% rename from src/parse/multiname.rs rename to orchidlang/src/parse/multiname.rs diff --git a/src/parse/numeric.rs b/orchidlang/src/parse/numeric.rs similarity index 100% rename from src/parse/numeric.rs rename to orchidlang/src/parse/numeric.rs diff --git a/src/parse/parse_plugin.rs b/orchidlang/src/parse/parse_plugin.rs similarity index 100% rename from src/parse/parse_plugin.rs rename to orchidlang/src/parse/parse_plugin.rs diff --git a/src/parse/parsed.rs b/orchidlang/src/parse/parsed.rs similarity index 100% rename from src/parse/parsed.rs rename to orchidlang/src/parse/parsed.rs diff --git a/src/parse/sourcefile.rs b/orchidlang/src/parse/sourcefile.rs similarity index 99% rename from src/parse/sourcefile.rs rename to orchidlang/src/parse/sourcefile.rs index d0a3865..546f70b 100644 --- a/src/parse/sourcefile.rs +++ b/orchidlang/src/parse/sourcefile.rs @@ -306,7 +306,7 @@ pub fn exprv_to_single( _ => { let f_range = &v.first().unwrap().range; let l_range = &v.last().unwrap().range; - let range = f_range.map_range(|r| r.start..l_range.range.end); + let range = f_range.map_range(|r| r.start..l_range.end()); Ok(Expr { range, value: Clause::S(PType::Par, Rc::new(v)) }) }, } diff --git a/src/pipeline/dealias/mod.rs b/orchidlang/src/pipeline/dealias/mod.rs similarity index 100% rename from src/pipeline/dealias/mod.rs rename to orchidlang/src/pipeline/dealias/mod.rs diff --git a/src/pipeline/dealias/resolve_aliases.rs b/orchidlang/src/pipeline/dealias/resolve_aliases.rs similarity index 100% rename from src/pipeline/dealias/resolve_aliases.rs rename to orchidlang/src/pipeline/dealias/resolve_aliases.rs diff --git a/src/pipeline/dealias/walk_with_links.rs b/orchidlang/src/pipeline/dealias/walk_with_links.rs similarity index 100% rename from src/pipeline/dealias/walk_with_links.rs rename to orchidlang/src/pipeline/dealias/walk_with_links.rs diff --git a/src/pipeline/load_project.rs b/orchidlang/src/pipeline/load_project.rs similarity index 97% rename from src/pipeline/load_project.rs rename to orchidlang/src/pipeline/load_project.rs index 52915de..c3bd152 100644 --- a/src/pipeline/load_project.rs +++ b/orchidlang/src/pipeline/load_project.rs @@ -105,14 +105,14 @@ pub fn load_project( }); if let Some((file, _, source)) = path_parts { let path = Sym::new(file.iter().cloned()).expect("loading from a DeclTree"); - let code = SourceCode { path: path.clone(), text: source }; if known_files.contains(&path) { continue; } - known_files.insert(path); + known_files.insert(path.clone()); + let code = SourceCode::new(path, source); let full_range = SourceRange { range: 0..code.text.len(), code: code.clone() }; let lines = parse_file(&ctx.parsing(code.clone())); - let report = process_ns(code.path, lines, full_range, ctx.reporter); + let report = process_ns(code.path(), lines, full_range, ctx.reporter); queue.extend((report.ext_refs.into_iter()).map(|(k, v)| (k, CodeOrigin::Source(v)))); let mut comments = Some(report.comments); let mut module = report.module; diff --git a/src/pipeline/mod.rs b/orchidlang/src/pipeline/mod.rs similarity index 100% rename from src/pipeline/mod.rs rename to orchidlang/src/pipeline/mod.rs diff --git a/src/pipeline/path.rs b/orchidlang/src/pipeline/path.rs similarity index 100% rename from src/pipeline/path.rs rename to orchidlang/src/pipeline/path.rs diff --git a/src/pipeline/process_source.rs b/orchidlang/src/pipeline/process_source.rs similarity index 100% rename from src/pipeline/process_source.rs rename to orchidlang/src/pipeline/process_source.rs diff --git a/src/pipeline/project.rs b/orchidlang/src/pipeline/project.rs similarity index 100% rename from src/pipeline/project.rs rename to orchidlang/src/pipeline/project.rs diff --git a/src/rule/matcher.rs b/orchidlang/src/rule/matcher.rs similarity index 100% rename from src/rule/matcher.rs rename to orchidlang/src/rule/matcher.rs diff --git a/src/rule/matcher_vectree/any_match.rs b/orchidlang/src/rule/matcher_vectree/any_match.rs similarity index 100% rename from src/rule/matcher_vectree/any_match.rs rename to orchidlang/src/rule/matcher_vectree/any_match.rs diff --git a/src/rule/matcher_vectree/build.rs b/orchidlang/src/rule/matcher_vectree/build.rs similarity index 100% rename from src/rule/matcher_vectree/build.rs rename to orchidlang/src/rule/matcher_vectree/build.rs diff --git a/src/rule/matcher_vectree/mod.rs b/orchidlang/src/rule/matcher_vectree/mod.rs similarity index 100% rename from src/rule/matcher_vectree/mod.rs rename to orchidlang/src/rule/matcher_vectree/mod.rs diff --git a/src/rule/matcher_vectree/scal_match.rs b/orchidlang/src/rule/matcher_vectree/scal_match.rs similarity index 100% rename from src/rule/matcher_vectree/scal_match.rs rename to orchidlang/src/rule/matcher_vectree/scal_match.rs diff --git a/src/rule/matcher_vectree/shared.rs b/orchidlang/src/rule/matcher_vectree/shared.rs similarity index 100% rename from src/rule/matcher_vectree/shared.rs rename to orchidlang/src/rule/matcher_vectree/shared.rs diff --git a/src/rule/matcher_vectree/vec_match.rs b/orchidlang/src/rule/matcher_vectree/vec_match.rs similarity index 100% rename from src/rule/matcher_vectree/vec_match.rs rename to orchidlang/src/rule/matcher_vectree/vec_match.rs diff --git a/src/rule/mod.rs b/orchidlang/src/rule/mod.rs similarity index 100% rename from src/rule/mod.rs rename to orchidlang/src/rule/mod.rs diff --git a/src/rule/prepare_rule.rs b/orchidlang/src/rule/prepare_rule.rs similarity index 100% rename from src/rule/prepare_rule.rs rename to orchidlang/src/rule/prepare_rule.rs diff --git a/src/rule/repository.rs b/orchidlang/src/rule/repository.rs similarity index 100% rename from src/rule/repository.rs rename to orchidlang/src/rule/repository.rs diff --git a/src/rule/rule_error.rs b/orchidlang/src/rule/rule_error.rs similarity index 100% rename from src/rule/rule_error.rs rename to orchidlang/src/rule/rule_error.rs diff --git a/src/rule/state.rs b/orchidlang/src/rule/state.rs similarity index 100% rename from src/rule/state.rs rename to orchidlang/src/rule/state.rs diff --git a/src/rule/update_first_seq.rs b/orchidlang/src/rule/update_first_seq.rs similarity index 100% rename from src/rule/update_first_seq.rs rename to orchidlang/src/rule/update_first_seq.rs diff --git a/src/rule/vec_attrs.rs b/orchidlang/src/rule/vec_attrs.rs similarity index 100% rename from src/rule/vec_attrs.rs rename to orchidlang/src/rule/vec_attrs.rs diff --git a/src/tree.rs b/orchidlang/src/tree.rs similarity index 100% rename from src/tree.rs rename to orchidlang/src/tree.rs diff --git a/orchidlang/src/utils/boxed_iter.rs b/orchidlang/src/utils/boxed_iter.rs new file mode 100644 index 0000000..d538944 --- /dev/null +++ b/orchidlang/src/utils/boxed_iter.rs @@ -0,0 +1,23 @@ +/// Utility functions to get rid of tedious explicit casts to +/// BoxedIter +use std::iter; + +/// A trait object of [Iterator] to be assigned to variables that may be +/// initialized form multiple iterators of incompatible types +pub type BoxedIter<'a, T> = Box + 'a>; +/// creates a [BoxedIter] of a single element +pub fn box_once<'a, T: 'a>(t: T) -> BoxedIter<'a, T> { Box::new(iter::once(t)) } +/// creates an empty [BoxedIter] +pub fn box_empty<'a, T: 'a>() -> BoxedIter<'a, T> { Box::new(iter::empty()) } + +/// Chain various iterators into a [BoxedIter] +macro_rules! box_chain { + ($curr:expr) => { + Box::new($curr) as BoxedIter<_> + }; + ($curr:expr, $($rest:expr),*) => { + Box::new($curr$(.chain($rest))*) as $crate::utils::boxed_iter::BoxedIter<_> + }; +} + +pub(crate) use box_chain; diff --git a/src/utils/clonable_iter.rs b/orchidlang/src/utils/clonable_iter.rs similarity index 100% rename from src/utils/clonable_iter.rs rename to orchidlang/src/utils/clonable_iter.rs diff --git a/orchidlang/src/utils/combine.rs b/orchidlang/src/utils/combine.rs new file mode 100644 index 0000000..fd344e1 --- /dev/null +++ b/orchidlang/src/utils/combine.rs @@ -0,0 +1,24 @@ +//! The concept of a fallible merger + +use never::Never; + +/// Fallible, type-preserving variant of [std::ops::Add] implemented by a +/// variety of types for different purposes. Very broadly, if the operation +/// succeeds, the result should represent _both_ inputs. +pub trait Combine: Sized { + /// Information about the failure + type Error; + + /// Merge two values into a value that represents both, if this is possible. + fn combine(self, other: Self) -> Result; +} + +impl Combine for Never { + type Error = Never; + fn combine(self, _: Self) -> Result { match self {} } +} + +impl Combine for () { + type Error = Never; + fn combine(self, (): Self) -> Result { Ok(()) } +} diff --git a/src/utils/ddispatch.rs b/orchidlang/src/utils/ddispatch.rs similarity index 100% rename from src/utils/ddispatch.rs rename to orchidlang/src/utils/ddispatch.rs diff --git a/src/utils/get_or.rs b/orchidlang/src/utils/get_or.rs similarity index 100% rename from src/utils/get_or.rs rename to orchidlang/src/utils/get_or.rs diff --git a/src/utils/iter_find.rs b/orchidlang/src/utils/iter_find.rs similarity index 100% rename from src/utils/iter_find.rs rename to orchidlang/src/utils/iter_find.rs diff --git a/orchidlang/src/utils/join.rs b/orchidlang/src/utils/join.rs new file mode 100644 index 0000000..16ec461 --- /dev/null +++ b/orchidlang/src/utils/join.rs @@ -0,0 +1,34 @@ +//! Join hashmaps with a callback for merging or failing on conflicting keys. + +use std::hash::Hash; + +use hashbrown::HashMap; +use never::Never; + +/// Combine two hashmaps via an infallible value merger. See also +/// [try_join_maps] +pub fn join_maps( + left: HashMap, + right: HashMap, + mut merge: impl FnMut(&K, V, V) -> V, +) -> HashMap { + try_join_maps(left, right, |k, l, r| Ok(merge(k, l, r))).unwrap_or_else(|e: Never| match e {}) +} + +/// Combine two hashmaps via a fallible value merger. See also [join_maps] +pub fn try_join_maps( + left: HashMap, + mut right: HashMap, + mut merge: impl FnMut(&K, V, V) -> Result, +) -> Result, E> { + let mut mixed = HashMap::with_capacity(left.len() + right.len()); + for (key, lval) in left { + let val = match right.remove(&key) { + None => lval, + Some(rval) => merge(&key, lval, rval)?, + }; + mixed.insert(key, val); + } + mixed.extend(right); + Ok(mixed) +} diff --git a/src/utils/mod.rs b/orchidlang/src/utils/mod.rs similarity index 100% rename from src/utils/mod.rs rename to orchidlang/src/utils/mod.rs diff --git a/src/utils/pure_seq.rs b/orchidlang/src/utils/pure_seq.rs similarity index 100% rename from src/utils/pure_seq.rs rename to orchidlang/src/utils/pure_seq.rs diff --git a/orchidlang/src/utils/sequence.rs b/orchidlang/src/utils/sequence.rs new file mode 100644 index 0000000..4fe162b --- /dev/null +++ b/orchidlang/src/utils/sequence.rs @@ -0,0 +1,27 @@ +//! An alternative to `Iterable` in many languages, a [Fn] that returns an +//! iterator. + +use std::rc::Rc; + +use trait_set::trait_set; + +use super::boxed_iter::BoxedIter; + +trait_set! { + trait Payload<'a, T> = Fn() -> BoxedIter<'a, T> + 'a; +} + +/// Dynamic iterator building callback. Given how many trait objects this +/// involves, it may actually be slower than C#. +pub struct Sequence<'a, T: 'a>(Rc>); +impl<'a, T: 'a> Sequence<'a, T> { + /// Construct from a concrete function returning a concrete iterator + pub fn new + 'a>(f: impl Fn() -> I + 'a) -> Self { + Self(Rc::new(move || Box::new(f().into_iter()))) + } + /// Get an iterator from the function + pub fn iter(&self) -> impl Iterator + '_ { (self.0)() } +} +impl<'a, T: 'a> Clone for Sequence<'a, T> { + fn clone(&self) -> Self { Self(self.0.clone()) } +} diff --git a/src/utils/side.rs b/orchidlang/src/utils/side.rs similarity index 100% rename from src/utils/side.rs rename to orchidlang/src/utils/side.rs diff --git a/src/utils/string_from_charset.rs b/orchidlang/src/utils/string_from_charset.rs similarity index 100% rename from src/utils/string_from_charset.rs rename to orchidlang/src/utils/string_from_charset.rs diff --git a/src/utils/take_with_output.rs b/orchidlang/src/utils/take_with_output.rs similarity index 100% rename from src/utils/take_with_output.rs rename to orchidlang/src/utils/take_with_output.rs diff --git a/src/utils/unwrap_or.rs b/orchidlang/src/utils/unwrap_or.rs similarity index 100% rename from src/utils/unwrap_or.rs rename to orchidlang/src/utils/unwrap_or.rs diff --git a/src/virt_fs/common.rs b/orchidlang/src/virt_fs/common.rs similarity index 100% rename from src/virt_fs/common.rs rename to orchidlang/src/virt_fs/common.rs diff --git a/src/virt_fs/decl.rs b/orchidlang/src/virt_fs/decl.rs similarity index 100% rename from src/virt_fs/decl.rs rename to orchidlang/src/virt_fs/decl.rs diff --git a/src/virt_fs/dir.rs b/orchidlang/src/virt_fs/dir.rs similarity index 100% rename from src/virt_fs/dir.rs rename to orchidlang/src/virt_fs/dir.rs diff --git a/src/virt_fs/embed.rs b/orchidlang/src/virt_fs/embed.rs similarity index 100% rename from src/virt_fs/embed.rs rename to orchidlang/src/virt_fs/embed.rs diff --git a/orchidlang/src/virt_fs/mod.rs b/orchidlang/src/virt_fs/mod.rs new file mode 100644 index 0000000..fc3d537 --- /dev/null +++ b/orchidlang/src/virt_fs/mod.rs @@ -0,0 +1,18 @@ +//! Abstractions and primitives to help define the namespace tree used by +//! Orchid. +//! +//! Although this may make it seem like the namespace tree is very flexible, +//! libraries are generally permitted and expected to hardcode their own +//! location in the tree, so it's up to the embedder to ensure that the flexible +//! structure retains the assumed location of all code. +mod common; +mod decl; +mod dir; +mod embed; +mod prefix; + +pub use common::{CodeNotFound, FSKind, FSResult, Loaded, VirtFS}; +pub use decl::{decl_file, DeclTree}; +pub use dir::DirNode; +pub use embed::EmbeddedFS; +pub use prefix::PrefixFS; diff --git a/src/virt_fs/prefix.rs b/orchidlang/src/virt_fs/prefix.rs similarity index 100% rename from src/virt_fs/prefix.rs rename to orchidlang/src/virt_fs/prefix.rs diff --git a/orcx/Cargo.toml b/orcx/Cargo.toml new file mode 100644 index 0000000..ba1e9eb --- /dev/null +++ b/orcx/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "orcx" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/orcx/src/main.rs b/orcx/src/main.rs new file mode 100644 index 0000000..80a1832 --- /dev/null +++ b/orcx/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/src/gen/tree.rs b/src/gen/tree.rs deleted file mode 100644 index 67de0ce..0000000 --- a/src/gen/tree.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! 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 intern_all::Tok; -use itertools::Itertools; -use substack::Substack; -use trait_set::trait_set; - -use super::tpl; -use super::traits::{Gen, GenClause}; -use crate::foreign::atom::{AtomGenerator, Atomic}; -use crate::foreign::fn_bridge::{xfn, Xfn}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::Expr; -use crate::location::CodeLocation; -use crate::tree::{ModEntry, ModMember, TreeConflict}; -use crate::utils::combine::Combine; - -trait_set! { - trait TreeLeaf = Gen + DynClone + Send; - trait XfnCB = FnOnce(Substack>) -> AtomGenerator + DynClone + Send; -} - -enum GCKind { - Const(Box), - Xfn(usize, Box), -} - -/// A leaf in the [ConstTree] -pub struct GenConst(GCKind); -impl GenConst { - fn c(data: impl GenClause + Send + Clone + 'static) -> Self { - Self(GCKind::Const(Box::new(data))) - } - fn f(f: impl Xfn) -> Self { - Self(GCKind::Xfn( - N, - Box::new(move |stck| AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f))), - )) - } - /// Instantiate as [crate::interpreter::nort] - pub fn gen_nort(self, stck: Substack>, location: CodeLocation) -> Expr { - match self.0 { - GCKind::Const(c) => c.template(nort_gen(location), []), - GCKind::Xfn(_, cb) => tpl::AnyAtom(cb(stck)).template(nort_gen(location), []), - } - } -} -impl fmt::Debug for GenConst { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.0 { - GCKind::Const(c) => write!(f, "{c:?}"), - GCKind::Xfn(n, _) => write!(f, "xfn/{n}"), - } - } -} -impl Clone for GenConst { - fn clone(&self) -> Self { - match &self.0 { - GCKind::Const(c) => Self(GCKind::Const(clone_box(&**c))), - GCKind::Xfn(n, cb) => Self(GCKind::Xfn(*n, clone_box(&**cb))), - } - } -} - -/// 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 { 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; - -/// 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>( - key: K, - [g]: [impl GenClause + Clone + Send + 'static; 1], -) -> (K, ConstTree) { - (key, leaf(g)) -} - -/// Describe an [Atomic] -#[must_use] -pub fn atom_leaf(atom: impl Atomic + Clone + Send + 'static) -> ConstTree { leaf(tpl::V(atom)) } - -/// Describe an [Atomic] which appears as an entry in a [ConstTree::tree] -/// -/// The unarray is used to trick rustfmt into breaking the atom into a block -/// without breaking this call into a block -#[must_use] -pub fn atom_ent>( - key: K, - [atom]: [impl Atomic + Clone + 'static; 1], -) -> (K, ConstTree) { - (key, atom_leaf(atom)) -} - -/// Describe a function -pub fn xfn_leaf(f: impl Xfn) -> ConstTree { - ModEntry::wrap(ModMember::Item(GenConst::f(f))) -} - -/// Describe a function 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 xfn_ent>( - key: K, - [f]: [impl Xfn; 1], -) -> (K, ConstTree) { - (key, xfn_leaf(f)) -} - -/// Errors produced duriung the merger of constant trees -pub type ConstCombineErr = TreeConflict; diff --git a/stdio-perftest/Cargo.toml b/stdio-perftest/Cargo.toml new file mode 100644 index 0000000..7ffe607 --- /dev/null +++ b/stdio-perftest/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "stdio-perftest" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/stdio-perftest/src/main.rs b/stdio-perftest/src/main.rs new file mode 100644 index 0000000..761ccaa --- /dev/null +++ b/stdio-perftest/src/main.rs @@ -0,0 +1,44 @@ +use std::env::{self, args}; +use std::io::{stdin, BufRead, BufReader, Read, Write}; +use std::process::{self, Child}; +use std::time::SystemTime; + +fn main() { + let is_child = env::args().any(|arg| arg == "child"); + if is_child { + loop { + let mut input = String::new(); + stdin().read_line(&mut input).unwrap(); + if input == "ping\n" { + println!("pong"); + } else if input.is_empty() { + process::exit(0); + } else { + panic!("Unrecognized input {input:?}"); + } + } + } else { + let steps = 10_000; + let mut child = process::Command::new(args().next().unwrap()) + .arg("child") + .stdin(process::Stdio::piped()) + .stdout(process::Stdio::piped()) + .spawn() + .unwrap(); + let mut bufr = BufReader::new(child.stdout.take().unwrap()); + let mut child_stdin = child.stdin.take().unwrap(); + let time = SystemTime::now(); + for _ in 0..steps { + writeln!(child_stdin, "ping"); + let mut buf = String::new(); + bufr.read_line(&mut buf).unwrap(); + if buf != "pong\n" { + panic!("Unrecognized output {buf:?}") + } + } + child.kill(); + let elapsed = time.elapsed().unwrap(); + let avg = elapsed / steps; + println!("A roundtrip takes {avg:?}, {}ms on average", (avg.as_nanos() as f64) / 1_000_000f64); + } +}