From ed0d64d52e9ca2dc7ced693d6436eabb461cc721 Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Fri, 23 Feb 2024 23:59:24 +0000 Subject: [PATCH] Almost Alpha Massive improvements across the board. One day I'll adopt incremental commits. --- Cargo.lock | 416 +++++++---------- Cargo.toml | 36 +- ROADMAP.md | 19 +- clippy.toml | 1 + examples/calculator/main.orc | 18 +- examples/file-browser/main.orc | 45 +- examples/hello-world/main.orc | 11 +- examples/list-processing/main.orc | 2 +- examples/maps/main.orc | 4 +- examples/match/main.orc | 4 +- examples/protocol/main.orc | 23 + language-server-log.txt | 9 + src/bin/features/macro_debug.rs | 54 ++- src/bin/features/mod.rs | 2 + src/bin/features/print_project.rs | 6 +- src/bin/features/shared.rs | 64 +++ src/bin/features/tests.rs | 111 +++++ src/bin/orcx.rs | 287 +++++------- src/error.rs | 220 ++++++--- src/facade/loader.rs | 188 +++++--- src/facade/macro_runner.rs | 114 +++-- src/facade/merge_trees.rs | 50 +- src/facade/mod.rs | 3 +- src/facade/pre_macro.rs | 111 ----- src/facade/process.rs | 104 +---- src/facade/system.rs | 11 +- src/facade/unbound_ref.rs | 94 ++++ src/foreign/atom.rs | 139 +++--- src/foreign/cps_box.rs | 27 +- src/foreign/error.rs | 48 +- src/foreign/fn_bridge.rs | 35 +- src/foreign/implementations.rs | 169 ------- src/foreign/inert.rs | 66 ++- src/foreign/mod.rs | 1 - src/foreign/process.rs | 29 +- src/foreign/to_clause.rs | 177 ++++++- src/foreign/try_from_expr.rs | 16 +- src/gen/tpl.rs | 16 +- src/gen/traits.rs | 31 +- src/gen/tree.rs | 40 +- src/intermediate/ast_to_ir.rs | 66 ++- src/intermediate/ir.rs | 67 ++- src/intermediate/ir_to_nort.rs | 5 +- src/intermediate/mod.rs | 10 +- src/interpreter/apply.rs | 29 +- src/interpreter/context.rs | 119 +++-- src/interpreter/error.rs | 65 +-- src/interpreter/gen_nort.rs | 76 +--- src/interpreter/handler.rs | 117 +++-- src/interpreter/mod.rs | 6 +- src/interpreter/nort.rs | 185 ++------ src/interpreter/nort_builder.rs | 13 +- src/interpreter/path_set.rs | 39 +- src/interpreter/run.rs | 348 +++++++++----- src/lib.rs | 10 +- src/libs/asynch/mod.rs | 2 +- src/libs/asynch/poller.rs | 31 +- src/libs/asynch/system.rs | 23 +- src/libs/directfs/commands.rs | 8 +- src/libs/directfs/osstring.rs | 7 +- src/libs/io/bindings.rs | 4 +- src/libs/io/flow.rs | 16 +- src/libs/io/instances.rs | 28 +- src/libs/io/io.orc | 6 +- src/libs/io/mod.rs | 10 +- src/libs/io/service.rs | 23 +- src/libs/scheduler/busy.rs | 22 +- src/libs/scheduler/id_map.rs | 3 +- src/libs/scheduler/system.rs | 18 +- src/libs/scheduler/thread_pool.rs | 9 +- src/libs/std/arithmetic_error.rs | 10 +- src/libs/std/binary.rs | 16 +- src/libs/std/bool.rs | 4 +- src/libs/std/conv.rs | 52 +-- src/libs/std/cross_pipeline.rs | 70 ++- src/libs/std/exit_status.rs | 15 +- src/libs/std/{functional.orc => fn.orc} | 0 src/libs/std/inspect.rs | 30 +- src/libs/std/list.orc | 4 +- src/libs/std/loop.orc | 2 +- src/libs/std/map.orc | 8 +- src/libs/std/mod.rs | 5 +- src/libs/std/number.rs | 56 +-- src/libs/std/option.orc | 8 +- src/libs/std/panic.rs | 16 +- src/libs/std/prelude.orc | 10 +- src/libs/std/protocol.orc | 8 + src/libs/std/protocol.rs | 430 ++++++++---------- src/libs/std/reflect.rs | 19 +- src/libs/std/result.orc | 2 +- src/libs/std/runtime_error.rs | 20 +- src/libs/std/state.rs | 4 +- src/libs/std/std_system.rs | 24 +- src/libs/std/string.rs | 268 ++++++++++- src/libs/std/tstring.rs | 81 ---- src/libs/std/tuple.orc | 4 +- src/libs/std/tuple.rs | 27 +- src/location.rs | 160 ++++--- src/name.rs | 408 +++++++++++------ src/parse/context.rs | 92 ++-- src/parse/errors.rs | 58 +-- src/parse/facade.rs | 30 +- src/parse/frag.rs | 40 +- src/parse/lex_plugin.rs | 14 +- src/parse/lexer.rs | 94 ++-- src/parse/mod.rs | 1 - src/parse/multiname.rs | 17 +- src/parse/numeric.rs | 24 +- src/parse/parse_plugin.rs | 82 ++-- src/parse/parsed.rs | 127 ++---- src/parse/sourcefile.rs | 121 ++--- src/parse/string.rs | 148 ------ src/pipeline/dealias/resolve_aliases.rs | 100 ++-- src/pipeline/dealias/walk_with_links.rs | 56 +-- .../{load_solution.rs => load_project.rs} | 117 +++-- src/pipeline/mod.rs | 2 +- src/pipeline/path.rs | 61 +-- src/pipeline/process_source.rs | 293 ++++++------ src/pipeline/project.rs | 95 ++-- src/rule/matcher.rs | 10 +- src/rule/matcher_vectree/build.rs | 76 +--- src/rule/matcher_vectree/mod.rs | 6 +- src/rule/matcher_vectree/scal_match.rs | 18 +- src/rule/matcher_vectree/shared.rs | 40 +- src/rule/matcher_vectree/vec_match.rs | 38 +- src/rule/prepare_rule.rs | 13 +- src/rule/repository.rs | 58 +-- src/rule/rule_error.rs | 56 +-- src/rule/state.rs | 50 +- src/rule/update_first_seq.rs | 13 +- src/rule/vec_attrs.rs | 6 +- src/tree.rs | 191 ++++---- src/utils/clonable_iter.rs | 3 +- src/utils/combine.rs | 8 +- src/utils/ddispatch.rs | 6 +- src/utils/join.rs | 2 +- src/utils/mod.rs | 6 +- src/utils/pure_seq.rs | 5 +- src/utils/sequence.rs | 15 +- src/utils/side.rs | 30 +- src/utils/string_from_charset.rs | 14 +- src/virt_fs/common.rs | 48 +- src/virt_fs/decl.rs | 38 +- src/virt_fs/dir.rs | 40 +- src/virt_fs/embed.rs | 7 +- src/virt_fs/mod.rs | 4 +- src/virt_fs/prefix.rs | 24 +- 147 files changed, 4121 insertions(+), 4203 deletions(-) create mode 100644 examples/protocol/main.orc create mode 100644 language-server-log.txt create mode 100644 src/bin/features/shared.rs create mode 100644 src/bin/features/tests.rs delete mode 100644 src/facade/pre_macro.rs create mode 100644 src/facade/unbound_ref.rs delete mode 100644 src/foreign/implementations.rs rename src/libs/std/{functional.orc => fn.orc} (100%) create mode 100644 src/libs/std/protocol.orc delete mode 100644 src/libs/std/tstring.rs delete mode 100644 src/parse/string.rs rename src/pipeline/{load_solution.rs => load_project.rs} (67%) diff --git a/Cargo.lock b/Cargo.lock index 51367bd..6efbf84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,20 +4,21 @@ version = 3 [[package]] name = "ahash" -version = "0.8.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +checksum = "d713b3834d76b85304d4d525563c1276e2e30dc97cc67bfb4585a4a29fc2c89f" dependencies = [ "cfg-if", "once_cell", "version_check", + "zerocopy", ] [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] @@ -30,9 +31,9 @@ checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "96b09b5178381e0874812a9b157f7fe84982617e48f71f4e3235482775e5b540" dependencies = [ "anstyle", "anstyle-parse", @@ -44,33 +45,33 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" -version = "0.2.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" dependencies = [ "anstyle", "windows-sys", @@ -99,12 +100,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - [[package]] name = "block-buffer" version = "0.10.4" @@ -115,21 +110,21 @@ dependencies = [ ] [[package]] -name = "bstr" -version = "1.5.0" +name = "bound" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +checksum = "6021ae095f16f54aaae093f4c723700430e71eab731d3b0a07fc8fe258fd5110" + +[[package]] +name = "bstr" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", ] -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - [[package]] name = "cfg-if" version = "1.0.0" @@ -138,9 +133,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.4.7" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac495e00dcec98c83465d5ad66c5c4fabd652fd6686e7c6269b117e729a6f17b" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" dependencies = [ "clap_builder", "clap_derive", @@ -148,9 +143,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.7" +version = "4.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77ed9a32a62e6ca27175d00d29d05ca32e396ea1eb5fb01d8256b669cec7663" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" dependencies = [ "anstream", "anstyle", @@ -160,21 +155,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.50", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "colorchoice" @@ -182,15 +177,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "const_format" version = "0.2.32" @@ -213,45 +199,37 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" [[package]] name = "crypto-common" @@ -275,42 +253,15 @@ dependencies = [ [[package]] name = "dyn-clone" -version = "1.0.11" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" +checksum = "545b22097d44f8a9581187cdf93de7a71e4722bf51200cfaba810865b49a495d" [[package]] name = "either" -version = "1.8.1" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "errno" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "generic-array" @@ -324,22 +275,22 @@ dependencies = [ [[package]] name = "globset" -version = "0.4.10" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" dependencies = [ "aho-corasick", "bstr", - "fnv", "log", - "regex", + "regex-automata", + "regex-syntax", ] [[package]] name = "hashbrown" -version = "0.14.2" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" dependencies = [ "ahash", "allocator-api2", @@ -362,19 +313,20 @@ dependencies = [ [[package]] name = "intern-all" -version = "0.2.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d79d55e732e243f6762e0fc7b245bfd9fa0e0246356ed6cfdba62d9c707e36c1" +checksum = "20c9bf7d7b0572f7b4398fddc93ac1a200a92d1ba319a27dac04649b2223c0f6" dependencies = [ "hashbrown", "lazy_static", + "trait-set", ] [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -397,48 +349,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.148" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdc71e17332e86d2e1d38c1f99edcb6288ee11b815fb1a4b049eaa2114d369b" - -[[package]] -name = "linux-raw-sys" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memorize" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b505dbd3a88b64417e29469500c32af2b538ba5f703100761f657540a1c442d" -dependencies = [ - "hashbrown", -] +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "never" @@ -448,9 +373,9 @@ checksum = "c96aba5aa877601bb3f6dd6a63a969e1f82e60646e81e71b14496995e9853c91" [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -469,20 +394,19 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "orchidlang" -version = "0.2.2" +version = "0.3.0" dependencies = [ + "bound", "clap", "const_format", "dyn-clone", "hashbrown", "intern-all", "itertools", - "memorize", "never", "once_cell", "ordered-float", "paste", - "polling", "rayon", "rust-embed", "substack", @@ -494,62 +418,42 @@ dependencies = [ [[package]] name = "ordered-float" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a540f3e3b3d7929c884e46d093d344e4e5bdeed54d08bf007df50c93cc85d5" +checksum = "a76df7075c7d4d01fdcb46c912dd17fba5b60c78ea480b475f2b6ab6f666584e" dependencies = [ "num-traits", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "polling" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e53b6af1f60f36f8c2ac2aad5459d75a5a9b4be1e8cdd40264f315d78193e531" -dependencies = [ - "cfg-if", - "concurrent-queue", - "pin-project-lite", - "rustix", - "tracing", - "windows-sys", -] +checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "rayon" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "fa7237101a77a10773db45d62004a272517633fbcc3df19d96455ede1122e051" dependencies = [ "either", "rayon-core", @@ -557,9 +461,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -571,7 +475,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -581,10 +485,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20145670ba436b55d91fc92d25e71160fbfbdd57831631c8d7d36377a476f1cb" [[package]] -name = "regex" -version = "1.7.3" +name = "regex-automata" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -593,15 +497,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "rust-embed" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -610,41 +514,28 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.13", + "syn 2.0.50", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" dependencies = [ "globset", "sha2", "walkdir", ] -[[package]] -name = "rustix" -version = "0.38.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "same-file" version = "1.0.6" @@ -655,22 +546,30 @@ dependencies = [ ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "serde" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] [[package]] -name = "serde" -version = "1.0.160" +name = "serde_derive" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -679,9 +578,9 @@ dependencies = [ [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" [[package]] name = "substack" @@ -702,9 +601,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" dependencies = [ "proc-macro2", "quote", @@ -742,23 +641,6 @@ dependencies = [ "winapi 0.2.8", ] -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" - [[package]] name = "trait-set" version = "0.3.0" @@ -772,21 +654,21 @@ dependencies = [ [[package]] name = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-xid" @@ -808,9 +690,9 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", @@ -846,9 +728,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi 0.3.9", ] @@ -861,18 +743,18 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -885,42 +767,62 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.50", +] diff --git a/Cargo.toml b/Cargo.toml index 22ae53a..0b9caac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,13 @@ [package] name = "orchidlang" -version = "0.2.2" +version = "0.3.0" edition = "2021" -license = "GPL-3.0-or-later" +license = "GPL-3.0" repository = "https://github.com/lbfalvy/orchid" description = """ An embeddable pure functional scripting language """ -authors = [ - "Lawrence Bethlenfalvy " -] +authors = ["Lawrence Bethlenfalvy "] [lib] path = "src/lib.rs" @@ -23,21 +21,21 @@ doc = false [dependencies] hashbrown = "0.14" -ordered-float = "4.1" +ordered-float = "4.2" itertools = "0.12" dyn-clone = "1.0" -clap = { version = "4.4", features = ["derive"] } trait-set = "0.3" paste = "1.0" -rust-embed = { version = "8.0", features = ["include-exclude"] } -take_mut = "0.2.2" -unicode-segmentation = "1.10.1" -polling = "3.3.0" -never = "0.1.0" -memorize = "2.0.0" -substack = "1.1.0" -rayon = "1.8.0" -intern-all = "0.2.0" -once_cell = "1.19.0" -const_format = "0.2.32" -termsize = "0.1.6" +rust-embed = { version = "8.2", features = ["include-exclude"] } +take_mut = "0.2" +unicode-segmentation = "1.11" +never = "0.1" +substack = "1.1" +intern-all = "0.4.1" +once_cell = "1.19" +const_format = "0.2" +bound = "0.5" +# Dependencies of orcx +clap = { version = "4.5", features = ["derive"] } +rayon = "1.8" +termsize = "0.1" diff --git a/ROADMAP.md b/ROADMAP.md index a589198..101c47b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -2,15 +2,15 @@ This document is a wishlist, its items aren't ordered in any way other than inli # Language -## Typeclasses -Elixir-style protocols probably, only with n-ary dispatch which I saw in SICP-js +None! Thanks to very aggressive modularization, changes to the core language are almost never needed to achieve specific goals # Rules ## Placeholder constraints Simultaneously match a pattern to a subexpression and give it a name to copy it over -- Copy unique 1->1 names over by default to preserve their location info +## Role annotations +Some way for the rule repository to record the roles certain tokens took in patterns, and some way for the macros to attach semantic information to these roles, so that dev tooling can understand the purpose of each token # STL @@ -20,11 +20,8 @@ Functions for each command type which destructure it and pass it to an Orchid ca ## Runtime error handling result? multipath cps utils? Not sure yet. -## Pattern matching -This was the main trick in Orchid, still want to do it, still need to polish the language first - ## Macro error handling -Error tokens with rules to lift them out. Kinda depends on preservation of location info in rules to be really useful +Error tokens with rules to lift them out. # Systems @@ -36,3 +33,11 @@ Event-driven I/O with single-fire events and resubscription to relay backpressur ## New: Marshall Serialization of Orchid data, including code, given customizable sets of serializable foreign items. Alternatively, code reflection so that all this can go in the STL + +# Miscellaneous + +## Language server +A very rudimentary language server to visually indicate what the macros do to your code + +## Type checker +In the distant hopeful future, I'd like to support a type system diff --git a/clippy.toml b/clippy.toml index 5e30523..de7abb5 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1 +1,2 @@ type-complexity-threshold = 300 +avoid-breaking-exported-api = false diff --git a/examples/calculator/main.orc b/examples/calculator/main.orc index e3f983e..80fecd0 100644 --- a/examples/calculator/main.orc +++ b/examples/calculator/main.orc @@ -1,14 +1,12 @@ -import std::(to_float, to_string, panic, string::char_at) +import std::(panic, string::char_at) +import std::conv::(to_float, to_string) export const main := do{ - cps print "left operand: "; - cps data = readln; + cps data = prompt "left operand: "; let a = to_float data; - cps print "operator: "; - cps op = readln; - cps println ("you selected \"" ++ op ++ "\""); - cps print "right operand: "; - cps data = readln; + cps op = prompt "operator: "; + cps println "you selected \"${op}\""; + cps data = prompt "right operand: "; let b = to_float data; let result = ( if op == "+" then a + b @@ -17,6 +15,6 @@ export const main := do{ else if op == "/" then a / b else (panic "Unsupported operation") ); - cps println ("Result: " ++ to_string result); - 0 + cps println "Result: ${result}"; + exit_status::success } diff --git a/examples/file-browser/main.orc b/examples/file-browser/main.orc index 59906b8..68dd365 100644 --- a/examples/file-browser/main.orc +++ b/examples/file-browser/main.orc @@ -1,46 +1,35 @@ import system::(io, fs, async) -import std::(to_string, to_uint, inspect) +import std::(conv::(to_string, to_uint), inspect) ---[ -const folder_view_old := \path. do{ - cps println $ "Contents of " ++ fs::os_print path; +const folder_view := \path. do cps { + cps println $ "Contents of ${path}"; cps entries = async::block_on $ fs::read_dir path; cps list::enumerate entries |> list::map ((t[id, t[name, is_dir]]) => - println $ to_string id ++ ": " ++ fs::os_print name ++ if is_dir then "/" else "" + println $ "${id}: ${name}" ++ if is_dir then "/" else "" ) |> list::chain; - cps print "select an entry, or .. to move up: "; - cps choice = readln; - if (choice == "..") then do { - let parent_path = fs::pop_path path - |> option::assume - |> tuple::pick 0 2; - next parent_path - } else do { + cps choice = prompt "select an entry, or .. to move up: "; + cps new_path = if choice == ".." then do cps { + let t[parent_path, _] = fs::pop_path path + |> option::assume; + cps pass parent_path; + } else do cps { let t[subname, is_dir] = to_uint choice |> (list::get entries) |> option::assume; let subpath = fs::join_paths path subname; - if is_dir then next subpath - else do { + cps if is_dir then pass subpath else do cps { cps file = async::block_on $ fs::read_file subpath; cps contents = async::block_on $ io::read_string file; cps println contents; - next path - } - } -} -]-- - -const folder_view := \path. do cps { - cps println $ "Contents of " ++ fs::os_print path; - cps entries = async::block_on $ fs::read_dir path; - let t[name, is_dir] = option::assume $ list::get entries 0; - cps println $ to_string name ++ " " ++ fs::os_print is_dir + cps _ = prompt "Hit Enter to return to the parent directory: "; + cps pass path; + }; + }; + cps pass new_path; } const main := loop_over (path = fs::cwd) { - cps folder_view path; + cps path = folder_view path; } - diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index d01a002..5fcba9c 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,10 +1 @@ -import std::exit_status -import std::conv -import std::number -import std::tuple -import std::list - -const main := match t["set", "foo", 1] { - t[= "set", key, val] => - $"Setting ${ key ++ $"${1 + 1}" } to ${val}" -} +const main := println "Hello World!" exit_status::success diff --git a/examples/list-processing/main.orc b/examples/list-processing/main.orc index 7700818..6004f99 100644 --- a/examples/list-processing/main.orc +++ b/examples/list-processing/main.orc @@ -9,5 +9,5 @@ export const main := do{ |> list::reduce ((a, b) => a + b) |> option::assume; cps println $ to_string sum; - 0 + exit_status::success } diff --git a/examples/maps/main.orc b/examples/maps/main.orc index 2c8b0f1..2c781d1 100644 --- a/examples/maps/main.orc +++ b/examples/maps/main.orc @@ -1,4 +1,4 @@ -import std::to_string +import std::conv::to_string export const main := do{ let foo = map::new[ @@ -10,5 +10,5 @@ export const main := do{ let num = map::get foo "bar" |> option::assume; cps println $ to_string num; - 0 + exit_status::success } diff --git a/examples/match/main.orc b/examples/match/main.orc index 053bfb3..812350c 100644 --- a/examples/match/main.orc +++ b/examples/match/main.orc @@ -15,9 +15,9 @@ const bar := map::new[ ] const test2 := match bar { - map::having ["is_alive" = true, "greeting" = foo] => foo + map::having ["is_alive" = true, "greeting" = hello, "name" = name] => hello } -const tests := test2 ++ ", " ++ test1 +const tests := "${test2}, ${test1}" const main := conv::to_string bar diff --git a/examples/protocol/main.orc b/examples/protocol/main.orc new file mode 100644 index 0000000..9fe327b --- /dev/null +++ b/examples/protocol/main.orc @@ -0,0 +1,23 @@ +protocol animal ( + const noise := vcall "noise" +) + +type dog ( + const new := \name. wrap name + impl super::animal := map::new [ + "noise" = \dog. "${dog}: Woof!" + ] +) + +type cat ( + const new := wrap 0 + impl super::animal := map::new [ + "noise" = \_. "a cat: Mew!" + ] +) + +const main := do { + list::new [dog::new "Pavlov", cat::new] + |> list::map (\a. println $ animal::noise a) + |> list::chain exit_status::success +} diff --git a/language-server-log.txt b/language-server-log.txt new file mode 100644 index 0000000..e3b9287 --- /dev/null +++ b/language-server-log.txt @@ -0,0 +1,9 @@ +Content-Length: 5860 + +Content-Length: 5860 + +Content-Length: 5860 + +Content-Length: 5860 + +{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"processId":85579,"clientInfo":{"name":"Visual Studio Code","version":"1.86.0"},"locale":"en","rootPath":"/home/lbfalvy/Code/orchid","rootUri":"file:///home/lbfalvy/Code/orchid","capabilities":{"workspace":{"applyEdit":true,"workspaceEdit":{"documentChanges":true,"resourceOperations":["create","rename","delete"],"failureHandling":"textOnlyTransactional","normalizesLineEndings":true,"changeAnnotationSupport":{"groupsOnLabel":true}},"configuration":true,"didChangeWatchedFiles":{"dynamicRegistration":true,"relativePatternSupport":true},"symbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"tagSupport":{"valueSet":[1]},"resolveSupport":{"properties":["location.range"]}},"codeLens":{"refreshSupport":true},"executeCommand":{"dynamicRegistration":true},"didChangeConfiguration":{"dynamicRegistration":true},"workspaceFolders":true,"foldingRange":{"refreshSupport":true},"semanticTokens":{"refreshSupport":true},"fileOperations":{"dynamicRegistration":true,"didCreate":true,"didRename":true,"didDelete":true,"willCreate":true,"willRename":true,"willDelete":true},"inlineValue":{"refreshSupport":true},"inlayHint":{"refreshSupport":true},"diagnostics":{"refreshSupport":true}},"textDocument":{"publishDiagnostics":{"relatedInformation":true,"versionSupport":false,"tagSupport":{"valueSet":[1,2]},"codeDescriptionSupport":true,"dataSupport":true},"synchronization":{"dynamicRegistration":true,"willSave":true,"willSaveWaitUntil":true,"didSave":true},"completion":{"dynamicRegistration":true,"contextSupport":true,"completionItem":{"snippetSupport":true,"commitCharactersSupport":true,"documentationFormat":["markdown","plaintext"],"deprecatedSupport":true,"preselectSupport":true,"tagSupport":{"valueSet":[1]},"insertReplaceSupport":true,"resolveSupport":{"properties":["documentation","detail","additionalTextEdits"]},"insertTextModeSupport":{"valueSet":[1,2]},"labelDetailsSupport":true},"insertTextMode":2,"completionItemKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25]},"completionList":{"itemDefaults":["commitCharacters","editRange","insertTextFormat","insertTextMode","data"]}},"hover":{"dynamicRegistration":true,"contentFormat":["markdown","plaintext"]},"signatureHelp":{"dynamicRegistration":true,"signatureInformation":{"documentationFormat":["markdown","plaintext"],"parameterInformation":{"labelOffsetSupport":true},"activeParameterSupport":true},"contextSupport":true},"definition":{"dynamicRegistration":true,"linkSupport":true},"references":{"dynamicRegistration":true},"documentHighlight":{"dynamicRegistration":true},"documentSymbol":{"dynamicRegistration":true,"symbolKind":{"valueSet":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26]},"hierarchicalDocumentSymbolSupport":true,"tagSupport":{"valueSet":[1]},"labelSupport":true},"codeAction":{"dynamicRegistration":true,"isPreferredSupport":true,"disabledSupport":true,"dataSupport":true,"resolveSupport":{"properties":["edit"]},"codeActionLiteralSupport":{"codeActionKind":{"valueSet":["","quickfix","refactor","refactor.extract","refactor.inline","refactor.rewrite","source","source.organizeImports"]}},"honorsChangeAnnotations":true},"codeLens":{"dynamicRegistration":true},"formatting":{"dynamicRegistration":true},"rangeFormatting":{"dynamicRegistration":true,"rangesSupport":true},"onTypeFormatting":{"dynamicRegistration":true},"rename":{"dynamicRegistration":true,"prepareSupport":true,"prepareSupportDefaultBehavior":1,"honorsChangeAnnotations":true},"documentLink":{"dynamicRegistration":true,"tooltipSupport":true},"typeDefinition":{"dynamicRegistration":true,"linkSupport":true},"implementation":{"dynamicRegistration":true,"linkSupport":true},"colorProvider":{"dynamicRegistration":true},"foldingRange":{"dynamicRegistration":true,"rangeLimit":5000,"lineFoldingOnly":true,"foldingRangeKind":{"valueSet":["comment","imports","region"]},"foldingRange":{"collapsedText":false}},"declaration":{"dynamicRegistration":true,"linkSupport":true},"selectionRange":{"dynamicRegistration":true},"callHierarchy":{"dynamicRegistration":true},"semanticTokens":{"dynamicRegistration":true,"tokenTypes":["namespace","type","class","enum","interface","struct","typeParameter","parameter","variable","property","enumMember","event","function","method","macro","keyword","modifier","comment","string","number","regexp","operator","decorator"],"tokenModifiers":["declaration","definition","readonly","static","deprecated","abstract","async","modification","documentation","defaultLibrary"],"formats":["relative"],"requests":{"range":true,"full":{"delta":true}},"multilineTokenSupport":false,"overlappingTokenSupport":false,"serverCancelSupport":true,"augmentsSyntaxTokens":true},"linkedEditingRange":{"dynamicRegistration":true},"typeHierarchy":{"dynamicRegistration":true},"inlineValue":{"dynamicRegistration":true},"inlayHint":{"dynamicRegistration":true,"resolveSupport":{"properties":["tooltip","textEdits","label.tooltip","label.location","label.command"]}},"diagnostic":{"dynamicRegistration":true,"relatedDocumentSupport":false}},"window":{"showMessage":{"messageActionItem":{"additionalPropertiesSupport":true}},"showDocument":{"support":true},"workDoneProgress":true},"general":{"staleRequestSupport":{"cancel":true,"retryOnContentModified":["textDocument/semanticTokens/full","textDocument/semanticTokens/range","textDocument/semanticTokens/full/delta"]},"regularExpressions":{"engine":"ECMAScript","version":"ES2020"},"markdown":{"parser":"marked","version":"1.1.0"},"positionEncodings":["utf-16"]},"notebookDocument":{"synchronization":{"dynamicRegistration":true,"executionSummarySupport":true}}},"trace":"off","workspaceFolders":[{"uri":"file:///home/lbfalvy/Code/orchid","name":"orchid"}]}} \ No newline at end of file diff --git a/src/bin/features/macro_debug.rs b/src/bin/features/macro_debug.rs index 1aae14a..545d28f 100644 --- a/src/bin/features/macro_debug.rs +++ b/src/bin/features/macro_debug.rs @@ -1,49 +1,61 @@ use itertools::Itertools; +use orchidlang::error::Reporter; use orchidlang::facade::macro_runner::MacroRunner; -use orchidlang::libs::std::exit_status::ExitStatus; +use orchidlang::libs::std::exit_status::OrcExitStatus; +use orchidlang::location::{CodeGenInfo, CodeLocation}; use orchidlang::name::Sym; +use orchidlang::pipeline::project::{ItemKind, ProjItem, ProjectTree}; +use orchidlang::sym; use crate::cli::cmd_prompt; -/// A little utility to step through the resolution of a macro set -pub fn main(macro_runner: MacroRunner, sym: Sym) -> ExitStatus { - let outname = sym.iter().join("::"); - let (mut code, location) = match macro_runner.consts.get(&sym) { - Some(rep) => (rep.value.clone(), rep.range.clone()), - None => { - let valid = macro_runner.consts.keys(); - let valid_str = valid.map(|t| t.iter().join("::")).join("\n\t"); - eprintln!("Symbol {outname} not found\nvalid symbols: \n\t{valid_str}\n"); - return ExitStatus::Failure; +/// A little utility to step through the reproject of a macro set +pub fn main(tree: ProjectTree, symbol: Sym) -> OrcExitStatus { + print!("Macro debugger starting on {symbol}"); + let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::macro_runner))); + let expr_ent = match tree.0.walk1_ref(&[], &symbol[..], |_| true) { + Ok((e, _)) => e.clone(), + Err(e) => { + eprintln!("{}", e.at(&location.origin())); + return OrcExitStatus::Failure; }, }; - print!("Debugging macros in {outname} defined at {location}"); - println!("\nInitial state: {code}"); + let mut expr = match expr_ent.item() { + Some(ProjItem { kind: ItemKind::Const(c) }) => c.clone(), + _ => { + eprintln!("macro-debug argument must be a constant"); + return OrcExitStatus::Failure; + }, + }; + let reporter = Reporter::new(); + let macro_runner = MacroRunner::new(&tree, None, &reporter); + reporter.assert_exit(); + println!("\nInitial state: {expr}"); // print_for_debug(&code); - let mut steps = macro_runner.step(sym).enumerate(); + let mut steps = macro_runner.step(expr.clone()).enumerate(); loop { let (cmd, _) = cmd_prompt("\ncmd> ").unwrap(); match cmd.trim() { "" | "n" | "next" => match steps.next() { None => print!("Halted"), Some((idx, c)) => { - code = c; - print!("Step {idx}: {code}"); + expr = c; + print!("Step {idx}: {expr}"); }, }, "p" | "print" => { - let glossary = code.value.collect_names(); - let gl_str = glossary.iter().map(|t| t.iter().join("::")).join(", "); - print!("code: {code}\nglossary: {gl_str}") + let glossary = expr.value.collect_names(); + let gl_str = glossary.iter().join(", "); + print!("code: {expr}\nglossary: {gl_str}") }, "d" | "dump" => print!("Rules: {}", macro_runner.repo), - "q" | "quit" => return ExitStatus::Success, + "q" | "quit" => return OrcExitStatus::Success, "complete" => { match steps.last() { Some((idx, c)) => print!("Step {idx}: {c}"), None => print!("Already halted"), } - return ExitStatus::Success; + return OrcExitStatus::Success; }, "h" | "help" => print!( "Available commands: diff --git a/src/bin/features/mod.rs b/src/bin/features/mod.rs index fc9f03b..166b976 100644 --- a/src/bin/features/mod.rs +++ b/src/bin/features/mod.rs @@ -1,2 +1,4 @@ pub mod macro_debug; pub mod print_project; +pub mod shared; +pub mod tests; diff --git a/src/bin/features/print_project.rs b/src/bin/features/print_project.rs index abcf788..f554df1 100644 --- a/src/bin/features/print_project.rs +++ b/src/bin/features/print_project.rs @@ -10,11 +10,7 @@ pub struct ProjPrintOpts { fn indent(amount: u16) -> String { " ".repeat(amount.into()) } -pub fn print_proj_mod( - module: &ProjectMod, - lvl: u16, - opts: ProjPrintOpts, -) -> String { +pub fn print_proj_mod(module: &ProjectMod, lvl: u16, opts: ProjPrintOpts) -> String { let mut acc = String::new(); let tab = indent(lvl); for (key, ModEntry { member, x }) in &module.entries { diff --git a/src/bin/features/shared.rs b/src/bin/features/shared.rs new file mode 100644 index 0000000..82f505e --- /dev/null +++ b/src/bin/features/shared.rs @@ -0,0 +1,64 @@ +use std::io::BufReader; +use std::thread; + +use orchidlang::facade::loader::Loader; +use orchidlang::libs::asynch::system::AsynchSystem; +use orchidlang::libs::directfs::DirectFS; +use orchidlang::libs::io::{IOService, Sink, Source, Stream}; +use orchidlang::libs::scheduler::system::SeqScheduler; +use orchidlang::libs::std::std_system::StdConfig; + +pub fn stdin_source() -> Source { BufReader::new(Box::new(std::io::stdin())) } +pub fn stdout_sink() -> Sink { Box::new(std::io::stdout()) } +pub fn stderr_sink() -> Sink { Box::new(std::io::stderr()) } + +pub fn with_std_env(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T { + with_env(stdin_source(), stdout_sink(), stderr_sink(), cb) +} + +pub fn with_env( + stdin: Source, + stdout: Sink, + stderr: Sink, + cb: impl for<'a> FnOnce(Loader<'a>) -> T, +) -> T { + let mut asynch = AsynchSystem::new(); + let scheduler = SeqScheduler::new(&mut asynch); + let std_streams = [ + ("stdin", Stream::Source(stdin)), + ("stdout", Stream::Sink(stdout)), + ("stderr", Stream::Sink(stderr)), + ]; + let env = Loader::new() + .add_system(StdConfig { impure: true }) + .add_system(asynch) + .add_system(scheduler.clone()) + .add_system(IOService::new(scheduler.clone(), std_streams)) + .add_system(DirectFS::new(scheduler)); + cb(env) +} + +pub fn worker_cnt() -> usize { thread::available_parallelism().map(usize::from).unwrap_or(1) } + +macro_rules! unwrap_exit { + ($param:expr) => { + match $param { + Ok(v) => v, + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + }, + } + }; + ($param:expr; $error:expr) => { + match $param { + Ok(v) => v, + Err(e) => { + eprintln!("{e}"); + return $error; + }, + } + }; +} + +pub(crate) use unwrap_exit; diff --git a/src/bin/features/tests.rs b/src/bin/features/tests.rs new file mode 100644 index 0000000..e25c7aa --- /dev/null +++ b/src/bin/features/tests.rs @@ -0,0 +1,111 @@ +use std::fmt; +use std::io::BufReader; +use std::path::Path; + +use hashbrown::HashMap; +use itertools::Itertools; +use orchidlang::error::{ProjectError, ProjectResult, Reporter}; +use orchidlang::facade::loader::Loader; +use orchidlang::facade::macro_runner::MacroRunner; +use orchidlang::facade::merge_trees::NortConst; +use orchidlang::facade::process::Process; +use orchidlang::foreign::error::{RTError, RTErrorObj, RTResult}; +use orchidlang::foreign::inert::Inert; +use orchidlang::interpreter::error::RunError; +use orchidlang::interpreter::nort; +use orchidlang::libs::io::{Sink, Source}; +use orchidlang::libs::std::exit_status::OrcExitStatus; +use orchidlang::name::Sym; +use rayon::iter::ParallelIterator; +use rayon::slice::ParallelSlice; + +use super::shared::{with_env, worker_cnt}; + +pub fn mock_source() -> Source { BufReader::new(Box::new(&[][..])) } +pub fn mock_sink() -> Sink { Box::>::default() } +pub fn with_mock_env(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T { + with_env(mock_source(), mock_sink(), mock_sink(), cb) +} + +#[derive(Clone)] +pub struct TestDidNotHalt(Sym); +impl RTError for TestDidNotHalt {} +impl fmt::Display for TestDidNotHalt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Test {} did not halt", self.0) + } +} + +#[derive(Clone)] +pub struct TestDidNotSucceed(Sym, nort::Expr); +impl RTError for TestDidNotSucceed {} +impl fmt::Display for TestDidNotSucceed { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Test {} settled on {}", self.0, self.1) + } +} + +pub fn run_test(proc: &mut Process, name: Sym, data: NortConst) -> RTResult<()> { + let res = proc.run(data.value, Some(10_000)).map_err(|e| match e { + RunError::Extern(e) => e, + RunError::Interrupted(_) => TestDidNotHalt(name.clone()).pack(), + })?; + match res.clone().downcast()? { + Inert(OrcExitStatus::Success) => Ok(()), + _ => Err(TestDidNotSucceed(name, res).pack()), + } +} +pub fn run_tests( + dir: &Path, + macro_limit: usize, + threads: Option, + tests: &[(Sym, NortConst)], +) -> ProjectResult<()> { + with_mock_env(|env| { + let reporter = Reporter::new(); + env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter); + reporter.bind() + })?; + let threads = threads.unwrap_or_else(worker_cnt); + rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap(); + let batch_size = tests.len().div_ceil(threads); + let errors = (tests.par_chunks(batch_size)) + .map(|tests| { + with_mock_env(|env| { + let reporter = Reporter::new(); + let mut proc = env.proc_dir(dir.to_owned(), true, Some(macro_limit), &reporter); + reporter.assert(); // checked above + (tests.iter()) + .filter_map(|(test, constant)| { + Some((test.clone(), run_test(&mut proc, test.clone(), constant.clone()).err()?)) + }) + .collect_vec() + }) + }) + .collect::>() + .into_iter() + .flatten() + .collect::>(); + if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) } +} + +pub struct TestsFailed(HashMap); +impl ProjectError for TestsFailed { + const DESCRIPTION: &'static str = "Various tests failed"; + fn message(&self) -> String { + ([format!("{} tests failed. Errors:", self.0.len())].into_iter()) + .chain(self.0.iter().map(|(k, e)| format!("In {k}, {e}"))) + .join("\n") + } +} + +pub fn get_tree_tests(dir: &Path, reporter: &Reporter) -> ProjectResult> { + with_mock_env(|env| { + let tree = env.load_dir(dir.to_owned(), reporter); + let tree = MacroRunner::new(&tree, Some(10_000), reporter).run_macros(tree, reporter); + (tree.all_consts().into_iter()) + .filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test")) + .map(|(k, v)| Ok((k.clone(), NortConst::convert_from(v, reporter)))) + .collect::>>() + }) +} diff --git a/src/bin/orcx.rs b/src/bin/orcx.rs index b82f919..fabda21 100644 --- a/src/bin/orcx.rs +++ b/src/bin/orcx.rs @@ -2,37 +2,37 @@ mod cli; mod features; use std::fs::File; -use std::io::BufReader; -use std::num::NonZeroUsize; -use std::path::{Path, PathBuf}; +use std::io::{stdin, stdout, Write}; +use std::path::PathBuf; use std::process::ExitCode; -use std::thread::available_parallelism; use clap::{Parser, Subcommand}; -use hashbrown::{HashMap, HashSet}; +use hashbrown::HashSet; use itertools::Itertools; -use orchidlang::error::{ProjectError, ProjectErrorObj, ProjectResult}; -use orchidlang::facade::loader::Loader; +use never::Never; +use orchidlang::error::Reporter; use orchidlang::facade::macro_runner::MacroRunner; -use orchidlang::facade::merge_trees::merge_trees; +use orchidlang::facade::merge_trees::{merge_trees, NortConst}; use orchidlang::facade::process::Process; use orchidlang::foreign::inert::Inert; -use orchidlang::interpreter::context::Halt; -use orchidlang::interpreter::nort; -use orchidlang::libs::asynch::system::AsynchSystem; -use orchidlang::libs::directfs::DirectFS; -use orchidlang::libs::io::{IOService, Stream}; -use orchidlang::libs::scheduler::system::SeqScheduler; -use orchidlang::libs::std::exit_status::ExitStatus; -use orchidlang::libs::std::std_system::StdConfig; -use orchidlang::location::{CodeGenInfo, CodeLocation}; +use orchidlang::gen::tpl; +use orchidlang::gen::traits::Gen; +use orchidlang::interpreter::gen_nort::nort_gen; +use orchidlang::interpreter::nort::{self}; +use orchidlang::libs::std::exit_status::OrcExitStatus; +use orchidlang::libs::std::string::OrcString; +use orchidlang::location::{CodeGenInfo, CodeLocation, SourceRange}; use orchidlang::name::Sym; +use orchidlang::parse::context::FlatLocContext; +use orchidlang::parse::lexer::{lex, Lexeme}; +use orchidlang::sym; use orchidlang::tree::{ModMemberRef, TreeTransforms}; -use rayon::prelude::ParallelIterator; -use rayon::slice::ParallelSlice; +use orchidlang::virt_fs::{decl_file, DeclTree}; use crate::features::macro_debug; use crate::features::print_project::{print_proj_mod, ProjPrintOpts}; +use crate::features::shared::{stderr_sink, stdout_sink, unwrap_exit, with_env, with_std_env}; +use crate::features::tests::{get_tree_tests, mock_source, run_test, run_tests, with_mock_env}; #[derive(Subcommand, Debug)] enum Command { @@ -58,6 +58,7 @@ enum Command { #[arg(long)] width: Option, }, + Repl, } /// Orchid interpreter #[derive(Parser, Debug)] @@ -111,132 +112,35 @@ impl Args { pub fn chk_proj(&self) -> Result<(), String> { self.chk_dir_main() } } -macro_rules! unwrap_exit { - ($param:expr) => { - match $param { - Ok(v) => v, - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; - }, - } - }; -} - -pub fn with_std_proc( - dir: &Path, - macro_limit: usize, - f: impl for<'a> FnOnce(Process<'a>) -> ProjectResult, -) -> ProjectResult { - with_std_env(|env| { - let mr = MacroRunner::new(&env.load_dir(dir.to_owned())?)?; - let source_syms = mr.run_macros(Some(macro_limit))?; - let consts = merge_trees(source_syms, env.systems())?; - let proc = Process::new(consts, env.handlers()); - f(proc) - }) -} - -// TODO -pub fn run_test(proc: &mut Process, name: Sym) -> ProjectResult<()> { Ok(()) } -pub fn run_tests( - dir: &Path, - macro_limit: usize, - threads: Option, - tests: &[Sym], -) -> ProjectResult<()> { - with_std_proc(dir, macro_limit, |proc| proc.validate_refs())?; - let threads = threads - .or_else(|| available_parallelism().ok().map(NonZeroUsize::into)) - .unwrap_or(1); - rayon::ThreadPoolBuilder::new().num_threads(threads).build_global().unwrap(); - let batch_size = tests.len().div_ceil(threads); - let errors = tests - .par_chunks(batch_size) - .map(|tests| { - let res = with_std_proc(dir, macro_limit, |mut proc| { - let mut errors = HashMap::new(); - for test in tests { - if let Err(e) = run_test(&mut proc, test.clone()) { - errors.insert(test.clone(), e); - } - } - Ok(errors) - }); - res.expect("Tested earlier") - }) - .reduce(HashMap::new, |l, r| l.into_iter().chain(r).collect()); - if errors.is_empty() { Ok(()) } else { Err(TestsFailed(errors).pack()) } -} - -pub struct TestsFailed(HashMap); -impl ProjectError for TestsFailed { - const DESCRIPTION: &'static str = "Various tests failed"; - fn message(&self) -> String { - format!( - "{} tests failed. Errors:\n{}", - self.0.len(), - self.0.iter().map(|(k, e)| format!("In {k}, {e}")).join("\n") - ) - } -} - -fn get_tree_tests(dir: &Path) -> ProjectResult> { - with_std_env(|env| { - env.load_dir(dir.to_owned()).map(|tree| { - (tree.all_consts().into_iter()) - .filter(|(_, rep)| rep.comments.iter().any(|s| s.trim() == "test")) - .map(|(k, _)| k.clone()) - .collect::>() - }) - }) -} - -pub fn with_std_env(cb: impl for<'a> FnOnce(Loader<'a>) -> T) -> T { - let mut asynch = AsynchSystem::new(); - let scheduler = SeqScheduler::new(&mut asynch); - let std_streams = [ - ("stdin", Stream::Source(BufReader::new(Box::new(std::io::stdin())))), - ("stdout", Stream::Sink(Box::new(std::io::stdout()))), - ("stderr", Stream::Sink(Box::new(std::io::stderr()))), - ]; - let env = Loader::new() - .add_system(StdConfig { impure: true }) - .add_system(asynch) - .add_system(scheduler.clone()) - .add_system(IOService::new(scheduler.clone(), std_streams)) - .add_system(DirectFS::new(scheduler)); - cb(env) -} - pub fn main() -> ExitCode { let args = Args::parse(); unwrap_exit!(args.chk_proj()); let dir = PathBuf::from(args.dir); - let main = args.main.map_or_else( - || Sym::literal("tree::main::main"), - |main| Sym::parse(&main).expect("--main cannot be empty"), - ); + let main_s = args.main.as_ref().map_or("tree::main::main", |s| s); + let main = Sym::parse(main_s).expect("--main cannot be empty"); + let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orcx::entrypoint))); + let reporter = Reporter::new(); // subcommands + #[allow(clippy::blocks_in_conditions)] match args.command { - Some(Command::ListMacros) => with_std_env(|env| { - let tree = unwrap_exit!(env.load_main(dir, main)); - let mr = unwrap_exit!(MacroRunner::new(&tree)); + Some(Command::ListMacros) => with_mock_env(|env| { + let tree = env.load_main(dir, [main], &reporter); + let mr = MacroRunner::new(&tree, None, &reporter); println!("Parsed rules: {}", mr.repo); ExitCode::SUCCESS }), Some(Command::ProjectTree { hide_locations, width }) => { - let tree = unwrap_exit!(with_std_env(|env| env.load_main(dir, main))); + let tree = with_mock_env(|env| env.load_main(dir, [main], &reporter)); let w = width.or_else(|| termsize::get().map(|s| s.cols)).unwrap_or(74); let print_opts = ProjPrintOpts { width: w, hide_locations }; println!("Project tree: {}", print_proj_mod(&tree.0, 0, print_opts)); ExitCode::SUCCESS }, - Some(Command::MacroDebug { symbol }) => with_std_env(|env| { - let tree = unwrap_exit!(env.load_main(dir, main)); + Some(Command::MacroDebug { symbol }) => with_mock_env(|env| { + let tree = env.load_main(dir, [main], &reporter); let symbol = Sym::parse(&symbol).expect("macro-debug needs an argument"); - macro_debug::main(unwrap_exit!(MacroRunner::new(&tree)), symbol).code() + macro_debug::main(tree, symbol).code() }), Some(Command::Test { only: Some(_), threads: Some(_), .. }) => { eprintln!( @@ -253,25 +157,33 @@ pub fn main() -> ExitCode { ExitCode::FAILURE }, Some(Command::Test { only: None, threads, system: None }) => { - let tree_tests = unwrap_exit!(get_tree_tests(&dir)); + let tree_tests = reporter.unwrap_exit(get_tree_tests(&dir, &reporter)); unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tree_tests)); ExitCode::SUCCESS }, Some(Command::Test { only: Some(symbol), threads: None, system: None }) => { let symbol = Sym::parse(&symbol).expect("Test needs an argument"); - unwrap_exit!(run_tests(&dir, args.macro_limit, Some(1), &[symbol])); - ExitCode::SUCCESS + with_env(mock_source(), stdout_sink(), stderr_sink(), |env| { + // iife in lieu of try blocks + let tree = env.load_main(dir.clone(), [symbol.clone()], &reporter); + let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); + let consts = mr.run_macros(tree, &reporter).all_consts(); + let test = consts.get(&symbol).expect("Test not found"); + let nc = NortConst::convert_from(test.clone(), &reporter); + let mut proc = Process::new(merge_trees(consts, env.systems(), &reporter), env.handlers()); + unwrap_exit!(run_test(&mut proc, symbol.clone(), nc.clone())); + ExitCode::SUCCESS + }) }, Some(Command::Test { only: None, threads, system: Some(system) }) => { - let subtrees = unwrap_exit!(with_std_env(|env| { + let subtrees = unwrap_exit!(with_mock_env(|env| { match env.systems().find(|s| s.name == system) { None => Err(format!("System {system} not found")), Some(sys) => { let mut paths = HashSet::new(); sys.code.search_all((), |path, node, ()| { if matches!(node, ModMemberRef::Item(_)) { - let name = Sym::new(path.unreverse()) - .expect("Empty path means global file"); + let name = Sym::new(path.unreverse()).expect("Empty path means global file"); paths.insert(name); } }); @@ -279,48 +191,93 @@ pub fn main() -> ExitCode { }, } })); - let in_subtrees = - |sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..])); - let tests = unwrap_exit!(with_std_env(|env| -> ProjectResult<_> { - let tree = env.load_main(dir.clone(), main.clone())?; - let mr = MacroRunner::new(&tree)?; - let src_consts = mr.run_macros(Some(args.macro_limit))?; - let consts = merge_trees(src_consts, env.systems())?; - let test_names = (consts.into_iter()) - .filter(|(k, v)| { - in_subtrees(k.clone()) - && v.comments.iter().any(|c| c.trim() == "test") - }) - .map(|p| p.0) - .collect_vec(); - Ok(test_names) - })); + let in_subtrees = |sym: Sym| subtrees.iter().any(|sub| sym[..].starts_with(&sub[..])); + let tests = with_mock_env(|env| { + let tree = env.load_main(dir.clone(), [main.clone()], &reporter); + let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); + let src_consts = mr.run_macros(tree, &reporter).all_consts(); + let consts = merge_trees(src_consts, env.systems(), &reporter); + (consts.into_iter()) + .filter(|(k, v)| in_subtrees(k.clone()) && v.comments.iter().any(|c| c.trim() == "test")) + .collect_vec() + }); eprintln!("Running {} tests", tests.len()); unwrap_exit!(run_tests(&dir, args.macro_limit, threads, &tests)); eprintln!("All tests pass"); ExitCode::SUCCESS }, None => with_std_env(|env| { - let tree = unwrap_exit!(env.load_main(dir, main.clone())); - let mr = unwrap_exit!(MacroRunner::new(&tree)); - let src_consts = unwrap_exit!(mr.run_macros(Some(args.macro_limit))); - let consts = unwrap_exit!(merge_trees(src_consts, env.systems())); - let mut proc = Process::new(consts, env.handlers()); - unwrap_exit!(proc.validate_refs()); - let main = nort::Clause::Constant(main.clone()) - .to_expr(CodeLocation::Gen(CodeGenInfo::no_details("entrypoint"))); - let ret = unwrap_exit!(proc.run(main, None)); - let Halt { state, inert, .. } = ret; + let proc = env.proc_main(dir, [main.clone()], true, Some(args.macro_limit), &reporter); + reporter.assert_exit(); + let ret = unwrap_exit!(proc.run(nort::Clause::Constant(main).into_expr(location), None)); drop(proc); - assert!(inert, "Gas is not used, only inert data should be yielded"); - match state.clone().downcast() { - Ok(Inert(ExitStatus::Success)) => ExitCode::SUCCESS, - Ok(Inert(ExitStatus::Failure)) => ExitCode::FAILURE, + match ret.clone().downcast() { + Ok(Inert(OrcExitStatus::Success)) => ExitCode::SUCCESS, + Ok(Inert(OrcExitStatus::Failure)) => ExitCode::FAILURE, Err(_) => { - println!("{}", state.clause); + println!("{}", ret.clause); ExitCode::SUCCESS }, } }), + Some(Command::Repl) => with_std_env(|env| { + let sctx = env.project_ctx(&reporter); + loop { + let reporter = Reporter::new(); + print!("orc"); + let mut src = String::new(); + let mut paren_tally = 0; + loop { + print!("> "); + stdout().flush().unwrap(); + let mut buf = String::new(); + stdin().read_line(&mut buf).unwrap(); + src += &buf; + let range = SourceRange::mock(); + let spctx = sctx.parsing(range.code()); + let pctx = FlatLocContext::new(&spctx, &range); + let res = + lex(Vec::new(), &buf, &pctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}); + res.tokens.iter().for_each(|e| match &e.lexeme { + Lexeme::LP(_) => paren_tally += 1, + Lexeme::RP(_) => paren_tally -= 1, + _ => (), + }); + if 0 == paren_tally { + break; + } + } + let tree = env.load_project_main( + [sym!(tree::main::__repl_input__)], + DeclTree::ns("tree::main", [decl_file(&format!("const __repl_input__ := {src}"))]), + &reporter, + ); + let mr = MacroRunner::new(&tree, Some(args.macro_limit), &reporter); + let proj_consts = mr.run_macros(tree, &reporter).all_consts(); + let consts = merge_trees(proj_consts, env.systems(), &reporter); + let ctx = nort_gen(location.clone()); + let to_string_tpl = tpl::A(tpl::C("std::string::convert"), tpl::Slot); + if let Err(err) = reporter.bind() { + eprintln!("{err}"); + continue; + } + let proc = Process::new(consts, env.handlers()); + let prompt = tpl::C("tree::main::__repl_input__").template(ctx.clone(), []); + let out = match proc.run(prompt, Some(1000)) { + Ok(out) => out, + Err(e) => { + eprintln!("{e}"); + continue; + }, + }; + if let Ok(out) = proc.run(to_string_tpl.template(ctx, [out.clone()]), Some(1000)) { + if let Ok(s) = out.clone().downcast::>() { + println!("{}", s.0.as_str()); + continue; + } + } + println!("{out}") + } + }), } } diff --git a/src/error.rs b/src/error.rs index 5c5be40..30f952f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,28 +1,30 @@ //! Abstractions for handling various code-related errors under a common trait //! object. -use core::fmt; use std::any::Any; -use std::fmt::{Debug, Display}; +use std::cell::RefCell; use std::sync::Arc; +use std::{fmt, process}; use dyn_clone::{clone_box, DynClone}; +use itertools::Itertools; -use crate::location::CodeLocation; +use crate::location::CodeOrigin; use crate::utils::boxed_iter::{box_once, BoxedIter}; #[allow(unused)] // for doc use crate::virt_fs::CodeNotFound; /// A point of interest in resolving the error, such as the point where /// processing got stuck, a command that is likely to be incorrect +#[derive(Clone)] pub struct ErrorPosition { - /// The suspected location - pub location: CodeLocation, - /// Any information about the role of this location + /// The suspected origin + pub origin: CodeOrigin, + /// Any information about the role of this origin pub message: Option, } -impl From for ErrorPosition { - fn from(location: CodeLocation) -> Self { Self { location, message: None } } +impl From for ErrorPosition { + fn from(origin: CodeOrigin) -> Self { Self { origin, message: None } } } /// Errors addressed to the developer which are to be resolved with @@ -36,13 +38,13 @@ pub trait ProjectError: Sized + Send + Sync + 'static { /// Code positions relevant to this error. If you don't implement this, you /// must implement [ProjectError::one_position] #[must_use] - fn positions(&self) -> impl IntoIterator { - box_once(ErrorPosition { location: self.one_position(), message: None }) + fn positions(&self) -> impl IntoIterator + '_ { + box_once(ErrorPosition { origin: self.one_position(), message: None }) } - /// Short way to provide a single location. If you don't implement this, you + /// Short way to provide a single origin. If you don't implement this, you /// must implement [ProjectError::positions] #[must_use] - fn one_position(&self) -> CodeLocation { unimplemented!() } + fn one_position(&self) -> CodeOrigin { unimplemented!() } /// Convert the error into an `Arc` to be able to /// handle various errors together #[must_use] @@ -53,7 +55,11 @@ pub trait ProjectError: Sized + Send + Sync + 'static { pub trait DynProjectError: Send + Sync { /// Access type information about this error #[must_use] - fn as_any(&self) -> &dyn Any; + fn as_any_ref(&self) -> &dyn Any; + /// Pack the error into a trait object, or leave it as-is if it's already a + /// trait object + #[must_use] + fn into_packed(self: Arc) -> ProjectErrorObj; /// A general description of this type of error #[must_use] fn description(&self) -> &str; @@ -62,13 +68,14 @@ pub trait DynProjectError: Send + Sync { fn message(&self) -> String { self.description().to_string() } /// Code positions relevant to this error. #[must_use] - fn positions(&self) -> BoxedIter; + fn positions(&self) -> BoxedIter<'_, ErrorPosition>; } impl DynProjectError for T where T: ProjectError { - fn as_any(&self) -> &dyn Any { self } + fn as_any_ref(&self) -> &dyn Any { self } + fn into_packed(self: Arc) -> ProjectErrorObj { self } fn description(&self) -> &str { T::DESCRIPTION } fn message(&self) -> String { ProjectError::message(self) } fn positions(&self) -> BoxedIter { @@ -76,19 +83,27 @@ where T: ProjectError } } -impl Display for dyn DynProjectError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl DynProjectError for ProjectErrorObj { + fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() } + fn description(&self) -> &str { (**self).description() } + fn into_packed(self: Arc) -> ProjectErrorObj { (*self).clone() } + fn message(&self) -> String { (**self).message() } + fn positions(&self) -> BoxedIter<'_, ErrorPosition> { (**self).positions() } +} + +impl fmt::Display for dyn DynProjectError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let description = self.description(); let message = self.message(); let positions = self.positions().collect::>(); writeln!(f, "Project error: {description}\n{message}")?; if positions.is_empty() { - writeln!(f, "No locations specified")?; + writeln!(f, "No origins specified")?; } else { - for ErrorPosition { location, message } in positions { + for ErrorPosition { origin, message } in positions { match message { - None => writeln!(f, "@{location}"), - Some(msg) => writeln!(f, "@{location}: {msg}"), + None => writeln!(f, "@{origin}"), + Some(msg) => writeln!(f, "@{origin}: {msg}"), }? } } @@ -96,22 +111,20 @@ impl Display for dyn DynProjectError { } } -impl Debug for dyn DynProjectError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{self}") - } +impl fmt::Debug for dyn DynProjectError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") } } /// Type-erased [ProjectError] implementor through the [DynProjectError] /// object-trait pub type ProjectErrorObj = Arc; -/// Alias for a result with an error of [Rc] of [ProjectError] trait object. -/// This is the type of result most commonly returned by pre-run operations. +/// Alias for a result with an error of [ProjectErrorObj]. This is the type of +/// result most commonly returned by pre-runtime operations. pub type ProjectResult = Result; -/// A trait for error types that are only missing a location. Do not depend on -/// this trait, refer to [DynErrorSansLocation] instead. -pub trait ErrorSansLocation: Clone + Sized + Send + Sync + 'static { +/// A trait for error types that are only missing an origin. Do not depend on +/// this trait, refer to [DynErrorSansOrigin] instead. +pub trait ErrorSansOrigin: Clone + Sized + Send + Sync + 'static { /// General description of the error condition const DESCRIPTION: &'static str; /// Specific description of the error including code fragments or concrete @@ -119,67 +132,154 @@ pub trait ErrorSansLocation: Clone + Sized + Send + Sync + 'static { fn message(&self) -> String { Self::DESCRIPTION.to_string() } /// Convert the error to a type-erased structure for handling on shared /// channels - fn pack(self) -> ErrorSansLocationObj { Box::new(self) } + fn pack(self) -> ErrorSansOriginObj { Box::new(self) } + /// A shortcut to streamline switching code between [ErrorSansOriginObj] and + /// concrete types + fn bundle(self, origin: &CodeOrigin) -> ProjectErrorObj { self.pack().bundle(origin) } } -/// Object-safe equivalent to [ErrorSansLocation]. Implement that one instead of -/// this. Typically found as [ErrorSansLocationObj] -pub trait DynErrorSansLocation: Any + Send + Sync + DynClone { +/// Object-safe equivalent to [ErrorSansOrigin]. Implement that one instead of +/// this. Typically found as [ErrorSansOriginObj] +pub trait DynErrorSansOrigin: Any + Send + Sync + DynClone { /// Allow to downcast the base object to distinguish between various errors. /// The main intended purpose is to trigger a fallback when [CodeNotFound] is /// encountered, but the possibilities are not limited to that. fn as_any_ref(&self) -> &dyn Any; + /// Regularize the type + fn into_packed(self: Box) -> ErrorSansOriginObj; /// Generic description of the error condition fn description(&self) -> &str; /// Specific description of this particular error fn message(&self) -> String; + /// Add an origin + fn bundle(self: Box, origin: &CodeOrigin) -> ProjectErrorObj; } -/// Type-erased [ErrorSansLocation] implementor through the object-trait -/// [DynErrorSansLocation]. This can be turned into a [ProjectErrorObj] with -/// [bundle_location]. -pub type ErrorSansLocationObj = Box; -/// A generic project result without location -pub type ResultSansLocation = Result; +/// Type-erased [ErrorSansOrigin] implementor through the object-trait +/// [DynErrorSansOrigin]. This can be turned into a [ProjectErrorObj] with +/// [ErrorSansOriginObj::bundle]. +pub type ErrorSansOriginObj = Box; +/// A generic project result without origin +pub type ResultSansOrigin = Result; -impl DynErrorSansLocation for T { +impl DynErrorSansOrigin for T { fn description(&self) -> &str { Self::DESCRIPTION } - fn message(&self) -> String { self.message() } + fn message(&self) -> String { (*self).message() } fn as_any_ref(&self) -> &dyn Any { self } + fn into_packed(self: Box) -> ErrorSansOriginObj { (*self).pack() } + fn bundle(self: Box, origin: &CodeOrigin) -> ProjectErrorObj { + Arc::new(OriginBundle(origin.clone(), *self)) + } } -impl Clone for ErrorSansLocationObj { +impl Clone for ErrorSansOriginObj { fn clone(&self) -> Self { clone_box(&**self) } } -impl DynErrorSansLocation for ErrorSansLocationObj { +impl DynErrorSansOrigin for ErrorSansOriginObj { fn description(&self) -> &str { (**self).description() } fn message(&self) -> String { (**self).message() } fn as_any_ref(&self) -> &dyn Any { (**self).as_any_ref() } + fn into_packed(self: Box) -> ErrorSansOriginObj { *self } + fn bundle(self: Box, origin: &CodeOrigin) -> ProjectErrorObj { (*self).bundle(origin) } } -impl Display for ErrorSansLocationObj { +impl fmt::Display for ErrorSansOriginObj { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - writeln!(f, "{}\nLocation missing from error", self.message()) + writeln!(f, "{}\nOrigin missing from error", self.message()) } } -impl Debug for ErrorSansLocationObj { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{self}") - } +impl fmt::Debug for ErrorSansOriginObj { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{self}") } } -struct LocationBundle(CodeLocation, Box); -impl DynProjectError for LocationBundle { - fn as_any(&self) -> &dyn Any { self.1.as_any_ref() } +struct OriginBundle(CodeOrigin, T); +impl DynProjectError for OriginBundle { + fn as_any_ref(&self) -> &dyn Any { self.1.as_any_ref() } + fn into_packed(self: Arc) -> ProjectErrorObj { self } fn description(&self) -> &str { self.1.description() } fn message(&self) -> String { self.1.message() } fn positions(&self) -> BoxedIter { - box_once(ErrorPosition { location: self.0.clone(), message: None }) + box_once(ErrorPosition { origin: self.0.clone(), message: None }) } } -/// Add a location to an [ErrorSansLocation] -pub fn bundle_location( - location: &CodeLocation, - details: &dyn DynErrorSansLocation, -) -> ProjectErrorObj { - Arc::new(LocationBundle(location.clone(), clone_box(details))) +/// A collection for tracking fatal errors without halting. Participating +/// functions return [ProjectResult] even if they only ever construct [Ok]. When +/// they call other participating functions, instead of directly forwarding +/// errors with `?` they should prefer constructing a fallback value with +/// [Reporter::fallback]. If any error is added to a [Reporter] in a function, +/// the return value is valid but its meaning need not be related in any way to +/// the inputs. +/// +/// Returning [Err] from a function that accepts `&mut Reporter` indicates not +/// that there was a fatal error but that it wasn't possible to construct a +/// fallback, so if it can, the caller should construct one. +pub struct Reporter(RefCell>); +impl Reporter { + /// Create a new error reporter + pub fn new() -> Self { Self(RefCell::new(Vec::new())) } + /// Returns true if any errors were regorded. If this ever returns true, it + /// will always return true in the future. + pub fn failing(&self) -> bool { !self.0.borrow().is_empty() } + /// Report a fatal error + pub fn report(&self, error: ProjectErrorObj) { self.0.borrow_mut().push(error) } + /// Catch a fatal error, report it, and substitute the value + pub fn fallback(&self, res: ProjectResult, cb: impl FnOnce(ProjectErrorObj) -> T) -> T { + res.inspect_err(|e| self.report(e.clone())).unwrap_or_else(cb) + } + /// Panic if there were errors + pub fn assert(&self) { self.unwrap(Ok(())) } + /// Exit with code -1 if there were errors + pub fn assert_exit(&self) { self.unwrap_exit(Ok(())) } + /// Panic with descriptive messages if there were errors. If there were no + /// errors, unwrap the result + pub fn unwrap(&self, res: ProjectResult) -> T { + if self.failing() { + panic!("Errors were encountered: \n{}", self.0.borrow().iter().join("\n")); + } + res.unwrap() + } + /// Print errors and exit if any occurred. If there were no errors, unwrap + /// the result + pub fn unwrap_exit(&self, res: ProjectResult) -> T { + if self.failing() { + eprintln!("Errors were encountered: \n{}", self.0.borrow().iter().join("\n")); + process::exit(-1) + } + res.unwrap_or_else(|e| { + eprintln!("{e}"); + process::exit(-1) + }) + } + /// Take the errors out of the reporter + #[must_use] + pub fn into_errors(self) -> Option> { + let v = self.0.into_inner(); + if v.is_empty() { None } else { Some(v) } + } + /// Raise an error if the reporter contains any errors + pub fn bind(self) -> ProjectResult<()> { + match self.into_errors() { + None => Ok(()), + Some(v) if v.len() == 1 => Err(v.into_iter().exactly_one().unwrap()), + Some(v) => Err(MultiError(v).pack()), + } + } +} + +impl Default for Reporter { + fn default() -> Self { Self::new() } +} + +struct MultiError(Vec); +impl ProjectError for MultiError { + const DESCRIPTION: &'static str = "Multiple errors occurred"; + fn message(&self) -> String { format!("{} errors occurred", self.0.len()) } + fn positions(&self) -> impl IntoIterator + '_ { + self.0.iter().flat_map(|e| { + e.positions().map(|pos| { + let emsg = e.message(); + let msg = if let Some(pmsg) = pos.message { format!("{emsg}: {pmsg}") } else { emsg }; + ErrorPosition { origin: pos.origin, message: Some(msg) } + }) + }) + } } diff --git a/src/facade/loader.rs b/src/facade/loader.rs index 6eabab9..ba9aa45 100644 --- a/src/facade/loader.rs +++ b/src/facade/loader.rs @@ -1,21 +1,28 @@ -use std::path::{Path, PathBuf}; -use std::{fs, iter}; +//! The main structure of the façade, collects systems and exposes various +//! operations over the whole set. -use intern_all::{i, Tok}; -use substack::Substack; +use std::borrow::Borrow; +use std::path::PathBuf; +use intern_all::i; + +use super::macro_runner::MacroRunner; +use super::merge_trees::merge_trees; +use super::process::Process; use super::system::{IntoSystem, System}; -use crate::error::ProjectResult; +use super::unbound_ref::validate_refs; +use crate::error::Reporter; use crate::gen::tree::ConstTree; +use crate::interpreter::context::RunEnv; use crate::interpreter::handler::HandlerTable; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::{Sym, VPath}; -use crate::pipeline::load_solution::{load_solution, SolutionContext}; +use crate::location::{CodeGenInfo, CodeOrigin}; +use crate::name::{PathSlice, Sym, VPath}; +use crate::pipeline::load_project::{load_project, ProjectContext}; use crate::pipeline::project::ProjectTree; +use crate::sym; use crate::utils::combine::Combine; use crate::utils::sequence::Sequence; -use crate::utils::unwrap_or::unwrap_or; -use crate::virt_fs::{DeclTree, DirNode, VirtFS}; +use crate::virt_fs::{DeclTree, DirNode, Loaded, VirtFS}; /// A compiled environment ready to load user code. It stores the list of /// systems and combines with usercode to produce a [Process] @@ -28,9 +35,7 @@ impl<'a> Loader<'a> { pub fn new() -> Self { Self { systems: Vec::new() } } /// Retrieve the list of systems - pub fn systems(&self) -> impl Iterator> { - self.systems.iter() - } + pub fn systems(&self) -> impl Iterator> { self.systems.iter() } /// Register a new system in the environment #[must_use] @@ -50,21 +55,22 @@ impl<'a> Loader<'a> { /// Combine the `constants` fields of all systems pub fn constants(&self) -> ConstTree { (self.systems()) - .try_fold(ConstTree::tree::<&str>([]), |acc, sys| { - acc.combine(sys.constants.clone()) - }) + .try_fold(ConstTree::tree::<&str>([]), |acc, sys| acc.combine(sys.constants.clone())) .expect("Conflicting const trees") } - pub fn handlers(self) -> HandlerTable<'a> { - (self.systems.into_iter()) - .fold(HandlerTable::new(), |t, sys| t.combine(sys.handlers)) + /// Extract the command handlers from the systems, consuming the loader in the + /// process. This has to consume the systems because handler tables aren't + /// Copy. It also establishes the practice that environments live on the + /// stack. + pub fn handlers(&self) -> HandlerTable<'_> { + (self.systems.iter()).fold(HandlerTable::new(), |t, sys| t.link(&sys.handlers)) } /// Compile the environment from the set of systems and return it directly. /// See [#load_dir] - pub fn solution_ctx(&self) -> ProjectResult { - Ok(SolutionContext { + pub fn project_ctx<'b>(&self, reporter: &'b Reporter) -> ProjectContext<'_, 'b> { + ProjectContext { lexer_plugins: Sequence::new(|| { self.systems().flat_map(|sys| &sys.lexer_plugins).map(|b| &**b) }), @@ -72,47 +78,113 @@ impl<'a> Loader<'a> { self.systems().flat_map(|sys| &sys.line_parsers).map(|b| &**b) }), preludes: Sequence::new(|| self.systems().flat_map(|sys| &sys.prelude)), - }) + reporter, + } } /// Combine source code from all systems with the specified directory into a /// common [VirtFS] - pub fn make_dir_tree(&self, dir: PathBuf) -> DeclTree { + pub fn make_dir_fs(&self, dir: PathBuf) -> DeclTree { let dir_node = DirNode::new(dir, ".orc").rc(); - let base = DeclTree::tree([("tree", DeclTree::leaf(dir_node))]); - (self.systems().try_fold(base, |acc, sub| acc.combine(sub.code.clone()))) + DeclTree::tree([("tree", DeclTree::leaf(dir_node))]) + } + + /// All system trees merged into one + pub fn system_fs(&self) -> DeclTree { + (self.systems().try_fold(DeclTree::empty(), |acc, sub| acc.combine(sub.code.clone()))) .expect("Conflicting system trees") } + /// A wrapper around [load_project] that only takes the arguments that aren't + /// fully specified by systems + pub fn load_project_main( + &self, + entrypoints: impl IntoIterator, + root: DeclTree, + reporter: &Reporter, + ) -> ProjectTree { + let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint))); + let constants = self.constants().unwrap_mod(); + let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone())); + let root = self.system_fs().combine(root).expect("System trees conflict with root"); + load_project(&self.project_ctx(reporter), targets, &constants, &root) + } + + /// A wrapper around [load_project] that only takes the arguments that aren't + /// fully specified by systems + pub fn load_project(&self, root: DeclTree, reporter: &Reporter) -> ProjectTree { + let mut orc_files: Vec = Vec::new(); + find_all_orc_files([].borrow(), &mut orc_files, &root); + let entrypoints = (orc_files.into_iter()).map(|p| p.name_with_suffix(i!(str: "tree")).to_sym()); + let tgt_loc = CodeOrigin::Gen(CodeGenInfo::no_details(sym!(facade::entrypoint))); + let constants = self.constants().unwrap_mod(); + let targets = entrypoints.into_iter().map(|s| (s, tgt_loc.clone())); + let root = self.system_fs().combine(root).expect("System trees conflict with root"); + load_project(&self.project_ctx(reporter), targets, &constants, &root) + } + /// Load a directory from the local file system as an Orchid project. /// File loading proceeds along import statements and ignores all files /// not reachable from the specified file. pub fn load_main( &self, dir: PathBuf, - target: Sym, - ) -> ProjectResult { - let ctx = self.solution_ctx()?; - let tgt_loc = - CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint")); - let root = self.make_dir_tree(dir.clone()); - let targets = iter::once((target, tgt_loc)); - let constants = self.constants().unwrap_mod(); - load_solution(ctx, targets, &constants, &root) + targets: impl IntoIterator, + reporter: &Reporter, + ) -> ProjectTree { + self.load_project_main(targets, self.make_dir_fs(dir), reporter) } /// Load every orchid file in a directory - pub fn load_dir(&self, dir: PathBuf) -> ProjectResult { - let ctx = self.solution_ctx()?; - let tgt_loc = - CodeLocation::Gen(CodeGenInfo::no_details("facade::entrypoint")); - let mut orc_files: Vec = Vec::new(); - find_all_orc_files(&dir, &mut orc_files, Substack::Bottom); - let root = self.make_dir_tree(dir.clone()); - let constants = self.constants().unwrap_mod(); - let targets = (orc_files.into_iter()) - .map(|p| (p.as_suffix_of(i("tree")).to_sym(), tgt_loc.clone())); - load_solution(ctx, targets, &constants, &root) + pub fn load_dir(&self, dir: PathBuf, reporter: &Reporter) -> ProjectTree { + self.load_project(self.make_dir_fs(dir), reporter) + } + + /// Build a process by calling other utilities in [crate::facade]. A sort of + /// facade over the facade. If you need a custom file system, consider + /// combining this with [Loader::load_project]. For usage with + /// [Loader::load_main] and [Loader::load_dir] we offer the shorthands + /// [Loader::proc_main] and [Loader::proc_dir]. + pub fn proc( + &'a self, + tree: ProjectTree, + check_refs: bool, + macro_limit: Option, + reporter: &Reporter, + ) -> Process<'a> { + let mr = MacroRunner::new(&tree, macro_limit, reporter); + let pm_tree = mr.run_macros(tree, reporter); + let consts = merge_trees(pm_tree.all_consts(), self.systems(), reporter); + if check_refs { + validate_refs(consts.keys().cloned().collect(), reporter, &mut |sym, location| { + (consts.get(&sym).map(|nc| nc.value.clone())) + .ok_or_else(|| RunEnv::sym_not_found(sym, location)) + }); + } + Process::new(consts, self.handlers()) + } + + /// Load a project and process everything + pub fn proc_dir( + &'a self, + dir: PathBuf, + check_refs: bool, + macro_limit: Option, + reporter: &Reporter, + ) -> Process<'a> { + self.proc(self.load_dir(dir.to_owned(), reporter), check_refs, macro_limit, reporter) + } + + /// Load a project and process everything to load specific symbols + pub fn proc_main( + &'a self, + dir: PathBuf, + targets: impl IntoIterator, + check_refs: bool, + macro_limit: Option, + reporter: &Reporter, + ) -> Process<'a> { + self.proc(self.load_main(dir.to_owned(), targets, reporter), check_refs, macro_limit, reporter) } } @@ -120,24 +192,12 @@ impl<'a> Default for Loader<'a> { fn default() -> Self { Self::new() } } -fn find_all_orc_files( - path: &Path, - paths: &mut Vec, - stack: Substack<'_, Tok>, -) { - assert!(path.exists(), "find_all_orc_files encountered missing path"); - if path.is_symlink() { - let path = unwrap_or!(fs::read_link(path).ok(); return); - find_all_orc_files(&path, paths, stack) - } else if path.is_file() { - if path.extension().and_then(|t| t.to_str()) == Some("orc") { - paths.push(VPath(stack.unreverse())) - } - } else if path.is_dir() { - let entries = unwrap_or!(path.read_dir().ok(); return); - for entry in entries.filter_map(Result::ok) { - let name = unwrap_or!(entry.file_name().into_string().ok(); return); - find_all_orc_files(&entry.path(), paths, stack.push(i(&name))) - } +fn find_all_orc_files(path: &PathSlice, paths: &mut Vec, vfs: &impl VirtFS) { + match vfs.read(path) { + Err(_) => (), + Ok(Loaded::Code(_)) => paths.push(path.to_vpath()), + Ok(Loaded::Collection(items)) => items + .iter() + .for_each(|suffix| find_all_orc_files(&path.to_vpath().suffix([suffix.clone()]), paths, vfs)), } } diff --git a/src/facade/macro_runner.rs b/src/facade/macro_runner.rs index f17d608..cdf18b6 100644 --- a/src/facade/macro_runner.rs +++ b/src/facade/macro_runner.rs @@ -1,66 +1,69 @@ +//! Encapsulates the macro runner's scaffolding. Relies on a [ProjectTree] +//! loaded by the [super::loader::Loader] + use std::iter; -use hashbrown::HashMap; - -use crate::error::{ProjectError, ProjectResult}; -use crate::location::CodeLocation; -use crate::name::Sym; +use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult, Reporter}; +use crate::location::CodeOrigin; use crate::parse::parsed; -use crate::pipeline::project::{ - ConstReport, ProjectTree, -}; +use crate::pipeline::project::{ItemKind, ProjItem, ProjectTree}; use crate::rule::repository::Repo; +use crate::tree::TreeTransforms; +/// Encapsulates the macro repository and the constant list, and allows querying +/// for macro execution results pub struct MacroRunner { /// Optimized catalog of substitution rules pub repo: Repo, /// Runtime code containing macro invocations - pub consts: HashMap, + pub timeout: Option, } impl MacroRunner { - pub fn new(tree: &ProjectTree) -> ProjectResult { + /// Initialize a macro runner + pub fn new(tree: &ProjectTree, timeout: Option, reporter: &Reporter) -> Self { let rules = tree.all_rules(); - let repo = Repo::new(rules).map_err(|(rule, e)| e.to_project(&rule))?; - Ok(Self { repo, consts: tree.all_consts().into_iter().collect() }) + let repo = Repo::new(rules, reporter); + Self { repo, timeout } } - pub fn run_macros( - &self, - timeout: Option, - ) -> ProjectResult> { - let mut symbols = HashMap::new(); - for (name, report) in self.consts.iter() { - let value = match timeout { - None => (self.repo.pass(&report.value)) - .unwrap_or_else(|| report.value.clone()), - Some(limit) => { - let (o, leftover_gas) = self.repo.long_step(&report.value, limit + 1); - match leftover_gas { - 1.. => o, - _ => { - let err = MacroTimeout { - location: CodeLocation::Source(report.range.clone()), - symbol: name.clone(), - limit, - }; - return Err(err.pack()); - }, - } - }, - }; - symbols.insert(name.clone(), ConstReport { value, ..report.clone() }); + /// Process the macros in an expression. + pub fn process_expr(&self, expr: parsed::Expr) -> ProjectResult { + match self.timeout { + None => Ok((self.repo.pass(&expr)).unwrap_or_else(|| expr.clone())), + Some(limit) => { + let (o, leftover_gas) = self.repo.long_step(&expr, limit + 1); + if 0 < leftover_gas { + return Ok(o); + } + Err(MacroTimeout { location: expr.range.origin(), limit }.pack()) + }, } - Ok(symbols) + } + + /// Run all macros in the project. + pub fn run_macros(&self, tree: ProjectTree, reporter: &Reporter) -> ProjectTree { + ProjectTree(tree.0.map_data( + |_, item| match &item.kind { + ItemKind::Const(c) => match self.process_expr(c.clone()) { + Ok(expr) => ProjItem { kind: ItemKind::Const(expr) }, + Err(e) => { + reporter.report(e); + item + }, + }, + _ => item, + }, + |_, x| x, + |_, x| x, + )) } /// Obtain an iterator that steps through the preprocessing of a constant /// for debugging macros - pub fn step(&self, sym: Sym) -> impl Iterator + '_ { - let mut target = - self.consts.get(&sym).expect("Target not found").value.clone(); + pub fn step(&self, mut expr: parsed::Expr) -> impl Iterator + '_ { iter::from_fn(move || { - target = self.repo.step(&target)?; - Some(target.clone()) + expr = self.repo.step(&expr)?; + Some(expr.clone()) }) } } @@ -68,17 +71,32 @@ impl MacroRunner { /// Error raised when a macro runs too long #[derive(Debug)] pub struct MacroTimeout { - location: CodeLocation, - symbol: Sym, + location: CodeOrigin, limit: usize, } impl ProjectError for MacroTimeout { const DESCRIPTION: &'static str = "Macro execution has not halted"; fn message(&self) -> String { - let Self { symbol, limit, .. } = self; - format!("Macro processing in {symbol} took more than {limit} steps") + let Self { limit, .. } = self; + format!("Macro processing took more than {limit} steps") } - fn one_position(&self) -> CodeLocation { self.location.clone() } + fn one_position(&self) -> CodeOrigin { self.location.clone() } +} + +struct MacroErrors(Vec); +impl ProjectError for MacroErrors { + const DESCRIPTION: &'static str = "Errors occurred during macro execution"; + fn positions(&self) -> impl IntoIterator + '_ { + self.0.iter().enumerate().flat_map(|(i, e)| { + e.positions().map(move |ep| ErrorPosition { + origin: ep.origin, + message: Some(match ep.message { + Some(msg) => format!("Error #{}: {}; {msg}", i + 1, e.message()), + None => format!("Error #{}: {}", i + 1, e.message()), + }), + }) + }) + } } diff --git a/src/facade/merge_trees.rs b/src/facade/merge_trees.rs index c2ce32f..e51cc6e 100644 --- a/src/facade/merge_trees.rs +++ b/src/facade/merge_trees.rs @@ -1,20 +1,27 @@ +//! Combine constants from [super::macro_runner::MacroRunner::run_macros] with +//! systems from [super::loader::Loader::systems] + use std::sync::Arc; use hashbrown::HashMap; use super::system::System; -use crate::error::ProjectResult; +use crate::error::Reporter; +use crate::foreign::inert::Inert; +use crate::foreign::to_clause::ToClause; use crate::intermediate::ast_to_ir::ast_to_ir; use crate::intermediate::ir_to_nort::ir_to_nort; use crate::interpreter::nort; use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::Sym; +use crate::name::{NameLike, Sym}; use crate::pipeline::project::ConstReport; +use crate::sym; use crate::tree::{ModMemberRef, TreeTransforms}; use crate::utils::unwrap_or::unwrap_or; /// Equivalent of [crate::pipeline::project::ConstReport] for the interpreter's /// representation, [crate::interpreter::nort]. +#[derive(Clone)] pub struct NortConst { /// Comments associated with the constant which may affect its interpretation pub comments: Vec>, @@ -23,31 +30,40 @@ pub struct NortConst { /// Value assigned to the constant pub value: nort::Expr, } +impl NortConst { + /// Convert into NORT constant from AST constant + pub fn convert_from(value: ConstReport, reporter: &Reporter) -> NortConst { + let module = Sym::new(value.name.split_last().1[..].iter()) + .expect("Constant names from source are at least 2 long"); + let location = CodeLocation::new_src(value.range.clone(), value.name); + let nort = match ast_to_ir(value.value, value.range, module.clone()) { + Ok(ir) => ir_to_nort(&ir), + Err(e) => { + reporter.report(e); + Inert(0).to_expr(location.clone()) + }, + }; + Self { value: nort, location, comments: value.comments } + } +} /// Combine a list of symbols loaded from source and the constant trees from /// each system. pub fn merge_trees<'a: 'b, 'b>( - source: impl IntoIterator + 'b, + source: impl IntoIterator, systems: impl IntoIterator> + 'b, -) -> ProjectResult + 'static> { + reporter: &Reporter, +) -> HashMap { let mut out = HashMap::new(); - for (name, rep) in source { - let ir = ast_to_ir(rep.value, name.clone())?; - // if name == Sym::literal("tree::main::main") { - // panic!("{ir:?}"); - // } - out.insert(name.clone(), NortConst { - value: ir_to_nort(&ir), - location: CodeLocation::Source(rep.range), - comments: rep.comments, - }); + for (name, rep) in source.into_iter() { + out.insert(name.clone(), NortConst::convert_from(rep, reporter)); } for system in systems { let const_module = system.constants.unwrap_mod_ref(); const_module.search_all((), |stack, node, ()| { let c = unwrap_or!(node => ModMemberRef::Item; return); - let location = CodeLocation::Gen(CodeGenInfo::details( - "constant from", + let location = CodeLocation::new_gen(CodeGenInfo::details( + sym!(facade::merge_tree), format!("system.name={}", system.name), )); let value = c.clone().gen_nort(stack.clone(), location.clone()); @@ -55,5 +71,5 @@ pub fn merge_trees<'a: 'b, 'b>( out.insert(Sym::new(stack.unreverse()).expect("root item is forbidden"), crep); }); } - Ok(out) + out } diff --git a/src/facade/mod.rs b/src/facade/mod.rs index 74f7c6c..01dfe96 100644 --- a/src/facade/mod.rs +++ b/src/facade/mod.rs @@ -3,6 +3,7 @@ pub mod loader; pub mod macro_runner; +pub mod merge_trees; pub mod process; pub mod system; -pub mod merge_trees; +pub mod unbound_ref; diff --git a/src/facade/pre_macro.rs b/src/facade/pre_macro.rs deleted file mode 100644 index b9ca874..0000000 --- a/src/facade/pre_macro.rs +++ /dev/null @@ -1,111 +0,0 @@ -use std::iter; - -use hashbrown::HashMap; -use never::Never; - -use super::process::Process; -use super::system::System; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; -use crate::intermediate::ast_to_ir::ast_to_ir; -use crate::intermediate::ir_to_nort::ir_to_nort; -use crate::interpreter::handler::HandlerTable; -use crate::location::{CodeGenInfo, CodeLocation}; -use crate::name::{Sym, VPath}; -use crate::parse::parsed; -use crate::pipeline::project::{ - collect_consts, collect_rules, ConstReport, ProjectTree, -}; -use crate::rule::repository::Repo; -use crate::tree::ModMember; - -/// Everything needed for macro execution, and constructing the process -pub struct PreMacro<'a> { - /// Optimized catalog of substitution rules - pub repo: Repo, - /// Runtime code containing macro invocations - pub consts: HashMap, - /// Libraries and plug-ins - pub systems: Vec>, -} -impl<'a> PreMacro<'a> { - /// Build a [PreMacro] from a source tree and system list - pub fn new( - tree: &ProjectTree, - systems: Vec>, - ) -> ProjectResult { - Ok(Self { - repo, - consts: (consts.into_iter()) - .map(|(name, expr)| { - let (ent, _) = (tree.0) - .walk1_ref(&[], &name.split_last().1[..], |_| true) - .expect("path sourced from symbol names"); - let location = (ent.x.locations.first().cloned()) - .unwrap_or_else(|| CodeLocation::Source(expr.value.range.clone())); - (name, (expr, location)) - }) - .collect(), - systems, - }) - } - - /// Run all macros to termination or the optional timeout. If a timeout does - /// not occur, returns a process which can execute Orchid code - pub fn run_macros( - self, - timeout: Option, - ) -> ProjectResult> { - let Self { systems, repo, consts } = self; - for sys in systems.iter() { - let const_module = sys.constants.unwrap_mod_ref(); - let _ = const_module.search_all((), &mut |path, module, ()| { - for (key, ent) in &module.entries { - if let ModMember::Item(c) = &ent.member { - let path = VPath::new(path.unreverse()).as_prefix_of(key.clone()); - let cginfo = CodeGenInfo::details( - "constant from", - format!("system.name={}", sys.name), - ); - symbols - .insert(path.to_sym(), c.gen_nort(CodeLocation::Gen(cginfo))); - } - } - Ok::<(), Never>(()) - }); - } - Ok(Process { - symbols, - handlers: (systems.into_iter()) - .fold(HandlerTable::new(), |tbl, sys| tbl.combine(sys.handlers)), - }) - } - - /// Obtain an iterator that steps through the preprocessing of a constant - /// for debugging macros - pub fn step(&self, sym: Sym) -> impl Iterator + '_ { - let mut target = - self.consts.get(&sym).expect("Target not found").0.value.clone(); - iter::from_fn(move || { - target = self.repo.step(&target)?; - Some(target.clone()) - }) - } -} - -/// Error raised when a macro runs too long -#[derive(Debug)] -pub struct MacroTimeout { - location: CodeLocation, - symbol: Sym, - limit: usize, -} -impl ProjectError for MacroTimeout { - const DESCRIPTION: &'static str = "Macro execution has not halted"; - - fn message(&self) -> String { - let Self { symbol, limit, .. } = self; - format!("Macro processing in {symbol} took more than {limit} steps") - } - - fn one_position(&self) -> CodeLocation { self.location.clone() } -} diff --git a/src/facade/process.rs b/src/facade/process.rs index 9ee2707..f8049d2 100644 --- a/src/facade/process.rs +++ b/src/facade/process.rs @@ -1,29 +1,31 @@ +//! Run Orchid commands in the context of the loaded environment. Either +//! returned by [super::loader::Loader::proc], or constructed manually from the +//! return value of [super::merge_trees::merge_trees] and +//! [super::loader::Loader::handlers]. + use hashbrown::HashMap; -use itertools::Itertools; use super::merge_trees::NortConst; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; -use crate::interpreter::context::{Halt, RunContext}; +use crate::interpreter::context::{Halt, RunEnv, RunParams}; use crate::interpreter::error::RunError; -use crate::interpreter::handler::{run_handler, HandlerTable}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::CodeLocation; +use crate::interpreter::handler::HandlerTable; +use crate::interpreter::nort::Expr; +use crate::interpreter::run::run; use crate::name::Sym; /// This struct ties the state of systems to loaded code, and allows to call /// Orchid-defined functions -pub struct Process<'a> { - pub(crate) symbols: HashMap, - pub(crate) handlers: HandlerTable<'a>, -} +pub struct Process<'a>(RunEnv<'a>); impl<'a> Process<'a> { /// Build a process from the return value of [crate::facade::merge_trees] and pub fn new( consts: impl IntoIterator, handlers: HandlerTable<'a>, ) -> Self { - let symbols = consts.into_iter().map(|(k, v)| (k, v.value)).collect(); - Self { handlers, symbols } + let symbols: HashMap<_, _> = consts.into_iter().map(|(k, v)| (k, v.value)).collect(); + Self(RunEnv::new(handlers, move |sym, location| { + symbols.get(&sym).cloned().ok_or_else(|| RunEnv::sym_not_found(sym, location)) + })) } /// Execute the given command in this process. If gas is specified, at most as @@ -31,81 +33,7 @@ impl<'a> Process<'a> { /// /// This is useful to catch infinite loops or ensure that a tenant program /// yields - pub fn run( - &mut self, - prompt: Expr, - gas: Option, - ) -> Result { - let ctx = RunContext { gas, symbols: &self.symbols, stack_size: 1000 }; - run_handler(prompt, &mut self.handlers, ctx) - } - - /// Find all unbound constant names in a symbol. This is often useful to - /// identify dynamic loading targets. - #[must_use] - pub fn unbound_refs(&self, key: Sym) -> Vec<(Sym, CodeLocation)> { - let mut errors = Vec::new(); - let sym = self.symbols.get(&key).expect("symbol must exist"); - sym.search_all(&mut |s: &Expr| { - if let Clause::Constant(sym) = &*s.cls() { - if !self.symbols.contains_key(sym) { - errors.push((sym.clone(), s.location())) - } - } - None::<()> - }); - errors - } - - /// Assert that the code contains no invalid constants. This ensures that, - /// unless [Clause::Constant]s are created procedurally, - /// a [crate::interpreter::error::RunError::MissingSymbol] cannot be produced - pub fn validate_refs(&self) -> ProjectResult<()> { - let mut errors = Vec::new(); - for key in self.symbols.keys() { - errors.extend(self.unbound_refs(key.clone()).into_iter().map( - |(symbol, location)| MissingSymbol { - symbol, - location, - referrer: key.clone(), - }, - )); - } - match errors.is_empty() { - true => Ok(()), - false => Err(MissingSymbols { errors }.pack()), - } - } -} - -#[derive(Debug, Clone)] -struct MissingSymbol { - referrer: Sym, - location: CodeLocation, - symbol: Sym, -} -#[derive(Debug)] -struct MissingSymbols { - errors: Vec, -} -impl ProjectError for MissingSymbols { - const DESCRIPTION: &'static str = "A name not referring to a known symbol was found in the source after \ - macro execution. This can either mean that a symbol name was mistyped, or \ - that macro execution didn't correctly halt."; - - fn message(&self) -> String { - format!( - "The following symbols do not exist:\n{}", - (self.errors.iter()) - .map(|MissingSymbol { symbol, referrer, .. }| format!( - "{symbol} referenced in {referrer}" - )) - .join("\n") - ) - } - - fn positions(&self) -> impl IntoIterator { - (self.errors.iter()) - .map(|i| ErrorPosition { location: i.location.clone(), message: None }) + pub fn run(&self, prompt: Expr, gas: Option) -> Result> { + run(prompt, &self.0, &mut RunParams { stack: 1000, gas }) } } diff --git a/src/facade/system.rs b/src/facade/system.rs index 8264a6f..60143e9 100644 --- a/src/facade/system.rs +++ b/src/facade/system.rs @@ -1,10 +1,13 @@ +//! Unified extension struct instances of which are catalogued by +//! [super::loader::Loader]. Language extensions must implement [IntoSystem]. + use crate::error::{ErrorPosition, ProjectError}; use crate::gen::tree::ConstTree; use crate::interpreter::handler::HandlerTable; use crate::name::VName; use crate::parse::lex_plugin::LexerPlugin; use crate::parse::parse_plugin::ParseLinePlugin; -use crate::pipeline::load_solution::Prelude; +use crate::pipeline::load_project::Prelude; use crate::virt_fs::DeclTree; /// A description of every point where an external library can hook into Orchid. @@ -48,8 +51,7 @@ pub struct MissingSystemCode { referrer: VName, } impl ProjectError for MissingSystemCode { - const DESCRIPTION: &'static str = - "A system tried to import a path that doesn't exist"; + const DESCRIPTION: &'static str = "A system tried to import a path that doesn't exist"; fn message(&self) -> String { format!( "Path {} imported by {} is not defined by {} or any system before it", @@ -61,8 +63,7 @@ impl ProjectError for MissingSystemCode { fn positions(&self) -> impl IntoIterator { [] } } -/// Trait for objects that can be converted into a [System] in the presence -/// of an [Interner]. +/// Trait for objects that can be converted into a [System]. pub trait IntoSystem<'a> { /// Convert this object into a system using an interner fn into_system(self) -> System<'a>; diff --git a/src/facade/unbound_ref.rs b/src/facade/unbound_ref.rs new file mode 100644 index 0000000..761d992 --- /dev/null +++ b/src/facade/unbound_ref.rs @@ -0,0 +1,94 @@ +//! Referencing a constant that doesn't exist is a runtime error in Orchid, even +//! though this common error condition is usually caused by faulty macro +//! execution. This module constains functions to detect and raise these errors +//! eagerly. + +use std::fmt; + +use hashbrown::HashSet; +use trait_set::trait_set; + +use crate::error::{ProjectError, Reporter}; +use crate::interpreter::nort::{Clause, Expr}; +use crate::location::{CodeGenInfo, CodeLocation}; +use crate::name::Sym; +use crate::sym; + +/// Start with a symbol +pub fn unbound_refs_sym( + symbol: Sym, + location: CodeLocation, + visited: &mut HashSet, + load: &mut impl FnMut(Sym, CodeLocation) -> Result, + reporter: &Reporter, +) { + if visited.insert(symbol.clone()) { + match load(symbol.clone(), location.clone()) { + Err(error) => reporter.report(MissingSymbol { symbol, location, error }.pack()), + Ok(expr) => unbound_refs_expr(expr, visited, load, reporter), + } + } +} + +/// Find all unbound constant names in a snippet. This is mostly useful to +/// detect macro errors. +pub fn unbound_refs_expr( + expr: Expr, + visited: &mut HashSet, + load: &mut impl FnMut(Sym, CodeLocation) -> Result, + reporter: &Reporter, +) { + expr.search_all(&mut |s: &Expr| { + if let Clause::Constant(symbol) = &*s.cls_mut() { + unbound_refs_sym(symbol.clone(), s.location(), visited, load, reporter) + } + None::<()> + }); +} + +/// Assert that the code contains no invalid references that reference missing +/// symbols. [Clause::Constant]s can be created procedurally, so this isn't a +/// total guarantee, more of a convenience. +pub fn validate_refs( + all_syms: HashSet, + reporter: &Reporter, + load: &mut impl FnMut(Sym, CodeLocation) -> Result, +) -> HashSet { + let mut visited = HashSet::new(); + for sym in all_syms { + let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(orchidlang::validate_refs))); + unbound_refs_sym(sym, location, &mut visited, load, reporter); + } + visited +} + +trait_set! { + /// Any error the reference walker can package into a [MissingSymbol] + pub trait SubError = fmt::Display + Clone + Send + Sync + 'static; +} + +/// Information about a reproject failure +#[derive(Clone)] +pub struct MissingSymbol { + /// The error returned by the loader function. This is usually a ismple "not + /// found", but with unusual setups it might provide some useful info. + pub error: E, + /// Location of the first reference to the missing symbol. + pub location: CodeLocation, + /// The symbol in question + pub symbol: Sym, +} +impl ProjectError for MissingSymbol { + const DESCRIPTION: &'static str = "A name not referring to a known symbol was found in the source after \ + macro execution. This can either mean that a symbol name was mistyped, or \ + that macro execution didn't correctly halt."; + fn message(&self) -> String { format!("{}: {}", self.symbol, self.error) } + fn one_position(&self) -> crate::location::CodeOrigin { self.location.origin() } +} + +// struct MissingSymbols { +// errors: Vec, +// } +// impl ProjectError for MissingSymbols { +// fn positions(&self) -> impl IntoIterator { +// self.errors.iter().cloned() } } diff --git a/src/foreign/atom.rs b/src/foreign/atom.rs index 5fba064..f3e8ba1 100644 --- a/src/foreign/atom.rs +++ b/src/foreign/atom.rs @@ -1,65 +1,70 @@ +//! Adaptor trait to embed Rust values in Orchid expressions + use std::any::Any; -use std::fmt::{Debug, Display}; +use std::fmt; use std::sync::{Arc, Mutex}; use never::Never; -use super::error::{ExternError, ExternResult}; -use crate::interpreter::context::RunContext; -use crate::interpreter::error::RunError; +use super::error::{RTError, RTResult}; +use crate::interpreter::context::{RunEnv, RunParams}; use crate::interpreter::nort; use crate::location::{CodeLocation, SourceRange}; use crate::name::NameLike; +use crate::parse::lexer::Lexeme; use crate::parse::parsed; use crate::utils::ddispatch::{request, Request, Responder}; /// Information returned by [Atomic::run]. pub enum AtomicReturn { /// No work was done. If the atom takes an argument, it can be provided now - Inert(nort::Clause), + Inert(Atom), /// Work was done, returns new clause and consumed gas. 1 gas is already /// consumed by the virtual call, so nonzero values indicate expensive /// operations. Change(usize, nort::Clause), } impl AtomicReturn { - /// Report indicating that the value is inert - pub fn inert(this: T) -> Result { - Ok(Self::Inert(this.atom_cls())) - } + /// Report indicating that the value is inert. The result here is always [Ok], + /// it's meant to match the return type of [Atomic::run] + #[allow(clippy::unnecessary_wraps)] + pub fn inert(this: T) -> Result { Ok(Self::Inert(Atom::new(this))) } } /// Returned by [Atomic::run] -pub type AtomicResult = Result; +pub type AtomicResult = RTResult; /// General error produced when a non-function [Atom] is applied to something as /// a function. #[derive(Clone)] pub struct NotAFunction(pub nort::Expr); -impl ExternError for NotAFunction {} -impl Display for NotAFunction { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl RTError for NotAFunction {} +impl fmt::Display for NotAFunction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?} is not a function", self.0) } } /// Information about a function call presented to an external function -pub struct CallData<'a> { +pub struct CallData<'a, 'b> { /// Location of the function expression pub location: CodeLocation, /// The argument the function was called on. Functions are curried pub arg: nort::Expr, - /// Information relating to this interpreter run - pub ctx: RunContext<'a>, + /// Globally available information such as the list of all constants + pub env: &'a RunEnv<'b>, + /// Resource limits and other details specific to this interpreter run + pub params: &'a mut RunParams, } /// Information about a normalization run presented to an atom -#[derive(Clone)] -pub struct RunData<'a> { +pub struct RunData<'a, 'b> { /// Location of the atom pub location: CodeLocation, - /// Information about the execution - pub ctx: RunContext<'a>, + /// Globally available information such as the list of all constants + pub env: &'a RunEnv<'b>, + /// Resource limits and other details specific to this interpreter run + pub params: &'a mut RunParams, } /// Functionality the interpreter needs to handle a value @@ -67,15 +72,15 @@ pub struct RunData<'a> { /// # Lifecycle methods /// /// Atomics expose the methods [Atomic::redirect], [Atomic::run], -/// [Atomic::apply] and [Atomic::apply_ref] to interact with the interpreter. +/// [Atomic::apply] and [Atomic::apply_mut] to interact with the interpreter. /// The interpreter first tries to call `redirect` to find a subexpression to /// normalize. If it returns `None` or the subexpression is inert, `run` is /// called. `run` takes ownership of the value and returns a new one. /// /// If `run` indicated in its return value that the result is inert and the atom -/// is in the position of a function, `apply` or `apply_ref` is called depending +/// is in the position of a function, `apply` or `apply_mut` is called depending /// upon whether the atom is referenced elsewhere. `apply` falls back to -/// `apply_ref` so implementing it is considered an optimization to avoid +/// `apply_mut` so implementing it is considered an optimization to avoid /// excessive copying. /// /// Atoms don't generally have to be copyable because clauses are refcounted in @@ -83,7 +88,7 @@ pub struct RunData<'a> { /// and apply them as functions to multiple different arguments so atoms that /// represent functions have to support application by-ref without consuming the /// function itself. -pub trait Atomic: Any + Debug + Responder + Send +pub trait Atomic: Any + fmt::Debug + Responder + Send where Self: 'static { /// Casts this value to [Any] so that its original value can be salvaged @@ -99,6 +104,12 @@ where Self: 'static /// See [Atomic::as_any], exactly the same but for references #[must_use] fn as_any_ref(&self) -> &dyn Any; + /// Print the atom's type name. Should only ever be implemented as + /// + /// ```ignore + /// fn type_name(&self) -> &'static str { std::any::type_name::() } + /// ``` + fn type_name(&self) -> &'static str; /// Returns a reference to a possible expression held inside the atom which /// can be reduced. For an overview of the lifecycle see [Atomic] @@ -112,22 +123,20 @@ where Self: 'static fn run(self: Box, run: RunData) -> AtomicResult; /// Combine the function with an argument to produce a new clause. Falls back - /// to [Atomic::apply_ref] by default. + /// to [Atomic::apply_mut] by default. /// /// For an overview of the lifecycle see [Atomic] - fn apply(self: Box, call: CallData) -> ExternResult { - self.apply_ref(call) - } + fn apply(mut self: Box, call: CallData) -> RTResult { self.apply_mut(call) } /// Combine the function with an argument to produce a new clause /// /// For an overview of the lifecycle see [Atomic] - fn apply_ref(&self, call: CallData) -> ExternResult; + fn apply_mut(&mut self, call: CallData) -> RTResult; /// Must return true for atoms parsed from identical source. /// If the atom cannot be parsed from source, it can safely be ignored #[allow(unused_variables)] - fn parser_eq(&self, other: &dyn Any) -> bool { false } + fn parser_eq(&self, other: &dyn Atomic) -> bool { false } /// Wrap the atom in a clause to be placed in an [AtomicResult]. #[must_use] @@ -139,29 +148,38 @@ where Self: 'static /// Shorthand for `self.atom_cls().to_inst()` fn atom_clsi(self) -> nort::ClauseInst where Self: Sized { - self.atom_cls().to_inst() + self.atom_cls().into_inst() } /// Wrap the atom in a new expression instance to be placed in a tree #[must_use] fn atom_expr(self, location: CodeLocation) -> nort::Expr where Self: Sized { - self.atom_clsi().to_expr(location) + self.atom_clsi().into_expr(location) } - /// Wrap the atom in a clause to be placed in a [sourcefile::FileEntry]. + /// Wrap the atom in a clause to be placed in a + /// [crate::parse::parsed::SourceLine]. #[must_use] fn ast_cls(self) -> parsed::Clause where Self: Sized + Clone { parsed::Clause::Atom(AtomGenerator::cloner(self)) } - /// Wrap the atom in an expression to be placed in a [sourcefile::FileEntry]. + /// Wrap the atom in an expression to be placed in a + /// [crate::parse::parsed::SourceLine]. #[must_use] fn ast_exp(self, range: SourceRange) -> parsed::Expr where Self: Sized + Clone { self.ast_cls().into_expr(range) } + + /// Wrap this atomic value in a lexeme. This means that the atom will + /// participate in macro reproject, so it must implement [Atomic::parser_eq]. + fn lexeme(self) -> Lexeme + where Self: Sized + Clone { + Lexeme::Atom(AtomGenerator::cloner(self)) + } } /// A struct for generating any number of [Atom]s. Since atoms aren't Clone, @@ -170,9 +188,7 @@ where Self: 'static pub struct AtomGenerator(Arc Atom + Send + Sync>); impl AtomGenerator { /// Use a factory function to create any number of atoms - pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self { - Self(Arc::new(f)) - } + pub fn new(f: impl Fn() -> Atom + Send + Sync + 'static) -> Self { Self(Arc::new(f)) } /// Clone a representative atom when called pub fn cloner(atom: impl Atomic + Clone) -> Self { let lock = Mutex::new(atom); @@ -181,26 +197,22 @@ impl AtomGenerator { /// Generate an atom pub fn run(&self) -> Atom { self.0() } } -impl Debug for AtomGenerator { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.run()) - } +impl fmt::Debug for AtomGenerator { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.run()) } +} +impl PartialEq for AtomGenerator { + fn eq(&self, other: &Self) -> bool { self.run().0.parser_eq(&*other.run().0) } } -/// Represents a black box unit of code with its own normalization steps. -/// Typically [ExternFn] will produce an [Atom] when applied to a [Clause], -/// this [Atom] will then forward `run` calls to the argument until it becomes -/// inert at which point the [Atom] will validate and process the argument, -/// returning a different [Atom] intended for processing by external code, a new -/// [ExternFn] to capture an additional argument, or an Orchid expression -/// to pass control back to the interpreter. +/// Represents a black box unit of data with its own normalization steps. +/// Typically Rust functions integrated with [super::fn_bridge::xfn] will +/// produce and consume [Atom]s to represent both raw data, pending +/// computational tasks, and curried partial calls awaiting their next argument. pub struct Atom(pub Box); impl Atom { /// Wrap an [Atomic] in a type-erased box #[must_use] - pub fn new(data: T) -> Self { - Self(Box::new(data) as Box) - } + pub fn new(data: T) -> Self { Self(Box::new(data) as Box) } /// Get the contained data #[must_use] pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } @@ -213,7 +225,7 @@ impl Atom { *self.0.as_any().downcast().expect("Type mismatch on Atom::cast") } /// Normalize the contained data - pub fn run(self, run: RunData) -> AtomicResult { self.0.run(run) } + pub fn run(self, run: RunData<'_, '_>) -> AtomicResult { self.0.run(run) } /// Request a delegate from the encapsulated data pub fn request(&self) -> Option { request(self.0.as_ref()) } /// Downcast the atom to a concrete atomic type, or return the original atom @@ -225,23 +237,15 @@ impl Atom { } } /// Downcast an atom by reference - pub fn downcast_ref(&self) -> Option<&T> { - self.0.as_any_ref().downcast_ref() - } + pub fn downcast_ref(&self) -> Option<&T> { self.0.as_any_ref().downcast_ref() } /// Combine the function with an argument to produce a new clause - pub fn apply(self, call: CallData) -> ExternResult { - self.0.apply(call) - } + pub fn apply(self, call: CallData) -> RTResult { self.0.apply(call) } /// Combine the function with an argument to produce a new clause - pub fn apply_ref(&self, call: CallData) -> ExternResult { - self.0.apply_ref(call) - } + pub fn apply_mut(&mut self, call: CallData) -> RTResult { self.0.apply_mut(call) } } -impl Debug for Atom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.data()) - } +impl fmt::Debug for Atom { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}", self.data()) } } impl Responder for Never { @@ -250,9 +254,8 @@ impl Responder for Never { impl Atomic for Never { fn as_any(self: Box) -> Box { match *self {} } fn as_any_ref(&self) -> &dyn Any { match *self {} } + fn type_name(&self) -> &'static str { match *self {} } fn redirect(&mut self) -> Option<&mut nort::Expr> { match *self {} } fn run(self: Box, _: RunData) -> AtomicResult { match *self {} } - fn apply_ref(&self, _: CallData) -> ExternResult { - match *self {} - } + fn apply_mut(&mut self, _: CallData) -> RTResult { match *self {} } } diff --git a/src/foreign/cps_box.rs b/src/foreign/cps_box.rs index 07e426b..039910f 100644 --- a/src/foreign/cps_box.rs +++ b/src/foreign/cps_box.rs @@ -1,13 +1,11 @@ //! Automated wrappers to make working with CPS commands easier. -use std::fmt::Debug; +use std::fmt; use trait_set::trait_set; -use super::atom::{ - Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData, -}; -use super::error::{ExternError, ExternResult}; +use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData}; +use super::error::{RTError, RTResult}; use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; use crate::utils::ddispatch::{Request, Responder}; @@ -15,9 +13,9 @@ use crate::utils::pure_seq::pushed_ref; trait_set! { /// A "well behaved" type that can be used as payload in a CPS box - pub trait CPSPayload = Clone + Debug + Send + 'static; + pub trait CPSPayload = Clone + fmt::Debug + Send + 'static; /// A function to handle a CPS box with a specific payload - pub trait CPSHandler = FnMut(&T, &Expr) -> ExternResult; + pub trait CPSHandler = FnMut(&T, &Expr) -> RTResult; } /// An Orchid Atom value encapsulating a payload and continuation points @@ -64,9 +62,9 @@ impl CPSBox { } } - fn assert_applicable(&self, err_loc: &CodeLocation) -> ExternResult<()> { + fn assert_applicable(&self, err_loc: &CodeLocation) -> RTResult<()> { match self.argc { - 0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).rc()), + 0 => Err(NotAFunction(self.clone().atom_expr(err_loc.clone())).pack()), _ => Ok(()), } } @@ -77,18 +75,17 @@ impl Responder for CPSBox { impl Atomic for CPSBox { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn parser_eq(&self, _: &dyn std::any::Any) -> bool { false } + fn type_name(&self) -> &'static str { std::any::type_name::() } + fn parser_eq(&self, _: &dyn Atomic) -> bool { false } fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { - AtomicReturn::inert(*self) - } - fn apply(mut self: Box, call: CallData) -> ExternResult { + fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } + fn apply(mut self: Box, call: CallData) -> RTResult { self.assert_applicable(&call.location)?; self.argc -= 1; self.continuations.push(call.arg); Ok(self.atom_cls()) } - fn apply_ref(&self, call: CallData) -> ExternResult { + fn apply_mut(&mut self, call: CallData) -> RTResult { self.assert_applicable(&call.location)?; let new = Self { argc: self.argc - 1, diff --git a/src/foreign/error.rs b/src/foreign/error.rs index c902db6..85c450e 100644 --- a/src/foreign/error.rs +++ b/src/foreign/error.rs @@ -1,34 +1,38 @@ +//! Errors produced by the interpreter + use std::error::Error; -use std::fmt::{Debug, Display}; +use std::fmt; use std::sync::Arc; use dyn_clone::DynClone; +use crate::error::ProjectErrorObj; use crate::location::CodeLocation; -/// Errors produced by external code -pub trait ExternError: Display + Send + Sync + DynClone { +/// Errors produced by external code when runtime-enforced assertions are +/// violated. +pub trait RTError: fmt::Display + Send + Sync + DynClone { /// Convert into trait object #[must_use] - fn rc(self) -> ExternErrorObj + fn pack(self) -> RTErrorObj where Self: 'static + Sized { Arc::new(self) } } -impl Debug for dyn ExternError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ExternError({self})") - } +impl fmt::Debug for dyn RTError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ExternError({self})") } } -impl Error for dyn ExternError {} +impl Error for dyn RTError {} + +impl RTError for ProjectErrorObj {} /// An error produced by Rust code called form Orchid. The error is type-erased. -pub type ExternErrorObj = Arc; +pub type RTErrorObj = Arc; /// A result produced by Rust code called from Orchid. -pub type ExternResult = Result; +pub type RTResult = Result; /// Some expectation (usually about the argument types of a function) did not /// hold. @@ -42,30 +46,22 @@ pub struct AssertionError { impl AssertionError { /// Construct, upcast and wrap in a Result that never succeeds for easy /// short-circuiting - pub fn fail( - location: CodeLocation, - message: &'static str, - details: String, - ) -> ExternResult { + pub fn fail(location: CodeLocation, message: &'static str, details: String) -> RTResult { Err(Self::ext(location, message, details)) } - /// Construct and upcast to [ExternError] - pub fn ext( - location: CodeLocation, - message: &'static str, - details: String, - ) -> ExternErrorObj { - Self { location, message, details }.rc() + /// Construct and upcast to [RTErrorObj] + pub fn ext(location: CodeLocation, message: &'static str, details: String) -> RTErrorObj { + Self { location, message, details }.pack() } } -impl Display for AssertionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for AssertionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error: expected {}", self.message)?; write!(f, " at {}", self.location)?; write!(f, " details: {}", self.details) } } -impl ExternError for AssertionError {} +impl RTError for AssertionError {} diff --git a/src/foreign/fn_bridge.rs b/src/foreign/fn_bridge.rs index e53c01c..e3e5e22 100644 --- a/src/foreign/fn_bridge.rs +++ b/src/foreign/fn_bridge.rs @@ -1,26 +1,27 @@ +//! Insert Rust functions in Orchid code almost seamlessly + use std::any::{Any, TypeId}; -use std::fmt::{Debug, Display}; +use std::fmt; use std::marker::PhantomData; use intern_all::{i, Tok}; use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; -use super::error::ExternResult; +use super::error::RTResult; use super::to_clause::ToClause; use super::try_from_expr::TryFromExpr; use crate::interpreter::nort::{Clause, Expr}; use crate::utils::ddispatch::Responder; /// Return a unary lambda wrapped in this struct to take an additional argument -/// in a function passed to Orchid through a member of the [super::xfn_1ary] -/// family. +/// in a function passed to Orchid through [super::fn_bridge::xfn]. /// /// Container for a unary [FnOnce] that uniquely states the argument and return /// type. Rust functions are never overloaded, but inexplicably the [Fn] traits /// take the argument tuple as a generic parameter which means that it cannot /// be a unique dispatch target. /// -/// If the function takes an instance of [Lazy], it will contain the expression +/// If the function takes an instance of [Thunk], it will contain the expression /// the function was applied to without any specific normalization. If it takes /// any other type, the argument will be fully normalized and cast using the /// type's [TryFromExpr] impl. @@ -45,11 +46,11 @@ impl Clone for Param { Self { name: self.name.clone(), data: self.data.clone(), _t: PhantomData, _u: PhantomData } } } -impl Display for Param { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(&self.name) } +impl fmt::Display for Param { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(&self.name) } } -impl Debug for Param { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Param { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("Param").field(&*self.name).finish() } } @@ -60,7 +61,7 @@ impl Debug for Param { #[derive(Debug, Clone)] pub struct Thunk(pub Expr); impl TryFromExpr for Thunk { - fn from_expr(expr: Expr) -> ExternResult { Ok(Thunk(expr)) } + fn from_expr(expr: Expr) -> RTResult { Ok(Thunk(expr)) } } struct FnMiddleStage { @@ -71,8 +72,8 @@ struct FnMiddleStage { impl Clone for FnMiddleStage { fn clone(&self) -> Self { Self { arg: self.arg.clone(), f: self.f.clone() } } } -impl Debug for FnMiddleStage { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for FnMiddleStage { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "FnMiddleStage({} {})", self.f, self.arg) } } @@ -85,6 +86,7 @@ impl< { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } + fn type_name(&self) -> &'static str { std::any::type_name::() } fn redirect(&mut self) -> Option<&mut Expr> { // this should be ctfe'd (TypeId::of::() != TypeId::of::()).then_some(&mut self.arg) @@ -93,7 +95,7 @@ impl< let Self { arg, f: Param { data: f, .. } } = *self; Ok(AtomicReturn::Change(0, f(arg.downcast()?).to_clause(r.location))) } - fn apply_ref(&self, _: CallData) -> ExternResult { panic!("Atom should have decayed") } + fn apply_mut(&mut self, _: CallData) -> RTResult { panic!("Atom should have decayed") } } impl Responder for Param {} @@ -106,12 +108,13 @@ impl< { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } + fn type_name(&self) -> &'static str { std::any::type_name::() } fn redirect(&mut self) -> Option<&mut Expr> { None } fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } - fn apply_ref(&self, call: CallData) -> ExternResult { + fn apply_mut(&mut self, call: CallData) -> RTResult { Ok(FnMiddleStage { arg: call.arg, f: self.clone() }.atom_cls()) } - fn apply(self: Box, call: CallData) -> ExternResult { + fn apply(self: Box, call: CallData) -> RTResult { Ok(FnMiddleStage { arg: call.arg, f: *self }.atom_cls()) } } @@ -134,7 +137,7 @@ pub fn xfn( /// - the return type must implement [ToClause] /// /// Take [Thunk] to consume the argument as-is, without normalization. -pub trait Xfn: Clone + 'static { +pub trait Xfn: Clone + Send + 'static { /// Convert Rust type to Orchid function, given a name for logging fn to_atomic(self, name: Tok) -> impl Atomic + Clone; } diff --git a/src/foreign/implementations.rs b/src/foreign/implementations.rs deleted file mode 100644 index 1d2dc46..0000000 --- a/src/foreign/implementations.rs +++ /dev/null @@ -1,169 +0,0 @@ -use std::any::Any; -use std::fmt::Debug; - -use super::atom::{Atom, Atomic, AtomicResult, CallData, RunData}; -use super::error::{ExternErrorObj, ExternResult}; -use super::process::Unstable; -use super::to_clause::ToClause; -use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::interpreter::error::RunError; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::{Clause, Expr}; -use crate::location::CodeLocation; -use crate::utils::clonable_iter::Clonable; -use crate::utils::ddispatch::Responder; - -impl ToClause for Option { - fn to_clause(self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self { - None => tpl::C("std::option::none").template(ctx, []), - Some(t) => tpl::A(tpl::C("std::option::some"), tpl::Slot) - .template(ctx, [t.to_clause(location)]), - } - } -} - -impl ToClause for Result { - fn to_clause(self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self { - Ok(t) => tpl::A(tpl::C("std::result::ok"), tpl::Slot) - .template(ctx, [t.to_clause(location)]), - Err(e) => tpl::A(tpl::C("std::result::err"), tpl::Slot) - .template(ctx, [e.to_clause(location)]), - } - } -} - -struct PendingError(ExternErrorObj); -impl Responder for PendingError {} -impl Debug for PendingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "PendingError({})", self.0) - } -} -impl Atomic for PendingError { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn Any { self } - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { - Err(RunError::Extern(self.0)) - } - fn apply_ref(&self, _: CallData) -> ExternResult { - panic!("This atom decays instantly") - } -} - -impl ToClause for ExternResult { - fn to_clause(self, location: CodeLocation) -> Clause { - match self { - Err(e) => PendingError(e).atom_cls(), - Ok(t) => t.to_clause(location), - } - } -} - -struct ListGen(Clonable) -where - I: Iterator + Send, - I::Item: ToClause + Send; -impl Clone for ListGen -where - I: Iterator + Send, - I::Item: ToClause + Send, -{ - fn clone(&self) -> Self { Self(self.0.clone()) } -} -impl ToClause for ListGen -where - I: Iterator + Send + 'static, - I::Item: ToClause + Clone + Send, -{ - fn to_clause(mut self, location: CodeLocation) -> Clause { - let ctx = nort_gen(location.clone()); - match self.0.next() { - None => tpl::C("std::lit::end").template(ctx, []), - Some(val) => { - let atom = Unstable::new(|run| self.to_clause(run.location)); - tpl::a2(tpl::C("std::lit::cons"), tpl::Slot, tpl::V(atom)) - .template(ctx, [val.to_clause(location)]) - }, - } - } -} - -/// Convert an iterator into a lazy-evaluated Orchid list. -pub fn list(items: I) -> impl ToClause -where - I: IntoIterator + Clone + Send + Sync + 'static, - I::IntoIter: Send, - I::Item: ToClause + Clone + Send, -{ - Unstable::new(move |RunData { location, .. }| { - ListGen(Clonable::new( - items.clone().into_iter().map(move |t| t.to_clsi(location.clone())), - )) - }) -} - -impl ToClause for Vec { - fn to_clause(self, location: CodeLocation) -> Clause { - list(self).to_clause(location) - } -} - -impl ToClause for Atom { - fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) } -} - -mod tuple_impls { - use std::sync::Arc; - - use super::ToClause; - use crate::foreign::atom::Atomic; - use crate::foreign::error::AssertionError; - use crate::foreign::implementations::ExternResult; - use crate::foreign::inert::Inert; - use crate::foreign::try_from_expr::TryFromExpr; - use crate::interpreter::nort::{Clause, Expr}; - use crate::libs::std::tuple::Tuple; - use crate::location::CodeLocation; - - macro_rules! gen_tuple_impl { - ( ($($T:ident)*) ($($t:ident)*)) => { - impl<$($T: ToClause),*> ToClause for ($($T,)*) { - fn to_clause(self, location: CodeLocation) -> Clause { - let ($($t,)*) = self; - Inert(Tuple(Arc::new(vec![ - $($t.to_expr(location.clone()),)* - ]))).atom_cls() - } - } - - impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) { - fn from_expr(ex: Expr) -> ExternResult { - let Inert(Tuple(slice)) = ex.clone().downcast()?; - match &slice[..] { - [$($t),*] => Ok(($($t.clone().downcast()?,)*)), - _ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}")) - } - } - } - }; - } - - gen_tuple_impl!((A)(a)); - gen_tuple_impl!((A B) (a b)); - gen_tuple_impl!((A B C) (a b c)); - gen_tuple_impl!((A B C D) (a b c d)); - gen_tuple_impl!((A B C D E) (a b c d e)); - gen_tuple_impl!((A B C D E F) (a b c d e f)); - gen_tuple_impl!((A B C D E F G) (a b c d e f g)); - gen_tuple_impl!((A B C D E F G H) (a b c d e f g h)); - gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i)); - gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j)); - gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k)); - gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l)); -} diff --git a/src/foreign/inert.rs b/src/foreign/inert.rs index f76414b..9eecf49 100644 --- a/src/foreign/inert.rs +++ b/src/foreign/inert.rs @@ -1,13 +1,13 @@ +//! An [Atomic] that wraps inert data. + use std::any::Any; -use std::fmt::{Debug, Display}; +use std::fmt; use std::ops::{Deref, DerefMut}; use ordered_float::NotNan; -use super::atom::{ - Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData, -}; -use super::error::{ExternError, ExternResult}; +use super::atom::{Atom, Atomic, AtomicResult, AtomicReturn, CallData, NotAFunction, RunData}; +use super::error::{RTError, RTResult}; use super::try_from_expr::TryFromExpr; use crate::foreign::error::AssertionError; use crate::interpreter::nort::{Clause, Expr}; @@ -17,11 +17,10 @@ use crate::utils::ddispatch::{Request, Responder}; /// A proxy trait that implements [Atomic] for blobs of data in Rust code that /// cannot be processed and always report inert. Since these are expected to be -/// parameters of functions defined with [define_fn] it also automatically -/// implements [TryFromExpr] so that a conversion doesn't have to be -/// provided in argument lists. -pub trait InertPayload: Debug + Clone + Send + 'static { - /// Typename to be shown in the error when a conversion from [ExprInst] fails +/// parameters of Rust functions it also automatically implements [TryFromExpr] +/// so that `Inert` arguments can be parsed directly. +pub trait InertPayload: fmt::Debug + Clone + Send + 'static { + /// Typename to be shown in the error when a conversion from [Expr] fails /// /// This will default to `type_name::()` when it becomes stable const TYPE_STR: &'static str; @@ -37,7 +36,8 @@ pub trait InertPayload: Debug + Clone + Send + 'static { /// ```ignore /// fn strict_eq(&self, other: &Self) -> bool { self == other } /// ``` - fn strict_eq(&self, _: &Self) -> bool { false } + #[allow(unused_variables)] + fn strict_eq(&self, other: &Self) -> bool { false } } /// An atom that stores a value and rejects all interpreter interactions. It is @@ -61,42 +61,36 @@ impl DerefMut for Inert { impl Responder for Inert { fn respond(&self, mut request: Request) { - if request.can_serve::() { - request.serve(self.0.clone()) - } else { - self.0.respond(request) - } + if request.can_serve::() { request.serve(self.0.clone()) } else { self.0.respond(request) } } } impl Atomic for Inert { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn Any { self } + fn type_name(&self) -> &'static str { std::any::type_name::() } fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { - AtomicReturn::inert(*self) + fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } + fn apply_mut(&mut self, call: CallData) -> RTResult { + Err(NotAFunction(self.clone().atom_expr(call.location)).pack()) } - fn apply_ref(&self, call: CallData) -> ExternResult { - Err(NotAFunction(self.clone().atom_expr(call.location)).rc()) - } - fn parser_eq(&self, other: &dyn Any) -> bool { - (other.downcast_ref::()) - .map_or(false, |other| self.0.strict_eq(&other.0)) + fn parser_eq(&self, other: &dyn Atomic) -> bool { + other.as_any_ref().downcast_ref::().map_or(false, |other| self.0.strict_eq(&other.0)) } } impl TryFromExpr for Inert { - fn from_expr(expr: Expr) -> ExternResult { + fn from_expr(expr: Expr) -> RTResult { let Expr { clause, location } = expr; match clause.try_unwrap() { - Ok(Clause::Atom(at)) => at.try_downcast::().map_err(|a| { - AssertionError::ext(location, T::TYPE_STR, format!("{a:?}")) - }), - Err(inst) => match &*inst.cls() { - Clause::Atom(at) => - at.downcast_ref::().cloned().ok_or_else(|| { - AssertionError::ext(location, T::TYPE_STR, format!("{inst}")) - }), + Ok(Clause::Atom(at)) => at + .try_downcast::() + .map_err(|a| AssertionError::ext(location, T::TYPE_STR, format!("{a:?}"))), + Err(inst) => match &*inst.cls_mut() { + Clause::Atom(at) => at + .downcast_ref::() + .cloned() + .ok_or_else(|| AssertionError::ext(location, T::TYPE_STR, format!("{inst}"))), cls => AssertionError::fail(location, "atom", format!("{cls}")), }, Ok(cls) => AssertionError::fail(location, "atom", format!("{cls}")), @@ -104,10 +98,8 @@ impl TryFromExpr for Inert { } } -impl Display for Inert { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } +impl fmt::Display for Inert { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl InertPayload for bool { diff --git a/src/foreign/mod.rs b/src/foreign/mod.rs index 31ec371..4b91d14 100644 --- a/src/foreign/mod.rs +++ b/src/foreign/mod.rs @@ -6,7 +6,6 @@ pub mod atom; pub mod cps_box; pub mod error; pub mod fn_bridge; -pub mod implementations; pub mod inert; pub mod process; pub mod to_clause; diff --git a/src/foreign/process.rs b/src/foreign/process.rs index 6116b91..f59e5a9 100644 --- a/src/foreign/process.rs +++ b/src/foreign/process.rs @@ -1,7 +1,11 @@ -use std::fmt::Debug; +//! An [Atomic] implementor that runs a callback and turns into the return +//! value. Useful to generate expressions that depend on values in the +//! interpreter's context, and to defer the generation of expensive +//! subexpressions +use std::fmt; -use super::atom::{Atomic, AtomicReturn, CallData, RunData}; -use super::error::ExternResult; +use super::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; +use super::error::RTResult; use super::to_clause::ToClause; use crate::interpreter::nort::{Clause, Expr}; use crate::utils::ddispatch::Responder; @@ -16,21 +20,20 @@ impl R + Send + 'static, R: ToClause> Unstable { pub const fn new(f: F) -> Self { Self(f) } } impl Responder for Unstable {} -impl Debug for Unstable { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Unstable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Unstable").finish_non_exhaustive() } } -impl R + Send + 'static, R: ToClause> Atomic - for Unstable -{ +impl R + Send + 'static, R: ToClause> Atomic for Unstable { fn as_any(self: Box) -> Box { self } fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn apply_ref(&self, _: CallData) -> ExternResult { - panic!("This atom decays instantly") - } - fn run(self: Box, run: RunData) -> super::atom::AtomicResult { - let clause = self.0(run.clone()).to_clause(run.location.clone()); + fn type_name(&self) -> &'static str { std::any::type_name::() } + + fn apply_mut(&mut self, _: CallData) -> RTResult { panic!("This atom decays instantly") } + fn run(self: Box, run: RunData) -> AtomicResult { + let loc = run.location.clone(); + let clause = self.0(run).to_clause(loc); Ok(AtomicReturn::Change(0, clause)) } fn redirect(&mut self) -> Option<&mut Expr> { None } diff --git a/src/foreign/to_clause.rs b/src/foreign/to_clause.rs index f363b38..169eb5e 100644 --- a/src/foreign/to_clause.rs +++ b/src/foreign/to_clause.rs @@ -1,10 +1,18 @@ -use super::atom::Atomic; +//! Conversions from Rust values to Orchid expressions. Many APIs and +//! [super::fn_bridge] in particular use this to automatically convert values on +//! the boundary. The opposite conversion is [super::try_from_expr::TryFromExpr] + +use super::atom::{Atomic, RunData}; +use super::process::Unstable; +use crate::gen::tpl; +use crate::gen::traits::Gen; +use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::nort::{Clause, ClauseInst, Expr}; use crate::location::CodeLocation; +use crate::utils::clonable_iter::Clonable; /// A trait for things that are infallibly convertible to [ClauseInst]. These -/// types can be returned by callbacks passed to the [super::xfn_1ary] family of -/// functions. +/// types can be returned by callbacks passed to [super::fn_bridge::xfn]. pub trait ToClause: Sized { /// Convert this value to a [Clause]. If your value can only be directly /// converted to a [ClauseInst], you can call `ClauseInst::to_clause` to @@ -29,15 +37,166 @@ impl ToClause for Clause { fn to_clause(self, _: CodeLocation) -> Clause { self } } impl ToClause for ClauseInst { - fn to_clause(self, _: CodeLocation) -> Clause { - self.into_cls() - } + fn to_clause(self, _: CodeLocation) -> Clause { self.into_cls() } fn to_clsi(self, _: CodeLocation) -> ClauseInst { self } } impl ToClause for Expr { - fn to_clause(self, location: CodeLocation) -> Clause { - self.clause.to_clause(location) - } + fn to_clause(self, location: CodeLocation) -> Clause { self.clause.to_clause(location) } fn to_clsi(self, _: CodeLocation) -> ClauseInst { self.clause } fn to_expr(self, _: CodeLocation) -> Expr { self } } + +struct ListGen(Clonable) +where + I: Iterator + Send, + I::Item: ToClause + Send; +impl Clone for ListGen +where + I: Iterator + Send, + I::Item: ToClause + Send, +{ + fn clone(&self) -> Self { Self(self.0.clone()) } +} +impl ToClause for ListGen +where + I: Iterator + Send + 'static, + I::Item: ToClause + Clone + Send, +{ + fn to_clause(mut self, location: CodeLocation) -> Clause { + let ctx = nort_gen(location.clone()); + match self.0.next() { + None => tpl::C("std::list::end").template(ctx, []), + Some(val) => { + let atom = Unstable::new(|run| self.to_clause(run.location)); + tpl::a2(tpl::C("std::list::cons"), tpl::Slot, tpl::V(atom)) + .template(ctx, [val.to_clause(location)]) + }, + } + } +} + +/// Convert an iterator into a lazy-evaluated Orchid list. +pub fn list(items: I) -> impl ToClause +where + I: IntoIterator + Clone + Send + Sync + 'static, + I::IntoIter: Send, + I::Item: ToClause + Clone + Send, +{ + Unstable::new(move |RunData { location, .. }| { + ListGen(Clonable::new(items.clone().into_iter().map(move |t| t.to_clsi(location.clone())))) + }) +} + +mod implementations { + use std::any::Any; + use std::fmt; + use std::sync::Arc; + + use super::{list, ToClause}; + use crate::foreign::atom::{Atom, Atomic, AtomicResult, CallData, RunData}; + use crate::foreign::error::{AssertionError, RTErrorObj, RTResult}; + use crate::foreign::inert::Inert; + use crate::foreign::try_from_expr::TryFromExpr; + use crate::gen::tpl; + use crate::gen::traits::Gen; + use crate::interpreter::gen_nort::nort_gen; + use crate::interpreter::nort::{Clause, Expr}; + use crate::libs::std::tuple::Tuple; + use crate::location::CodeLocation; + use crate::utils::ddispatch::Responder; + + impl ToClause for Option { + fn to_clause(self, location: CodeLocation) -> Clause { + let ctx = nort_gen(location.clone()); + match self { + None => tpl::C("std::option::none").template(ctx, []), + Some(t) => + tpl::A(tpl::C("std::option::some"), tpl::Slot).template(ctx, [t.to_clause(location)]), + } + } + } + + impl ToClause for Result { + fn to_clause(self, location: CodeLocation) -> Clause { + let ctx = nort_gen(location.clone()); + match self { + Ok(t) => + tpl::A(tpl::C("std::result::ok"), tpl::Slot).template(ctx, [t.to_clause(location)]), + Err(e) => + tpl::A(tpl::C("std::result::err"), tpl::Slot).template(ctx, [e.to_clause(location)]), + } + } + } + + struct PendingError(RTErrorObj); + impl Responder for PendingError {} + impl fmt::Debug for PendingError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "PendingError({})", self.0) + } + } + impl Atomic for PendingError { + fn as_any(self: Box) -> Box { self } + fn as_any_ref(&self) -> &dyn Any { self } + fn type_name(&self) -> &'static str { std::any::type_name::() } + + fn redirect(&mut self) -> Option<&mut Expr> { None } + fn run(self: Box, _: RunData) -> AtomicResult { Err(self.0) } + fn apply_mut(&mut self, _: CallData) -> RTResult { + panic!("This atom decays instantly") + } + } + + impl ToClause for RTResult { + fn to_clause(self, location: CodeLocation) -> Clause { + match self { + Err(e) => PendingError(e).atom_cls(), + Ok(t) => t.to_clause(location), + } + } + } + + impl ToClause for Vec { + fn to_clause(self, location: CodeLocation) -> Clause { list(self).to_clause(location) } + } + + impl ToClause for Atom { + fn to_clause(self, _: CodeLocation) -> Clause { Clause::Atom(self) } + } + + macro_rules! gen_tuple_impl { + ( ($($T:ident)*) ($($t:ident)*)) => { + impl<$($T: ToClause),*> ToClause for ($($T,)*) { + fn to_clause(self, location: CodeLocation) -> Clause { + let ($($t,)*) = self; + Inert(Tuple(Arc::new(vec![ + $($t.to_expr(location.clone()),)* + ]))).atom_cls() + } + } + + impl<$($T: TryFromExpr),*> TryFromExpr for ($($T,)*) { + fn from_expr(ex: Expr) -> RTResult { + let Inert(Tuple(slice)) = ex.clone().downcast()?; + match &slice[..] { + [$($t),*] => Ok(($($t.clone().downcast()?,)*)), + _ => AssertionError::fail(ex.location(), "Tuple length mismatch", format!("{ex}")) + } + } + } + }; + } + + gen_tuple_impl!((A)(a)); + gen_tuple_impl!((A B) (a b)); + gen_tuple_impl!((A B C) (a b c)); + gen_tuple_impl!((A B C D) (a b c d)); + gen_tuple_impl!((A B C D E) (a b c d e)); + gen_tuple_impl!((A B C D E F) (a b c d e f)); + gen_tuple_impl!((A B C D E F G) (a b c d e f g)); + gen_tuple_impl!((A B C D E F G H) (a b c d e f g h)); + gen_tuple_impl!((A B C D E F G H I) (a b c d e f g h i)); + gen_tuple_impl!((A B C D E F G H I J) (a b c d e f g h i j)); + gen_tuple_impl!((A B C D E F G H I J K) (a b c d e f g h i j k)); + gen_tuple_impl!((A B C D E F G H I J K L) (a b c d e f g h i j k l)); +} diff --git a/src/foreign/try_from_expr.rs b/src/foreign/try_from_expr.rs index f6ab809..2bd7776 100644 --- a/src/foreign/try_from_expr.rs +++ b/src/foreign/try_from_expr.rs @@ -1,4 +1,8 @@ -use super::error::ExternResult; +//! Conversions from Orchid expressions to Rust values. Many APIs and +//! [super::fn_bridge] in particular use this to automatically convert values on +//! the boundary. Failures cause an interpreter exit + +use super::error::RTResult; use crate::interpreter::nort::{ClauseInst, Expr}; use crate::location::CodeLocation; @@ -6,15 +10,15 @@ use crate::location::CodeLocation; /// foreign functions request automatic argument downcasting. pub trait TryFromExpr: Sized { /// Match and clone the value out of an [Expr] - fn from_expr(expr: Expr) -> ExternResult; + fn from_expr(expr: Expr) -> RTResult; } impl TryFromExpr for Expr { - fn from_expr(expr: Expr) -> ExternResult { Ok(expr) } + fn from_expr(expr: Expr) -> RTResult { Ok(expr) } } impl TryFromExpr for ClauseInst { - fn from_expr(expr: Expr) -> ExternResult { Ok(expr.clsi()) } + fn from_expr(expr: Expr) -> RTResult { Ok(expr.clsi()) } } /// Request a value of a particular type and also return its location for @@ -22,7 +26,5 @@ impl TryFromExpr for ClauseInst { #[derive(Debug, Clone)] pub struct WithLoc(pub CodeLocation, pub T); impl TryFromExpr for WithLoc { - fn from_expr(expr: Expr) -> ExternResult { - Ok(Self(expr.location(), T::from_expr(expr)?)) - } + fn from_expr(expr: Expr) -> RTResult { Ok(Self(expr.location(), T::from_expr(expr)?)) } } diff --git a/src/gen/tpl.rs b/src/gen/tpl.rs index 61169a9..6cf7d81 100644 --- a/src/gen/tpl.rs +++ b/src/gen/tpl.rs @@ -1,8 +1,6 @@ //! Various elemental components to build expression trees that all implement //! [GenClause]. -use std::fmt::Debug; - use super::traits::{GenClause, Generable}; use crate::foreign::atom::{Atom, AtomGenerator, Atomic}; @@ -44,11 +42,7 @@ impl GenClause for A { } /// Apply a function to two arguments -pub fn a2( - f: impl GenClause, - x: impl GenClause, - y: impl GenClause, -) -> impl GenClause { +pub fn a2(f: impl GenClause, x: impl GenClause, y: impl GenClause) -> impl GenClause { A(A(f, x), y) } @@ -65,16 +59,12 @@ impl GenClause for L { #[derive(Debug, Clone)] pub struct P(pub &'static str); impl GenClause for P { - fn generate(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T { - T::arg(ctx, self.0) - } + fn generate(&self, ctx: T::Ctx<'_>, _: &impl Fn() -> T) -> T { T::arg(ctx, self.0) } } /// Slot for an Orchid value to be specified during execution #[derive(Debug, Clone)] pub struct Slot; impl GenClause for Slot { - fn generate(&self, _: T::Ctx<'_>, pop: &impl Fn() -> T) -> T { - pop() - } + fn generate(&self, _: T::Ctx<'_>, pop: &impl Fn() -> T) -> T { pop() } } diff --git a/src/gen/traits.rs b/src/gen/traits.rs index 5d2b5c7..12a1028 100644 --- a/src/gen/traits.rs +++ b/src/gen/traits.rs @@ -3,7 +3,7 @@ use std::backtrace::Backtrace; use std::cell::RefCell; use std::collections::VecDeque; -use std::fmt::Debug; +use std::fmt; use crate::foreign::atom::Atom; @@ -15,10 +15,7 @@ pub trait Generable: Sized { /// Wrap external data. fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self; /// Generate a reference to a constant - fn constant<'a>( - ctx: Self::Ctx<'_>, - name: impl IntoIterator, - ) -> Self; + fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator) -> Self; /// Generate a function call given the function and its argument fn apply( ctx: Self::Ctx<'_>, @@ -27,11 +24,7 @@ pub trait Generable: Sized { ) -> Self; /// Generate a function. The argument name is only valid within the same /// [Generable]. - fn lambda( - ctx: Self::Ctx<'_>, - name: &str, - body: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self; + fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self; /// Generate a reference to a function argument. The argument name is only /// valid within the same [Generable]. fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self; @@ -39,11 +32,11 @@ pub trait Generable: Sized { /// Expression templates which can be instantiated in multiple representations /// of Orchid. Expressions can be built from the elements defined in -/// [super::lit]. +/// [super::tpl]. /// /// Do not depend on this trait, use [Gen] instead. Conversely, implement this /// instead of [Gen]. -pub trait GenClause: Debug + Sized { +pub trait GenClause: fmt::Debug + Sized { /// Enact the template at runtime to build a given type. /// `pop` pops from the runtime template parameter list passed to the /// generator. @@ -56,7 +49,7 @@ pub trait GenClause: Debug + Sized { /// /// Do not implement this trait, it's the frontend for [GenClause]. Conversely, /// do not consume [GenClause]. -pub trait Gen: Debug { +pub trait Gen: fmt::Debug { /// Create an instance of this template with some parameters fn template(&self, ctx: T::Ctx<'_>, params: U) -> T; } @@ -64,11 +57,15 @@ pub trait Gen: Debug { impl, G: GenClause> Gen for G { fn template(&self, ctx: T::Ctx<'_>, params: I) -> T { let values = RefCell::new(params.into_iter().collect::>()); - let t = self.generate(ctx, &|| { - values.borrow_mut().pop_front().expect("Not enough values provided") - }); + let t = + self.generate(ctx, &|| values.borrow_mut().pop_front().expect("Not enough values provided")); let leftover = values.borrow().len(); - assert_eq!(leftover, 0, "Too many values provided ({leftover} left) {}", Backtrace::force_capture()); + assert_eq!( + leftover, + 0, + "Too many values provided ({leftover} left) {}", + Backtrace::force_capture() + ); t } } diff --git a/src/gen/tree.rs b/src/gen/tree.rs index 32bbf69..67de0ce 100644 --- a/src/gen/tree.rs +++ b/src/gen/tree.rs @@ -1,7 +1,7 @@ //! Components to build in-memory module trees that in Orchid. These modules //! can only contain constants and other modules. -use std::fmt::Debug; +use std::fmt; use dyn_clone::{clone_box, DynClone}; use intern_all::Tok; @@ -20,8 +20,8 @@ use crate::tree::{ModEntry, ModMember, TreeConflict}; use crate::utils::combine::Combine; trait_set! { - trait TreeLeaf = Gen + DynClone; - trait XfnCB = FnOnce(Substack>) -> AtomGenerator + DynClone; + trait TreeLeaf = Gen + DynClone + Send; + trait XfnCB = FnOnce(Substack>) -> AtomGenerator + DynClone + Send; } enum GCKind { @@ -32,11 +32,14 @@ enum GCKind { /// A leaf in the [ConstTree] pub struct GenConst(GCKind); impl GenConst { - fn c(data: impl GenClause + Clone + 'static) -> Self { Self(GCKind::Const(Box::new(data))) } + fn c(data: impl GenClause + Send + Clone + 'static) -> Self { + Self(GCKind::Const(Box::new(data))) + } fn f(f: impl Xfn) -> Self { - Self(GCKind::Xfn(N, Box::new(move |stck| { - AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f)) - }))) + Self(GCKind::Xfn( + N, + Box::new(move |stck| AtomGenerator::cloner(xfn(&stck.unreverse().iter().join("::"), f))), + )) } /// Instantiate as [crate::interpreter::nort] pub fn gen_nort(self, stck: Substack>, location: CodeLocation) -> Expr { @@ -46,8 +49,8 @@ impl GenConst { } } } -impl Debug for GenConst { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for GenConst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.0 { GCKind::Const(c) => write!(f, "{c:?}"), GCKind::Xfn(n, _) => write!(f, "xfn/{n}"), @@ -75,19 +78,30 @@ impl Combine for GenConst { } /// A lightweight module tree that can be built declaratively by hand to -/// describe libraries of external functions in Rust. It implements [Add] for -/// added convenience +/// describe libraries of external functions in Rust. It implements [Combine] +/// for merging libraries. pub type ConstTree = ModEntry; /// Describe a constant #[must_use] -pub fn leaf(value: impl GenClause + Clone + 'static) -> ConstTree { +pub fn leaf(value: impl GenClause + Clone + Send + 'static) -> ConstTree { ModEntry::wrap(ModMember::Item(GenConst::c(value))) } +/// Describe a constant which appears in [ConstTree::tree]. +/// +/// The unarray tricks rustfmt into keeping this call as a single line even if +/// it chooses to break the argument into a block. +pub fn ent>( + key: K, + [g]: [impl GenClause + Clone + Send + 'static; 1], +) -> (K, ConstTree) { + (key, leaf(g)) +} + /// Describe an [Atomic] #[must_use] -pub fn atom_leaf(atom: impl Atomic + Clone + 'static) -> ConstTree { leaf(tpl::V(atom)) } +pub fn atom_leaf(atom: impl Atomic + Clone + Send + 'static) -> ConstTree { leaf(tpl::V(atom)) } /// Describe an [Atomic] which appears as an entry in a [ConstTree::tree] /// diff --git a/src/intermediate/ast_to_ir.rs b/src/intermediate/ast_to_ir.rs index b53b81a..ae6fa10 100644 --- a/src/intermediate/ast_to_ir.rs +++ b/src/intermediate/ast_to_ir.rs @@ -7,7 +7,7 @@ use substack::Substack; use super::ir; use crate::error::{ProjectError, ProjectResult}; -use crate::location::{CodeLocation, SourceRange}; +use crate::location::{CodeOrigin, SourceRange}; use crate::name::Sym; use crate::parse::parsed; use crate::utils::unwrap_or::unwrap_or; @@ -17,20 +17,24 @@ trait IRErrorKind: Clone + Send + Sync + 'static { } #[derive(Clone)] -struct IRError(SourceRange, Sym, T); +struct IRError { + at: SourceRange, + sym: SourceRange, + _kind: T, +} +impl IRError { + fn new(at: SourceRange, sym: SourceRange, _kind: T) -> Self { Self { at, sym, _kind } } +} impl ProjectError for IRError { const DESCRIPTION: &'static str = T::DESCR; - fn message(&self) -> String { format!("In {}, {}", self.1, T::DESCR) } - fn one_position(&self) -> CodeLocation { - CodeLocation::Source(self.0.clone()) - } + fn message(&self) -> String { format!("In {}, {}", self.sym, T::DESCR) } + fn one_position(&self) -> CodeOrigin { CodeOrigin::Source(self.at.clone()) } } #[derive(Clone)] struct EmptyS; impl IRErrorKind for EmptyS { - const DESCR: &'static str = - "`()` as a clause is meaningless in lambda calculus"; + const DESCR: &'static str = "`()` as a clause is meaningless in lambda calculus"; } #[derive(Clone)] @@ -54,26 +58,29 @@ impl IRErrorKind for PhLeak { } /// Try to convert an expression from AST format to typed lambda -pub fn ast_to_ir(expr: parsed::Expr, symbol: Sym) -> ProjectResult { - expr_rec(expr, Context::new(symbol)) +pub fn ast_to_ir(expr: parsed::Expr, symbol: SourceRange, module: Sym) -> ProjectResult { + expr_rec(expr, Context::new(symbol, module)) } #[derive(Clone)] struct Context<'a> { names: Substack<'a, Sym>, - symbol: Sym, + range: SourceRange, + module: Sym, } impl<'a> Context<'a> { #[must_use] fn w_name<'b>(&'b self, name: Sym) -> Context<'b> where 'a: 'b { - Context { names: self.names.push(name), symbol: self.symbol.clone() } + Context { names: self.names.push(name), range: self.range.clone(), module: self.module.clone() } } } impl Context<'static> { #[must_use] - fn new(symbol: Sym) -> Self { Self { names: Substack::Bottom, symbol } } + fn new(symbol: SourceRange, module: Sym) -> Self { + Self { names: Substack::Bottom, range: symbol, module } + } } /// Process an expression sequence @@ -83,29 +90,25 @@ fn exprv_rec( location: SourceRange, ) -> ProjectResult { let last = unwrap_or! {v.pop_back(); { - return Err(IRError(location, ctx.symbol, EmptyS).pack()); + return Err(IRError::new(location, ctx.range, EmptyS).pack()); }}; let v_end = match v.back() { None => return expr_rec(last, ctx), Some(penultimate) => penultimate.range.range.end, }; let f = exprv_rec(v, ctx.clone(), location.map_range(|r| r.start..v_end))?; - let x = expr_rec(last, ctx)?; + let x = expr_rec(last, ctx.clone())?; let value = ir::Clause::Apply(Rc::new(f), Rc::new(x)); - Ok(ir::Expr { value, location: CodeLocation::Source(location) }) + Ok(ir::Expr::new(value, location, ctx.module)) } /// Process an expression -fn expr_rec( - parsed::Expr { value, range }: parsed::Expr, - ctx: Context, -) -> ProjectResult { +fn expr_rec(parsed::Expr { value, range }: parsed::Expr, ctx: Context) -> ProjectResult { match value { parsed::Clause::S(parsed::PType::Par, body) => { return exprv_rec(body.to_vec().into(), ctx, range); }, - parsed::Clause::S(..) => - return Err(IRError(range, ctx.symbol, BadGroup).pack()), + parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()), _ => (), } let value = match value { @@ -114,29 +117,24 @@ fn expr_rec( let name = match &arg[..] { [parsed::Expr { value: parsed::Clause::Name(name), .. }] => name, [parsed::Expr { value: parsed::Clause::Placeh { .. }, .. }] => - return Err(IRError(range.clone(), ctx.symbol, PhLeak).pack()), - _ => return Err(IRError(range.clone(), ctx.symbol, InvalidArg).pack()), + return Err(IRError::new(range.clone(), ctx.range, PhLeak).pack()), + _ => return Err(IRError::new(range.clone(), ctx.range, InvalidArg).pack()), }; let body_ctx = ctx.w_name(name.clone()); let body = exprv_rec(b.to_vec().into(), body_ctx, range.clone())?; ir::Clause::Lambda(Rc::new(body)) }, parsed::Clause::Name(name) => { - let lvl_opt = (ctx.names.iter()) - .enumerate() - .find(|(_, n)| **n == name) - .map(|(lvl, _)| lvl); + let lvl_opt = (ctx.names.iter()).enumerate().find(|(_, n)| **n == name).map(|(lvl, _)| lvl); match lvl_opt { Some(lvl) => ir::Clause::LambdaArg(lvl), None => ir::Clause::Constant(name.clone()), } }, parsed::Clause::S(parsed::PType::Par, entries) => - exprv_rec(entries.to_vec().into(), ctx, range.clone())?.value, - parsed::Clause::S(..) => - return Err(IRError(range, ctx.symbol, BadGroup).pack()), - parsed::Clause::Placeh { .. } => - return Err(IRError(range, ctx.symbol, PhLeak).pack()), + exprv_rec(entries.to_vec().into(), ctx.clone(), range.clone())?.value, + parsed::Clause::S(..) => return Err(IRError::new(range, ctx.range, BadGroup).pack()), + parsed::Clause::Placeh { .. } => return Err(IRError::new(range, ctx.range, PhLeak).pack()), }; - Ok(ir::Expr::new(value, range.clone())) + Ok(ir::Expr::new(value, range, ctx.module)) } diff --git a/src/intermediate/ir.rs b/src/intermediate/ir.rs index 36bb859..b9cbab0 100644 --- a/src/intermediate/ir.rs +++ b/src/intermediate/ir.rs @@ -3,7 +3,7 @@ //! innovations in the processing and execution of code will likely operate on //! this representation. -use std::fmt::{Debug, Write}; +use std::fmt; use std::rc::Rc; use crate::foreign::atom::AtomGenerator; @@ -16,40 +16,44 @@ use crate::utils::string_from_charset::string_from_charset; #[derive(PartialEq, Eq, Clone, Copy)] struct Wrap(bool, bool); +/// Code element with associated metadata #[derive(Clone)] pub struct Expr { + /// Code element pub value: Clause, + /// Location metadata pub location: CodeLocation, } impl Expr { - pub fn new(value: Clause, location: SourceRange) -> Self { - Self { value, location: CodeLocation::Source(location) } + /// Create an IR expression + pub fn new(value: Clause, location: SourceRange, module: Sym) -> Self { + Self { value, location: CodeLocation::new_src(location, module) } } - fn deep_fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - depth: usize, - tr: Wrap, - ) -> std::fmt::Result { + fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, tr: Wrap) -> fmt::Result { let Expr { value, .. } = self; value.deep_fmt(f, depth, tr)?; Ok(()) } } -impl Debug for Expr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deep_fmt(f, 0, Wrap(false, false)) } } +/// Semantic code element #[derive(Clone)] pub enum Clause { + /// Function call expression Apply(Rc, Rc), + /// Function expression Lambda(Rc), + /// Reference to an external constant Constant(Sym), + /// Reference to a function argument LambdaArg(usize), /// An opaque non-callable value, eg. a file handle Atom(AtomGenerator), @@ -58,32 +62,27 @@ pub enum Clause { const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz"; fn parametric_fmt( - f: &mut std::fmt::Formatter<'_>, + f: &mut fmt::Formatter<'_>, depth: usize, prefix: &str, body: &Expr, wrap_right: bool, -) -> std::fmt::Result { - // if wrap_right { - f.write_char('(')?; - // } +) -> fmt::Result { + if wrap_right { + write!(f, "(")?; + } f.write_str(prefix)?; f.write_str(&string_from_charset(depth as u64, ARGNAME_CHARSET))?; f.write_str(".")?; body.deep_fmt(f, depth + 1, Wrap(false, false))?; - // if wrap_right { - f.write_char(')')?; - // } + if wrap_right { + write!(f, ")")?; + } Ok(()) } impl Clause { - fn deep_fmt( - &self, - f: &mut std::fmt::Formatter<'_>, - depth: usize, - Wrap(wl, wr): Wrap, - ) -> std::fmt::Result { + fn deep_fmt(&self, f: &mut fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap) -> fmt::Result { match self { Self::Atom(a) => write!(f, "{a:?}"), Self::Lambda(body) => parametric_fmt(f, depth, "\\", body, wr), @@ -92,15 +91,15 @@ impl Clause { f.write_str(&string_from_charset(lambda_depth, ARGNAME_CHARSET)) }, Self::Apply(func, x) => { - // if wl { - f.write_char('(')?; - // } + if wl { + write!(f, "(")?; + } func.deep_fmt(f, depth, Wrap(false, true))?; - f.write_char(' ')?; + write!(f, " ")?; x.deep_fmt(f, depth, Wrap(true, wr && !wl))?; - // if wl { - f.write_char(')')?; - // } + if wl { + write!(f, ")")?; + } Ok(()) }, Self::Constant(token) => write!(f, "{token}"), @@ -108,8 +107,8 @@ impl Clause { } } -impl Debug for Clause { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Clause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.deep_fmt(f, 0, Wrap(false, false)) } } diff --git a/src/intermediate/ir_to_nort.rs b/src/intermediate/ir_to_nort.rs index c4bebfe..f02f99e 100644 --- a/src/intermediate/ir_to_nort.rs +++ b/src/intermediate/ir_to_nort.rs @@ -5,7 +5,7 @@ use crate::interpreter::nort; use crate::interpreter::nort_builder::NortBuilder; fn expr(expr: &ir::Expr, ctx: NortBuilder<(), usize>) -> nort::Expr { - clause(&expr.value, ctx).to_expr(expr.location.clone()) + clause(&expr.value, ctx).into_expr(expr.location.clone()) } fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause { @@ -21,6 +21,7 @@ fn clause(cls: &ir::Clause, ctx: NortBuilder<(), usize>) -> nort::Clause { } } +/// Convert an expression. pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr { let c = NortBuilder::new(&|count| { let mut count: usize = *count; @@ -32,5 +33,5 @@ pub fn ir_to_nort(expr: &ir::Expr) -> nort::Expr { }, }) }); - nort::ClauseInst::new(clause(&expr.value, c)).to_expr(expr.location.clone()) + nort::ClauseInst::new(clause(&expr.value, c)).into_expr(expr.location.clone()) } diff --git a/src/intermediate/mod.rs b/src/intermediate/mod.rs index 0943548..fc03ef2 100644 --- a/src/intermediate/mod.rs +++ b/src/intermediate/mod.rs @@ -1,3 +1,7 @@ -pub(crate) mod ast_to_ir; -pub(crate) mod ir; -pub(crate) mod ir_to_nort; +//! Intermediate representation. Currently just an indirection between +//! [super::parse::parsed] and [super::interpreter::nort], in the future +//! hopefully a common point for alternate encodings, optimizations and targets. + +pub mod ast_to_ir; +pub mod ir; +pub mod ir_to_nort; diff --git a/src/interpreter/apply.rs b/src/interpreter/apply.rs index f9aa86c..3acde03 100644 --- a/src/interpreter/apply.rs +++ b/src/interpreter/apply.rs @@ -1,10 +1,10 @@ use never::Never; -use super::context::RunContext; -use super::error::RunError; +use super::context::{RunEnv, RunParams}; use super::nort::{Clause, ClauseInst, Expr}; use super::path_set::{PathSet, Step}; use crate::foreign::atom::CallData; +use crate::foreign::error::RTResult; /// Process the clause at the end of the provided path. Note that paths always /// point to at least one target. Note also that this is not cached as a @@ -16,12 +16,12 @@ fn map_at( ) -> Result { // Pass through some unambiguous wrapper clauses match source { - Clause::Identity(alt) => return map_at(path, &alt.cls(), mapper), + Clause::Identity(alt) => return map_at(path, &alt.cls_mut(), mapper), Clause::Lambda { args, body: Expr { location: b_loc, clause } } => return Ok(Clause::Lambda { args: args.clone(), body: Expr { - clause: map_at(path, &clause.cls(), mapper)?.to_inst(), + clause: map_at(path, &clause.cls_mut(), mapper)?.into_inst(), location: b_loc.clone(), }, }), @@ -33,7 +33,7 @@ fn map_at( (val, None) => mapper(val)?, // If it's an Apply, execute the next step in the path (Clause::Apply { f, x }, Some(head)) => { - let proc = |x: &Expr| Ok(map_at(path, &x.cls(), mapper)?.to_expr(x.location())); + let proc = |x: &Expr| Ok(map_at(path, &x.cls_mut(), mapper)?.into_expr(x.location())); match head { None => Clause::Apply { f: proc(f)?, x: x.clone() }, Some(n) => { @@ -68,12 +68,12 @@ pub fn substitute( let mut argv = x.clone(); let f = match conts.get(&None) { None => f.clone(), - Some(sp) => substitute(sp, value.clone(), &f.cls(), on_sub).to_expr(f.location()), + Some(sp) => substitute(sp, value.clone(), &f.cls_mut(), on_sub).into_expr(f.location()), }; for (i, old) in argv.iter_mut().rev().enumerate() { if let Some(sp) = conts.get(&Some(i)) { - let tmp = substitute(sp, value.clone(), &old.cls(), on_sub); - *old = tmp.to_expr(old.location()); + let tmp = substitute(sp, value.clone(), &old.cls_mut(), on_sub); + *old = tmp.into_expr(old.location()); } } Ok(Clause::Apply { f, x: argv }) @@ -89,15 +89,20 @@ pub fn substitute( .unwrap_or_else(|e| match e {}) } -pub(super) fn apply_as_atom(f: Expr, arg: Expr, ctx: RunContext) -> Result { - let call = CallData { location: f.location(), arg, ctx }; +pub(super) fn apply_as_atom( + f: Expr, + arg: Expr, + env: &RunEnv, + params: &mut RunParams, +) -> RTResult { + let call = CallData { location: f.location(), arg, env, params }; match f.clause.try_unwrap() { Ok(clause) => match clause { Clause::Atom(atom) => Ok(atom.apply(call)?), _ => panic!("Not an atom"), }, - Err(clsi) => match &*clsi.cls() { - Clause::Atom(atom) => Ok(atom.apply_ref(call)?), + Err(clsi) => match &mut *clsi.cls_mut() { + Clause::Atom(atom) => Ok(atom.apply_mut(call)?), _ => panic!("Not an atom"), }, } diff --git a/src/interpreter/context.rs b/src/interpreter/context.rs index bf29c38..6a29baa 100644 --- a/src/interpreter/context.rs +++ b/src/interpreter/context.rs @@ -1,19 +1,67 @@ +//! Addiitional information passed to the interpreter + +use std::cell::RefCell; +use std::fmt; + use hashbrown::HashMap; -use super::nort::Expr; +use super::handler::HandlerTable; +use super::nort::{Clause, Expr}; +use crate::foreign::error::{RTError, RTErrorObj, RTResult}; +use crate::location::CodeLocation; use crate::name::Sym; -/// All the data associated with an interpreter run -#[derive(Clone)] -pub struct RunContext<'a> { - /// Table used to resolve constants - pub symbols: &'a HashMap, - /// The number of reduction steps the interpreter can take before returning - pub gas: Option, - /// The limit of recursion - pub stack_size: usize, +/// Data that must not change except in well-defined ways while any data +/// associated with this process persists +pub struct RunEnv<'a> { + /// Mutable callbacks the code can invoke with continuation passing + pub handlers: HandlerTable<'a>, + /// Constants referenced in the code in [super::nort::Clause::Constant] nodes + pub symbols: RefCell>>, + /// Callback to invoke when a symbol is not found + pub symbol_cb: Box RTResult + 'a>, } -impl<'a> RunContext<'a> { + +impl<'a> RunEnv<'a> { + /// Create a new context. The return values of the symbol callback are cached + pub fn new( + handlers: HandlerTable<'a>, + symbol_cb: impl Fn(Sym, CodeLocation) -> RTResult + 'a, + ) -> Self { + Self { handlers, symbols: RefCell::new(HashMap::new()), symbol_cb: Box::new(symbol_cb) } + } + + /// Produce an error indicating that a symbol was missing + pub fn sym_not_found(sym: Sym, location: CodeLocation) -> RTErrorObj { + MissingSymbol { location, sym }.pack() + } + + /// Load a symbol from cache or invoke the callback + pub fn load(&self, sym: Sym, location: CodeLocation) -> RTResult { + let mut guard = self.symbols.borrow_mut(); + let (_, r) = (guard.raw_entry_mut().from_key(&sym)) + .or_insert_with(|| (sym.clone(), (self.symbol_cb)(sym, location))); + r.clone() + } + + /// Attempt to resolve the command with the command handler table + pub fn dispatch(&self, expr: &Clause, location: CodeLocation) -> Option { + match expr { + Clause::Atom(at) => self.handlers.dispatch(&*at.0, location), + _ => None, + } + } +} + +/// Limits and other context that is subject to change +pub struct RunParams { + /// Number of reduction steps permitted before the program is preempted + pub gas: Option, + /// Maximum recursion depth. Orchid uses a soft stack so this can be very + /// large, but it must not be + pub stack: usize, +} +impl RunParams { /// Consume some gas if it is being counted pub fn use_gas(&mut self, amount: usize) { if let Some(g) = self.gas.as_mut() { @@ -22,39 +70,26 @@ impl<'a> RunContext<'a> { } /// Gas is being counted and there is none left pub fn no_gas(&self) -> bool { self.gas == Some(0) } -} - -/// All the data produced by an interpreter run -#[derive(Clone)] -pub struct Halt { - /// The new expression tree - pub state: Expr, - /// Leftover [Context::gas] if counted - pub gas: Option, - /// If true, the next run would not modify the expression - pub inert: bool, -} -impl Halt { - /// Check if gas has run out. Returns false if gas is not being used - pub fn preempted(&self) -> bool { self.gas.map_or(false, |g| g == 0) } - /// Returns a general report of the return - pub fn status(&self) -> ReturnStatus { - if self.preempted() { - ReturnStatus::Preempted - } else if self.inert { - ReturnStatus::Inert - } else { - ReturnStatus::Active + /// Add gas to make execution longer, or to resume execution in a preempted + /// expression + pub fn add_gas(&mut self, amount: usize) { + if let Some(g) = self.gas.as_mut() { + *g = g.saturating_add(amount) } } } -/// Possible states of a [Return] -pub enum ReturnStatus { - /// The data is not normalizable any further - Inert, - /// Gas is being used and it ran out - Preempted, - /// Normalization stopped for a different reason and should continue. - Active, +/// The interpreter's sole output excluding error conditions is an expression +pub type Halt = Expr; + +#[derive(Clone)] +pub(crate) struct MissingSymbol { + pub sym: Sym, + pub location: CodeLocation, +} +impl RTError for MissingSymbol {} +impl fmt::Display for MissingSymbol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}, called at {} is not loaded", self.sym, self.location) + } } diff --git a/src/interpreter/error.rs b/src/interpreter/error.rs index d03454f..90be6b0 100644 --- a/src/interpreter/error.rs +++ b/src/interpreter/error.rs @@ -1,66 +1,33 @@ -use std::fmt::{self, Debug, Display}; +//! Error produced by the interpreter. -use itertools::Itertools; +use std::fmt; -use super::nort::Expr; -use super::run::Interrupted; -use crate::foreign::error::{ExternError, ExternErrorObj}; -use crate::location::CodeLocation; -use crate::name::Sym; +use super::run::State; +use crate::foreign::error::{RTError, RTErrorObj}; -/// Print a stack trace -pub fn strace(stack: &[Expr]) -> String { - stack.iter().rev().map(|x| format!("{x}\n at {}", x.location)).join("\n") -} - -/// Problems in the process of execution -#[derive(Debug, Clone)] -pub enum RunError { +/// Error produced by the interpreter. This could be because the code is faulty, +/// but equally because gas was being counted and it ran out. +#[derive(Debug)] +pub enum RunError<'a> { /// A Rust function encountered an error - Extern(ExternErrorObj), + Extern(RTErrorObj), /// Ran out of gas - Interrupted(Interrupted), + Interrupted(State<'a>), } -impl From for RunError { - fn from(value: T) -> Self { Self::Extern(value.rc()) } +impl<'a, T: RTError + 'static> From for RunError<'a> { + fn from(value: T) -> Self { Self::Extern(value.pack()) } } -impl From for RunError { - fn from(value: ExternErrorObj) -> Self { Self::Extern(value) } +impl<'a> From for RunError<'a> { + fn from(value: RTErrorObj) -> Self { Self::Extern(value) } } -impl Display for RunError { +impl<'a> fmt::Display for RunError<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Interrupted(i) => { - write!(f, "Ran out of gas:\n{}", strace(&i.stack)) - }, + Self::Interrupted(i) => write!(f, "Ran out of gas:\n{i}"), Self::Extern(e) => write!(f, "Program fault: {e}"), } } } - -#[derive(Clone)] -pub(crate) struct StackOverflow { - pub stack: Vec, -} -impl ExternError for StackOverflow {} -impl Display for StackOverflow { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let limit = self.stack.len() - 2; // 1 for failed call, 1 for current - write!(f, "Stack depth exceeded {limit}:\n{}", strace(&self.stack)) - } -} - -#[derive(Clone)] -pub(crate) struct MissingSymbol { - pub sym: Sym, - pub loc: CodeLocation, -} -impl ExternError for MissingSymbol {} -impl Display for MissingSymbol { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}, called at {} is not loaded", self.sym, self.loc) - } -} diff --git a/src/interpreter/gen_nort.rs b/src/interpreter/gen_nort.rs index 90963d9..7326d58 100644 --- a/src/interpreter/gen_nort.rs +++ b/src/interpreter/gen_nort.rs @@ -26,56 +26,33 @@ impl Generable for Expr { f_cb: impl FnOnce(Self::Ctx<'_>) -> Self, x_cb: impl FnOnce(Self::Ctx<'_>) -> Self, ) -> Self { - (ctx - .1 - .apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c)))) - .to_expr(ctx.0.clone()) + (ctx.1.apply_logic(|c| f_cb((ctx.0.clone(), c)), |c| x_cb((ctx.0.clone(), c)))) + .into_expr(ctx.0.clone()) } fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { - Clause::arg(ctx.clone(), name).to_expr(ctx.0.clone()) + Clause::arg(ctx.clone(), name).into_expr(ctx.0.clone()) } fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { - Clause::atom(ctx.clone(), a).to_expr(ctx.0.clone()) + Clause::atom(ctx.clone(), a).into_expr(ctx.0.clone()) } - fn constant<'a>( - ctx: Self::Ctx<'_>, - name: impl IntoIterator, - ) -> Self { - Clause::constant(ctx.clone(), name).to_expr(ctx.0.clone()) + fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator) -> Self { + Clause::constant(ctx.clone(), name).into_expr(ctx.0.clone()) } - fn lambda( - ctx: Self::Ctx<'_>, - name: &str, - body: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - (ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)))) - .to_expr(ctx.0.clone()) + fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { + (ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)))).into_expr(ctx.0.clone()) } } impl Generable for ClauseInst { type Ctx<'a> = NortGenCtx<'a>; - fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { - Clause::arg(ctx, name).to_inst() + fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { Clause::arg(ctx, name).into_inst() } + fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { Clause::atom(ctx, a).into_inst() } + fn constant<'a>(ctx: Self::Ctx<'_>, name: impl IntoIterator) -> Self { + Clause::constant(ctx, name).into_inst() } - fn atom(ctx: Self::Ctx<'_>, a: Atom) -> Self { - Clause::atom(ctx, a).to_inst() - } - fn constant<'a>( - ctx: Self::Ctx<'_>, - name: impl IntoIterator, - ) -> Self { - Clause::constant(ctx, name).to_inst() - } - fn lambda( - ctx: Self::Ctx<'_>, - name: &str, - body: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - (ctx - .1 - .lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone()))) - .to_clsi(ctx.0.clone()) + fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { + (ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone()))) + .to_clsi(ctx.0.clone()) } fn apply( ctx: Self::Ctx<'_>, @@ -83,8 +60,8 @@ impl Generable for ClauseInst { x: impl FnOnce(Self::Ctx<'_>) -> Self, ) -> Self { (ctx.1.apply_logic( - |c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()), - |c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()), + |c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()), + |c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()), )) .to_clsi(ctx.0.clone()) } @@ -93,10 +70,7 @@ impl Generable for ClauseInst { impl Generable for Clause { type Ctx<'a> = NortGenCtx<'a>; fn atom(_: Self::Ctx<'_>, a: Atom) -> Self { Clause::Atom(a) } - fn constant<'a>( - _: Self::Ctx<'_>, - name: impl IntoIterator, - ) -> Self { + fn constant<'a>(_: Self::Ctx<'_>, name: impl IntoIterator) -> Self { let sym = Sym::new(name.into_iter().map(i)).expect("Empty constant"); Clause::Constant(sym) } @@ -106,21 +80,15 @@ impl Generable for Clause { x: impl FnOnce(Self::Ctx<'_>) -> Self, ) -> Self { ctx.1.apply_logic( - |c| f((ctx.0.clone(), c)).to_expr(ctx.0.clone()), - |c| x((ctx.0.clone(), c)).to_expr(ctx.0.clone()), + |c| f((ctx.0.clone(), c)).into_expr(ctx.0.clone()), + |c| x((ctx.0.clone(), c)).into_expr(ctx.0.clone()), ) } fn arg(ctx: Self::Ctx<'_>, name: &str) -> Self { ctx.1.arg_logic(name); Clause::LambdaArg } - fn lambda( - ctx: Self::Ctx<'_>, - name: &str, - body: impl FnOnce(Self::Ctx<'_>) -> Self, - ) -> Self { - ctx - .1 - .lambda_logic(name, |c| body((ctx.0.clone(), c)).to_expr(ctx.0.clone())) + fn lambda(ctx: Self::Ctx<'_>, name: &str, body: impl FnOnce(Self::Ctx<'_>) -> Self) -> Self { + ctx.1.lambda_logic(name, |c| body((ctx.0.clone(), c)).into_expr(ctx.0.clone())) } } diff --git a/src/interpreter/handler.rs b/src/interpreter/handler.rs index 709694a..077685e 100644 --- a/src/interpreter/handler.rs +++ b/src/interpreter/handler.rs @@ -1,25 +1,39 @@ +//! Impure functions that can be triggered by Orchid code when a command +//! evaluates to an atom representing a command + use std::any::{Any, TypeId}; +use std::cell::RefCell; use hashbrown::HashMap; use trait_set::trait_set; -use super::context::{Halt, RunContext}; -use super::error::RunError; -use super::nort::{Clause, Expr}; -use super::run::run; -use crate::foreign::atom::{Atom, Atomic}; -use crate::foreign::error::ExternResult; +use super::nort::Expr; +use crate::foreign::atom::Atomic; +use crate::foreign::error::RTResult; use crate::foreign::to_clause::ToClause; use crate::location::CodeLocation; trait_set! { - trait Handler = for<'a> FnMut(&'a dyn Any, CodeLocation) -> Expr; + trait Handler = for<'a> Fn(&'a dyn Any, CodeLocation) -> Expr; } -/// A table of command handlers +enum HTEntry<'a> { + Handler(Box), + Forward(&'a (dyn Handler + 'a)), +} +impl<'a> AsRef for HTEntry<'a> { + fn as_ref(&self) -> &(dyn Handler + 'a) { + match self { + HTEntry::Handler(h) => &**h, + HTEntry::Forward(h) => *h, + } + } +} + +/// A table of impure command handlers exposed to Orchid #[derive(Default)] pub struct HandlerTable<'a> { - handlers: HashMap>, + handlers: HashMap>, } impl<'a> HandlerTable<'a> { /// Create a new [HandlerTable] @@ -28,35 +42,25 @@ impl<'a> HandlerTable<'a> { /// Add a handler function to interpret a command and select the continuation. /// See [HandlerTable#with] for a declarative option. - pub fn register( - &mut self, - mut f: impl for<'b> FnMut(&'b T) -> R + 'a, - ) { + pub fn register(&mut self, f: impl for<'b> FnMut(&'b T) -> R + 'a) { + let cell = RefCell::new(f); let cb = move |a: &dyn Any, loc: CodeLocation| { - f(a.downcast_ref().expect("found by TypeId")).to_expr(loc) + cell.borrow_mut()(a.downcast_ref().expect("found by TypeId")).to_expr(loc) }; - let prev = self.handlers.insert(TypeId::of::(), Box::new(cb)); + let prev = self.handlers.insert(TypeId::of::(), HTEntry::Handler(Box::new(cb))); assert!(prev.is_none(), "A handler for this type is already registered"); } /// Add a handler function to interpret a command and select the continuation. /// See [HandlerTable#register] for a procedural option. - pub fn with( - mut self, - f: impl FnMut(&T) -> ExternResult + 'a, - ) -> Self { + pub fn with(mut self, f: impl FnMut(&T) -> RTResult + 'a) -> Self { self.register(f); self } /// Find and execute the corresponding handler for this type - pub fn dispatch( - &mut self, - arg: &dyn Atomic, - loc: CodeLocation, - ) -> Option { - (self.handlers.get_mut(&arg.as_any_ref().type_id())) - .map(|f| f(arg.as_any_ref(), loc)) + pub fn dispatch(&self, arg: &dyn Atomic, loc: CodeLocation) -> Option { + (self.handlers.get(&arg.as_any_ref().type_id())).map(|ent| ent.as_ref()(arg.as_any_ref(), loc)) } /// Combine two non-overlapping handler sets @@ -68,30 +72,45 @@ impl<'a> HandlerTable<'a> { } self } -} -/// [run] orchid code, executing any commands it returns using the specified -/// [Handler]s. -pub fn run_handler( - mut state: Expr, - handlers: &mut HandlerTable, - mut ctx: RunContext, -) -> Result { - loop { - let halt = run(state, ctx.clone())?; - state = halt.state; - ctx.use_gas(halt.gas.unwrap_or(0)); - let state_cls = state.cls(); - if let Clause::Atom(Atom(a)) = &*state_cls { - if let Some(res) = handlers.dispatch(a.as_ref(), state.location()) { - drop(state_cls); - state = res; - continue; - } - } - if halt.inert || ctx.no_gas() { - drop(state_cls); - break Ok(Halt { gas: ctx.gas, inert: halt.inert, state }); + /// Add entries that forward requests to a borrowed non-overlapping handler + /// set + pub fn link<'b: 'a>(mut self, other: &'b HandlerTable<'b>) -> Self { + for (key, value) in other.handlers.iter() { + let prev = self.handlers.insert(*key, HTEntry::Forward(value.as_ref())); + assert!(prev.is_none(), "Duplicate handlers") } + self + } +} + +#[cfg(test)] +#[allow(unconditional_recursion)] +#[allow(clippy::ptr_arg)] +mod test { + use std::marker::PhantomData; + + use super::HandlerTable; + + /// Ensure that the method I use to verify covariance actually passes with + /// covariant and fails with invariant + /// + /// The failing case: + /// ``` + /// struct Cov2<'a>(PhantomData<&'a mut &'a ()>); + /// fn fail<'a>(_c: &Cov2<'a>, _s: &'a String) { fail(_c, &String::new()) } + /// ``` + #[allow(unused)] + fn covariant_control() { + struct Cov<'a>(PhantomData<&'a ()>); + fn pass<'a>(_c: &Cov<'a>, _s: &'a String) { pass(_c, &String::new()) } + } + + /// The &mut ensures that 'a in the two functions must be disjoint, and that + /// ht must outlive both. For this to compile, Rust has to cast ht to the + /// shorter lifetimes, ensuring covariance + #[allow(unused)] + fn assert_covariant() { + fn pass<'a>(_ht: HandlerTable<'a>, _s: &'a String) { pass(_ht, &String::new()) } } } diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 3350841..6993cf8 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -1,10 +1,10 @@ -//! functions to interact with Orchid code -pub mod apply; +//! functions to execute Orchid code +mod apply; pub mod context; pub mod error; pub mod gen_nort; pub mod handler; -pub mod nort_builder; pub mod nort; +pub mod nort_builder; pub(crate) mod path_set; pub mod run; diff --git a/src/interpreter/nort.rs b/src/interpreter/nort.rs index b3ae06e..77e0409 100644 --- a/src/interpreter/nort.rs +++ b/src/interpreter/nort.rs @@ -13,25 +13,23 @@ //! function calls store multiple arguments in a deque. use std::collections::VecDeque; -use std::fmt::{Debug, Display}; -use std::ops::{Deref, DerefMut}; -use std::sync::{Arc, Mutex, TryLockError}; +use std::fmt; +use std::ops::DerefMut; +use std::sync::{Arc, Mutex, MutexGuard, TryLockError}; use itertools::Itertools; -use super::error::RunError; use super::path_set::PathSet; use crate::foreign::atom::Atom; #[allow(unused)] // for doc use crate::foreign::atom::Atomic; -use crate::foreign::error::ExternResult; +use crate::foreign::error::{RTErrorObj, RTResult}; use crate::foreign::try_from_expr::TryFromExpr; use crate::location::CodeLocation; use crate::name::Sym; #[allow(unused)] // for doc use crate::parse::parsed; use crate::utils::ddispatch::request; -use crate::utils::take_with_output::take_with_output; /// Kinda like [AsMut] except it supports a guard pub(crate) trait AsDerefMut { @@ -48,19 +46,17 @@ pub struct Expr { } impl Expr { /// Constructor - pub fn new(clause: ClauseInst, location: CodeLocation) -> Self { - Self { clause, location } - } + pub fn new(clause: ClauseInst, location: CodeLocation) -> Self { Self { clause, location } } /// Obtain the location of the expression pub fn location(&self) -> CodeLocation { self.location.clone() } /// Convert into any type that implements [TryFromExpr]. Calls to this /// function are generated wherever a conversion is elided in an extern /// function. - pub fn downcast(self) -> ExternResult { + pub fn downcast(self) -> RTResult { let Expr { mut clause, location } = self; loop { - let cls_deref = clause.cls(); + let cls_deref = clause.cls_mut(); match &*cls_deref { Clause::Identity(alt) => { let temp = alt.clone(); @@ -79,22 +75,16 @@ impl Expr { /// returning [Some] /// /// See also [parsed::Expr::search_all] - pub fn search_all( - &self, - predicate: &mut impl FnMut(&Self) -> Option, - ) -> Option { + pub fn search_all(&self, predicate: &mut impl FnMut(&Self) -> Option) -> Option { if let Some(t) = predicate(self) { return Some(t); } self.clause.inspect(|c| match c { Clause::Identity(_alt) => unreachable!("Handled by inspect"), - Clause::Apply { f, x } => (f.search_all(predicate)) - .or_else(|| x.iter().find_map(|x| x.search_all(predicate))), + Clause::Apply { f, x } => + (f.search_all(predicate)).or_else(|| x.iter().find_map(|x| x.search_all(predicate))), Clause::Lambda { body, .. } => body.search_all(predicate), - Clause::Constant(_) - | Clause::LambdaArg - | Clause::Atom(_) - | Clause::Bottom(_) => None, + Clause::Constant(_) | Clause::LambdaArg | Clause::Atom(_) | Clause::Bottom(_) => None, }) } @@ -102,47 +92,28 @@ impl Expr { #[must_use] pub fn clsi(&self) -> ClauseInst { self.clause.clone() } - /// Readonly access to the [Clause] - /// - /// # Panics - /// - /// if the clause is already borrowed - #[must_use] - pub fn cls(&self) -> impl Deref + '_ { self.clause.cls() } - /// Read-Write access to the [Clause] /// /// # Panics /// /// if the clause is already borrowed - #[must_use] - pub fn cls_mut(&self) -> impl DerefMut + '_ { - self.clause.cls_mut() - } + pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.clause.cls_mut() } } -impl Debug for Expr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{:?}@{}", self.clause, self.location) } } -impl Display for Expr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.clause) - } +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.clause) } } impl AsDerefMut for Expr { - fn as_deref_mut(&mut self) -> impl DerefMut + '_ { - self.clause.cls_mut() - } + fn as_deref_mut(&mut self) -> impl DerefMut + '_ { self.clause.cls_mut() } } -/// [ExprInst::with_literal] produces this marker unit to indicate that the -/// expression is not a literal -pub struct NotALiteral; - /// A wrapper around expressions to handle their multiple occurences in /// the tree together #[derive(Clone)] @@ -159,91 +130,17 @@ impl ClauseInst { Arc::try_unwrap(self.0).map(|c| c.into_inner().unwrap()).map_err(Self) } - /// Read-only access to the shared clause instance - /// - /// # Panics - /// - /// if the clause is already borrowed in read-write mode - #[must_use] - pub fn cls(&self) -> impl Deref + '_ { - self.0.lock().unwrap() - } - /// Read-Write access to the shared clause instance /// - /// # Panics - /// - /// if the clause is already borrowed - #[must_use] - pub fn cls_mut(&self) -> impl DerefMut + '_ { - self.0.lock().unwrap() - } - - /// Call a normalization function on the expression. The expr is - /// updated with the new clause which affects all copies of it - /// across the tree. - /// - /// This function bypasses and collapses identities, but calling it in a plain - /// loop intermittently re-acquires the mutex, and looping inside of it breaks - /// identity collapsing. [ClauseInst::try_normalize_trampoline] solves these - /// problems. - pub fn try_normalize( - &self, - mapper: impl FnOnce(Clause) -> Result<(Clause, T), RunError>, - ) -> Result<(Self, T), RunError> { - enum Report { - Nested(ClauseInst, T), - Plain(T), - } - let ret = take_with_output(&mut *self.cls_mut(), |clause| match &clause { - // don't modify identities, instead update and return the nested clause - Clause::Identity(alt) => match alt.try_normalize(mapper) { - Ok((nested, t)) => (clause, Ok(Report::Nested(nested, t))), - Err(e) => (Clause::Bottom(e.clone()), Err(e)), - }, - _ => match mapper(clause) { - Err(e) => (Clause::Bottom(e.clone()), Err(e)), - Ok((clause, t)) => (clause, Ok(Report::Plain(t))), - }, - })?; - Ok(match ret { - Report::Nested(nested, t) => (nested, t), - Report::Plain(t) => (self.clone(), t), - }) - } - - /// Repeatedly call a normalization function on the held clause, switching - /// [ClauseInst] values as needed to ensure that - pub fn try_normalize_trampoline( - mut self, - mut mapper: impl FnMut(Clause) -> Result<(Clause, Option), RunError>, - ) -> Result<(Self, T), RunError> { - loop { - let (next, exit) = self.try_normalize(|mut cls| { - loop { - if matches!(cls, Clause::Identity(_)) { - break Ok((cls, None)); - } - let (next, exit) = mapper(cls)?; - if let Some(exit) = exit { - break Ok((next, Some(exit))); - } - cls = next; - } - })?; - if let Some(exit) = exit { - break Ok((next, exit)); - } - self = next - } - } + /// if the clause is already borrowed, this will block until it is released. + pub fn cls_mut(&self) -> MutexGuard<'_, Clause> { self.0.lock().unwrap() } /// Call a predicate on the clause, returning whatever the /// predicate returns. This is a convenience function for reaching /// through the [Mutex]. The clause will never be [Clause::Identity]. #[must_use] pub fn inspect(&self, predicate: impl FnOnce(&Clause) -> T) -> T { - match &*self.cls() { + match &*self.cls_mut() { Clause::Identity(sub) => sub.inspect(predicate), x => predicate(x), } @@ -253,7 +150,7 @@ impl ClauseInst { /// If it's not an atomic, fail the request automatically. #[must_use = "your request might not have succeeded"] pub fn request(&self) -> Option { - match &*self.cls() { + match &*self.cls_mut() { Clause::Atom(a) => request(&*a.0), Clause::Identity(alt) => alt.request(), _ => None, @@ -261,7 +158,7 @@ impl ClauseInst { } /// Associate a location with this clause - pub fn to_expr(self, location: CodeLocation) -> Expr { + pub fn into_expr(self, location: CodeLocation) -> Expr { Expr { clause: self.clone(), location: location.clone() } } /// Check ahead-of-time if this clause contains an atom. Calls @@ -269,7 +166,7 @@ impl ClauseInst { /// /// Since atoms cannot become normalizable, if this is true and previous /// normalization failed, the atom is known to be in normal form. - pub fn is_atom(&self) -> bool { matches!(&*self.cls(), Clause::Atom(_)) } + pub fn is_atom(&self) -> bool { matches!(&*self.cls_mut(), Clause::Atom(_)) } /// Tries to unwrap the [Arc]. If that fails, clones it field by field. /// If it's a [Clause::Atom] which cannot be cloned, wraps it in a @@ -278,21 +175,24 @@ impl ClauseInst { /// Implementation of [crate::foreign::to_clause::ToClause::to_clause]. The /// trait is more general so it requires a location which this one doesn't. pub fn into_cls(self) -> Clause { - self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls() { + self.try_unwrap().unwrap_or_else(|clsi| match &*clsi.cls_mut() { Clause::Apply { f, x } => Clause::Apply { f: f.clone(), x: x.clone() }, Clause::Atom(_) => Clause::Identity(clsi.clone()), Clause::Bottom(e) => Clause::Bottom(e.clone()), Clause::Constant(c) => Clause::Constant(c.clone()), Clause::Identity(sub) => Clause::Identity(sub.clone()), - Clause::Lambda { args, body } => - Clause::Lambda { args: args.clone(), body: body.clone() }, + Clause::Lambda { args, body } => Clause::Lambda { args: args.clone(), body: body.clone() }, Clause::LambdaArg => Clause::LambdaArg, }) } + + /// Decides if this clause is the exact same instance as another. Most useful + /// to detect potential deadlocks. + pub fn is_same(&self, other: &Self) -> bool { Arc::ptr_eq(&self.0, &other.0) } } -impl Debug for ClauseInst { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for ClauseInst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0.try_lock() { Ok(expr) => write!(f, "{expr:?}"), Err(TryLockError::Poisoned(_)) => write!(f, ""), @@ -301,8 +201,8 @@ impl Debug for ClauseInst { } } -impl Display for ClauseInst { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ClauseInst { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.0.try_lock() { Ok(expr) => write!(f, "{expr}"), Err(TryLockError::Poisoned(_)) => write!(f, ""), @@ -312,16 +212,14 @@ impl Display for ClauseInst { } impl AsDerefMut for ClauseInst { - fn as_deref_mut(&mut self) -> impl DerefMut + '_ { - self.cls_mut() - } + fn as_deref_mut(&mut self) -> impl DerefMut + '_ { self.cls_mut() } } /// Distinct types of expressions recognized by the interpreter #[derive(Debug)] pub enum Clause { /// An expression that causes an error - Bottom(RunError), + Bottom(RTErrorObj), /// Indicates that this [ClauseInst] has the same value as the other /// [ClauseInst]. This has two benefits; /// @@ -358,21 +256,18 @@ pub enum Clause { } impl Clause { /// Wrap a clause in a refcounted lock - pub fn to_inst(self) -> ClauseInst { ClauseInst::new(self) } + pub fn into_inst(self) -> ClauseInst { ClauseInst::new(self) } /// Wrap a clause in an expression. - pub fn to_expr(self, location: CodeLocation) -> Expr { - self.to_inst().to_expr(location) - } + pub fn into_expr(self, location: CodeLocation) -> Expr { self.into_inst().into_expr(location) } } -impl Display for Clause { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Clause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Clause::Atom(a) => write!(f, "{a:?}"), Clause::Bottom(err) => write!(f, "bottom({err})"), Clause::LambdaArg => write!(f, "arg"), - Clause::Apply { f: fun, x } => - write!(f, "({fun} {})", x.iter().join(" ")), + Clause::Apply { f: fun, x } => write!(f, "({fun} {})", x.iter().join(" ")), Clause::Lambda { args, body } => match args { Some(path) => write!(f, "[\\{path}.{body}]"), None => write!(f, "[\\_.{body}]"), diff --git a/src/interpreter/nort_builder.rs b/src/interpreter/nort_builder.rs index 7a61da2..04427e6 100644 --- a/src/interpreter/nort_builder.rs +++ b/src/interpreter/nort_builder.rs @@ -1,3 +1,5 @@ +//! Helper for generating the interpreter's internal representation + use std::cell::RefCell; use std::mem; @@ -34,8 +36,7 @@ impl ArgCollector { /// the callback it returns is called on every lambda ancestor's associated /// data from closest to outermost ancestor. The first lambda where this /// callback returns true is considered to own the argument. -pub type LambdaPicker<'a, T, U> = - &'a dyn for<'b> Fn(&'b U) -> Box bool + 'b>; +pub type LambdaPicker<'a, T, U> = &'a dyn for<'b> Fn(&'b U) -> Box bool + 'b>; /// Bundle of information passed down through recursive fnuctions to instantiate /// runtime [Expr], [super::nort::ClauseInst] or [Clause]. @@ -83,7 +84,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> { IntGenData::Apply(_) => panic!("This is removed after handling"), IntGenData::Lambda(n, rc) => match lambda_chk(n) { false => Ok(path), - true => Err((path, *rc)) + true => Err((path, *rc)), }, IntGenData::AppArg(n) => Ok(pushed(path, Some(*n))), IntGenData::AppF => Ok(pushed(path, None)), @@ -100,11 +101,7 @@ impl<'a, T: ?Sized, U: ?Sized> NortBuilder<'a, T, U> { /// Push a stackframe corresponding to a lambda expression, build the body, /// then record the path set collected by [NortBuilder::arg_logic] calls /// within the body. - pub fn lambda_logic( - self, - name: &T, - body: impl FnOnce(NortBuilder) -> Expr, - ) -> Clause { + pub fn lambda_logic(self, name: &T, body: impl FnOnce(NortBuilder) -> Expr) -> Clause { let coll = ArgCollector::new(); let frame = IntGenData::Lambda(name, &coll.0); let body = self.non_app_step(|ctx| body(ctx.push(frame))); diff --git a/src/interpreter/path_set.rs b/src/interpreter/path_set.rs index a642cda..43c1afd 100644 --- a/src/interpreter/path_set.rs +++ b/src/interpreter/path_set.rs @@ -10,7 +10,7 @@ use crate::utils::join::join_maps; /// function. If [Some(n)], it steps to the `n`th _last_ argument. pub type Step = Option; fn print_step(step: Step) -> String { - if let Some(n) = step { format!("{n}>") } else { "f>".to_string() } + if let Some(n) = step { format!("{n}") } else { "f".to_string() } } /// A branching path selecting some placeholders (but at least one) in a Lambda @@ -59,10 +59,7 @@ impl PathSet { }; let short_len = short.steps.len(); let long_len = long.steps.len(); - let match_len = (short.steps.iter()) - .zip(long.steps.iter()) - .take_while(|(a, b)| a == b) - .count(); + let match_len = (short.steps.iter()).zip(long.steps.iter()).take_while(|(a, b)| a == b).count(); // fact: match_len <= short_len <= long_len if short_len == match_len && match_len == long_len { // implies match_len == short_len == long_len @@ -71,10 +68,8 @@ impl PathSet { (Some(_), None) | (None, Some(_)) => { panic!("One of these paths is faulty") }, - (Some(s), Some(l)) => Self::branch( - short.steps.iter().cloned(), - join_maps(s, l, |_, l, r| l.overlay(r)), - ), + (Some(s), Some(l)) => + Self::branch(short.steps.iter().cloned(), join_maps(s, l, |_, l, r| l.overlay(r))), } } else if short_len == match_len { // implies match_len == short_len < long_len @@ -102,10 +97,7 @@ impl PathSet { let new_short = Self { next: short.next.clone(), steps: new_short_steps }; let new_long_steps = long.steps.split_off(match_len + 1); let new_long = Self { next: long.next.clone(), steps: new_long_steps }; - Self::branch(short.steps, [ - (short_last, new_short), - (long.steps[match_len], new_long), - ]) + Self::branch(short.steps, [(short_last, new_short), (long.steps[match_len], new_long)]) } } @@ -119,22 +111,25 @@ impl PathSet { impl fmt::Display for PathSet { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let step_s = self.steps.iter().copied().map(print_step).join(""); + let step_s = self.steps.iter().copied().map(print_step).join(">"); match &self.next { Some(conts) => { - let opts = - conts.iter().map(|(h, t)| format!("{}{t}", print_step(*h))).join("|"); - write!(f, "{step_s}({opts})") + let opts = (conts.iter()) + .sorted_unstable_by_key(|(k, _)| k.map_or(0, |n| n + 1)) + .map(|(h, t)| format!("{}>{t}", print_step(*h))) + .join("|"); + if !step_s.is_empty() { + write!(f, "{step_s}>")?; + } + write!(f, "({opts})") }, - None => write!(f, "{step_s}x"), + None => write!(f, "{step_s}"), } } } impl fmt::Debug for PathSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "PathSet({self})") - } + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "PathSet({self})") } } #[cfg(test)] @@ -146,7 +141,7 @@ mod tests { let ps1 = PathSet { next: None, steps: VecDeque::from([Some(2), None]) }; let ps2 = PathSet { next: None, steps: VecDeque::from([Some(3), Some(1)]) }; let sum = ps1.clone().overlay(ps2.clone()); - assert_eq!(format!("{sum}"), "(2>f>x|3>1>x)"); + assert_eq!(format!("{sum}"), "(2>f|3>1)"); } fn extend_scaffold() -> PathSet { diff --git a/src/interpreter/run.rs b/src/interpreter/run.rs index 0913885..0966514 100644 --- a/src/interpreter/run.rs +++ b/src/interpreter/run.rs @@ -1,129 +1,249 @@ -use std::mem; +//! Executes Orchid code -use super::context::{Halt, RunContext}; +use std::ops::{Deref, DerefMut}; +use std::sync::{MutexGuard, TryLockError}; +use std::{fmt, mem}; + +use bound::Bound; +use itertools::Itertools; + +use super::context::{Halt, RunEnv, RunParams}; use super::error::RunError; use super::nort::{Clause, Expr}; use crate::foreign::atom::{AtomicReturn, RunData}; -use crate::foreign::error::ExternError; +use crate::foreign::error::{RTError, RTErrorObj}; use crate::interpreter::apply::{apply_as_atom, substitute}; -use crate::interpreter::error::{strace, MissingSymbol, StackOverflow}; -use crate::utils::pure_seq::pushed; +use crate::location::CodeLocation; +use crate::utils::take_with_output::take_with_output; + +#[derive(Debug)] +struct Stackframe { + expr: Expr, + cls: Bound, Expr>, +} +impl Stackframe { + pub fn new(expr: Expr) -> Option { + match Bound::try_new(expr.clone(), |e| e.clause.0.try_lock()) { + Ok(cls) => Some(Stackframe { cls, expr }), + Err(bound_e) if matches!(bound_e.wrapped(), TryLockError::WouldBlock) => None, + Err(bound_e) => panic!("{:?}", bound_e.wrapped()), + } + } + pub fn wait_new(expr: Expr) -> Self { + Self { cls: Bound::new(expr.clone(), |e| e.clause.0.lock().unwrap()), expr } + } + pub fn record_cycle(&mut self) -> RTErrorObj { + let err = CyclicalExpression(self.expr.clone()).pack(); + *self.cls = Clause::Bottom(err.clone()); + err + } +} +impl Deref for Stackframe { + type Target = Clause; + fn deref(&self) -> &Self::Target { &self.cls } +} +impl DerefMut for Stackframe { + fn deref_mut(&mut self) -> &mut Self::Target { &mut self.cls } +} +impl fmt::Display for Stackframe { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}\n at {}", *self.cls, self.expr.location) + } +} /// Interpreter state when processing was interrupted -#[derive(Debug, Clone)] -pub struct Interrupted { - /// Cached soft stack to save the interpreter having to rebuild it from the - /// bottom. - pub stack: Vec, -} -impl Interrupted { - /// Continue processing where it was interrupted - pub fn resume(self, ctx: RunContext) -> Result { run_stack(self.stack, ctx) } +pub struct State<'a> { + stack: Vec, + popped: Option, + env: &'a RunEnv<'a>, } +impl<'a> State<'a> { + /// Create a new trivial state with a specified stack size and a single + /// element on the stack + fn new(base: Expr, env: &'a RunEnv<'a>) -> Self { + let stack = vec![Stackframe::new(base).expect("Initial state should not be locked")]; + State { stack, popped: None, env } + } -/// Normalize an expression using beta reduction with memoization -pub fn run(expr: Expr, ctx: RunContext) -> Result { - let mut v = Vec::with_capacity(1000); - v.push(expr); - run_stack(v, ctx) -} + /// Try to push an expression on the stack, raise appropriate errors if the + /// expression is already on the stack (and thus references itself), or if the + /// stack now exceeds the pre-defined height + fn push_expr(&'_ mut self, expr: Expr, params: &RunParams) -> Result<(), RunError<'a>> { + let sf = match Stackframe::new(expr.clone()) { + Some(sf) => sf, + None => match self.stack.iter_mut().rev().find(|sf| sf.expr.clause.is_same(&expr.clause)) { + None => Stackframe::wait_new(expr), + Some(sf) => return Err(RunError::Extern(sf.record_cycle())), + }, + }; + self.stack.push(sf); + if params.stack < self.stack.len() { + let so = StackOverflow(self.stack.iter().map(|sf| sf.expr.clone()).collect()); + return Err(RunError::Extern(so.pack())); + } + Ok(()) + } -fn run_stack(mut stack: Vec, mut ctx: RunContext) -> Result { - let mut expr = stack.pop().expect("Empty stack"); - let mut popped = false; - loop { - // print!("Now running {expr}"); - // let trace = strace(&stack); - // if trace.is_empty() { - // println!("\n") - // } else { - // println!("\n{trace}\n") - // }; - if ctx.no_gas() { - return Err(RunError::Interrupted(Interrupted { stack: pushed(stack, expr) })); - } - ctx.use_gas(1); - enum Res { - Inert, - Cont, - Push(Expr), - } - let (next_clsi, res) = expr.clause.try_normalize(|cls| match cls { - Clause::Identity(_) => panic!("Passed by try_normalize"), - Clause::LambdaArg => panic!("Unbound argument"), - Clause::Lambda { .. } => Ok((cls, Res::Inert)), - Clause::Bottom(b) => Err(b), - Clause::Constant(n) => match ctx.symbols.get(&n) { - Some(expr) => Ok((Clause::Identity(expr.clsi()), Res::Cont)), - None => Err(RunError::Extern(MissingSymbol { sym: n.clone(), loc: expr.location() }.rc())), - }, - Clause::Atom(mut a) => { - if !popped { - if let Some(delegate) = a.0.redirect() { - let next = delegate.clone(); - return Ok((Clause::Atom(a), Res::Push(next))); - } + /// Process this state until it either completes, runs out of gas as specified + /// in the context, or produces an error. + pub fn run(mut self, params: &mut RunParams) -> Result> { + loop { + if params.no_gas() { + return Err(RunError::Interrupted(self)); + } + params.use_gas(1); + let top = self.stack.last_mut().expect("Stack never empty"); + let location = top.expr.location(); + let op = take_with_output(&mut *top.cls, |c| { + match step(c, self.popped, location, self.env, params) { + Err(e) => (Clause::Bottom(e.clone()), Err(RunError::Extern(e))), + Ok((cls, cmd)) => (cls, Ok(cmd)), } - let rd = RunData { ctx: ctx.clone(), location: expr.location() }; - match a.run(rd)? { - AtomicReturn::Inert(c) => Ok((c, Res::Inert)), - AtomicReturn::Change(gas, c) => { - ctx.use_gas(gas); - Ok((c, Res::Cont)) - }, - } - }, - Clause::Apply { f, mut x } => { - if x.is_empty() { - return Ok((Clause::Identity(f.clsi()), Res::Cont)); - } - match &*f.cls() { - Clause::Identity(f2) => - return Ok((Clause::Apply { f: f2.clone().to_expr(f.location()), x }, Res::Cont)), - Clause::Apply { f, x: x2 } => { - for item in x2.iter().rev() { - x.push_front(item.clone()) + })?; + self.popped = None; + match op { + StackOp::Nop => continue, + StackOp::Push(ex) => self.push_expr(ex, params)?, + StackOp::Swap(ex) => { + self.stack.pop().expect("Stack never empty"); + self.push_expr(ex, params)? + }, + StackOp::Pop => { + let ret = self.stack.pop().expect("last_mut called above"); + if self.stack.is_empty() { + if let Some(alt) = self.env.dispatch(&ret.cls, ret.expr.location()) { + self.push_expr(alt, params)?; + params.use_gas(1); + continue; } - return Ok((Clause::Apply { f: f.clone(), x }, Res::Cont)); - }, - _ => (), - } - if !popped { - return Ok((Clause::Apply { f: f.clone(), x }, Res::Push(f))); - } - let f_cls = f.cls(); - let arg = x.pop_front().expect("checked above"); - let loc = f.location(); - let f = match &*f_cls { - Clause::Atom(_) => { - mem::drop(f_cls); - apply_as_atom(f, arg, ctx.clone())? - }, - Clause::Lambda { args, body } => match args { - None => body.clsi().into_cls(), - Some(args) => substitute(args, arg.clsi(), &body.cls(), &mut || ctx.use_gas(1)), - }, - c => panic!("Run should never settle on {c}"), - }; - Ok((Clause::Apply { f: f.to_expr(loc), x }, Res::Cont)) - }, - })?; - expr.clause = next_clsi; - popped = matches!(res, Res::Inert); - match res { - Res::Cont => continue, - Res::Inert => match stack.pop() { - None => return Ok(Halt { state: expr, gas: ctx.gas, inert: true }), - Some(prev) => expr = prev, - }, - Res::Push(next) => { - if stack.len() == ctx.stack_size { - stack.extend([expr, next]); - return Err(RunError::Extern(StackOverflow { stack }.rc())); - } - stack.push(expr); - expr = next; - }, + return Ok(ret.expr); + } else { + self.popped = Some(ret.expr); + } + }, + } } } } +impl<'a> fmt::Display for State<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.stack.iter().rev().join("\n")) + } +} +impl<'a> fmt::Debug for State<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "State({self})") } +} + +/// Process an expression with specific resource limits +pub fn run<'a>( + base: Expr, + env: &'a RunEnv<'a>, + params: &mut RunParams, +) -> Result> { + State::new(base, env).run(params) +} + +enum StackOp { + Pop, + Nop, + Swap(Expr), + Push(Expr), +} + +fn step( + top: Clause, + popped: Option, + location: CodeLocation, + env: &RunEnv, + params: &mut RunParams, +) -> Result<(Clause, StackOp), RTErrorObj> { + match top { + Clause::Bottom(err) => Err(err), + Clause::LambdaArg => Ok((Clause::Bottom(UnboundArg(location).pack()), StackOp::Nop)), + l @ Clause::Lambda { .. } => Ok((l, StackOp::Pop)), + Clause::Identity(other) => + Ok((Clause::Identity(other.clone()), StackOp::Swap(other.into_expr(location)))), + Clause::Constant(name) => { + let expr = env.load(name, location)?; + Ok((Clause::Identity(expr.clsi()), StackOp::Swap(expr.clone()))) + }, + Clause::Atom(mut at) => { + if let Some(delegate) = at.0.redirect() { + match popped { + Some(popped) => *delegate = popped, + None => { + let tmp = delegate.clone(); + return Ok((Clause::Atom(at), StackOp::Push(tmp))); + }, + } + } + match at.run(RunData { params, env, location })? { + AtomicReturn::Inert(at) => Ok((Clause::Atom(at), StackOp::Pop)), + AtomicReturn::Change(gas, c) => { + params.use_gas(gas); + Ok((c, StackOp::Nop)) + }, + } + }, + Clause::Apply { mut f, mut x } => { + if x.is_empty() { + return Ok((Clause::Identity(f.clsi()), StackOp::Swap(f))); + } + f = match popped { + None => return Ok((Clause::Apply { f: f.clone(), x }, StackOp::Push(f))), + Some(ex) => ex, + }; + let val = x.pop_front().expect("Empty args handled above"); + let f_mut = f.clause.cls_mut(); + let mut cls = match &*f_mut { + Clause::Lambda { args, body } => match args { + None => Clause::Identity(body.clsi()), + Some(args) => substitute(args, val.clsi(), &body.cls_mut(), &mut || params.use_gas(1)), + }, + Clause::Atom(_) => { + mem::drop(f_mut); + apply_as_atom(f, val, env, params)? + }, + c => unreachable!("Run should never settle on {c}"), + }; + if !x.is_empty() { + cls = Clause::Apply { f: cls.into_expr(location), x }; + } + Ok((cls, StackOp::Nop)) + }, + } +} + +#[derive(Clone)] +pub(crate) struct StackOverflow(Vec); +impl RTError for StackOverflow {} +impl fmt::Display for StackOverflow { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "Stack depth exceeded {}:", self.0.len() - 1)?; // 1 for failed call, 1 for current + for item in self.0.iter().rev() { + match Stackframe::new(item.clone()) { + Some(sf) => writeln!(f, "{sf}")?, + None => writeln!(f, "Locked frame at {}", item.location)?, + } + } + Ok(()) + } +} + +#[derive(Clone)] +pub(crate) struct UnboundArg(CodeLocation); +impl RTError for UnboundArg {} +impl fmt::Display for UnboundArg { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Unbound argument found at {}. This is likely a codegen error", self.0) + } +} + +#[derive(Clone)] +pub(crate) struct CyclicalExpression(Expr); +impl RTError for CyclicalExpression {} +impl fmt::Display for CyclicalExpression { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "The expression {} contains itself", self.0) + } +} diff --git a/src/lib.rs b/src/lib.rs index e673273..3037d07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,8 @@ #![warn(missing_docs)] -#![doc( - html_logo_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg" -)] -#![doc( - html_favicon_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg" -)] +#![warn(unit_bindings)] +#![warn(clippy::unnecessary_wraps)] +#![doc(html_logo_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg")] +#![doc(html_favicon_url = "https://raw.githubusercontent.com/lbfalvy/orchid/master/icon.svg")] //! Orchid is a lazy, pure scripting language to be embedded in Rust //! applications. Check out the repo for examples and other links. pub mod error; diff --git a/src/libs/asynch/mod.rs b/src/libs/asynch/mod.rs index 9c2ba08..3c3e5ab 100644 --- a/src/libs/asynch/mod.rs +++ b/src/libs/asynch/mod.rs @@ -4,6 +4,6 @@ //! beyond being general Rust functions. //! It also exposes timers. +mod delete_cell; pub mod poller; pub mod system; -mod delete_cell; diff --git a/src/libs/asynch/poller.rs b/src/libs/asynch/poller.rs index 817bbd5..c9eb7c2 100644 --- a/src/libs/asynch/poller.rs +++ b/src/libs/asynch/poller.rs @@ -33,23 +33,17 @@ struct Timer { kind: TimerKind, } impl Clone for Timer { - fn clone(&self) -> Self { - Self { expires: self.expires, kind: self.kind.clone() } - } + fn clone(&self) -> Self { Self { expires: self.expires, kind: self.kind.clone() } } } impl Eq for Timer {} impl PartialEq for Timer { fn eq(&self, other: &Self) -> bool { self.expires.eq(&other.expires) } } impl PartialOrd for Timer { - fn partial_cmp(&self, other: &Self) -> Option { - Some(other.cmp(self)) - } + fn partial_cmp(&self, other: &Self) -> Option { Some(other.cmp(self)) } } impl Ord for Timer { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - other.expires.cmp(&self.expires) - } + fn cmp(&self, other: &Self) -> std::cmp::Ordering { other.expires.cmp(&self.expires) } } /// Representation of a scheduled timer @@ -76,25 +70,16 @@ impl Poller { } /// Set a single-fire timer - pub fn set_timeout( - &mut self, - duration: Duration, - data: TOnce, - ) -> TimerHandle { + pub fn set_timeout(&mut self, duration: Duration, data: TOnce) -> TimerHandle { let data_cell = DeleteCell::new(data); - self.timers.push(Timer { - kind: TimerKind::Once(data_cell.clone()), - expires: Instant::now() + duration, - }); + self + .timers + .push(Timer { kind: TimerKind::Once(data_cell.clone()), expires: Instant::now() + duration }); TimerHandle(data_cell) } /// Set a recurring timer - pub fn set_interval( - &mut self, - period: Duration, - data: TRec, - ) -> TimerHandle { + pub fn set_interval(&mut self, period: Duration, data: TRec) -> TimerHandle { let data_cell = DeleteCell::new(data); self.timers.push(Timer { expires: Instant::now() + period, diff --git a/src/libs/asynch/system.rs b/src/libs/asynch/system.rs index 60d729c..eec26b3 100644 --- a/src/libs/asynch/system.rs +++ b/src/libs/asynch/system.rs @@ -5,7 +5,7 @@ use std::any::{type_name, Any, TypeId}; use std::cell::RefCell; use std::collections::VecDeque; -use std::fmt::{Debug, Display}; +use std::fmt; use std::rc::Rc; use std::sync::mpsc::Sender; use std::sync::{Arc, Mutex}; @@ -19,7 +19,7 @@ use super::poller::{PollEvent, Poller, TimerHandle}; use crate::facade::system::{IntoSystem, System}; use crate::foreign::atom::Atomic; use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::ExternError; +use crate::foreign::error::RTError; use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tpl; use crate::gen::traits::Gen; @@ -29,6 +29,7 @@ use crate::interpreter::handler::HandlerTable; use crate::interpreter::nort::Expr; use crate::libs::std::number::Numeric; use crate::location::{CodeGenInfo, CodeLocation}; +use crate::sym; use crate::utils::unwrap_or::unwrap_or; use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS}; @@ -50,8 +51,8 @@ impl CancelTimer { } pub fn cancel(&self) { self.0.lock().unwrap()() } } -impl Debug for CancelTimer { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for CancelTimer { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("CancelTimer").finish_non_exhaustive() } } @@ -66,9 +67,9 @@ impl InertPayload for Yield { /// exited #[derive(Clone)] pub struct InfiniteBlock; -impl ExternError for InfiniteBlock {} -impl Display for InfiniteBlock { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl RTError for InfiniteBlock {} +impl fmt::Display for InfiniteBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { static MSG: &str = "User code yielded, but there are no timers or event \ producers to wake it up in the future"; write!(f, "{}", MSG) @@ -83,7 +84,7 @@ impl MessagePort { pub fn send(&mut self, message: T) { let _ = self.0.send(Box::new(message)); } } -fn gen() -> CodeGenInfo { CodeGenInfo::no_details("asynch") } +fn gen() -> CodeGenInfo { CodeGenInfo::no_details(sym!(asynch)) } #[derive(RustEmbed)] #[folder = "src/libs/asynch"] @@ -92,7 +93,7 @@ struct AsynchEmbed; fn code() -> DeclTree { DeclTree::ns("system::async", [DeclTree::leaf( - PrefixFS::new(EmbeddedFS::new::(".orc", gen()), "", "io").rc(), + PrefixFS::new(EmbeddedFS::new::(".orc", gen()), "", "async").rc(), )]) } @@ -172,7 +173,7 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { let mut polly = polly.borrow_mut(); loop { let next = unwrap_or!(polly.run(); - return Err(InfiniteBlock.rc()) + return Err(InfiniteBlock.pack()) ); match next { PollEvent::Once(expr) => return Ok(expr), @@ -185,7 +186,7 @@ impl<'a> IntoSystem<'a> for AsynchSystem<'a> { if !events.is_empty() { microtasks = VecDeque::from(events); // trampoline - let loc = CodeLocation::Gen(CodeGenInfo::no_details("system::asynch")); + let loc = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(system::asynch))); return Ok(Inert(Yield).atom_expr(loc)); } }, diff --git a/src/libs/directfs/commands.rs b/src/libs/directfs/commands.rs index 383de1f..2865f0b 100644 --- a/src/libs/directfs/commands.rs +++ b/src/libs/directfs/commands.rs @@ -6,7 +6,7 @@ use super::osstring::os_string_lib; use crate::facade::system::{IntoSystem, System}; use crate::foreign::atom::Atomic; use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::ExternResult; +use crate::foreign::error::RTResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; @@ -84,7 +84,7 @@ fn read_dir(sched: &SeqScheduler, cmd: &CPSBox) -> Expr { .collect::, Clause>>(); match converted { Err(e) => { - let e = e.to_expr(fail.location()); + let e = e.into_expr(fail.location()); let tpl = tpl::A(tpl::Slot, tpl::Slot); vec![tpl.template(nort_gen(fail.location()), [fail, e])] }, @@ -141,7 +141,7 @@ fn join_paths(root: OsString, sub: OsString) -> OsString { fn pop_path(path: Inert) -> Option<(Inert, Inert)> { let mut path = PathBuf::from(path.0); let sub = path.file_name()?.to_owned(); - debug_assert!(path.pop(), "file_name above returned Some"); + assert!(path.pop(), "file_name above returned Some"); Some((Inert(path.into_os_string()), Inert(sub))) } @@ -177,7 +177,7 @@ impl IntoSystem<'static> for DirectFS { xfn_ent("append_file", [open_file_append_cmd]), xfn_ent("join_paths", [join_paths]), xfn_ent("pop_path", [pop_path]), - atom_ent("cwd", [Unstable::new(|_| -> ExternResult<_> { + atom_ent("cwd", [Unstable::new(|_| -> RTResult<_> { let path = std::env::current_dir().map_err(|e| RuntimeError::ext(e.to_string(), "reading CWD"))?; Ok(Inert(path.into_os_string())) diff --git a/src/libs/directfs/osstring.rs b/src/libs/directfs/osstring.rs index 4812426..8df9034 100644 --- a/src/libs/directfs/osstring.rs +++ b/src/libs/directfs/osstring.rs @@ -1,7 +1,7 @@ use std::ffi::OsString; use crate::foreign::atom::Atomic; -use crate::foreign::error::ExternResult; +use crate::foreign::error::RTResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; @@ -12,9 +12,12 @@ use crate::location::CodeLocation; impl InertPayload for OsString { const TYPE_STR: &'static str = "OsString"; + fn respond(&self, mut request: crate::utils::ddispatch::Request) { + request.serve_with(|| OrcString::from(self.to_string_lossy().to_string())) + } } impl TryFromExpr for OsString { - fn from_expr(exi: Expr) -> ExternResult { Ok(Inert::from_expr(exi)?.0) } + fn from_expr(exi: Expr) -> RTResult { Ok(Inert::from_expr(exi)?.0) } } impl ToClause for OsString { fn to_clause(self, _: CodeLocation) -> Clause { Inert(self).atom_cls() } diff --git a/src/libs/io/bindings.rs b/src/libs/io/bindings.rs index 50cab49..f7124d3 100644 --- a/src/libs/io/bindings.rs +++ b/src/libs/io/bindings.rs @@ -2,7 +2,7 @@ use super::flow::IOCmdHandlePack; use super::instances::{BRead, ReadCmd, SRead, WriteCmd}; use super::service::{Sink, Source}; use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::ExternResult; +use crate::foreign::error::RTResult; use crate::foreign::inert::Inert; use crate::gen::tree::{xfn_ent, ConstTree}; use crate::libs::scheduler::system::SharedHandle; @@ -36,7 +36,7 @@ pub fn read_bytes(Inert(handle): ReadHandle, n: Inert) -> ReadCmdPack { pub fn read_until( Inert(handle): ReadHandle, Inert(pattern): Inert, -) -> ExternResult { +) -> RTResult { let pattern = pattern.try_into().map_err(|_| { let msg = format!("{pattern} doesn't fit into a byte"); RuntimeError::ext(msg, "converting number to byte") diff --git a/src/libs/io/flow.rs b/src/libs/io/flow.rs index e4e41f0..a8c14ca 100644 --- a/src/libs/io/flow.rs +++ b/src/libs/io/flow.rs @@ -1,6 +1,6 @@ -use std::fmt::Display; +use std::fmt; -use crate::foreign::error::ExternError; +use crate::foreign::error::RTError; use crate::libs::scheduler::cancel_flag::CancelFlag; pub trait IOHandler { @@ -22,11 +22,7 @@ pub trait IOCmd: Send { type Result: Send; type Handle; - fn execute( - self, - stream: &mut Self::Stream, - cancel: CancelFlag, - ) -> Self::Result; + fn execute(self, stream: &mut Self::Stream, cancel: CancelFlag) -> Self::Result; } #[derive(Debug, Clone)] @@ -37,9 +33,9 @@ pub struct IOCmdHandlePack { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] pub struct NoActiveStream(usize); -impl ExternError for NoActiveStream {} -impl Display for NoActiveStream { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl RTError for NoActiveStream {} +impl fmt::Display for NoActiveStream { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "The stream {} had already been closed", self.0) } } diff --git a/src/libs/io/instances.rs b/src/libs/io/instances.rs index 600ddf4..d883969 100644 --- a/src/libs/io/instances.rs +++ b/src/libs/io/instances.rs @@ -13,6 +13,7 @@ use crate::libs::scheduler::system::SharedHandle; use crate::libs::std::binary::Binary; use crate::libs::std::string::OrcString; use crate::location::{CodeGenInfo, CodeLocation}; +use crate::sym; /// String reading command #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -42,11 +43,7 @@ impl IOCmd for ReadCmd { // This is a buggy rule, check manually #[allow(clippy::read_zero_byte_vec)] - fn execute( - self, - stream: &mut Self::Stream, - _cancel: CancelFlag, - ) -> Self::Result { + fn execute(self, stream: &mut Self::Stream, _cancel: CancelFlag) -> Self::Result { match self { Self::RBytes(bread) => { let mut buf = Vec::new(); @@ -84,21 +81,18 @@ pub(super) enum ReadResult { impl ReadResult { pub fn dispatch(self, succ: Expr, fail: Expr) -> Vec { vec![match self { - ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) => - io_error_handler(e, fail), - ReadResult::RBin(_, Ok(bytes)) => - tpl::A(tpl::Slot, tpl::V(Inert(Binary(Arc::new(bytes))))) - .template(nort_gen(succ.location()), [succ]), - ReadResult::RStr(_, Ok(text)) => - tpl::A(tpl::Slot, tpl::V(Inert(OrcString::from(text)))) - .template(nort_gen(succ.location()), [succ]), + ReadResult::RBin(_, Err(e)) | ReadResult::RStr(_, Err(e)) => io_error_handler(e, fail), + ReadResult::RBin(_, Ok(bytes)) => tpl::A(tpl::Slot, tpl::V(Inert(Binary(Arc::new(bytes))))) + .template(nort_gen(succ.location()), [succ]), + ReadResult::RStr(_, Ok(text)) => tpl::A(tpl::Slot, tpl::V(Inert(OrcString::from(text)))) + .template(nort_gen(succ.location()), [succ]), }] } } /// Function to convert [io::Error] to Orchid data pub(crate) fn io_error_handler(_e: io::Error, handler: Expr) -> Expr { - let ctx = nort_gen(CodeLocation::Gen(CodeGenInfo::no_details("io_error"))); + let ctx = nort_gen(CodeLocation::new_gen(CodeGenInfo::no_details(sym!(system::io::io_error)))); tpl::A(tpl::Slot, tpl::V(Inert(0usize))).template(ctx, [handler]) } @@ -115,11 +109,7 @@ impl IOCmd for WriteCmd { type Handle = SharedHandle; type Result = WriteResult; - fn execute( - self, - stream: &mut Self::Stream, - _cancel: CancelFlag, - ) -> Self::Result { + fn execute(self, stream: &mut Self::Stream, _cancel: CancelFlag) -> Self::Result { let result = match &self { Self::Flush => stream.flush(), Self::WStr(str) => write!(stream, "{}", str).map(|_| ()), diff --git a/src/libs/io/io.orc b/src/libs/io/io.orc index 7795d97..da38a31 100644 --- a/src/libs/io/io.orc +++ b/src/libs/io/io.orc @@ -24,8 +24,12 @@ export const readln := \ok. ( \_. yield ) +export const prompt := \line. \ok. ( + print line (readln ok) +) + export module prelude ( import super::* - export ::(print, println, readln) + export ::(print, println, readln, prompt) ) diff --git a/src/libs/io/mod.rs b/src/libs/io/mod.rs index 91294f5..4210705 100644 --- a/src/libs/io/mod.rs +++ b/src/libs/io/mod.rs @@ -8,14 +8,14 @@ //! and [crate::libs::std] for `std::panic`. //! //! ``` +//! use std::io::BufReader; +//! +//! use orchidlang::facade::loader::Loader; //! use orchidlang::libs::asynch::system::AsynchSystem; +//! use orchidlang::libs::io::{IOService, Stream}; //! use orchidlang::libs::scheduler::system::SeqScheduler; //! use orchidlang::libs::std::std_system::StdConfig; -//! use orchidlang::libs::io::{IOService, Stream}; -//! use orchidlang::facade::loader::Loader; -//! use std::io::BufReader; -//! -//! +//! //! let mut asynch = AsynchSystem::new(); //! let scheduler = SeqScheduler::new(&mut asynch); //! let std_streams = [ diff --git a/src/libs/io/service.rs b/src/libs/io/service.rs index 36ce43a..8fcf230 100644 --- a/src/libs/io/service.rs +++ b/src/libs/io/service.rs @@ -19,9 +19,9 @@ use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::handler::HandlerTable; use crate::libs::scheduler::system::{SeqScheduler, SharedHandle}; use crate::location::CodeGenInfo; -use crate::name::VName; -use crate::pipeline::load_solution::Prelude; +use crate::pipeline::load_project::Prelude; use crate::virt_fs::{DeclTree, EmbeddedFS, PrefixFS, VirtFS}; +use crate::{sym, vname}; /// Any type that we can read controlled amounts of data from pub type Source = BufReader>; @@ -42,7 +42,7 @@ trait_set! { pub(super) trait StreamTable<'a> = IntoIterator } -fn gen() -> CodeGenInfo { CodeGenInfo::no_details("system::io") } +fn gen() -> CodeGenInfo { CodeGenInfo::no_details(sym!(system::io)) } #[derive(RustEmbed)] #[folder = "src/libs/io"] @@ -68,9 +68,7 @@ impl<'a, ST: IntoIterator> IOService<'a, ST> { } } -impl<'a, ST: IntoIterator> IntoSystem<'static> - for IOService<'a, ST> -{ +impl<'a, ST: IntoIterator> IntoSystem<'static> for IOService<'a, ST> { fn into_system(self) -> System<'static> { let scheduler = self.scheduler.clone(); let mut handlers = HandlerTable::new(); @@ -89,8 +87,7 @@ impl<'a, ST: IntoIterator> IntoSystem<'static> match result { Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))) .template(nort_gen(cont.location()), [cont]), - Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))) - .template(nort_gen(fail.location()), [fail]), + Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))).template(nort_gen(fail.location()), [fail]), } }); let scheduler = self.scheduler.clone(); @@ -109,15 +106,13 @@ impl<'a, ST: IntoIterator> IntoSystem<'static> match result { Ok(cancel) => tpl::A(tpl::Slot, tpl::V(CPSBox::new(1, cancel))) .template(nort_gen(cont.location()), [cont]), - Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))) - .template(nort_gen(fail.location()), [fail]), + Err(e) => tpl::A(tpl::Slot, tpl::V(Inert(e))).template(nort_gen(fail.location()), [fail]), } }); let streams = self.global_streams.into_iter().map(|(n, stream)| { let handle = match stream { Stream::Sink(sink) => leaf(tpl::V(Inert(SharedHandle::wrap(sink)))), - Stream::Source(source) => - leaf(tpl::V(Inert(SharedHandle::wrap(source)))), + Stream::Source(source) => leaf(tpl::V(Inert(SharedHandle::wrap(source)))), }; (n, handle) }); @@ -127,8 +122,8 @@ impl<'a, ST: IntoIterator> IntoSystem<'static> constants: io_bindings(streams), code: code(), prelude: vec![Prelude { - target: VName::literal("system::io::prelude"), - exclude: VName::literal("system::io"), + target: vname!(system::io::prelude), + exclude: vname!(system::io), owner: gen(), }], lexer_plugins: vec![], diff --git a/src/libs/scheduler/busy.rs b/src/libs/scheduler/busy.rs index 1428f7a..b21134f 100644 --- a/src/libs/scheduler/busy.rs +++ b/src/libs/scheduler/busy.rs @@ -8,8 +8,7 @@ pub type SyncResult = (T, Box); /// Output from handlers contains the resource being processed and any Orchid /// handlers executed as a result of the operation pub type HandlerRes = (T, Vec); -pub type SyncOperation = - Box SyncResult + Send>; +pub type SyncOperation = Box SyncResult + Send>; pub type SyncOpResultHandler = Box, CancelFlag) -> (T, Vec) + Send>; @@ -22,12 +21,7 @@ struct SyncQueueItem { pub enum NextItemReportKind { Free(T), - Next { - instance: T, - cancelled: CancelFlag, - operation: SyncOperation, - rest: BusyState, - }, + Next { instance: T, cancelled: CancelFlag, operation: SyncOperation, rest: BusyState }, Taken, } @@ -47,9 +41,7 @@ impl BusyState { ) -> Self { BusyState { handler: Box::new(|t, payload, cancel| { - let u = *payload - .downcast() - .expect("mismatched initial handler and operation"); + let u = *payload.downcast().expect("mismatched initial handler and operation"); handler(t, u, cancel) }), queue: VecDeque::new(), @@ -84,10 +76,7 @@ impl BusyState { Some(cancelled) } - pub fn seal( - &mut self, - recipient: impl FnOnce(T) -> Vec + Send + 'static, - ) { + pub fn seal(&mut self, recipient: impl FnOnce(T) -> Vec + Send + 'static) { assert!(self.seal.is_none(), "Already sealed"); self.seal = Some(Box::new(recipient)) } @@ -100,8 +89,7 @@ impl BusyState { result: Box, cancelled: CancelFlag, ) -> NextItemReport { - let (mut instance, mut events) = - (self.handler)(instance, result, cancelled); + let (mut instance, mut events) = (self.handler)(instance, result, cancelled); let next_item = loop { if let Some(candidate) = self.queue.pop_front() { if candidate.cancelled.is_cancelled() { diff --git a/src/libs/scheduler/id_map.rs b/src/libs/scheduler/id_map.rs index 80e8307..9ba3fd5 100644 --- a/src/libs/scheduler/id_map.rs +++ b/src/libs/scheduler/id_map.rs @@ -20,8 +20,7 @@ impl IdMap { pub fn insert(&mut self, t: T) -> u64 { let id = self.next_id; self.next_id += 1; - (self.data.try_insert(id, t)) - .unwrap_or_else(|_| panic!("IdMap keys should be unique")); + (self.data.try_insert(id, t)).unwrap_or_else(|_| panic!("IdMap keys should be unique")); id } diff --git a/src/libs/scheduler/system.rs b/src/libs/scheduler/system.rs index fac8f34..bfba7c4 100644 --- a/src/libs/scheduler/system.rs +++ b/src/libs/scheduler/system.rs @@ -17,7 +17,7 @@ use std::any::{type_name, Any}; use std::cell::RefCell; -use std::fmt::Debug; +use std::fmt; use std::rc::Rc; use std::sync::{Arc, Mutex}; @@ -29,7 +29,7 @@ use super::id_map::IdMap; use super::thread_pool::ThreadPool; use crate::facade::system::{IntoSystem, System}; use crate::foreign::cps_box::CPSBox; -use crate::foreign::error::{AssertionError, ExternResult}; +use crate::foreign::error::{AssertionError, RTResult}; use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tree::{xfn_ent, ConstTree}; use crate::interpreter::handler::HandlerTable; @@ -84,7 +84,7 @@ impl SharedHandle { /// Remove the value from the handle if it's free. To interact with a handle /// you probably want to use a [SeqScheduler], but sometimes this makes /// sense as eg. an optimization. You can return the value after processing - /// via [SyncHandle::untake]. + /// via [SharedHandle::untake]. pub fn take(&self) -> Option { take_with_output(&mut *self.0.lock().unwrap(), |state| match state { SharedResource::Free(t) => (SharedResource::Taken, Some(t)), @@ -94,7 +94,7 @@ impl SharedHandle { /// Return the value to a handle that doesn't have one. The intended use case /// is to return values synchronously after they have been removed with - /// [SyncHandle::untake]. + /// [SharedHandle::take]. pub fn untake(&self, value: T) -> Result<(), T> { take_with_output(&mut *self.0.lock().unwrap(), |state| match state { SharedResource::Taken => (SharedResource::Free(value), Ok(())), @@ -105,8 +105,8 @@ impl SharedHandle { impl Clone for SharedHandle { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Debug for SharedHandle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for SharedHandle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SharedHandle") .field("state", &self.state()) .field("type", &type_name::()) @@ -127,8 +127,8 @@ impl InertPayload for SharedHandle { #[derive(Clone)] struct TakeCmd(pub Arc); -impl Debug for TakeCmd { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for TakeCmd { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "A command to drop a shared resource") } } @@ -141,7 +141,7 @@ impl InertPayload for SealedOrTaken { const TYPE_STR: &'static str = "SealedOrTaken"; } -fn take_and_drop(x: Expr) -> ExternResult> { +fn take_and_drop(x: Expr) -> RTResult> { match x.clause.request() { Some(t) => Ok(CPSBox::::new(1, t)), None => AssertionError::fail(x.location(), "SharedHandle", format!("{x}")), diff --git a/src/libs/scheduler/thread_pool.rs b/src/libs/scheduler/thread_pool.rs index f98cbca..3200b40 100644 --- a/src/libs/scheduler/thread_pool.rs +++ b/src/libs/scheduler/thread_pool.rs @@ -36,13 +36,8 @@ pub trait Query: Send + 'static { /// runs exactly one type of task, this can appear only once in the code for /// a given thread pool. It is practical in a narrow set of cases, most of the /// time however you are better off defining an explicit reporter. - fn then( - self, - callback: F, - ) -> QueryTask - where - Self: Sized, - { + fn then(self, callback: F) -> QueryTask + where Self: Sized { QueryTask { query: self, callback } } } diff --git a/src/libs/std/arithmetic_error.rs b/src/libs/std/arithmetic_error.rs index 89eb6b4..91d20ae 100644 --- a/src/libs/std/arithmetic_error.rs +++ b/src/libs/std/arithmetic_error.rs @@ -1,8 +1,8 @@ //! Error produced by numeric opperations -use std::fmt::Display; +use std::fmt; -use crate::foreign::error::ExternError; +use crate::foreign::error::RTError; /// Various errors produced by arithmetic operations #[derive(Clone)] @@ -17,8 +17,8 @@ pub enum ArithmeticError { NaN, } -impl Display for ArithmeticError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ArithmeticError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::NaN => write!(f, "Operation resulted in NaN"), Self::Overflow => write!(f, "Integer overflow"), @@ -28,4 +28,4 @@ impl Display for ArithmeticError { } } -impl ExternError for ArithmeticError {} +impl RTError for ArithmeticError {} diff --git a/src/libs/std/binary.rs b/src/libs/std/binary.rs index 3adcdea..7582dc9 100644 --- a/src/libs/std/binary.rs +++ b/src/libs/std/binary.rs @@ -1,6 +1,6 @@ //! `std::binary` Operations on binary buffers. -use std::fmt::Debug; +use std::fmt; use std::ops::Deref; use std::sync::Arc; @@ -8,7 +8,7 @@ use itertools::Itertools; use super::runtime_error::RuntimeError; use crate::foreign::atom::Atomic; -use crate::foreign::error::ExternResult; +use crate::foreign::error::RTResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::nort::Clause; @@ -29,8 +29,8 @@ impl Deref for Binary { fn deref(&self) -> &Self::Target { &self.0 } } -impl Debug for Binary { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Binary { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut iter = self.0.iter().copied(); f.write_str("Binary")?; for mut chunk in iter.by_ref().take(32).chunks(4).into_iter() { @@ -51,7 +51,7 @@ pub fn concatenate(a: Inert, b: Inert) -> Inert { } /// Extract a subsection of the binary data -pub fn slice(s: Inert, i: Inert, len: Inert) -> ExternResult> { +pub fn slice(s: Inert, i: Inert, len: Inert) -> RTResult> { if i.0 + len.0 < s.0.0.len() { RuntimeError::fail("Byte index out of bounds".to_string(), "indexing binary")? } @@ -65,7 +65,7 @@ pub fn find(haystack: Inert, needle: Inert) -> Option { } /// Split binary data block into two smaller blocks -pub fn split(bin: Inert, i: Inert) -> ExternResult<(Inert, Inert)> { +pub fn split(bin: Inert, i: Inert) -> RTResult<(Inert, Inert)> { if bin.0.0.len() < i.0 { RuntimeError::fail("Byte index out of bounds".to_string(), "splitting binary")? } @@ -79,7 +79,7 @@ pub fn get_num( loc: Inert, size: Inert, is_le: Inert, -) -> ExternResult> { +) -> RTResult> { if buf.0.0.len() < (loc.0 + size.0) { RuntimeError::fail("section out of range".to_string(), "reading number from binary data")? } @@ -106,7 +106,7 @@ pub fn from_num( size: Inert, is_le: Inert, data: Inert, -) -> ExternResult> { +) -> RTResult> { if INT_BYTES < size.0 { RuntimeError::fail( "more than std::bin::int_bytes bytes requested".to_string(), diff --git a/src/libs/std/bool.rs b/src/libs/std/bool.rs index 2171f48..2a398cc 100644 --- a/src/libs/std/bool.rs +++ b/src/libs/std/bool.rs @@ -1,6 +1,6 @@ use super::number::Numeric; use super::string::OrcString; -use crate::foreign::error::{AssertionError, ExternResult}; +use crate::foreign::error::{AssertionError, RTResult}; use crate::foreign::inert::Inert; use crate::foreign::try_from_expr::WithLoc; use crate::gen::tpl; @@ -26,7 +26,7 @@ pub fn if_then_else(WithLoc(loc, b): WithLoc>) -> Expr { /// - both are string, /// - both are bool, /// - both are either uint or num -pub fn equals(WithLoc(loc, a): WithLoc, b: Expr) -> ExternResult> { +pub fn equals(WithLoc(loc, a): WithLoc, b: Expr) -> RTResult> { Ok(Inert(if let Ok(l) = a.clone().downcast::>() { b.downcast::>().is_ok_and(|r| *l == *r) } else if let Ok(l) = a.clone().downcast::>() { diff --git a/src/libs/std/conv.rs b/src/libs/std/conv.rs index 5383235..cb5dc6c 100644 --- a/src/libs/std/conv.rs +++ b/src/libs/std/conv.rs @@ -1,69 +1,45 @@ -use once_cell::sync::Lazy; use ordered_float::NotNan; use super::number::Numeric; -use super::protocol::{gen_resolv, Protocol}; use super::string::OrcString; -use crate::foreign::atom::Atomic; -use crate::foreign::error::{AssertionError, ExternResult}; +use crate::foreign::error::{AssertionError, RTResult}; use crate::foreign::inert::Inert; use crate::foreign::try_from_expr::WithLoc; use crate::gen::tpl; -use crate::gen::traits::Gen; -use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::gen_nort::nort_gen; -use crate::interpreter::nort::{ClauseInst, Expr}; +use crate::gen::tree::{leaf, xfn_ent, ConstTree}; +use crate::interpreter::nort::ClauseInst; use crate::parse::numeric::parse_num; -pub static TO_STRING: Lazy = - Lazy::new(|| Protocol::new("to_string", [])); - -fn to_numeric(WithLoc(loc, a): WithLoc) -> ExternResult { +fn to_numeric(WithLoc(loc, a): WithLoc) -> RTResult { if let Some(n) = a.request::() { return Ok(n); } if let Some(s) = a.request::() { - return parse_num(s.as_str()).map_err(|e| { - AssertionError::ext(loc, "number syntax", format!("{e:?}")) - }); + return parse_num(s.as_str()) + .map_err(|e| AssertionError::ext(loc, "number syntax", format!("{e:?}"))); } AssertionError::fail(loc, "string or number", format!("{a}")) } /// parse a number. Accepts the same syntax Orchid does. -pub fn to_float(a: WithLoc) -> ExternResult>> { +pub fn to_float(a: WithLoc) -> RTResult>> { to_numeric(a).map(|n| Inert(n.as_float())) } /// Parse an unsigned integer. Accepts the same formats Orchid does. If the /// input is a number, floors it. -pub fn to_uint(a: WithLoc) -> ExternResult> { +pub fn to_uint(a: WithLoc) -> RTResult> { to_numeric(a).map(|n| match n { Numeric::Float(f) => Inert(f.floor() as usize), Numeric::Uint(i) => Inert(i), }) } -/// Convert a literal to a string using Rust's conversions for floats, chars and -/// uints respectively -pub fn to_string(WithLoc(loc, a): WithLoc) -> Expr { - match a.clone().downcast::>() { - Ok(str) => str.atom_expr(loc), - Err(_) => match a.clause.request::() { - Some(str) => Inert(str).atom_expr(loc), - None => tpl::a2(gen_resolv("std::to_string"), tpl::Slot, tpl::Slot) - .template(nort_gen(loc), [a.clone(), a]), - }, - } -} - pub fn conv_lib() -> ConstTree { - ConstTree::ns("std", [ConstTree::tree([ - TO_STRING.as_tree_ent([]), - ConstTree::tree_ent("conv", [ - xfn_ent("to_float", [to_float]), - xfn_ent("to_uint", [to_uint]), - xfn_ent("to_string", [to_string]), - ]), - ])]) + ConstTree::ns("std", [ConstTree::tree([ConstTree::tree_ent("conv", [ + xfn_ent("to_float", [to_float]), + xfn_ent("to_uint", [to_uint]), + // conversion logic moved to the string library + ("to_string", leaf(tpl::C("std::string::convert"))), + ])])]) } diff --git a/src/libs/std/cross_pipeline.rs b/src/libs/std/cross_pipeline.rs index 03cf915..bdcc49a 100644 --- a/src/libs/std/cross_pipeline.rs +++ b/src/libs/std/cross_pipeline.rs @@ -1,22 +1,23 @@ -use std::collections::VecDeque; use std::iter; use std::rc::Rc; +use itertools::Itertools; + use crate::foreign::atom::Atomic; use crate::foreign::fn_bridge::xfn; use crate::foreign::process::Unstable; use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::TryFromExpr; -use crate::location::{CodeLocation, SourceRange}; +use crate::foreign::try_from_expr::{TryFromExpr, WithLoc}; +use crate::location::SourceRange; use crate::parse::parsed::{self, PType}; use crate::utils::pure_seq::pushed; -pub trait DeferredRuntimeCallback: - Fn(Vec<(T, U)>) -> R + Clone + Send + 'static +pub trait DeferredRuntimeCallback: + FnOnce(Vec) -> R + Clone + Send + 'static { } -impl) -> R + Clone + Send + 'static> - DeferredRuntimeCallback for F +impl) -> R + Clone + Send + 'static> DeferredRuntimeCallback + for F { } @@ -26,56 +27,39 @@ impl) -> R + Clone + Send + 'static> /// # Panics /// /// If the list of remaining keys is empty -fn table_receiver_rec< - T: Clone + Send + 'static, - U: TryFromExpr + Clone + Send + 'static, - R: ToClause + 'static, ->( - range: SourceRange, - results: Vec<(T, U)>, - mut remaining_keys: VecDeque, - callback: impl DeferredRuntimeCallback, +fn table_receiver_rec( + results: Vec, + items: usize, + callback: impl DeferredRuntimeCallback, ) -> impl Atomic { - let t = remaining_keys.pop_front().expect("empty handled elsewhere"); - xfn("__table_receiver__", move |u: U| { - let results = pushed(results, (t, u)); - match remaining_keys.is_empty() { - true => callback(results).to_clause(CodeLocation::Source(range)), - false => table_receiver_rec(range, results, remaining_keys, callback).atom_cls(), + xfn("__table_receiver__", move |WithLoc(loc, t): WithLoc| { + let results: Vec = pushed(results, t); + match items == results.len() { + true => callback(results).to_clause(loc), + false => table_receiver_rec(results, items, callback).atom_cls(), } }) } -fn table_receiver< - T: Clone + Send + 'static, - U: TryFromExpr + Clone + Send + 'static, - R: ToClause + 'static, ->( - range: SourceRange, - keys: VecDeque, - callback: impl DeferredRuntimeCallback, +fn table_receiver( + items: usize, + callback: impl DeferredRuntimeCallback, ) -> parsed::Clause { - if keys.is_empty() { + if items == 0 { Unstable::new(move |_| callback(Vec::new())).ast_cls() } else { - Unstable::new(move |_| table_receiver_rec(range, Vec::new(), keys, callback).atom_cls()) - .ast_cls() + Unstable::new(move |_| table_receiver_rec(Vec::new(), items, callback).atom_cls()).ast_cls() } } /// Defers the execution of the callback to runtime, allowing it to depend on /// the result of Otchid expressions. -pub fn defer_to_runtime< - T: Clone + Send + 'static, - U: TryFromExpr + Clone + Send + 'static, - R: ToClause + 'static, ->( +pub fn defer_to_runtime( range: SourceRange, - pairs: impl IntoIterator)>, - callback: impl DeferredRuntimeCallback, + exprs: impl Iterator>, + callback: impl DeferredRuntimeCallback, ) -> parsed::Clause { - let (keys, ast_values) = pairs.into_iter().unzip::<_, _, VecDeque<_>, Vec<_>>(); - let items = iter::once(table_receiver(range.clone(), keys, callback)) - .chain(ast_values.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v)))); + let argv = exprs.into_iter().map(|v| parsed::Clause::S(PType::Par, Rc::new(v))).collect_vec(); + let items = iter::once(table_receiver(argv.len(), callback)).chain(argv); parsed::Clause::s('(', items, range) } diff --git a/src/libs/std/exit_status.rs b/src/libs/std/exit_status.rs index b8e9af8..9441f23 100644 --- a/src/libs/std/exit_status.rs +++ b/src/libs/std/exit_status.rs @@ -9,14 +9,17 @@ use crate::foreign::inert::{Inert, InertPayload}; use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; /// An Orchid equivalent to Rust's binary exit status model +/// +/// The "namespaced" name is to easily separate in autocomplete lists from both +/// [std::process::ExitStatus] and [std::process::ExitCode] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ExitStatus { +pub enum OrcExitStatus { /// unix exit code 0 Success, /// unix exit code 1 Failure, } -impl ExitStatus { +impl OrcExitStatus { /// Convert to Rust-land [ExitCode] pub fn code(self) -> ExitCode { match self { @@ -26,15 +29,15 @@ impl ExitStatus { } } -impl InertPayload for ExitStatus { +impl InertPayload for OrcExitStatus { const TYPE_STR: &'static str = "ExitStatus"; } pub(super) fn exit_status_lib() -> ConstTree { - let is_success = |es: Inert| Inert(es.0 == ExitStatus::Success); + let is_success = |es: Inert| Inert(es.0 == OrcExitStatus::Success); ConstTree::ns("std::exit_status", [ConstTree::tree([ - atom_ent("success", [Inert(ExitStatus::Success)]), - atom_ent("failure", [Inert(ExitStatus::Failure)]), + atom_ent("success", [Inert(OrcExitStatus::Success)]), + atom_ent("failure", [Inert(OrcExitStatus::Failure)]), xfn_ent("is_success", [is_success]), ])]) } diff --git a/src/libs/std/functional.orc b/src/libs/std/fn.orc similarity index 100% rename from src/libs/std/functional.orc rename to src/libs/std/fn.orc diff --git a/src/libs/std/inspect.rs b/src/libs/std/inspect.rs index f9e010e..05f440b 100644 --- a/src/libs/std/inspect.rs +++ b/src/libs/std/inspect.rs @@ -1,29 +1,13 @@ -use std::fmt::Debug; - -use crate::foreign::atom::{Atomic, AtomicResult, AtomicReturn, CallData, RunData}; -use crate::foreign::error::ExternResult; -use crate::foreign::to_clause::ToClause; -use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; -use crate::interpreter::nort::{Clause, Expr}; -use crate::utils::ddispatch::Responder; - -#[derive(Debug, Clone)] -struct Inspect; -impl Responder for Inspect {} -impl Atomic for Inspect { - fn as_any(self: Box) -> Box { self } - fn as_any_ref(&self) -> &dyn std::any::Any { self } - fn redirect(&mut self) -> Option<&mut Expr> { None } - fn run(self: Box, _: RunData) -> AtomicResult { AtomicReturn::inert(*self) } - fn apply_ref(&self, call: CallData) -> ExternResult { - eprintln!("{}", call.arg); - Ok(call.arg.to_clause(call.location)) - } -} +use crate::foreign::fn_bridge::Thunk; +use crate::gen::tree::{xfn_ent, ConstTree}; +use crate::interpreter::nort::Expr; pub fn inspect_lib() -> ConstTree { ConstTree::ns("std", [ConstTree::tree([ - atom_ent("inspect", [Inspect]), + xfn_ent("inspect", [|x: Thunk| { + eprintln!("{}", x.0); + x.0 + }]), xfn_ent("tee", [|x: Expr| { eprintln!("{x}"); x diff --git a/src/libs/std/list.orc b/src/libs/std/list.orc index 7d8b97f..68b622e 100644 --- a/src/libs/std/list.orc +++ b/src/libs/std/list.orc @@ -1,8 +1,8 @@ import super::(option, tuple, tuple::t, panic, pmatch, pmatch::=>, macro, tee) -import super::(functional::*, procedural::*) +import super::(fn::*, procedural::*) import super::(loop::*, bool::*, known::*, number::*) -as_type list () +as_type () export const cons := \hd. \tl. wrap (option::some t[hd, unwrap tl]) export const end := wrap option::none diff --git a/src/libs/std/loop.orc b/src/libs/std/loop.orc index 0cb8f20..2f9e57c 100644 --- a/src/libs/std/loop.orc +++ b/src/libs/std/loop.orc @@ -1,6 +1,6 @@ import super::procedural::* import super::bool::* -import super::functional::(return, identity) +import super::fn::(return, identity) import super::known::* --[ diff --git a/src/libs/std/map.orc b/src/libs/std/map.orc index 184cca6..50c2fa3 100644 --- a/src/libs/std/map.orc +++ b/src/libs/std/map.orc @@ -1,8 +1,8 @@ -import super::(bool::*, functional::*, known::*, loop::*, procedural::*, string::*) -import super::(panic, pmatch, macro, option, list, tuple, to_string, conv, pmatch::[=>]) +import super::(bool::*, fn::*, known::*, loop::*, procedural::*, string::*) +import super::(panic, pmatch, macro, option, list, string, tuple, conv, pmatch::[=>]) -as_type map ( - impl to_string := \map. "map[" ++ ( +as_type ( + impl string::conversion := \map. "map[" ++ ( unwrap map |> list::map ( (tuple::t[k, v]) => conv::to_string k ++ " = " ++ conv::to_string v diff --git a/src/libs/std/mod.rs b/src/libs/std/mod.rs index 07751ce..809033e 100644 --- a/src/libs/std/mod.rs +++ b/src/libs/std/mod.rs @@ -1,5 +1,5 @@ //! Basic types and their functions, frequently used tools with no environmental -//! dependencies. +//! dependencies. pub mod arithmetic_error; pub mod binary; mod bool; @@ -9,11 +9,10 @@ pub mod exit_status; mod inspect; pub mod number; mod panic; -mod protocol; +pub mod protocol; pub mod reflect; pub mod runtime_error; mod state; pub mod std_system; pub mod string; pub mod tuple; -mod tstring; diff --git a/src/libs/std/number.rs b/src/libs/std/number.rs index f624f28..0a6c583 100644 --- a/src/libs/std/number.rs +++ b/src/libs/std/number.rs @@ -4,7 +4,7 @@ use ordered_float::NotNan; use super::arithmetic_error::ArithmeticError; use crate::foreign::atom::Atomic; -use crate::foreign::error::{AssertionError, ExternError, ExternResult}; +use crate::foreign::error::{AssertionError, RTError, RTResult}; use crate::foreign::inert::Inert; use crate::foreign::to_clause::ToClause; use crate::foreign::try_from_expr::TryFromExpr; @@ -34,27 +34,25 @@ impl Numeric { pub fn as_float(&self) -> NotNan { match self { Numeric::Float(n) => *n, - Numeric::Uint(i) => - NotNan::new(*i as f64).expect("ints cannot cast to NaN"), + Numeric::Uint(i) => NotNan::new(*i as f64).expect("ints cannot cast to NaN"), } } /// Wrap a f64 in a Numeric - pub fn new(value: f64) -> ExternResult { + pub fn new(value: f64) -> RTResult { match value.is_finite() { - false => Err(ArithmeticError::Infinity.rc()), + false => Err(ArithmeticError::Infinity.pack()), true => match NotNan::new(value) { Ok(f) => Ok(Self::Float(f)), - Err(_) => Err(ArithmeticError::NaN.rc()), + Err(_) => Err(ArithmeticError::NaN.pack()), }, } } } impl TryFromExpr for Numeric { - fn from_expr(exi: Expr) -> ExternResult { - (exi.clause.request()).ok_or_else(|| { - AssertionError::ext(exi.location(), "a numeric value", format!("{exi}")) - }) + fn from_expr(exi: Expr) -> RTResult { + (exi.clause.request()) + .ok_or_else(|| AssertionError::ext(exi.location(), "a numeric value", format!("{exi}"))) } } @@ -69,20 +67,18 @@ impl ToClause for Numeric { /// Add two numbers. If they're both uint, the output is uint. If either is /// number, the output is number. -pub fn add(a: Numeric, b: Numeric) -> ExternResult { +pub fn add(a: Numeric, b: Numeric) -> RTResult { match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => a - .checked_add(b) - .map(Numeric::Uint) - .ok_or_else(|| ArithmeticError::Overflow.rc()), + (Numeric::Uint(a), Numeric::Uint(b)) => + a.checked_add(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::Overflow.pack()), (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a + b)), - (Numeric::Float(a), Numeric::Uint(b)) - | (Numeric::Uint(b), Numeric::Float(a)) => Numeric::new(*a + b as f64), + (Numeric::Float(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Float(a)) => + Numeric::new(*a + b as f64), } } /// Subtract a number from another. Always returns Number. -pub fn subtract(a: Numeric, b: Numeric) -> ExternResult { +pub fn subtract(a: Numeric, b: Numeric) -> RTResult { match (a, b) { (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::new(a as f64 - b as f64), (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a - b)), @@ -93,36 +89,32 @@ pub fn subtract(a: Numeric, b: Numeric) -> ExternResult { /// Multiply two numbers. If they're both uint, the output is uint. If either /// is number, the output is number. -pub fn multiply(a: Numeric, b: Numeric) -> ExternResult { +pub fn multiply(a: Numeric, b: Numeric) -> RTResult { match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => a - .checked_mul(b) - .map(Numeric::Uint) - .ok_or_else(|| ArithmeticError::Overflow.rc()), + (Numeric::Uint(a), Numeric::Uint(b)) => + a.checked_mul(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::Overflow.pack()), (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a * b)), - (Numeric::Uint(a), Numeric::Float(b)) - | (Numeric::Float(b), Numeric::Uint(a)) => Numeric::new(a as f64 * *b), + (Numeric::Uint(a), Numeric::Float(b)) | (Numeric::Float(b), Numeric::Uint(a)) => + Numeric::new(a as f64 * *b), } } /// Divide a number by another. Always returns Number. -pub fn divide(a: Numeric, b: Numeric) -> ExternResult { +pub fn divide(a: Numeric, b: Numeric) -> RTResult { let a: f64 = a.as_f64(); let b: f64 = b.as_f64(); if b == 0.0 { - return Err(ArithmeticError::DivByZero.rc()); + return Err(ArithmeticError::DivByZero.pack()); } Numeric::new(a / b) } /// Take the remainder of two numbers. If they're both uint, the output is /// uint. If either is number, the output is number. -pub fn remainder(a: Numeric, b: Numeric) -> ExternResult { +pub fn remainder(a: Numeric, b: Numeric) -> RTResult { match (a, b) { - (Numeric::Uint(a), Numeric::Uint(b)) => a - .checked_rem(b) - .map(Numeric::Uint) - .ok_or_else(|| ArithmeticError::DivByZero.rc()), + (Numeric::Uint(a), Numeric::Uint(b)) => + a.checked_rem(b).map(Numeric::Uint).ok_or_else(|| ArithmeticError::DivByZero.pack()), (Numeric::Float(a), Numeric::Float(b)) => Numeric::new(*(a % b)), (Numeric::Uint(a), Numeric::Float(b)) => Numeric::new(a as f64 % *b), (Numeric::Float(a), Numeric::Uint(b)) => Numeric::new(*a % b as f64), diff --git a/src/libs/std/option.orc b/src/libs/std/option.orc index 482c7a2..b635249 100644 --- a/src/libs/std/option.orc +++ b/src/libs/std/option.orc @@ -1,8 +1,8 @@ -import std::(panic, pmatch, to_string, conv) -import std::(functional::*, string::*) +import std::(panic, pmatch, string, conv) +import std::(fn::*, string::*) -as_type option ( - impl to_string := \opt. ( +as_type ( + impl string::conversion := \opt. ( handle opt "none" \x. "some(" ++ conv::to_string x ++ ")" ) ) diff --git a/src/libs/std/panic.rs b/src/libs/std/panic.rs index 9755310..0aa4997 100644 --- a/src/libs/std/panic.rs +++ b/src/libs/std/panic.rs @@ -1,30 +1,30 @@ -use std::fmt::Display; +use std::fmt; use std::sync::Arc; use never::Never; use super::string::OrcString; -use crate::foreign::error::{ExternError, ExternResult}; +use crate::foreign::error::{RTError, RTResult}; use crate::foreign::inert::Inert; -use crate::gen::tree::{xfn_leaf, ConstTree}; +use crate::gen::tree::{xfn_leaf, ConstTree}; /// An unrecoverable error in Orchid land. Because Orchid is lazy, this only /// invalidates expressions that reference the one that generated it. #[derive(Clone)] pub struct OrchidPanic(Arc); -impl Display for OrchidPanic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for OrchidPanic { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Orchid code panicked: {}", self.0) } } -impl ExternError for OrchidPanic {} +impl RTError for OrchidPanic {} /// Takes a message, returns an [ExternError] unconditionally. -pub fn orc_panic(msg: Inert) -> ExternResult { +pub fn orc_panic(msg: Inert) -> RTResult { // any return value would work, but Clause is the simplest - Err(OrchidPanic(Arc::new(msg.0.get_string())).rc()) + Err(OrchidPanic(Arc::new(msg.0.get_string())).pack()) } pub fn panic_lib() -> ConstTree { ConstTree::ns("std::panic", [xfn_leaf(orc_panic)]) } diff --git a/src/libs/std/prelude.orc b/src/libs/std/prelude.orc index 1d15a79..6974bf7 100644 --- a/src/libs/std/prelude.orc +++ b/src/libs/std/prelude.orc @@ -4,7 +4,7 @@ import std::string::* export ::[++] import std::bool::* export ::([== !=], if, then, else, true, false, and, or, not) -import std::functional::* +import std::fn::* export ::([$ |>], identity, pass, pass2, return) import std::procedural::* export ::(do, let, cps) @@ -12,16 +12,14 @@ import std::tuple::t export ::(t) import std::pmatch::(match, [=>]) export ::(match, [=>]) -import std::tuple -import std::list -import std::map -import std::option -export ::(tuple, list, map, option) import std::loop::* export ::(loop_over, recursive, while) import std::known::* export ::[, _ ; . =] +import std::(tuple, list, map, option, exit_status) +export ::(tuple, list, map, option, exit_status) + import std export ::(std) diff --git a/src/libs/std/protocol.orc b/src/libs/std/protocol.orc new file mode 100644 index 0000000..6e353da --- /dev/null +++ b/src/libs/std/protocol.orc @@ -0,0 +1,8 @@ +import std::(map, option, fn::*) + +export const vcall := \proto. \key. \val. ( + resolve proto val + |> map::get key + |> option::assume + $ break val +) diff --git a/src/libs/std/protocol.rs b/src/libs/std/protocol.rs index 218d56d..c621a11 100644 --- a/src/libs/std/protocol.rs +++ b/src/libs/std/protocol.rs @@ -1,6 +1,16 @@ -use std::fmt::Debug; -use std::iter; +//! Polymorphism through Elixir-style protocols associated with type-tagged +//! values. A type-tag seals the value, and can only be unwrapped explicitly by +//! providing the correct type. This ensures that code that uses this explicit +//! polymorphism never uses the implicit polymorphism of dynamic typing. +//! +//! Atoms can also participate in this controlled form of polymorphism by +//! offering a [Tag] in their [crate::utils::ddispatch::Responder] callback. +//! +//! Protocols and types are modules with magic elements that distinguish them +//! from regular modules. + use std::sync::Arc; +use std::{fmt, iter}; use const_format::formatcp; use hashbrown::HashMap; @@ -8,18 +18,17 @@ use intern_all::{i, Tok}; use itertools::Itertools; use super::cross_pipeline::defer_to_runtime; -use super::reflect::{refer_seq, RefEqual}; +use super::reflect::refer_seq; use super::runtime_error::RuntimeError; use crate::error::ProjectResult; use crate::foreign::atom::Atomic; -use crate::foreign::error::ExternResult; +use crate::foreign::error::RTResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::process::Unstable; -use crate::foreign::to_clause::ToClause; use crate::gen::tpl; use crate::gen::traits::GenClause; -use crate::gen::tree::{atom_leaf, xfn_ent, ConstTree}; -use crate::interpreter::nort as int; +use crate::gen::tree::{atom_ent, leaf, xfn_ent, ConstTree}; +use crate::interpreter::nort; use crate::interpreter::nort::ClauseInst; use crate::libs::parse_custom_line::custom_line; use crate::location::SourceRange; @@ -28,133 +37,175 @@ use crate::parse::frag::Frag; use crate::parse::lexer::Lexeme; use crate::parse::parse_plugin::{ParseLinePlugin, ParsePluginReq}; use crate::parse::parsed::{ - self, Constant, Member, MemberKind, ModuleBlock, PType, SourceLine, - SourceLineKind, + self, Constant, Member, MemberKind, ModuleBlock, PType, SourceLine, SourceLineKind, }; use crate::utils::ddispatch::Request; +// TODO: write an example that thoroughly tests this module. Test rust-interop +// with Tuple + +/// Information available both for protocols and for tagged values +#[derive(Clone)] pub struct TypeData { - pub id: RefEqual, - pub display_name: Tok, - pub impls: HashMap, + /// The full path of the module designated to represent this type or protocol. + /// Also used to key impl tables in the counterpart (tag or protocol) + pub id: Sym, + /// Maps IDs of the counterpart (tag or protocol) to implementations + pub impls: Arc>, +} +impl TypeData { + /// Create a new type data record from a known name and impls + pub fn new(id: Sym, impls: impl IntoIterator) -> Self { + Self { id, impls: Arc::new(impls.into_iter().collect()) } + } +} + +fn mk_mod<'a>( + rest: impl IntoIterator, + impls: HashMap, + profile: ImplsProfile, +) -> ConstTree { + ConstTree::tree(rest.into_iter().chain([ + (profile.own_id, leaf(tpl::A(tpl::C("std::reflect::modname"), tpl::V(Inert(1))))), + atom_ent(TYPE_KEY, [use_wrap(profile.wrap, impls)]), + ])) +} + +fn to_mod<'a>( + rest: impl IntoIterator, + data: TypeData, + profile: ImplsProfile, +) -> ConstTree { + let id = data.id.clone(); + ConstTree::tree(rest.into_iter().chain([ + atom_ent(profile.own_id, [Unstable::new(move |r| { + assert!(r.location.module == id, "Pre-initilaized type lib mounted on wrong prefix"); + Inert(r.location.module) + })]), + atom_ent(TYPE_KEY, [profile.wrap.wrap(data)]), + ])) } /// Key for type data. The value is either [Inert] or [Inert] const TYPE_KEY: &str = "__type_data__"; +/// A shared behaviour that may implement itself for types, and may be +/// implemented by types. #[derive(Clone)] -pub struct Protocol(pub Arc); +pub struct Protocol(pub TypeData); impl Protocol { - const ID_KEY: &'static str = "__protocol_id__"; - - pub fn new_id( - id: RefEqual, - display_name: Tok, - impls: impl IntoIterator, - ) -> Self { - let impls = impls.into_iter().collect(); - Protocol(Arc::new(TypeData { id, display_name, impls })) + /// Name of the member the ID must be assigned to for a module to be + /// recognized as a protocol. + pub const ID_KEY: &'static str = "__protocol_id__"; + const fn profile() -> ImplsProfile { + ImplsProfile { + wrap: |t| Inert(Protocol(t)), + own_id: Protocol::ID_KEY, + other_id: Tag::ID_KEY, + prelude: formatcp!( + "import std + const {} := std::reflect::modname 1 + const resolve := std::protocol::resolve {TYPE_KEY} + const vcall := std::protocol::vcall {TYPE_KEY}", + Protocol::ID_KEY + ), + } } - pub fn new( - display_name: &'static str, - impls: impl IntoIterator, - ) -> Self { - Self::new_id(RefEqual::new(), i(display_name), impls) + /// Create a new protocol with a pre-determined name + pub fn new(id: Sym, impls: impl IntoIterator) -> Self { + Self(TypeData::new(id, impls)) } - pub fn id(&self) -> RefEqual { self.0.id.clone() } + /// Attach a pre-existing protocol to the tree. Consider [Protocol::tree]. + pub fn to_tree<'a>(&self, rest: impl IntoIterator) -> ConstTree { + to_mod(rest, self.0.clone(), Self::profile()) + } - pub fn as_tree_ent<'a>( - &'a self, + /// Create a new protocol definition + pub fn tree<'a>( + impls: impl IntoIterator, rest: impl IntoIterator, - ) -> (&str, ConstTree) { - ConstTree::tree_ent( - self.0.display_name.as_str(), - rest.into_iter().chain([ - (Self::ID_KEY, atom_leaf(Inert(self.id()))), - (TYPE_KEY, atom_leaf(Inert(self.clone()))), - ]), - ) + ) -> ConstTree { + mk_mod(rest, impls.into_iter().collect(), Self::profile()) } } -impl Debug for Protocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple(&self.0.display_name).field(&self.0.id.id()).finish() - } +impl fmt::Debug for Protocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Protocol({})", self.0.id) } } impl InertPayload for Protocol { const TYPE_STR: &'static str = "Protocol"; } +/// A type marker that can be attached to values to form a [Tagged] #[derive(Clone)] -pub struct Tag(pub Arc); +pub struct Tag(pub TypeData); impl Tag { const ID_KEY: &'static str = "__type_id__"; - - pub fn new_id( - id: RefEqual, - display_name: Tok, - impls: impl IntoIterator, - ) -> Self { - let impls = impls.into_iter().collect(); - Self(Arc::new(TypeData { id, display_name, impls })) + const fn profile() -> ImplsProfile { + ImplsProfile { + wrap: |t| Inert(Tag(t)), + own_id: Tag::ID_KEY, + other_id: Protocol::ID_KEY, + prelude: formatcp!( + "import std + const {} := std::reflect::modname 1 + const unwrap := std::protocol::unwrap {TYPE_KEY} + const wrap := std::protocol::wrap {TYPE_KEY}", + Tag::ID_KEY + ), + } } - pub fn new( - display_name: &'static str, - impls: impl IntoIterator, - ) -> Self { - Self::new_id(RefEqual::new(), i(display_name), impls) + /// Create a new type-tag with a pre-determined name + pub fn new(id: Sym, impls: impl IntoIterator) -> Self { + Self(TypeData::new(id, impls)) } - pub fn id(&self) -> RefEqual { self.0.id.clone() } + /// Attach a pre-existing type-tag to the tree. Consider [Tag::tree] + pub fn to_tree<'a>(&self, rest: impl IntoIterator) -> ConstTree { + to_mod(rest, self.0.clone(), Self::profile()) + } - pub fn as_tree_ent<'a>( - &'a self, + /// Create a new tag + pub fn tree<'a>( + impls: impl IntoIterator, rest: impl IntoIterator, - ) -> (&str, ConstTree) { - ConstTree::tree_ent( - self.0.display_name.as_str(), - rest.into_iter().chain([ - (Self::ID_KEY, atom_leaf(Inert(self.id()))), - (TYPE_KEY, atom_leaf(Inert(self.clone()))), - ]), - ) + ) -> ConstTree { + mk_mod(rest, impls.into_iter().collect(), Self::profile()) } } -impl Debug for Tag { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple(&self.0.display_name).field(&self.0.id.id()).finish() - } +impl fmt::Debug for Tag { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Tag({})", self.0.id) } } impl InertPayload for Tag { const TYPE_STR: &'static str = "Tag"; fn strict_eq(&self, other: &Self) -> bool { self.0.id == other.0.id } } +/// A value with a type [Tag] #[derive(Clone)] pub struct Tagged { + /// Type information pub tag: Tag, - pub value: int::Expr, + /// Value + pub value: nort::Expr, } -impl Debug for Tagged { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("Tagged").field(&self.tag).field(&self.value).finish() +impl fmt::Debug for Tagged { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Tagged({:?} {})", self.tag, self.value) } } impl InertPayload for Tagged { const TYPE_STR: &'static str = "Tagged"; - fn respond(&self, mut request: Request) { - request.serve_with(|| self.tag.clone()) - } + fn respond(&self, mut request: Request) { request.serve_with(|| self.tag.clone()) } } fn parse_impl( tail: Frag, req: &dyn ParsePluginReq, ) -> Option> { - custom_line(tail, i("impl"), false, req).map(|res| { + custom_line(tail, i!(str: "impl"), false, req).map(|res| { let (_, tail, _) = res?; let (name, tail) = req.parse_nsname(tail)?; let (walrus, tail) = req.pop(tail.trim())?; @@ -183,200 +234,125 @@ fn extract_impls( match parse_impl(line, req) { Some(result) => { let (name, value) = result?; - let target = - Sym::new(name.suffix([typeid_name.clone()])).unwrap(); + let target = Sym::new(name.suffix([typeid_name.clone()])).unwrap(); impls.push(Impl { target, value }); }, - None => lines.extend( - (req.parse_line(line)?.into_iter()).map(|k| k.wrap(range.clone())), - ), + None => lines.extend((req.parse_line(line)?.into_iter()).map(|k| k.wrap(range.clone()))), } } Ok((lines, impls)) } -trait WrapImpl: Clone + Send + Sync + 'static { - type R: ToClause; - fn wrap(&self, data: Arc) -> Self::R; +trait WrapImpl: Clone + Copy + Send + Sync + 'static { + type R: Atomic + Clone + 'static; + fn wrap(&self, data: TypeData) -> Self::R; } -impl) -> R + Clone + Send + Sync + 'static> +impl R + Clone + Copy + Send + Sync + 'static> WrapImpl for F { type R = R; - fn wrap(&self, data: Arc) -> Self::R { self(data) } + fn wrap(&self, data: TypeData) -> Self::R { self(data) } +} +fn use_wrap(wrap: impl WrapImpl, impls: HashMap) -> impl Atomic + Clone + 'static { + Unstable::new(move |r| wrap.wrap(TypeData::new(r.location.module, impls))) } -struct ImplsProfile<'a, W: WrapImpl> { +#[derive(Debug, Clone)] +struct ImplsProfile { wrap: W, - own_id: Tok, - other_id: Tok, - prelude: &'a str, + own_id: &'static str, + other_id: &'static str, + prelude: &'static str, } -fn parse_body_with_impls( - display_name: Tok, +fn parse_body_with_impls( body: Frag, req: &dyn ParsePluginReq, range: SourceRange, - profile: &ImplsProfile<'static, W>, + profile: ImplsProfile, ) -> ProjectResult> { - let id = RefEqual::new(); - let (lines, impls) = - extract_impls(body, req, range.clone(), profile.other_id.clone())?; - - Ok( - req - .parse_entries(profile.prelude, range.clone()) - .into_iter() - .chain( - [ - (profile.own_id.clone(), Inert(id.clone()).ast_cls()), - ( - i(TYPE_KEY), - defer_to_runtime( - range.clone(), - impls.into_iter().flat_map({ - let line_loc = range.clone(); - move |Impl { target, value }| { - [ - parsed::Clause::Name(target).into_expr(line_loc.clone()), - value, - ] - .map(|e| ((), vec![e])) - } - }), - { - let display_name = display_name.clone(); - let wrap = profile.wrap.clone(); - move |pairs: Vec<((), int::Expr)>| -> ExternResult<_> { - let mut impls = HashMap::new(); - debug_assert_eq!(pairs.len() % 2, 0, "key-value pairs"); - let mut nvnvnv = pairs.into_iter().map(|t| t.1); - while let Some((name, value)) = nvnvnv.next_tuple() { - let key = name.downcast::>()?; - impls.insert(key.0, value); - } - let id = id.clone(); - let display_name = display_name.clone(); - Ok(wrap.wrap(Arc::new(TypeData { id, display_name, impls }))) - } - }, - ), - ), - ] - .map(|(name, value)| { - let value = parsed::Expr { value, range: range.clone() }; - MemberKind::Constant(Constant { name, value }) - .to_line(true, range.clone()) - }), - ) - .chain(lines) - .collect(), - ) -} - -fn protocol_impls_profile() -> ImplsProfile<'static, impl WrapImpl> { - ImplsProfile { - wrap: |t| Inert(Protocol(t)), - own_id: i(Protocol::ID_KEY), - other_id: i(Tag::ID_KEY), - prelude: formatcp!( - "import std::protocol - const resolve := protocol::resolve {TYPE_KEY} - const get_impl := protocol::get_impl {TYPE_KEY}" - ), - } -} - -fn type_impls_profile() -> ImplsProfile<'static, impl WrapImpl> { - ImplsProfile { - wrap: |t| Inert(Tag(t)), - own_id: i(Tag::ID_KEY), - other_id: i(Protocol::ID_KEY), - prelude: formatcp!( - "import std::protocol - const unwrap := protocol::unwrap {TYPE_KEY} - const wrap := protocol::wrap {TYPE_KEY}" - ), - } + let ImplsProfile { other_id, prelude, wrap, .. } = profile.clone(); + let (mut lines, impls) = extract_impls(body, req, range.clone(), i(other_id))?; + let line_loc = range.clone(); + let type_data = defer_to_runtime( + range.clone(), + impls.into_iter().flat_map(move |Impl { target, value }| { + [vec![parsed::Clause::Name(target).into_expr(line_loc.clone())], vec![value]] + }), + move |pairs: Vec| -> RTResult<_> { + debug_assert_eq!(pairs.len() % 2, 0, "key-value pairs"); + let mut impls = HashMap::with_capacity(pairs.len() / 2); + for (name, value) in pairs.into_iter().tuples() { + impls.insert(name.downcast::>()?.0, value); + } + Ok(use_wrap(wrap, impls)) + }, + ); + let type_data_line = Constant { name: i(TYPE_KEY), value: type_data.into_expr(range.clone()) }; + lines.extend(req.parse_entries(prelude, range.clone())); + lines.push(MemberKind::Constant(type_data_line).into_line(true, range)); + Ok(lines) } +#[derive(Clone)] struct ProtocolParser; impl ParseLinePlugin for ProtocolParser { - fn parse( - &self, - req: &dyn ParsePluginReq, - ) -> Option>> { - custom_line(req.frag(), i("protocol"), true, req).map(|res| { + fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { + custom_line(req.frag(), i!(str: "protocol"), true, req).map(|res| { let (exported, tail, line_loc) = res?; let (name, tail) = req.pop(tail)?; let name = req.expect_name(name)?; let tail = req.expect_block(tail, PType::Par)?; - let profile = protocol_impls_profile(); - let body = - parse_body_with_impls(name.clone(), tail, req, line_loc, &profile)?; + let body = parse_body_with_impls(tail, req, line_loc, Protocol::profile())?; let kind = MemberKind::Module(ModuleBlock { name, body }); Ok(vec![SourceLineKind::Member(Member { exported, kind })]) }) } } +#[derive(Clone)] struct TypeParser; impl ParseLinePlugin for TypeParser { - fn parse( - &self, - req: &dyn ParsePluginReq, - ) -> Option>> { - custom_line(req.frag(), i("type"), true, req).map(|res| { + fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { + custom_line(req.frag(), i!(str: "type"), true, req).map(|res| { let (exported, tail, line_loc) = res?; let (name, tail) = req.pop(tail)?; let name = req.expect_name(name)?; let tail = req.expect_block(tail, PType::Par)?; - let profile = type_impls_profile(); - let body = - parse_body_with_impls(name.clone(), tail, req, line_loc, &profile)?; + let body = parse_body_with_impls(tail, req, line_loc, Tag::profile())?; let kind = MemberKind::Module(ModuleBlock { name, body }); Ok(vec![SourceLineKind::Member(Member { exported, kind })]) }) } } +#[derive(Clone)] struct AsProtocolParser; impl ParseLinePlugin for AsProtocolParser { - fn parse( - &self, - req: &dyn ParsePluginReq, - ) -> Option>> { - custom_line(req.frag(), i("as_protocol"), false, req).map(|res| { + fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { + custom_line(req.frag(), i!(str: "as_protocol"), false, req).map(|res| { let (_, tail, line_loc) = res?; - let (name, tail) = req.pop(tail)?; - let name = req.expect_name(name)?; let body = req.expect_block(tail, PType::Par)?; - let profile = protocol_impls_profile(); - parse_body_with_impls(name, body, req, line_loc, &profile) + parse_body_with_impls(body, req, line_loc, Protocol::profile()) .map(|v| v.into_iter().map(|e| e.kind).collect()) }) } } +#[derive(Clone)] struct AsTypeParser; impl ParseLinePlugin for AsTypeParser { - fn parse( - &self, - req: &dyn ParsePluginReq, - ) -> Option>> { - custom_line(req.frag(), i("as_type"), false, req).map(|res| { + fn parse(&self, req: &dyn ParsePluginReq) -> Option>> { + custom_line(req.frag(), i!(str: "as_type"), false, req).map(|res| { let (_, tail, line_loc) = res?; - let (name, tail) = req.pop(tail)?; - let name = req.expect_name(name)?; let body = req.expect_block(tail, PType::Par)?; - let profile = type_impls_profile(); - parse_body_with_impls(name, body, req, line_loc, &profile) + parse_body_with_impls(body, req, line_loc, Tag::profile()) .map(|v| v.into_iter().map(|e| e.kind).collect()) }) } } +/// Collection of all the parser plugins defined here pub fn parsers() -> Vec> { vec![ Box::new(ProtocolParser), @@ -386,10 +362,8 @@ pub fn parsers() -> Vec> { ] } -pub fn unwrap( - tag: Inert, - tagged: Inert, -) -> ExternResult { +/// Check and remove the type tag from a value +pub fn unwrap(tag: Inert, tagged: Inert) -> RTResult { if tagged.tag.strict_eq(&tag) { return Ok(tagged.value.clone()); } @@ -397,49 +371,41 @@ pub fn unwrap( RuntimeError::fail(msg, "unwrapping type-tagged value") } -pub fn wrap(tag: Inert, value: int::Expr) -> Inert { +/// Attach a type tag to a value +pub fn wrap(tag: Inert, value: nort::Expr) -> Inert { Inert(Tagged { tag: tag.0, value }) } -pub fn resolve( - protocol: Inert, - value: ClauseInst, -) -> ExternResult { +/// Find the implementation of a protocol for a given value +pub fn resolve(protocol: Inert, value: ClauseInst) -> RTResult { let tag = value.request::().ok_or_else(|| { let msg = format!("{value} is not type-tagged"); RuntimeError::ext(msg, "resolving protocol impl") })?; - get_impl(protocol, Inert(tag)) -} - -pub fn get_impl( - Inert(proto): Inert, - Inert(tag): Inert, -) -> ExternResult { - if let Some(implem) = proto.0.impls.get(&tag.0.id) { + if let Some(implem) = protocol.0.0.impls.get(&tag.0.id) { Ok(implem.clone()) - } else if let Some(implem) = tag.0.impls.get(&proto.0.id) { + } else if let Some(implem) = tag.0.impls.get(&protocol.0.0.id) { Ok(implem.clone()) } else { - let message = format!("{tag:?} doesn't implement {proto:?}"); + let message = format!("{tag:?} doesn't implement {protocol:?}"); RuntimeError::fail(message, "dispatching protocol") } } +/// Generate a call to [resolve] bound to the given protocol pub const fn gen_resolv(name: &'static str) -> impl GenClause { tpl::A( tpl::C("std::protocol::resolve"), - tpl::V(Unstable::new(move |_| { - refer_seq(name.split("::").chain(iter::once(TYPE_KEY))) - })), + tpl::V(Unstable::new(move |_| refer_seq(name.split("::").chain(iter::once(TYPE_KEY))))), ) } +/// All the functions exposed by the std::protocol library pub fn protocol_lib() -> ConstTree { ConstTree::ns("std::protocol", [ConstTree::tree([ xfn_ent("unwrap", [unwrap]), xfn_ent("wrap", [wrap]), - xfn_ent("get_impl", [get_impl]), xfn_ent("resolve", [resolve]), + xfn_ent("break", [|t: Inert| t.0.value]), ])]) } diff --git a/src/libs/std/reflect.rs b/src/libs/std/reflect.rs index 9baeffc..a7cb236 100644 --- a/src/libs/std/reflect.rs +++ b/src/libs/std/reflect.rs @@ -1,20 +1,23 @@ //! `std::reflect` Abstraction-breaking operations for dynamically constructing //! [Clause::Constant] references. -use std::cmp; -use std::fmt::Debug; use std::hash::Hash; use std::sync::atomic::{self, AtomicUsize}; +use std::{cmp, fmt}; use intern_all::i; +use super::runtime_error::RuntimeError; +use super::string::OrcString; use crate::foreign::inert::{Inert, InertPayload}; +use crate::foreign::try_from_expr::WithLoc; use crate::gen::tree::{xfn_ent, ConstTree}; -use crate::interpreter::nort::Clause; +use crate::interpreter::nort::{self, Clause}; use crate::name::Sym; impl InertPayload for Sym { const TYPE_STR: &'static str = "SymbolName"; + fn strict_eq(&self, o: &Self) -> bool { self == o } } /// Generate a constant reference at runtime. Referencing a nonexistent constant @@ -39,8 +42,8 @@ impl RefEqual { /// Return the unique identifier of this [RefEqual] and its copies pub fn id(&self) -> usize { self.0 } } -impl Debug for RefEqual { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for RefEqual { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_tuple("RefEqual").field(&self.id()).finish() } } @@ -65,5 +68,11 @@ impl Hash for RefEqual { pub(super) fn reflect_lib() -> ConstTree { ConstTree::ns("std::reflect", [ConstTree::tree([ xfn_ent("ref_equal", [|l: Inert, r: Inert| Inert(l.0.id() == r.0.id())]), + xfn_ent("modname", [|WithLoc(loc, _): WithLoc| Inert(loc.module)]), + xfn_ent("symbol", [|s: Inert| { + Sym::parse(s.0.as_str()) + .map(Inert) + .map_err(|_| RuntimeError::ext("empty string".to_string(), "converting string to Symbol")) + }]), ])]) } diff --git a/src/libs/std/result.orc b/src/libs/std/result.orc index 53b0685..defb946 100644 --- a/src/libs/std/result.orc +++ b/src/libs/std/result.orc @@ -1,6 +1,6 @@ import std::panic -as_type result () +as_type () export const ok := \v. wrap \fe. \fv. fv v export const err := \e. wrap \fe. \fv. fe e diff --git a/src/libs/std/runtime_error.rs b/src/libs/std/runtime_error.rs index 79b08d1..dc9503e 100644 --- a/src/libs/std/runtime_error.rs +++ b/src/libs/std/runtime_error.rs @@ -1,9 +1,9 @@ //! Errors thrown by the standard library in lieu of in-language error handling //! for runtime errors such as missing files. -use std::fmt::Display; +use std::fmt; -use crate::foreign::error::{ExternError, ExternErrorObj, ExternResult}; +use crate::foreign::error::{RTError, RTErrorObj, RTResult}; /// Some external event prevented the operation from succeeding #[derive(Clone)] @@ -15,20 +15,20 @@ pub struct RuntimeError { impl RuntimeError { /// Construct, upcast and wrap in a Result that never succeeds for easy /// short-circuiting - pub fn fail(message: String, operation: &'static str) -> ExternResult { - Err(Self { message, operation }.rc()) + pub fn fail(message: String, operation: &'static str) -> RTResult { + Err(Self { message, operation }.pack()) } - /// Construct and upcast to [ExternError] - pub fn ext(message: String, operation: &'static str) -> ExternErrorObj { - Self { message, operation }.rc() + /// Construct and upcast to [RTErrorObj] + pub fn ext(message: String, operation: &'static str) -> RTErrorObj { + Self { message, operation }.pack() } } -impl Display for RuntimeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for RuntimeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Error while {}: {}", self.operation, self.message) } } -impl ExternError for RuntimeError {} +impl RTError for RuntimeError {} diff --git a/src/libs/std/state.rs b/src/libs/std/state.rs index 299fa3b..1f31611 100644 --- a/src/libs/std/state.rs +++ b/src/libs/std/state.rs @@ -37,9 +37,7 @@ fn new_state(default: Thunk, cont: Thunk) -> Inert { Inert(NewStateCmd(default.0, cont.0)) } -fn get_state(s: Inert, cont: Thunk) -> Inert { - Inert(GetStateCmd(s.0, cont.0)) -} +fn get_state(s: Inert, cont: Thunk) -> Inert { Inert(GetStateCmd(s.0, cont.0)) } fn set_state(s: Inert, value: Thunk, cont: Thunk) -> Inert { Inert(SetStateCmd(s.0, value.0, cont.0)) diff --git a/src/libs/std/std_system.rs b/src/libs/std/std_system.rs index d11934a..af119ea 100644 --- a/src/libs/std/std_system.rs +++ b/src/libs/std/std_system.rs @@ -14,17 +14,16 @@ use super::panic::panic_lib; use super::protocol::{parsers, protocol_lib}; use super::reflect::reflect_lib; use super::state::{state_handlers, state_lib}; -use super::string::str_lib; -use super::tstring::TStringLexer; +use super::string::{str_lib, StringLexer}; use super::tuple::tuple_lib; use crate::facade::system::{IntoSystem, System}; use crate::gen::tree::{ConstCombineErr, ConstTree}; use crate::location::CodeGenInfo; -use crate::name::VName; -use crate::pipeline::load_solution::Prelude; +use crate::pipeline::load_project::Prelude; use crate::tree::ModEntry; use crate::utils::combine::Combine; use crate::virt_fs::{EmbeddedFS, VirtFS}; +use crate::{sym, vname}; #[derive(RustEmbed)] #[folder = "src/libs/std"] @@ -51,13 +50,6 @@ impl StdConfig { .combine(reflect_lib())? .combine(state_lib())? .combine(str_lib())?; - // panic!( - // "{:?}", - // pure_tree - // .unwrap_mod_ref() - // .walk1_ref(&[], &[i("std"), i("protocol")], |_| true) - // .map(|p| p.0) - // ); if !self.impure { return Ok(pure_tree); } @@ -71,15 +63,15 @@ impl IntoSystem<'static> for StdConfig { name: "stdlib", constants: self.stdlib().expect("stdlib tree is malformed"), code: ModEntry::ns("std", [ModEntry::leaf( - EmbeddedFS::new::(".orc", CodeGenInfo::no_details("std::fs")).rc(), + EmbeddedFS::new::(".orc", CodeGenInfo::no_details(sym!(std::fs))).rc(), )]), prelude: vec![Prelude { - target: VName::literal("std::prelude"), - exclude: VName::literal("std"), - owner: CodeGenInfo::no_details("std::prelude"), + target: vname!(std::prelude), + exclude: vname!(std), + owner: CodeGenInfo::no_details(sym!(std::prelude)), }], handlers: state_handlers(), - lexer_plugins: vec![Box::new(TStringLexer)], + lexer_plugins: vec![Box::new(StringLexer)], line_parsers: parsers(), } } diff --git a/src/libs/std/string.rs b/src/libs/std/string.rs index ec4b120..455f3fb 100644 --- a/src/libs/std/string.rs +++ b/src/libs/std/string.rs @@ -1,22 +1,34 @@ //! `std::string` String processing -use std::fmt::Debug; +use std::fmt; +use std::fmt::Write as _; use std::hash::Hash; use std::ops::Deref; use std::sync::Arc; use intern_all::{i, Tok}; +use itertools::Itertools; use unicode_segmentation::UnicodeSegmentation; +use super::protocol::{gen_resolv, Protocol}; use super::runtime_error::RuntimeError; -use crate::foreign::atom::Atomic; -use crate::foreign::error::ExternResult; +use crate::error::{ProjectErrorObj, ProjectResult}; +use crate::foreign::atom::{AtomGenerator, Atomic}; +use crate::foreign::error::RTResult; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::to_clause::ToClause; -use crate::foreign::try_from_expr::TryFromExpr; +use crate::foreign::try_from_expr::{TryFromExpr, WithLoc}; +use crate::gen::tpl; +use crate::gen::traits::Gen; use crate::gen::tree::{xfn_ent, ConstTree}; +use crate::interpreter::gen_nort::nort_gen; use crate::interpreter::nort::{Clause, Expr}; use crate::location::CodeLocation; +use crate::parse::context::ParseCtx; +use crate::parse::errors::ParseErrorKind; +use crate::parse::lex_plugin::{LexPluginRecur, LexPluginReq, LexerPlugin}; +use crate::parse::lexer::{Entry, LexRes, Lexeme}; +use crate::parse::parsed::PType; use crate::utils::iter_find::iter_find; /// An Orchid string which may or may not be interned @@ -28,8 +40,8 @@ pub enum OrcString { Runtime(Arc), } -impl Debug for OrcString { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for OrcString { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Runtime(s) => write!(f, "r\"{s}\""), Self::Interned(t) => write!(f, "i\"{t}\""), @@ -49,7 +61,7 @@ impl OrcString { pub fn get_string(self) -> String { match self { Self::Interned(s) => s.as_str().to_owned(), - Self::Runtime(rc) => Arc::try_unwrap(rc).unwrap_or_else(|rc| (*rc).clone()), + Self::Runtime(rc) => Arc::unwrap_or_clone(rc), } } } @@ -73,6 +85,10 @@ impl From for OrcString { fn from(value: String) -> Self { Self::Runtime(Arc::new(value)) } } +impl From<&str> for OrcString { + fn from(value: &str) -> Self { Self::from(value.to_string()) } +} + impl From> for OrcString { fn from(value: Tok) -> Self { Self::Interned(value) } } @@ -96,13 +112,15 @@ impl ToClause for String { } impl TryFromExpr for String { - fn from_expr(exi: Expr) -> ExternResult { + fn from_expr(exi: Expr) -> RTResult { Ok(exi.downcast::>()?.0.get_string()) } } pub(super) fn str_lib() -> ConstTree { ConstTree::ns("std::string", [ConstTree::tree([ + // String conversion protocol implementable by external types + ("conversion", Protocol::tree([], [])), xfn_ent("slice", [|s: Inert, i: Inert, len: Inert| { let graphs = s.0.as_str().graphemes(true); if i.0 == 0 { @@ -145,5 +163,239 @@ pub(super) fn str_lib() -> ConstTree { x => x, }) }]), + xfn_ent("convert", [|WithLoc(loc, a): WithLoc| match a.clone().downcast() { + Ok(str) => Inert::::atom_expr(str, loc), + Err(_) => match a.clause.request::() { + Some(str) => Inert(str).atom_expr(loc), + None => tpl::a2(gen_resolv("std::string::conversion"), tpl::Slot, tpl::Slot) + .template(nort_gen(loc), [a.clone(), a]), + }, + }]), ])]) } + +/// Reasons why [parse_string] might fail. See [StringError] +enum StringErrorKind { + /// A unicode escape sequence wasn't followed by 4 hex digits + NotHex, + /// A unicode escape sequence contained an unassigned code point + BadCodePoint, + /// An unrecognized escape sequence was found + BadEscSeq, +} + +/// Error produced by [parse_string] +struct StringError { + /// Character where the error occured + pos: usize, + /// Reason for the error + kind: StringErrorKind, +} + +impl StringError { + /// Convert into project error for reporting + pub fn into_proj(self, ctx: &dyn ParseCtx, pos: usize) -> ProjectErrorObj { + let start = pos + self.pos; + let location = ctx.range_loc(&(start..start + 1)); + match self.kind { + StringErrorKind::NotHex => NotHex.pack(location), + StringErrorKind::BadCodePoint => BadCodePoint.pack(location), + StringErrorKind::BadEscSeq => BadEscapeSequence.pack(location), + } + } +} + +/// Process escape sequences in a string literal +fn parse_string(str: &str) -> Result { + let mut target = String::new(); + let mut iter = str.char_indices(); + while let Some((_, c)) = iter.next() { + if c != '\\' { + target.push(c); + continue; + } + let (mut pos, code) = iter.next().expect("lexer would have continued"); + let next = match code { + c @ ('\\' | '/' | '"') => c, + 'b' => '\x08', + 'f' => '\x0f', + 'n' => '\n', + 'r' => '\r', + 't' => '\t', + '\n' => 'skipws: loop { + match iter.next() { + None => return Ok(target), + Some((_, c)) => + if !c.is_whitespace() { + break 'skipws c; + }, + } + }, + 'u' => { + let acc = ((0..4).rev()) + .map(|radical| { + let (j, c) = (iter.next()).ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; + pos = j; + let b = u32::from_str_radix(&String::from(c), 16) + .map_err(|_| StringError { pos, kind: StringErrorKind::NotHex })?; + Ok(16u32.pow(radical) + b) + }) + .fold_ok(0, u32::wrapping_add)?; + char::from_u32(acc).ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? + }, + _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }), + }; + target.push(next); + } + Ok(target) +} + +/// [LexerPlugin] for a string literal that supports interpolateion. +#[derive(Clone)] +pub struct StringLexer; +impl LexerPlugin for StringLexer { + fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>> { + req.tail().strip_prefix('\"').map(|mut txt| { + let ctx = req.ctx(); + let mut parts = vec![Entry::new(ctx.range(0, txt), Lexeme::LP(PType::Par))]; + let mut str = String::new(); + let commit_str = |str: &mut String, tail: &str, parts: &mut Vec| -> ProjectResult<_> { + let str_val = parse_string(str).unwrap_or_else(|e| { + ctx.reporter().report(e.into_proj(ctx, ctx.pos(txt))); + String::new() + }); + let ag = AtomGenerator::cloner(Inert(OrcString::from(i(&str_val)))); + parts.push(Entry::new(ctx.range(str.len(), tail), Lexeme::Atom(ag))); + *str = String::new(); + Ok(()) + }; + loop { + if let Some(rest) = txt.strip_prefix('"') { + commit_str(&mut str, txt, &mut parts)?; + parts.push(Entry::new(ctx.range(0, rest), Lexeme::RP(PType::Par))); + if parts.len() == 3 { + return Ok(LexRes { tail: rest, tokens: vec![parts[1].clone()] }); + } + return Ok(LexRes { tail: rest, tokens: parts }); + } + if let Some(rest) = txt.strip_prefix("${") { + let mut depth = 0; + commit_str(&mut str, rest, &mut parts)?; + parts.extend(req.insert("++ std::string::convert (", ctx.source_range(0, rest))); + let res = req.recurse(LexPluginRecur { + tail: rest, + exit: &mut |c| { + match c.chars().next() { + None => return Err(UnclosedInterpolation.pack(ctx.source_range(2, rest))), + Some('{') => depth += 1, + Some('}') if depth == 0 => return Ok(true), + Some('}') => depth -= 1, + _ => (), + } + Ok(false) + }, + })?; + txt = &res.tail[1..]; // account for final } + parts.extend(res.tokens); + parts.extend(req.insert(") ++", ctx.source_range(0, txt))); + } else { + let mut chars = txt.chars(); + match chars.next() { + None => return Err(NoStringEnd.pack(ctx.source_range(req.tail().len(), ""))), + Some('\\') => match chars.next() { + None => write!(str, "\\").expect("writing \\ into string"), + Some(next) => write!(str, "\\{next}").expect("writing \\ and char into string"), + }, + Some(c) => write!(str, "{c}").expect("writing char into string"), + } + txt = chars.as_str(); + } + } + }) + } +} + +/// An interpolated string section started with ${ wasn't closed with a balanced +/// } +pub struct UnclosedInterpolation; +impl ParseErrorKind for UnclosedInterpolation { + const DESCRIPTION: &'static str = "A ${ block within a $-string wasn't closed"; +} + +/// String literal never ends +pub(super) struct NoStringEnd; +impl ParseErrorKind for NoStringEnd { + const DESCRIPTION: &'static str = "A string literal was not closed with `\"`"; +} + +/// A unicode escape sequence contains something other than a hex digit +pub(super) struct NotHex; +impl ParseErrorKind for NotHex { + const DESCRIPTION: &'static str = "Expected a hex digit"; +} + +/// A unicode escape sequence contains a number that isn't a unicode code point. +pub(super) struct BadCodePoint; +impl ParseErrorKind for BadCodePoint { + const DESCRIPTION: &'static str = "\\uXXXX escape sequence does not describe valid code point"; +} + +/// An unrecognized escape sequence occurred in a string. +pub(super) struct BadEscapeSequence; +impl ParseErrorKind for BadEscapeSequence { + const DESCRIPTION: &'static str = "Unrecognized escape sequence"; +} +#[cfg(test)] +mod test { + use intern_all::i; + + use super::StringLexer; + use crate::foreign::atom::Atomic; + use crate::foreign::inert::Inert; + use crate::libs::std::string::OrcString; + use crate::parse::context::MockContext; + use crate::parse::lex_plugin::{LexPlugReqImpl, LexerPlugin}; + use crate::parse::lexer::Lexeme; + use crate::parse::parsed::PType; + + #[test] + fn plain_string() { + let source = r#""Hello world!" - says the programmer"#; + let ctx = MockContext::new(); + let req = LexPlugReqImpl { ctx: &ctx, tail: source }; + let res = (StringLexer.lex(&req)) + .expect("the snippet starts with a quote") + .expect("it contains a valid string"); + let expected = [Inert(OrcString::from("Hello world!")).lexeme()]; + assert_eq!(res.tokens, expected); + assert_eq!(res.tail, " - says the programmer"); + assert!(!ctx.0.failing(), "No errors were generated") + } + + #[test] + #[rustfmt::skip] + fn template_string() { + let source = r#""I <${1 + 2} parsers" - this dev"#; + let ctx = MockContext::new(); + let req = LexPlugReqImpl { ctx: &ctx, tail: source }; + let res = (StringLexer.lex(&req)) + .expect("the snippet starts with a quote") + .expect("it contains a valid string"); + use Lexeme::{Name, LP, NS, RP}; + let expected = [ + LP(PType::Par), + Inert(OrcString::from("I <")).lexeme(), + Name(i!(str: "++")), + // std::string::convert + Name(i!(str: "std")), NS, Name(i!(str: "string")), NS, Name(i!(str: "convert")), + // (1 + 1) + LP(PType::Par), Inert(1).lexeme(), Name(i!(str: "+")), Inert(2).lexeme(), RP(PType::Par), + Name(i!(str: "++")), + Inert(OrcString::from(" parsers")).lexeme(), + RP(PType::Par), + ]; + assert_eq!(res.tokens, expected); + assert_eq!(res.tail, " - this dev"); + assert!(!ctx.0.failing(), "No errors were generated"); + } +} diff --git a/src/libs/std/tstring.rs b/src/libs/std/tstring.rs deleted file mode 100644 index 758a338..0000000 --- a/src/libs/std/tstring.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::fmt::Write; - -use intern_all::i; - -use crate::error::ProjectResult; -use crate::foreign::atom::AtomGenerator; -use crate::foreign::inert::Inert; -use crate::libs::std::string::OrcString; -use crate::parse::errors::ParseErrorKind; -use crate::parse::lex_plugin::{LexPluginRecur, LexPluginReq, LexerPlugin}; -use crate::parse::lexer::{Entry, LexRes, Lexeme}; -use crate::parse::parsed::PType; -use crate::parse::string::parse_string; - -pub struct TStringLexer; -impl LexerPlugin for TStringLexer { - fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>> { - req.tail().strip_prefix("$\"").map(|mut txt| { - let ctx = req.ctx(); - let mut parts = vec![Entry::new(ctx.range(0, txt), Lexeme::LP(PType::Par))]; - let mut str = String::new(); - let commit_str = |str: &mut String, tail: &str, parts: &mut Vec| -> ProjectResult<_> { - let str_val = parse_string(str).map_err(|e| e.to_proj(ctx, ctx.pos(txt)))?; - let ag = AtomGenerator::cloner(Inert(OrcString::from(i(&str_val)))); - parts.push(Entry::new(ctx.range(str.len(), tail), Lexeme::Atom(ag))); - *str = String::new(); - Ok(()) - }; - loop { - if let Some(rest) = txt.strip_prefix('"') { - commit_str(&mut str, txt, &mut parts)?; - parts.push(Entry::new(ctx.range(0, rest), Lexeme::RP(PType::Par))); - return Ok(LexRes { tail: rest, tokens: parts }); - } - if let Some(rest) = txt.strip_prefix("${") { - let mut depth = 0; - commit_str(&mut str, rest, &mut parts)?; - parts.extend(req.insert("++ std::conv::to_string (", ctx.source_range(0, rest))); - let res = req.recurse(LexPluginRecur { - tail: rest, - exit: &mut |c| { - match c.chars().next() { - None => return Err(UnclosedInterpolation.pack(ctx.source_range(2, rest))), - Some('{') => depth += 1, - Some('}') if depth == 0 => return Ok(true), - Some('}') => depth -= 1, - _ => (), - } - Ok(false) - }, - })?; - txt = &res.tail[1..]; // account for final } - parts.extend(res.tokens); - parts.extend(req.insert(") ++", ctx.source_range(0, txt))); - } else { - let mut chars = txt.chars(); - match chars.next() { - None => return Err(NoTStringEnd.pack(ctx.source_range(req.tail().len(), ""))), - Some('\\') => match chars.next() { - None => write!(str, "\\").expect("writing \\ into string"), - Some(next) => write!(str, "\\{next}").expect("writing \\ and char into string"), - }, - Some(c) => write!(str, "{c}").expect("writing char into string"), - } - txt = chars.as_str(); - } - } - }) - } -} - -pub struct UnclosedInterpolation; -impl ParseErrorKind for UnclosedInterpolation { - const DESCRIPTION: &'static str = "A ${ block within a $-string wasn't closed"; -} - -/// String literal never ends -pub(super) struct NoTStringEnd; -impl ParseErrorKind for NoTStringEnd { - const DESCRIPTION: &'static str = "A $-string literal was not closed with `\"`"; -} diff --git a/src/libs/std/tuple.orc b/src/libs/std/tuple.orc index 830109e..a2080a9 100644 --- a/src/libs/std/tuple.orc +++ b/src/libs/std/tuple.orc @@ -1,6 +1,6 @@ -import super::(known::*, bool::*, number::*, string::*, functional::*) +import super::(known::*, bool::*, number::*, string::*, fn::*) import super::loop::recursive -import super::(to_string, pmatch, macro, panic, conv, list, option) +import super::(pmatch, macro, panic, conv, list, option) -- referenced in the impl table in Rust const to_string_impl := \t. "tuple[" ++ ( diff --git a/src/libs/std/tuple.rs b/src/libs/std/tuple.rs index defec8b..54fc024 100644 --- a/src/libs/std/tuple.rs +++ b/src/libs/std/tuple.rs @@ -1,26 +1,29 @@ //! `std::tuple` A vector-based sequence for storing short sequences. -use std::fmt::Debug; +use std::fmt; use std::sync::Arc; use once_cell::sync::Lazy; -use super::conv::TO_STRING; use super::protocol::Tag; use super::reflect::refer; -use crate::foreign::error::{AssertionError, ExternResult}; +use crate::foreign::error::{AssertionError, RTResult}; use crate::foreign::fn_bridge::Thunk; use crate::foreign::inert::{Inert, InertPayload}; use crate::foreign::try_from_expr::WithLoc; use crate::gen::tree::{atom_ent, xfn_ent, ConstTree}; use crate::interpreter::nort::Expr; use crate::location::{CodeGenInfo, CodeLocation}; +use crate::sym; use crate::utils::ddispatch::Request; use crate::utils::pure_seq::pushed; static TUPLE_TAG: Lazy = Lazy::new(|| { - let location = CodeLocation::Gen(CodeGenInfo::no_details("stdlib::tuple::tag")); - Tag::new("tuple", [(TO_STRING.id(), refer("std::tuple::to_string_impl").to_expr(location))]) + let location = CodeLocation::new_gen(CodeGenInfo::no_details(sym!(std::tuple))); + Tag::new(sym!(std::tuple), [( + sym!(std::string::conversion), + refer("std::tuple::to_string_impl").into_expr(location), + )]) }); /// A short contiquous random access sequence of Orchid values. @@ -30,8 +33,8 @@ impl InertPayload for Tuple { const TYPE_STR: &'static str = "tuple"; fn respond(&self, mut request: Request) { request.serve_with(|| TUPLE_TAG.clone()) } } -impl Debug for Tuple { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Tuple { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Tuple")?; f.debug_list().entries(self.0.iter().map(|e| &e.clause)).finish() } @@ -39,23 +42,23 @@ impl Debug for Tuple { fn length(tuple: Inert) -> Inert { Inert(tuple.0.0.len()) } -fn pick(WithLoc(loc, tuple): WithLoc>, idx: Inert) -> ExternResult { +fn pick(WithLoc(loc, tuple): WithLoc>, idx: Inert) -> RTResult { (tuple.0.0.get(idx.0).cloned()).ok_or_else(|| { let msg = format!("{} <= {idx}", tuple.0.0.len()); AssertionError::ext(loc, "Tuple index out of bounds", msg) - }) + }) } fn push(Inert(tuple): Inert, item: Thunk) -> Inert { - let items = Arc::try_unwrap(tuple.0).unwrap_or_else(|a| (*a).clone()); + let items = Arc::unwrap_or_clone(tuple.0); Inert(Tuple(Arc::new(pushed(items, item.0)))) } pub(super) fn tuple_lib() -> ConstTree { - ConstTree::ns("std", [ConstTree::tree([TUPLE_TAG.as_tree_ent([ + ConstTree::ns("std::tuple", [TUPLE_TAG.to_tree([ atom_ent("empty", [Inert(Tuple(Arc::new(Vec::new())))]), xfn_ent("length", [length]), xfn_ent("pick", [pick]), xfn_ent("push", [push]), - ])])]) + ])]) } diff --git a/src/location.rs b/src/location.rs index b7a44bf..dea74cf 100644 --- a/src/location.rs +++ b/src/location.rs @@ -1,19 +1,28 @@ -use std::fmt::{Debug, Display}; +//! Structures that show where code or semantic elements came from + +use std::fmt; use std::hash::Hash; use std::ops::Range; use std::sync::Arc; use itertools::Itertools; -use crate::name::VPath; +use crate::name::{NameLike, Sym}; +use crate::sym; /// A full source code unit, such as a source file #[derive(Clone, Eq)] pub struct SourceCode { + pub(crate) path: Sym, + pub(crate) text: Arc, +} +impl SourceCode { + /// Create a new source file description + pub fn new(path: Sym, source: Arc) -> Self { Self { path, text: source } } /// Location the source code was loaded from in the virtual tree - pub path: Arc, + pub fn path(&self) -> Sym { self.path.clone() } /// Raw source code string - pub source: Arc, + pub fn text(&self) -> Arc { self.text.clone() } } impl PartialEq for SourceCode { fn eq(&self, other: &Self) -> bool { self.path == other.path } @@ -21,45 +30,55 @@ impl PartialEq for SourceCode { impl Hash for SourceCode { fn hash(&self, state: &mut H) { self.path.hash(state) } } -impl Debug for SourceCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "CodeInfo({self})") - } +impl fmt::Debug for SourceCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeInfo({self})") } } -impl Display for SourceCode { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SourceCode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}.orc", self.path.str_iter().join("/")) } } +impl AsRef for SourceCode { + fn as_ref(&self) -> &str { &self.text } +} /// Exact source code location. Includes where the code was loaded from, what /// the original source code was, and a byte range. #[derive(Clone, PartialEq, Eq, Hash)] pub struct SourceRange { - /// Source code - pub code: SourceCode, - /// Byte range - pub range: Range, + pub(crate) code: SourceCode, + pub(crate) range: Range, } impl SourceRange { + /// 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 } + } + /// Source code + pub fn code(&self) -> SourceCode { self.code.clone() } + /// Source text + pub fn text(&self) -> Arc { self.code.text.clone() } + /// Path the source text was loaded from + pub fn path(&self) -> Sym { self.code.path.clone() } + /// Byte range + pub fn range(&self) -> Range { self.range.clone() } + /// 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.clone(), range: map(self.range.clone()) } + pub fn map_range(&self, map: impl FnOnce(Range) -> Range) -> Self { + Self { code: self.code(), range: map(self.range()) } } } -impl Debug for SourceRange { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "CodeRange({} {:?})", self.code, self.range) - } +impl fmt::Debug for SourceRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeRange({self})") } } -impl Display for SourceRange { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SourceRange { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { code, range } = self; - let (sl, sc) = pos2lc(code.source.as_str(), range.start); - let (el, ec) = pos2lc(code.source.as_str(), range.end); + let (sl, sc) = pos2lc(code.text.as_str(), range.start); + let (el, ec) = pos2lc(code.text.as_str(), range.end); write!(f, "{code} {sl}:{sc}")?; if el == sl { if sc + 1 == ec { Ok(()) } else { write!(f, "..{ec}") } @@ -73,49 +92,40 @@ impl Display for SourceRange { #[derive(Clone, PartialEq, Eq, Hash)] pub struct CodeGenInfo { /// formatted like a Rust namespace - pub generator: &'static str, + pub generator: Sym, /// Unformatted user message with relevant circumstances and parameters pub details: Arc, } impl CodeGenInfo { /// A codegen marker with no user message and parameters - pub fn no_details(generator: &'static str) -> Self { - Self { generator, details: Arc::new(String::new()) } - } + pub fn no_details(generator: Sym) -> Self { Self { generator, details: Arc::new(String::new()) } } /// A codegen marker with a user message or parameters - pub fn details(generator: &'static str, details: impl AsRef) -> Self { + pub fn details(generator: Sym, details: impl AsRef) -> Self { Self { generator, details: Arc::new(details.as_ref().to_string()) } } } -impl Debug for CodeGenInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "CodeGenInfo({self})") - } +impl fmt::Debug for CodeGenInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeGenInfo({self})") } } -impl Display for CodeGenInfo { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for CodeGenInfo { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "generated by {}", self.generator)?; - if !self.details.is_empty() { - write!(f, ", details: {}", self.details) - } else { - write!(f, ".") - } + if !self.details.is_empty() { write!(f, ", details: {}", self.details) } else { write!(f, ".") } } } -/// A location for error reporting. In the context of an error, identifies a -/// sequence of suspect characters or the reason the code was generated for -/// generated code. Meaningful within the context of a project. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum CodeLocation { +/// identifies a sequence of characters that contributed to the enclosing +/// construct or the reason the code was generated for generated code. +#[derive(Clone, PartialEq, Eq, Hash)] +pub enum CodeOrigin { /// Character sequence Source(SourceRange), /// Generated construct Gen(CodeGenInfo), } -impl Display for CodeLocation { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for CodeOrigin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Gen(info) => write!(f, "{info}"), Self::Source(cr) => write!(f, "{cr}"), @@ -123,9 +133,53 @@ impl Display for CodeLocation { } } +impl fmt::Debug for CodeOrigin { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeOrigin({self})") } +} + +/// Location data associated with any code fragment. Identifies where the code +/// came from and where it resides in the tree. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct CodeLocation { + pub(crate) origin: CodeOrigin, + pub(crate) module: Sym, +} +impl CodeLocation { + pub(crate) fn new_src(range: SourceRange, module: Sym) -> Self { + Self { origin: CodeOrigin::Source(range), module } + } + /// Create a location for generated code. The generator string must not be + /// empty. For code, the generator string must contain at least one `::` + pub fn new_gen(gen: CodeGenInfo) -> Self { + Self { module: gen.generator.clone(), origin: CodeOrigin::Gen(gen) } + } + + /// Get the syntactic location + pub fn origin(&self) -> CodeOrigin { self.origin.clone() } + /// Get the name of the containing module + pub fn module(&self) -> Sym { self.module.clone() } +} + +impl fmt::Display for CodeLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.module[..].is_empty() { + write!(f, "global {}", self.origin) + } else { + write!(f, "{} in {}", self.origin, self.module) + } + } +} + +impl fmt::Debug for CodeLocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "CodeLocation({self})") } +} + #[must_use] fn pos2lc(s: &str, i: usize) -> (usize, usize) { - s.chars().take(i).fold((1, 1), |(line, col), char| { - if char == '\n' { (line + 1, 1) } else { (line, col + 1) } - }) + s.chars().take(i).fold( + (1, 1), + |(line, col), char| { + if char == '\n' { (line + 1, 1) } else { (line, col + 1) } + }, + ) } diff --git a/src/name.rs b/src/name.rs index 0dbf043..7f13ee2 100644 --- a/src/name.rs +++ b/src/name.rs @@ -1,45 +1,123 @@ +//! Various datatypes that all represent namespaced names. + use std::borrow::Borrow; -use std::fmt::{Debug, Display}; use std::hash::Hash; +use std::iter::Cloned; use std::num::NonZeroUsize; -use std::ops::Index; -use std::vec; +use std::ops::{Deref, Index}; +use std::path::Path; +use std::{fmt, slice, vec}; use intern_all::{i, Tok}; use itertools::Itertools; +use trait_set::trait_set; -use crate::utils::boxed_iter::BoxedIter; +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. -pub struct PathSlice<'a>(pub &'a [Tok]); -impl<'a> PathSlice<'a> { +#[derive(Hash, PartialEq, Eq)] +#[repr(transparent)] +pub struct PathSlice([Tok]); +impl PathSlice { + /// Create a new [PathSlice] + pub fn new(slice: &[Tok]) -> &PathSlice { + // SAFETY: This is ok because PathSlice is #[repr(transparent)] + unsafe { &*(slice as *const [Tok] 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())) } -} -impl<'a> Debug for PathSlice<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "VName({self})") + /// 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) -> &[Tok] { self } + /// Global empty path slice + pub fn empty() -> &'static Self { PathSlice::new(&[]) } } -impl<'a> Display for PathSlice<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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<'a> Borrow<[Tok]> for PathSlice<'a> { - fn borrow(&self) -> &[Tok] { self.0 } +impl Borrow<[Tok]> for PathSlice { + fn borrow(&self) -> &[Tok] { &self.0 } +} +impl<'a> IntoIterator for &'a PathSlice { + type IntoIter = Cloned>>; + type Item = Tok; + fn into_iter(self) -> Self::IntoIter { self.0.iter().cloned() } } -impl<'a, T> Index for PathSlice<'a> -where [Tok]: Index -{ - type Output = <[Tok] as Index>::Output; - fn index(&self, index: T) -> &Self::Output { &self.0[index] } +mod idx_impls { + use std::ops; + + use intern_all::Tok; + + use super::PathSlice; + + impl ops::Index for PathSlice { + type Output = Tok; + 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 = [Tok]; + + fn deref(&self) -> &Self::Target { &self.0 } +} +impl Borrow for [Tok] { + fn borrow(&self) -> &PathSlice { PathSlice::new(self) } +} +impl Borrow for [Tok; 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, @@ -51,6 +129,11 @@ impl VPath { 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()) @@ -71,22 +154,30 @@ impl VPath { 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 as_prefix_of(self, name: Tok) -> VName { + pub fn name_with_prefix(self, name: Tok) -> 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 as_suffix_of(self, name: Tok) -> VName { + pub fn name_with_suffix(self, name: Tok) -> VName { VName([name].into_iter().chain(self).collect()) } -} -impl Debug for VPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "VName({self})") + + /// 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(i)).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 Display for VPath { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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("::")) } } @@ -103,13 +194,20 @@ impl IntoIterator for VPath { impl Borrow<[Tok]> for VPath { fn borrow(&self) -> &[Tok] { 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 Vec>: Index +where PathSlice: Index { - type Output = > as Index>::Output; + type Output = >::Output; - fn index(&self, index: T) -> &Self::Output { &self.0[index] } + fn index(&self, index: T) -> &Self::Output { &Borrow::::borrow(self)[index] } } /// A mutable representation of a namespaced identifier of at least one segment. @@ -123,9 +221,7 @@ 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 { + pub fn new(items: impl IntoIterator>) -> Result { let data: Vec<_> = items.into_iter().collect(); if data.is_empty() { Err(EmptyNameError) } else { Ok(Self(data)) } } @@ -138,20 +234,8 @@ impl VName { 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(i(&self.0)) } - /// like Slice's split_first, but non-optional and tokens are cheap to clone - pub fn split_first(&self) -> (Tok, &[Tok]) { - let (h, t) = self.0.split_first().expect("VName can never be empty"); - (h.clone(), t) - } - /// like Slice's split_last, but non-optional and tokens are cheap to clone - pub fn split_last(&self) -> (Tok, &[Tok]) { - let (f, b) = self.0.split_last().expect("VName can never be empty"); - (f.clone(), b) - } /// If this name has only one segment, return it - pub fn as_root(&self) -> Option> { - self.0.iter().exactly_one().ok().cloned() - } + 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 { @@ -163,33 +247,15 @@ impl VName { Self(self.0.into_iter().chain(items).collect()) } /// Read a `::` separated namespaced name - pub fn parse(s: &str) -> Result { - Self::new(VPath::parse(s)) - } - /// Read a name from a string literal which can be known not to be empty - pub fn literal(s: &'static str) -> Self { - Self::parse(s).expect("name literal should not be empty") - } - /// Find the longest shared prefix of this name and another sequence - pub fn coprefix(&self, other: &[Tok]) -> &[Tok] { - &self.0 - [0..self.0.iter().zip(other.iter()).take_while(|(l, r)| l == r).count()] - } + 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() - } - - /// Convert to [PathSlice] - pub fn as_path_slice(&self) -> PathSlice { PathSlice(&self[..]) } + pub fn iter(&self) -> impl Iterator> + '_ { self.0.iter().cloned() } } -impl Debug for VName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "VName({self})") - } +impl fmt::Debug for VName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "VName({self})") } } -impl Display for VName { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for VName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.str_iter().join("::")) } } @@ -199,15 +265,22 @@ impl IntoIterator for VName { fn into_iter(self) -> Self::IntoIter { self.0.into_iter() } } impl Index for VName -where Vec>: Index +where PathSlice: Index { - type Output = > as Index>::Output; + type Output = >::Output; - fn index(&self, index: T) -> &Self::Output { &self.0[index] } + fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } } impl Borrow<[Tok]> for VName { fn borrow(&self) -> &[Tok] { 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 @@ -230,23 +303,12 @@ pub struct Sym(Tok>>); 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 { + pub fn new(v: impl IntoIterator>) -> Result { let items = v.into_iter().collect::>(); Self::from_tok(i(&items)) } - /// Read a `::` separated namespaced name. - pub fn parse(s: &str) -> Result { - Ok(Sym(i(&VName::parse(s)?.into_vec()))) - } - - /// Parse a string and panic if it's not empty - pub fn literal(s: &'static str) -> Self { - Self::parse(s).expect("name literal should not be empty") - } - + pub fn parse(s: &str) -> Result { Ok(Sym(i(&VName::parse(s)?.into_vec()))) } /// Assert that a token isn't empty, and wrap it in a [Sym] pub fn from_tok(t: Tok>>) -> Result { if t.is_empty() { Err(EmptyNameError) } else { Ok(Self(t)) } @@ -255,80 +317,160 @@ impl Sym { pub fn tok(&self) -> Tok>> { self.0.clone() } /// Get a number unique to this name suitable for arbitrary ordering. pub fn id(&self) -> NonZeroUsize { self.0.id() } - /// Get an iterator over the tokens in this name - pub fn iter(&self) -> impl Iterator> + '_ { - self.0.iter().cloned() - } - - /// Like Slice's split_last, except this slice is never empty - pub fn split_last(&self) -> (Tok, PathSlice) { - let (foot, torso) = self.0.split_last().expect("Sym never empty"); - (foot.clone(), PathSlice(torso)) - } - - /// Like Slice's split_first, except this slice is never empty - pub fn split_first(&self) -> (Tok, PathSlice) { - let (head, tail) = self.0.split_first().expect("Sym never empty"); - (head.clone(), PathSlice(tail)) - } - /// Extern the sym for editing pub fn to_vname(&self) -> VName { VName(self[..].to_vec()) } - - /// Convert to [PathSlice] - pub fn as_path_slice(&self) -> PathSlice { PathSlice(&self[..]) } } -impl Debug for Sym { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Sym({self})") - } +impl fmt::Debug for Sym { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "Sym({self})") } } -impl Display for Sym { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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 Vec>: Index +where PathSlice: Index { - type Output = > as Index>::Output; + type Output = >::Output; - fn index(&self, index: T) -> &Self::Output { &(&*self.0)[index] } + fn index(&self, index: T) -> &Self::Output { &self.deref()[index] } } impl Borrow<[Tok]> for Sym { - fn borrow(&self) -> &[Tok] { self.0.borrow() } + fn borrow(&self) -> &[Tok] { &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 + Debug + Display { +pub trait NameLike: + 'static + Clone + Eq + Hash + fmt::Debug + fmt::Display + Borrow +{ + /// Convert into held slice + fn as_slice(&self) -> &[Tok] { 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.str_iter().map(str::to_owned).collect() - } + 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.str_iter().join("/")) - } + 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.str_iter().count()) - .expect("NameLike never empty") + NonZeroUsize::try_from(self.iter().count()).expect("NameLike never empty") } - /// Fully resolve the name for printing - fn str_iter(&self) -> BoxedIter<'_, &str>; + /// Like slice's `split_first` except we know that it always returns Some + fn split_first(&self) -> (Tok, &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) -> (Tok, &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) -> Tok { self.split_first().0 } + /// Get the last element + fn last(&self) -> Tok { self.split_last().0 } } -impl NameLike for Sym { - fn str_iter(&self) -> BoxedIter<'_, &str> { - Box::new(self.0.iter().map(|s| s.as_str())) +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(intern_all::i!([intern_all::Tok]: &[ + intern_all::i!(str: stringify!($seg1)) + $( , intern_all::i!(str: stringify!($seg)) )* + ][..])).unwrap() + }; +} + +/// 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([ + intern_all::i!(str: stringify!($seg1)) + $( , intern_all::i!(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![ + intern_all::i!(str: stringify!($seg1)) + $( , intern_all::i!(str: stringify!($seg)) )+ + ]) + }; + () => { + $crate::name::VPath(vec![]) } } -impl NameLike for VName { - fn str_iter(&self) -> BoxedIter<'_, &str> { - Box::new(self.0.iter().map(|s| s.as_str())) +/// 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(&[ + intern_all::i!(str: stringify!($seg1)) + $( , intern_all::i!(str: stringify!($seg)) )+ + ]) + }; + () => { + $crate::name::PathSlice::new(&[]) + } +} + +#[cfg(test)] +mod test { + use std::borrow::Borrow; + + use intern_all::{i, Tok}; + + use super::{PathSlice, Sym, VName}; + use crate::name::VPath; + + #[test] + fn recur() { + let myname = vname!(foo::bar); + let _borrowed_slice: &[Tok] = myname.borrow(); + let _borrowed_pathslice: &PathSlice = myname.borrow(); + let _deref_pathslice: &PathSlice = &myname; + let _as_slice_out: &[Tok] = myname.as_slice(); + } + + #[test] + fn literals() { + assert_eq!(sym!(foo::bar::baz), Sym::new([i("foo"), i("bar"), i("baz")]).unwrap()); + assert_eq!(vname!(foo::bar::baz), VName::new([i("foo"), i("bar"), i("baz")]).unwrap()); + assert_eq!(vpath!(foo::bar::baz), VPath::new([i("foo"), i("bar"), i("baz")])); + assert_eq!(path_slice!(foo::bar::baz), PathSlice::new(&[i("foo"), i("bar"), i("baz")])); } } diff --git a/src/parse/context.rs b/src/parse/context.rs index b69db03..b842132 100644 --- a/src/parse/context.rs +++ b/src/parse/context.rs @@ -5,14 +5,14 @@ use std::sync::Arc; use super::lex_plugin::LexerPlugin; use super::parse_plugin::ParseLinePlugin; +use crate::error::Reporter; use crate::location::{SourceCode, SourceRange}; -use crate::name::VPath; use crate::utils::boxed_iter::{box_empty, BoxedIter}; use crate::utils::sequence::Sequence; /// Trait enclosing all context features /// -/// The main implementation is [ParsingContext] +/// The main implementation is [ParseCtxImpl] pub trait ParseCtx { /// Get an object describing the file this source code comes from #[must_use] @@ -23,11 +23,20 @@ pub trait ParseCtx { /// Get the list of all parser plugins #[must_use] fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin>; + /// Error reporter + #[must_use] + fn reporter(&self) -> &Reporter; /// Find our position in the text given the text we've yet to parse #[must_use] - fn pos(&self, tail: &str) -> usize { self.source().len() - tail.len() } + fn pos(&self, tail: &str) -> usize { + let tail_len = tail.len(); + let source_len = self.source().len(); + (self.source().len().checked_sub(tail.len())).unwrap_or_else(|| { + panic!("tail.len()={tail_len} greater than self.source().len()={source_len}; tail={tail:?}") + }) + } /// Generate a location given the length of a token and the unparsed text - /// after it. See also [Context::range_loc] if the maths gets complex. + /// after it. See also [ParseCtx::range_loc] if the maths gets complex. #[must_use] fn range(&self, len: usize, tl: &str) -> Range { match self.pos(tl).checked_sub(len) { @@ -50,14 +59,13 @@ pub trait ParseCtx { /// Get a reference to the full source text. This should not be used for /// position math. #[must_use] - fn source(&self) -> Arc { self.code_info().source.clone() } + fn source(&self) -> Arc { self.code_info().text.clone() } } impl<'a, C: ParseCtx + 'a + ?Sized> ParseCtx for &'a C { + fn reporter(&self) -> &Reporter { (*self).reporter() } fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { (*self).lexers() } - fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { - (*self).line_parsers() - } + fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { (*self).line_parsers() } fn pos(&self, tail: &str) -> usize { (*self).pos(tail) } fn code_info(&self) -> SourceCode { (*self).code_info() } fn source(&self) -> Arc { (*self).source() } @@ -66,20 +74,21 @@ impl<'a, C: ParseCtx + 'a + ?Sized> ParseCtx for &'a C { /// Struct implementing context #[derive(Clone)] -pub struct ParseCtxImpl<'a> { +pub struct ParseCtxImpl<'a, 'b> { /// File to be parsed; where it belongs in the tree and its text pub code: SourceCode, + /// Error aggregator + pub reporter: &'b Reporter, /// Lexer plugins for parsing custom literals pub lexers: Sequence<'a, &'a (dyn LexerPlugin + 'a)>, /// Parser plugins for parsing custom line structures pub line_parsers: Sequence<'a, &'a dyn ParseLinePlugin>, } -impl<'a> ParseCtx for ParseCtxImpl<'a> { +impl<'a, 'b> ParseCtx for ParseCtxImpl<'a, 'b> { + fn reporter(&self) -> &Reporter { self.reporter } // Rust doesn't realize that this lifetime is covariant #[allow(clippy::map_identity)] - fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { - Box::new(self.lexers.iter().map(|r| r)) - } + fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { Box::new(self.lexers.iter().map(|r| r)) } #[allow(clippy::map_identity)] fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { Box::new(self.line_parsers.iter().map(|r| r)) @@ -87,24 +96,31 @@ impl<'a> ParseCtx for ParseCtxImpl<'a> { fn code_info(&self) -> SourceCode { self.code.clone() } } -/// Context instance for testing -pub struct MockContext; +/// Context instance for testing. Implicitly provides a reporter and panics if +/// any errors are reported +pub struct MockContext(pub Reporter); +impl MockContext { + /// Create a new mock + pub fn new() -> Self { Self(Reporter::new()) } +} +impl Default for MockContext { + fn default() -> Self { Self::new() } +} impl ParseCtx for MockContext { + fn reporter(&self) -> &Reporter { &self.0 } fn pos(&self, tail: &str) -> usize { usize::MAX / 2 - tail.len() } // these are expendable - fn code_info(&self) -> SourceCode { - SourceCode { - path: Arc::new(VPath(vec![])), - source: Arc::new(String::new()), - } - } + fn code_info(&self) -> SourceCode { SourceRange::mock().code() } fn lexers(&self) -> BoxedIter<'_, &dyn LexerPlugin> { box_empty() } fn line_parsers(&self) -> BoxedIter<'_, &dyn ParseLinePlugin> { box_empty() } } +impl Drop for MockContext { + fn drop(&mut self) { self.0.assert() } +} /// Context that assigns the same location to every subset of the source code. /// Its main use case is to process source code that was dynamically generated -/// in response to some user code. +/// in response to some user code. See also [ReporterContext] pub struct FlatLocContext<'a, C: ParseCtx + ?Sized> { sub: &'a C, range: &'a SourceRange, @@ -115,13 +131,33 @@ impl<'a, C: ParseCtx + ?Sized> FlatLocContext<'a, C> { pub fn new(sub: &'a C, range: &'a SourceRange) -> Self { Self { sub, range } } } impl<'a, C: ParseCtx + ?Sized> ParseCtx for FlatLocContext<'a, C> { + fn reporter(&self) -> &Reporter { self.sub.reporter() } 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 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 range(&self, _: usize, _: &str) -> Range { self.range.range.clone() } +} + +/// Context that forwards everything to a wrapped context except for error +/// reporting. See also [FlatLocContext] +pub struct ReporterContext<'a, C: ParseCtx + ?Sized> { + sub: &'a C, + reporter: &'a Reporter, +} +impl<'a, C: ParseCtx + ?Sized> ReporterContext<'a, C> { + /// Create a new context that will collect errors separately and forward + /// everything else to an enclosed context + pub fn new(sub: &'a C, reporter: &'a Reporter) -> Self { Self { sub, reporter } } +} +impl<'a, C: ParseCtx + ?Sized> ParseCtx for ReporterContext<'a, C> { + fn reporter(&self) -> &Reporter { self.reporter } + fn pos(&self, tail: &str) -> usize { self.sub.pos(tail) } + 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.sub.code_info() } + fn range(&self, len: usize, tl: &str) -> Range { self.sub.range(len, tl) } + fn range_loc(&self, range: &Range) -> SourceRange { self.sub.range_loc(range) } + fn source(&self) -> Arc { self.sub.source() } + fn source_range(&self, len: usize, tl: &str) -> SourceRange { self.sub.source_range(len, tl) } } diff --git a/src/parse/errors.rs b/src/parse/errors.rs index bdea4a3..268845c 100644 --- a/src/parse/errors.rs +++ b/src/parse/errors.rs @@ -8,7 +8,7 @@ use super::context::ParseCtx; use super::frag::Frag; use super::lexer::{Entry, Lexeme}; use crate::error::{ProjectError, ProjectErrorObj, ProjectResult}; -use crate::location::{CodeLocation, SourceRange}; +use crate::location::{CodeOrigin, SourceRange}; use crate::parse::parsed::PType; /// Parse error information without a location. Location data is added by the @@ -20,9 +20,7 @@ pub trait ParseErrorKind: Sized + Send + Sync + 'static { fn message(&self) -> String { Self::DESCRIPTION.to_string() } /// Convert this error to a type-erased [ProjectError] to be handled together /// with other Orchid errors. - fn pack(self, range: SourceRange) -> ProjectErrorObj { - ParseError { kind: self, range }.pack() - } + fn pack(self, range: SourceRange) -> ProjectErrorObj { ParseError { kind: self, range }.pack() } } struct ParseError { @@ -31,9 +29,7 @@ struct ParseError { } impl ProjectError for ParseError { const DESCRIPTION: &'static str = T::DESCRIPTION; - fn one_position(&self) -> CodeLocation { - CodeLocation::Source(self.range.clone()) - } + fn one_position(&self) -> CodeOrigin { self.range.origin() } fn message(&self) -> String { self.kind.message() } } @@ -41,9 +37,7 @@ impl ProjectError for ParseError { pub(super) struct LineNeedsPrefix(pub Lexeme); impl ParseErrorKind for LineNeedsPrefix { const DESCRIPTION: &'static str = "This linetype requires a prefix"; - fn message(&self) -> String { - format!("{} cannot appear at the beginning of a line", self.0) - } + fn message(&self) -> String { format!("{} cannot appear at the beginning of a line", self.0) } } /// The line ends abruptly. Raised on the last token @@ -51,8 +45,7 @@ pub(super) struct UnexpectedEOL(pub Lexeme); impl ParseErrorKind for UnexpectedEOL { const DESCRIPTION: &'static str = "The line ended abruptly"; fn message(&self) -> String { - "In Orchid, all line breaks outside parentheses start a new declaration" - .to_string() + "In Orchid, all line breaks outside parentheses start a new declaration".to_string() } } @@ -105,11 +98,7 @@ impl ParseErrorKind for Expected { } } /// Assert that the entry contains exactly the specified lexeme -pub(super) fn expect( - l: Lexeme, - e: &Entry, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult<()> { +pub(super) fn expect(l: Lexeme, e: &Entry, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<()> { if e.lexeme.strict_eq(&l) { return Ok(()); } @@ -134,9 +123,7 @@ pub(super) struct BadTokenInRegion { } impl ParseErrorKind for BadTokenInRegion { const DESCRIPTION: &'static str = "An unexpected token was found"; - fn message(&self) -> String { - format!("{} cannot appear in {}", self.lexeme, self.region) - } + fn message(&self) -> String { format!("{} cannot appear in {}", self.lexeme, self.region) } } /// Some construct was searched but not found. @@ -171,12 +158,6 @@ impl ParseErrorKind for GlobExport { const DESCRIPTION: &'static str = "Globstars are not allowed in exports"; } -/// String literal never ends -pub(super) struct NoStringEnd; -impl ParseErrorKind for NoStringEnd { - const DESCRIPTION: &'static str = "A string literal was not closed with `\"`"; -} - /// Comment never ends pub(super) struct NoCommentEnd; impl ParseErrorKind for NoCommentEnd { @@ -199,8 +180,7 @@ impl ParseErrorKind for NaNLiteral { /// A sequence of digits in a number literal overflows [usize]. pub(super) struct LiteralOverflow; impl ParseErrorKind for LiteralOverflow { - const DESCRIPTION: &'static str = - "number literal described number greater than usize::MAX"; + const DESCRIPTION: &'static str = "number literal described number greater than usize::MAX"; } /// A digit was expected but something else was found @@ -209,25 +189,6 @@ impl ParseErrorKind for ExpectedDigit { const DESCRIPTION: &'static str = "expected a digit"; } -/// A unicode escape sequence contains something other than a hex digit -pub(super) struct NotHex; -impl ParseErrorKind for NotHex { - const DESCRIPTION: &'static str = "Expected a hex digit"; -} - -/// A unicode escape sequence contains a number that isn't a unicode code point. -pub(super) struct BadCodePoint; -impl ParseErrorKind for BadCodePoint { - const DESCRIPTION: &'static str = - "\\uXXXX escape sequence does not describe valid code point"; -} - -/// An unrecognized escape sequence occurred in a string. -pub(super) struct BadEscapeSequence; -impl ParseErrorKind for BadEscapeSequence { - const DESCRIPTION: &'static str = "Unrecognized escape sequence"; -} - /// Expected a parenthesized block at the end of the line pub(super) struct ExpectedBlock; impl ParseErrorKind for ExpectedBlock { @@ -250,6 +211,5 @@ pub(super) fn expect_block<'a>( /// was found. pub(super) struct ExpectedSingleName; impl ParseErrorKind for ExpectedSingleName { - const DESCRIPTION: &'static str = - "expected a single name, no wildcards, no branches"; + const DESCRIPTION: &'static str = "expected a single name, no wildcards, no branches"; } diff --git a/src/parse/facade.rs b/src/parse/facade.rs index 3b8ef1f..50d5a6e 100644 --- a/src/parse/facade.rs +++ b/src/parse/facade.rs @@ -1,22 +1,22 @@ //! Entrypoints to the parser that combine lexing and parsing -use super::context::{FlatLocContext, ParseCtx}; +use never::Never; + +use super::context::{FlatLocContext, ParseCtx, ReporterContext}; use super::frag::Frag; use super::lexer::lex; use super::sourcefile::parse_module_body; -use crate::error::ProjectResult; +use crate::error::Reporter; use crate::location::SourceRange; use crate::parse::parsed::SourceLine; use crate::parse::sourcefile::{parse_line, split_lines}; /// Parse a file -pub fn parse_file(ctx: &impl ParseCtx) -> ProjectResult> { - let tokens = lex(vec![], ctx.source().as_str(), ctx, |_| Ok(false))?.tokens; - if tokens.is_empty() { - Ok(Vec::new()) - } else { - parse_module_body(Frag::from_slice(&tokens), ctx) - } +pub fn parse_file(ctx: &impl ParseCtx) -> Vec { + let tokens = lex(vec![], ctx.source().as_str(), ctx, |_| Ok::<_, Never>(false)) + .unwrap_or_else(|e| match e {}) + .tokens; + if tokens.is_empty() { Vec::new() } else { parse_module_body(Frag::from_slice(&tokens), ctx) } } /// Parse a statically defined line sequence @@ -29,10 +29,14 @@ pub fn parse_entries( text: &'static str, range: SourceRange, ) -> Vec { - let ctx = FlatLocContext::new(ctx, &range); - let res = lex(vec![], text, &ctx, |_| Ok(false)).expect("pre-specified source"); - split_lines(Frag::from_slice(&res.tokens), &ctx) + let reporter = Reporter::new(); + let flctx = FlatLocContext::new(ctx, &range); + let ctx = ReporterContext::new(&flctx, &reporter); + let res = lex(vec![], text, &ctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}); + let out = split_lines(Frag::from_slice(&res.tokens), &ctx) .flat_map(|tokens| parse_line(tokens, &ctx).expect("pre-specified source")) .map(|kind| kind.wrap(range.clone())) - .collect() + .collect(); + reporter.assert(); + out } diff --git a/src/parse/frag.rs b/src/parse/frag.rs index 45bb816..6be9abf 100644 --- a/src/parse/frag.rs +++ b/src/parse/frag.rs @@ -21,9 +21,7 @@ pub struct Frag<'a> { } impl<'a> Frag<'a> { /// Create a new fragment - pub fn new(fallback: &'a Entry, data: &'a [Entry]) -> Self { - Self { fallback, data } - } + pub fn new(fallback: &'a Entry, data: &'a [Entry]) -> Self { Self { fallback, data } } /// Remove comments and line breaks from both ends of the text pub fn trim(self) -> Self { @@ -45,19 +43,12 @@ impl<'a> Frag<'a> { } /// Get the first entry - pub fn pop( - self, - ctx: &(impl ParseCtx + ?Sized), - ) -> ProjectResult<(&'a Entry, Self)> { + pub fn pop(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<(&'a Entry, Self)> { Ok((self.get(0, ctx)?, self.step(ctx)?)) } - /// Retrieve an index from a slice or raise an [UnexpectedEOL]. - pub fn get( - self, - idx: usize, - ctx: &(impl ParseCtx + ?Sized), - ) -> ProjectResult<&'a Entry> { + /// Retrieve an index from a slice or raise an error if it isn't found. + pub fn get(self, idx: usize, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<&'a Entry> { self.data.get(idx).ok_or_else(|| { let entry = self.data.last().unwrap_or(self.fallback).clone(); UnexpectedEOL(entry.lexeme).pack(ctx.range_loc(&entry.range)) @@ -74,7 +65,7 @@ impl<'a> Frag<'a> { } /// Find a given token, split the fragment there and read some value from the - /// separator. See also [fragment::find] + /// separator. See also [Frag::find] pub fn find_map( self, msg: &'static str, @@ -91,7 +82,7 @@ impl<'a> Frag<'a> { } /// Split the fragment at a token and return just the two sides. - /// See also [fragment::find_map]. + /// See also [Frag::find_map]. pub fn find( self, descr: &'static str, @@ -103,15 +94,10 @@ impl<'a> Frag<'a> { } /// Remove the last item from the fragment - pub fn pop_back( - self, - ctx: &(impl ParseCtx + ?Sized), - ) -> ProjectResult<(&'a Entry, Self)> { + pub fn pop_back(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<(&'a Entry, Self)> { let Self { data, fallback } = self; - let (last, data) = (data.split_last()).ok_or_else(|| { - UnexpectedEOL(fallback.lexeme.clone()) - .pack(ctx.range_loc(&fallback.range)) - })?; + let (last, data) = (data.split_last()) + .ok_or_else(|| UnexpectedEOL(fallback.lexeme.clone()).pack(ctx.range_loc(&fallback.range)))?; Ok((last, Self { fallback, data })) } @@ -119,16 +105,12 @@ impl<'a> Frag<'a> { /// /// If the slice is empty pub fn from_slice(data: &'a [Entry]) -> Self { - let fallback = - (data.first()).expect("Empty slice cannot be converted into a parseable"); + let fallback = (data.first()).expect("Empty slice cannot be converted into a parseable"); Self { data, fallback } } /// Assert that the fragment is empty. - pub fn expect_empty( - self, - ctx: &(impl ParseCtx + ?Sized), - ) -> ProjectResult<()> { + pub fn expect_empty(self, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult<()> { match self.data.first() { Some(x) => Err(ExpectedEOL.pack(ctx.range_loc(&x.range))), None => Ok(()), diff --git a/src/parse/lex_plugin.rs b/src/parse/lex_plugin.rs index 7862eda..c301638 100644 --- a/src/parse/lex_plugin.rs +++ b/src/parse/lex_plugin.rs @@ -1,5 +1,8 @@ //! Abstractions for dynamic extensions to the lexer to parse custom literals +use dyn_clone::DynClone; +use never::Never; + use super::context::{FlatLocContext, ParseCtx}; use super::lexer::{lex, Entry, LexRes}; use crate::error::ProjectResult; @@ -37,13 +40,16 @@ pub trait LexPluginReq<'a> { /// External plugin that parses a literal into recognized Orchid lexemes, most /// likely atoms. -pub trait LexerPlugin: Send + Sync { +pub trait LexerPlugin: Send + Sync + DynClone { /// Run the lexer fn lex<'a>(&self, req: &'_ dyn LexPluginReq<'a>) -> Option>>; } -pub(super) struct LexPlugReqImpl<'a, 'b, TCtx: ParseCtx> { +/// Implementation of [LexPluginReq] +pub struct LexPlugReqImpl<'a, 'b, TCtx: ParseCtx> { + /// Text to be lexed pub tail: &'a str, + /// Context data pub ctx: &'b TCtx, } impl<'a, 'b, TCtx: ParseCtx> LexPluginReq<'a> for LexPlugReqImpl<'a, 'b, TCtx> { @@ -54,8 +60,6 @@ impl<'a, 'b, TCtx: ParseCtx> LexPluginReq<'a> for LexPlugReqImpl<'a, 'b, TCtx> { } fn insert(&self, data: &str, range: SourceRange) -> Vec { let ctx = FlatLocContext::new(self.ctx as &dyn ParseCtx, &range); - lex(Vec::new(), data, &ctx, |_| Ok(false)) - .expect("Insert failed to lex") - .tokens + lex(Vec::new(), data, &ctx, |_| Ok::<_, Never>(false)).unwrap_or_else(|e| match e {}).tokens } } diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 448471a..eb7370f 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -4,7 +4,7 @@ //! Literals lose their syntax form here and are handled in an abstract //! representation hence -use std::fmt::Display; +use std::fmt; use std::ops::Range; use std::sync::Arc; @@ -16,8 +16,6 @@ use super::context::ParseCtx; use super::errors::{FloatPlacehPrio, NoCommentEnd}; use super::lex_plugin::LexerPlugin; use super::numeric::{numstart, parse_num, print_nat16}; -use super::string::StringLexer; -use crate::error::ProjectResult; use crate::foreign::atom::AtomGenerator; use crate::libs::std::number::Numeric; use crate::parse::errors::ParseErrorKind; @@ -36,23 +34,23 @@ pub struct Entry { impl Entry { /// Checks if the lexeme is a comment or line break #[must_use] - pub fn is_filler(&self) -> bool { - matches!(self.lexeme, Lexeme::Comment(_) | Lexeme::BR) - } + pub fn is_filler(&self) -> bool { matches!(self.lexeme, Lexeme::Comment(_) | Lexeme::BR) } /// Create a new entry #[must_use] pub fn new(range: Range, lexeme: Lexeme) -> Self { Self { lexeme, range } } } -impl Display for Entry { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.lexeme.fmt(f) - } +impl fmt::Display for Entry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.lexeme.fmt(f) } +} + +impl PartialEq for Entry { + fn eq(&self, other: &Lexeme) -> bool { self.lexeme == *other } } /// A unit of syntax -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub enum Lexeme { /// Atoms parsed by plugins Atom(AtomGenerator), @@ -82,8 +80,8 @@ pub enum Lexeme { Placeh(Placeholder), } -impl Display for Lexeme { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Lexeme { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Atom(a) => write!(f, "{a:?}"), Self::Name(token) => write!(f, "{}", **token), @@ -112,8 +110,7 @@ impl Lexeme { (Self::BS, Self::BS) => true, (Self::NS, Self::NS) | (Self::Type, Self::Type) => true, (Self::Walrus, Self::Walrus) => true, - (Self::Atom(a1), Self::Atom(a2)) => - a1.run().0.parser_eq(a2.run().0.as_any_ref()), + (Self::Atom(a1), Self::Atom(a2)) => a1.run().0.parser_eq(&*a2.run().0), (Self::Comment(c1), Self::Comment(c2)) => c1 == c2, (Self::LP(p1), Self::LP(p2)) | (Self::RP(p1), Self::RP(p2)) => p1 == p2, (Self::Name(n1), Self::Name(n2)) => n1 == n2, @@ -145,10 +142,7 @@ pub fn opchar(c: char) -> bool { } /// Split off all characters from the beginning that match a filter -pub fn split_filter( - s: &str, - mut pred: impl FnMut(char) -> bool, -) -> (&str, &str) { +pub fn split_filter(s: &str, mut pred: impl FnMut(char) -> bool) -> (&str, &str) { s.find(|c| !pred(c)).map_or((s, ""), |i| s.split_at(i)) } @@ -169,17 +163,17 @@ fn lit_table() -> impl IntoIterator { ] } -static BUILTIN_ATOMS: &[&dyn LexerPlugin] = &[&NumericLexer, &StringLexer]; +static BUILTIN_ATOMS: &[&dyn LexerPlugin] = &[&NumericLexer]; /// Convert source code to a flat list of tokens. The bail callback will be /// called between lexemes. When it returns true, the remaining text is /// returned without processing. -pub fn lex<'a>( +pub fn lex<'a, E>( mut tokens: Vec, mut data: &'a str, ctx: &'_ impl ParseCtx, - mut bail: impl FnMut(&str) -> ProjectResult, -) -> ProjectResult> { + mut bail: impl FnMut(&str) -> Result, +) -> Result, E> { let mut prev_len = data.len() + 1; 'tail: loop { if prev_len == data.len() { @@ -195,10 +189,12 @@ pub fn lex<'a>( None => return Ok(LexRes { tokens, tail: data }), Some(h) => h, }; - let req = LexPlugReqImpl { tail: data, ctx }; for lexer in ctx.lexers().chain(BUILTIN_ATOMS.iter().copied()) { + let req = LexPlugReqImpl { tail: data, ctx }; if let Some(res) = lexer.lex(&req) { - let LexRes { tail, tokens: mut new_tokens } = res?; + let LexRes { tail, tokens: mut new_tokens } = + ctx.reporter().fallback(res, |_| LexRes { tail: "", tokens: vec![] }); + // fallback: no tokens left, no additional tokens parsed if tail.len() == data.len() { panic!("lexer plugin consumed 0 characters") } @@ -216,14 +212,15 @@ pub fn lex<'a>( } if let Some(tail) = data.strip_prefix(',') { - let lexeme = Lexeme::Name(i(",")); - tokens.push(Entry::new(ctx.range(1, tail), lexeme)); + tokens.push(Entry::new(ctx.range(1, tail), Lexeme::Name(i!(str: ",")))); data = tail; continue 'tail; } if let Some(tail) = data.strip_prefix("--[") { - let (note, tail) = (tail.split_once("]--")) - .ok_or_else(|| NoCommentEnd.pack(ctx.source_range(tail.len(), "")))?; + let (note, tail) = tail.split_once("]--").unwrap_or_else(|| { + ctx.reporter().report(NoCommentEnd.pack(ctx.source_range(tail.len(), ""))); + (tail, "") // fallback: the rest of the file is in the comment + }); let lexeme = Lexeme::Comment(Arc::new(note.to_string())); tokens.push(Entry::new(ctx.range(note.len() + 3, tail), lexeme)); data = tail; @@ -236,12 +233,15 @@ pub fn lex<'a>( data = tail; continue 'tail; } + // Parse a rule arrow if let Some(tail) = data.strip_prefix('=') { if tail.chars().next().map_or(false, numstart) { let (num, post_num) = split_filter(tail, numchar); if let Some(tail) = post_num.strip_prefix("=>") { - let prio = parse_num(num) - .map_err(|e| e.into_proj(num.len(), post_num, ctx))?; + let prio = parse_num(num).unwrap_or_else(|e| { + ctx.reporter().report(e.into_proj(num.len(), post_num, ctx)); + Numeric::Uint(0) + }); let lexeme = Lexeme::Arrow(prio.as_float()); tokens.push(Entry::new(ctx.range(num.len() + 3, tail), lexeme)); data = tail; @@ -249,10 +249,9 @@ pub fn lex<'a>( } } } - // todo: parse placeholders, don't forget vectorials! + // Parse scalar placeholder $_name or $name if let Some(tail) = data.strip_prefix('$') { - let (nameonly, tail) = - tail.strip_prefix('_').map_or((false, tail), |t| (true, t)); + let (nameonly, tail) = tail.strip_prefix('_').map_or((false, tail), |t| (true, t)); let (name, tail) = split_filter(tail, namechar); if !name.is_empty() { let class = if nameonly { PHClass::Name } else { PHClass::Scalar }; @@ -262,9 +261,10 @@ pub fn lex<'a>( continue 'tail; } } + // Parse vectorial placeholder. `..` or `...`, then `$name`, then an optional + // `:n` where n is a number. if let Some(tail) = data.strip_prefix("..") { - let (nonzero, tail) = - tail.strip_prefix('.').map_or((false, tail), |t| (true, t)); + let (nonzero, tail) = tail.strip_prefix('.').map_or((false, tail), |t| (true, t)); if let Some(tail) = tail.strip_prefix('$') { let (name, tail) = split_filter(tail, namechar); if !name.is_empty() { @@ -273,17 +273,19 @@ pub fn lex<'a>( .map(|tail| split_filter(tail, numchar)) .filter(|(num, _)| !num.is_empty()) .map(|(num_str, tail)| { - parse_num(num_str) - .map_err(|e| e.into_proj(num_str.len(), tail, ctx)) - .and_then(|num| match num { - Numeric::Uint(usize) => Ok(usize), - Numeric::Float(_) => Err( - FloatPlacehPrio.pack(ctx.source_range(num_str.len(), tail)), - ), - }) - .map(|p| (p, num_str.len() + 1, tail)) + let p = ctx.reporter().fallback( + parse_num(num_str).map_err(|e| e.into_proj(num_str.len(), tail, ctx)).and_then( + |num| match num { + Numeric::Uint(usize) => Ok(usize), + Numeric::Float(_) => + Err(FloatPlacehPrio.pack(ctx.source_range(num_str.len(), tail))), + }, + ), + |_| 0, + ); + (p, num_str.len() + 1, tail) }) - .unwrap_or(Ok((0, 0, tail)))?; + .unwrap_or((0, 0, tail)); let byte_len = if nonzero { 4 } else { 3 } + priolen + name.len(); let class = PHClass::Vec { nonzero, prio }; let lexeme = Lexeme::Placeh(Placeholder { name: i(name), class }); diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 040da49..8927bb6 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -10,4 +10,3 @@ pub mod numeric; pub mod parse_plugin; pub mod parsed; mod sourcefile; -pub mod string; diff --git a/src/parse/multiname.rs b/src/parse/multiname.rs index 22e9267..6983a10 100644 --- a/src/parse/multiname.rs +++ b/src/parse/multiname.rs @@ -51,7 +51,7 @@ fn parse_multiname_branch<'a>( cursor: Frag<'a>, ctx: &(impl ParseCtx + ?Sized), ) -> ProjectResult<(BoxedIter<'a, Subresult>, Frag<'a>)> { - let comma = i(","); + let comma = i!(str: ","); let (subnames, cursor) = parse_multiname_rec(cursor, ctx)?; let (Entry { lexeme, range }, cursor) = cursor.trim().pop(ctx)?; match &lexeme { @@ -72,8 +72,6 @@ fn parse_multiname_rec<'a>( cursor: Frag<'a>, ctx: &(impl ParseCtx + ?Sized), ) -> ProjectResult<(BoxedIter<'a, Subresult>, Frag<'a>)> { - let star = i("*"); - let comma = i(","); let (head, mut cursor) = cursor.trim().pop(ctx)?; match &head.lexeme { Lexeme::LP(PType::Par) => parse_multiname_branch(cursor, ctx), @@ -96,15 +94,15 @@ fn parse_multiname_rec<'a>( } } Ok(( - Box::new(names.into_iter().map(|(name, location)| { - Subresult::new_named(name.clone(), location) - })), + Box::new( + names.into_iter().map(|(name, location)| Subresult::new_named(name.clone(), location)), + ), cursor, )) }, - Lexeme::Name(n) if *n == star => + Lexeme::Name(n) if *n == i!(str: "*") => Ok((box_once(Subresult::new_glob(&head.range)), cursor)), - Lexeme::Name(n) if ![comma, star].contains(n) => { + Lexeme::Name(n) if ![i!(str: ","), i!(str: "*")].contains(n) => { let cursor = cursor.trim(); if cursor.get(0, ctx).map_or(false, |e| e.lexeme.strict_eq(&Lexeme::NS)) { let cursor = cursor.step(ctx)?; @@ -117,8 +115,7 @@ fn parse_multiname_rec<'a>( }, _ => { let expected = vec![Lexeme::LP(PType::Par)]; - let err = - Expected { expected, or_name: true, found: head.lexeme.clone() }; + let err = Expected { expected, or_name: true, found: head.lexeme.clone() }; Err(err.pack(ctx.range_loc(&head.range))) }, } diff --git a/src/parse/numeric.rs b/src/parse/numeric.rs index db3643f..8769212 100644 --- a/src/parse/numeric.rs +++ b/src/parse/numeric.rs @@ -7,9 +7,7 @@ use std::ops::Range; use ordered_float::NotNan; use super::context::ParseCtx; -use super::errors::{ - ExpectedDigit, LiteralOverflow, NaNLiteral, ParseErrorKind, -}; +use super::errors::{ExpectedDigit, LiteralOverflow, NaNLiteral, ParseErrorKind}; use super::lex_plugin::LexPluginReq; #[allow(unused)] // for doc use super::lex_plugin::LexerPlugin; @@ -49,7 +47,7 @@ pub struct NumError { } impl NumError { - /// Convert into [ProjectError] trait object + /// Convert into [ProjectErrorObj] pub fn into_proj( self, len: usize, @@ -68,13 +66,11 @@ impl NumError { /// Parse a numbre literal out of text pub fn parse_num(string: &str) -> Result { - let overflow_err = - NumError { range: 0..string.len(), kind: NumErrorKind::Overflow }; - let (radix, noprefix, pos) = - (string.strip_prefix("0x").map(|s| (16u8, s, 2))) - .or_else(|| string.strip_prefix("0b").map(|s| (2u8, s, 2))) - .or_else(|| string.strip_prefix("0o").map(|s| (8u8, s, 2))) - .unwrap_or((10u8, string, 0)); + let overflow_err = NumError { range: 0..string.len(), kind: NumErrorKind::Overflow }; + let (radix, noprefix, pos) = (string.strip_prefix("0x").map(|s| (16u8, s, 2))) + .or_else(|| string.strip_prefix("0b").map(|s| (2u8, s, 2))) + .or_else(|| string.strip_prefix("0o").map(|s| (8u8, s, 2))) + .unwrap_or((10u8, string, 0)); // identity let (base, exponent) = match noprefix.split_once('p') { Some((b, e)) => { @@ -137,12 +133,10 @@ pub fn print_nat16(num: NotNan) -> String { } /// [LexerPlugin] for a number literal +#[derive(Clone)] pub struct NumericLexer; impl LexerPlugin for NumericLexer { - fn lex<'b>( - &self, - req: &'_ dyn LexPluginReq<'b>, - ) -> Option>> { + fn lex<'b>(&self, req: &'_ dyn LexPluginReq<'b>) -> Option>> { req.tail().chars().next().filter(|c| numstart(*c)).map(|_| { let (num_str, tail) = split_filter(req.tail(), numchar); let ag = match parse_num(num_str) { diff --git a/src/parse/parse_plugin.rs b/src/parse/parse_plugin.rs index 004cdab..65b4e5d 100644 --- a/src/parse/parse_plugin.rs +++ b/src/parse/parse_plugin.rs @@ -1,9 +1,10 @@ //! Abstractions for dynamic extensions to the parser that act across entries. //! Macros are the primary syntax extension mechanism, but they only operate -//! within a constant and can't interfere with name resolution. +//! within a constant and can't interfere with name reproject. use std::ops::Range; +use dyn_clone::DynClone; use intern_all::Tok; use super::context::ParseCtx; @@ -11,14 +12,12 @@ use super::errors::{expect, expect_block, expect_name}; use super::facade::parse_entries; use super::frag::Frag; use super::lexer::{Entry, Lexeme}; -use super::parsed::{ - Constant, Expr, ModuleBlock, PType, Rule, SourceLine, SourceLineKind, -}; +use super::parsed::{Constant, Expr, ModuleBlock, PType, Rule, SourceLine, SourceLineKind}; use super::sourcefile::{ - parse_const, parse_exprv, parse_line, parse_module, parse_module_body, - parse_nsname, parse_rule, split_lines, vec_to_single, + exprv_to_single, parse_const, parse_exprv, parse_line, parse_module, parse_module_body, + parse_nsname, parse_rule, split_lines, }; -use crate::error::ProjectResult; +use crate::error::{ProjectErrorObj, ProjectResult}; use crate::location::SourceRange; use crate::name::VName; use crate::utils::boxed_iter::BoxedIter; @@ -61,20 +60,12 @@ pub trait ParsePluginReq<'t> { fn parse_module(&self, frag: Frag) -> ProjectResult; /// Parse a sequence of expressions. In principle, it never makes sense to /// parse a single expression because it could always be a macro invocation. - fn parse_exprv<'a>( - &self, - f: Frag<'a>, - p: Option, - ) -> ProjectResult<(Vec, Frag<'a>)>; + fn parse_exprv<'a>(&self, f: Frag<'a>, p: Option) -> ProjectResult<(Vec, Frag<'a>)>; /// Parse a prepared string of code fn parse_entries(&self, t: &'static str, r: SourceRange) -> Vec; /// Convert a sequence of expressions to a single one by parenthesization if /// necessary - fn vec_to_single( - &self, - fallback: &Entry, - v: Vec, - ) -> ProjectResult; + fn vec_to_single(&self, fallback: &Entry, v: Vec) -> ProjectResult; // ################ Assertions ################ @@ -86,17 +77,17 @@ pub trait ParsePluginReq<'t> { fn expect_block<'a>(&self, f: Frag<'a>, p: PType) -> ProjectResult>; /// Ensure that the fragment is empty fn expect_empty(&self, f: Frag) -> ProjectResult<()>; + /// Report a fatal error while also producing output to be consumed by later + /// stages for improved error reporting + fn report_err(&self, e: ProjectErrorObj); } /// External plugin that parses an unrecognized source line into lines of /// recognized types -pub trait ParseLinePlugin: Sync + Send { +pub trait ParseLinePlugin: Sync + Send + DynClone { /// Attempt to parse a line. Returns [None] if the line isn't recognized, /// [Some][Err] if it's recognized but incorrect. - fn parse( - &self, - req: &dyn ParsePluginReq, - ) -> Option>>; + fn parse(&self, req: &dyn ParsePluginReq) -> Option>>; } /// Implementation of [ParsePluginReq] exposing sub-parsers and data to the @@ -107,15 +98,11 @@ pub struct ParsePlugReqImpl<'a, TCtx: ParseCtx + ?Sized> { /// Context for recursive commands and to expose to the plugin pub ctx: &'a TCtx, } -impl<'ty, TCtx: ParseCtx + ?Sized> ParsePluginReq<'ty> - for ParsePlugReqImpl<'ty, TCtx> -{ +impl<'ty, TCtx: ParseCtx + ?Sized> ParsePluginReq<'ty> for ParsePlugReqImpl<'ty, TCtx> { fn frag(&self) -> Frag { self.frag } fn frag_loc(&self, f: Frag) -> SourceRange { self.range_loc(f.range()) } fn range_loc(&self, r: Range) -> SourceRange { self.ctx.range_loc(&r) } - fn pop<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)> { - f.pop(self.ctx) - } + fn pop<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)> { f.pop(self.ctx) } fn pop_back<'a>(&self, f: Frag<'a>) -> ProjectResult<(&'a Entry, Frag<'a>)> { f.pop_back(self.ctx) } @@ -127,46 +114,29 @@ impl<'ty, TCtx: ParseCtx + ?Sized> ParsePluginReq<'ty> Box::new(split_lines(f, self.ctx)) } fn parse_module_body(&self, f: Frag) -> ProjectResult> { - parse_module_body(f, self.ctx) - } - fn parse_line(&self, f: Frag) -> ProjectResult> { - parse_line(f, self.ctx) - } - fn parse_rule(&self, f: Frag) -> ProjectResult { - parse_rule(f, self.ctx) - } - fn parse_const(&self, f: Frag) -> ProjectResult { - parse_const(f, self.ctx) + Ok(parse_module_body(f, self.ctx)) } + fn parse_line(&self, f: Frag) -> ProjectResult> { parse_line(f, self.ctx) } + fn parse_rule(&self, f: Frag) -> ProjectResult { parse_rule(f, self.ctx) } + fn parse_const(&self, f: Frag) -> ProjectResult { parse_const(f, self.ctx) } fn parse_nsname<'a>(&self, f: Frag<'a>) -> ProjectResult<(VName, Frag<'a>)> { parse_nsname(f, self.ctx) } - fn parse_module(&self, f: Frag) -> ProjectResult { - parse_module(f, self.ctx) - } - fn parse_exprv<'a>( - &self, - f: Frag<'a>, - p: Option, - ) -> ProjectResult<(Vec, Frag<'a>)> { + fn parse_module(&self, f: Frag) -> ProjectResult { parse_module(f, self.ctx) } + fn parse_exprv<'a>(&self, f: Frag<'a>, p: Option) -> ProjectResult<(Vec, Frag<'a>)> { parse_exprv(f, p, self.ctx) } fn parse_entries(&self, s: &'static str, r: SourceRange) -> Vec { parse_entries(&self.ctx, s, r) } fn vec_to_single(&self, fb: &Entry, v: Vec) -> ProjectResult { - vec_to_single(fb, v, self.ctx) - } - fn expect_name(&self, e: &Entry) -> ProjectResult> { - expect_name(e, self.ctx) - } - fn expect(&self, l: Lexeme, e: &Entry) -> ProjectResult<()> { - expect(l, e, self.ctx) + exprv_to_single(fb, v, self.ctx) } + fn expect_name(&self, e: &Entry) -> ProjectResult> { expect_name(e, self.ctx) } + fn expect(&self, l: Lexeme, e: &Entry) -> ProjectResult<()> { expect(l, e, self.ctx) } fn expect_block<'a>(&self, f: Frag<'a>, t: PType) -> ProjectResult> { expect_block(f, t, self.ctx) } - fn expect_empty(&self, f: Frag) -> ProjectResult<()> { - f.expect_empty(self.ctx) - } + fn expect_empty(&self, f: Frag) -> ProjectResult<()> { f.expect_empty(self.ctx) } + fn report_err(&self, e: ProjectErrorObj) { self.ctx.reporter().report(e) } } diff --git a/src/parse/parsed.rs b/src/parse/parsed.rs index 2da0827..8f8b8d2 100644 --- a/src/parse/parsed.rs +++ b/src/parse/parsed.rs @@ -3,7 +3,7 @@ //! These structures are produced by the pipeline, processed by the macro //! executor, and then converted to other usable formats. -use std::fmt::Display; +use std::fmt; use std::hash::Hash; use std::rc::Rc; @@ -32,50 +32,34 @@ impl Expr { /// Process all names with the given mapper. /// Return a new object if anything was processed #[must_use] - pub fn map_names( - &self, - pred: &mut impl FnMut(Sym) -> Option, - ) -> Option { - (self.value.map_names(pred)) - .map(|value| Self { value, range: self.range.clone() }) + pub fn map_names(&self, pred: &mut impl FnMut(Sym) -> Option) -> Option { + (self.value.map_names(pred)).map(|value| Self { value, range: self.range.clone() }) } /// Visit all expressions in the tree. The search can be exited early by /// returning [Some] /// /// See also [crate::interpreter::nort::Expr::search_all] - pub fn search_all( - &self, - f: &mut impl FnMut(&Self) -> Option, - ) -> Option { + pub fn search_all(&self, f: &mut impl FnMut(&Self) -> Option) -> Option { f(self).or_else(|| self.value.search_all(f)) } } /// Visit all expression sequences including this sequence itself. -pub fn search_all_slcs( - this: &[Expr], - f: &mut impl FnMut(&[Expr]) -> Option, -) -> Option { +pub fn search_all_slcs(this: &[Expr], f: &mut impl FnMut(&[Expr]) -> Option) -> Option { f(this).or_else(|| this.iter().find_map(|expr| expr.value.search_all_slcs(f))) } impl Expr { /// Add the specified prefix to every Name #[must_use] - pub fn prefix( - &self, - prefix: &[Tok], - except: &impl Fn(Tok) -> bool, - ) -> Self { + pub fn prefix(&self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { Self { value: self.value.prefix(prefix, except), range: self.range.clone() } } } -impl Display for Expr { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.value.fmt(f) - } +impl fmt::Display for Expr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.value.fmt(f) } } /// Various types of placeholders @@ -103,8 +87,8 @@ pub struct Placeholder { pub class: PHClass, } -impl Display for Placeholder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Placeholder { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let name = &self.name; match self.class { PHClass::Scalar => write!(f, "${name}"), @@ -197,11 +181,7 @@ impl Clause { /// Convert with identical meaning #[must_use] pub fn from_exprv(exprv: &Rc>) -> Option { - if exprv.len() < 2 { - Self::from_exprs(exprv) - } else { - Some(Self::S(PType::Par, exprv.clone())) - } + if exprv.len() < 2 { Self::from_exprs(exprv) } else { Some(Self::S(PType::Par, exprv.clone())) } } /// Collect all names that appear in this expression. @@ -226,10 +206,7 @@ impl Clause { /// Process all names with the given mapper. /// Return a new object if anything was processed #[must_use] - pub fn map_names( - &self, - pred: &mut impl FnMut(Sym) -> Option, - ) -> Option { + pub fn map_names(&self, pred: &mut impl FnMut(Sym) -> Option) -> Option { match self { Clause::Atom(_) | Clause::Placeh(_) => None, Clause::Name(name) => pred(name.clone()).map(Clause::Name), @@ -261,20 +238,13 @@ impl Clause { val.unwrap_or_else(|| e.clone()) }) .collect(); - if any_some { - Some(Clause::Lambda(Rc::new(new_arg), Rc::new(new_body))) - } else { - None - } + if any_some { Some(Clause::Lambda(Rc::new(new_arg), Rc::new(new_body))) } else { None } }, } } /// Pair of [Expr::search_all] - pub fn search_all( - &self, - f: &mut impl FnMut(&Expr) -> Option, - ) -> Option { + pub fn search_all(&self, f: &mut impl FnMut(&Expr) -> Option) -> Option { match self { Clause::Lambda(arg, body) => arg.iter().chain(body.iter()).find_map(|expr| expr.search_all(f)), @@ -283,25 +253,17 @@ impl Clause { } } - /// Pair of [Expr::search_all_slcs] - pub fn search_all_slcs( - &self, - f: &mut impl FnMut(&[Expr]) -> Option, - ) -> Option { + /// Visit all expression sequences. Most useful when looking for some pattern + pub fn search_all_slcs(&self, f: &mut impl FnMut(&[Expr]) -> Option) -> Option { match self { - Clause::Lambda(arg, body) => - search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)), + Clause::Lambda(arg, body) => search_all_slcs(arg, f).or_else(|| search_all_slcs(body, f)), Clause::Name(_) | Clause::Atom(_) | Clause::Placeh(_) => None, Clause::S(_, body) => search_all_slcs(body, f), } } /// Generate a parenthesized expression sequence - pub fn s( - delimiter: char, - body: impl IntoIterator, - range: SourceRange, - ) -> Self { + pub fn s(delimiter: char, body: impl IntoIterator, range: SourceRange) -> Self { let ptype = match delimiter { '(' => PType::Par, '[' => PType::Sqr, @@ -316,17 +278,12 @@ impl Clause { impl Clause { /// Add the specified prefix to every Name #[must_use] - pub fn prefix( - &self, - prefix: &[Tok], - except: &impl Fn(Tok) -> bool, - ) -> Self { + pub fn prefix(&self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { self .map_names(&mut |name| match except(name[0].clone()) { true => None, false => { - let prefixed = - prefix.iter().chain(name[..].iter()).cloned().collect::>(); + let prefixed = prefix.iter().cloned().chain(name.iter()).collect::>(); Some(Sym::from_tok(name.tok().interner().i(&prefixed)).unwrap()) }, }) @@ -334,8 +291,8 @@ impl Clause { } } -impl Display for Clause { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Clause { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Atom(a) => write!(f, "{a:?}"), Self::Name(name) => write!(f, "{}", name), @@ -364,8 +321,8 @@ pub struct Rule { pub template: Vec, } -impl Display for Rule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Rule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "rule {} ={}=> {}", @@ -385,8 +342,8 @@ pub struct Constant { pub value: Expr, } -impl Display for Constant { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Constant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "const {} := {}", *self.name, self.value) } } @@ -431,8 +388,8 @@ impl Import { } } -impl Display for Import { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Import { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.name { None => write!(f, "{}::*", self.path), Some(n) => write!(f, "{}::{}", self.path, n), @@ -449,8 +406,8 @@ pub struct ModuleBlock { pub body: Vec, } -impl Display for ModuleBlock { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ModuleBlock { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let bodys = self.body.iter().map(|e| e.to_string()).join("\n"); write!(f, "module {} {{\n{}\n}}", self.name, bodys) } @@ -469,13 +426,13 @@ pub enum MemberKind { } impl MemberKind { /// Convert to [SourceLine] - pub fn to_line(self, exported: bool, range: SourceRange) -> SourceLine { + pub fn into_line(self, exported: bool, range: SourceRange) -> SourceLine { SourceLineKind::Member(Member { exported, kind: self }).wrap(range) } } -impl Display for MemberKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for MemberKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Constant(c) => c.fmt(f), Self::Module(m) => m.fmt(f), @@ -494,8 +451,8 @@ pub struct Member { pub exported: bool, } -impl Display for Member { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Member { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self { exported: true, kind } => write!(f, "export {kind}"), Self { exported: false, kind } => write!(f, "{kind}"), @@ -518,13 +475,11 @@ pub enum SourceLineKind { } impl SourceLineKind { /// Wrap with no location - pub fn wrap(self, range: SourceRange) -> SourceLine { - SourceLine { kind: self, range } - } + pub fn wrap(self, range: SourceRange) -> SourceLine { SourceLine { kind: self, range } } } -impl Display for SourceLineKind { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SourceLineKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Comment(s) => write!(f, "--[{s}]--"), Self::Export(s) => { @@ -547,8 +502,6 @@ pub struct SourceLine { pub range: SourceRange, } -impl Display for SourceLine { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.kind.fmt(f) - } +impl fmt::Display for SourceLine { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.kind.fmt(f) } } diff --git a/src/parse/sourcefile.rs b/src/parse/sourcefile.rs index 8207d78..d0a3865 100644 --- a/src/parse/sourcefile.rs +++ b/src/parse/sourcefile.rs @@ -1,3 +1,5 @@ +//! Internal states of the parser. + use std::iter; use std::rc::Rc; @@ -6,9 +8,8 @@ use itertools::Itertools; use super::context::ParseCtx; use super::errors::{ - expect, expect_block, expect_name, BadTokenInRegion, ExpectedSingleName, - GlobExport, LeadingNS, MisalignedParen, NamespacedExport, ParseErrorKind, - ReservedToken, UnexpectedEOL, + expect, expect_block, expect_name, BadTokenInRegion, ExpectedSingleName, GlobExport, LeadingNS, + MisalignedParen, NamespacedExport, ParseErrorKind, ReservedToken, UnexpectedEOL, }; use super::frag::Frag; use super::lexer::{Entry, Lexeme}; @@ -17,9 +18,10 @@ use super::parse_plugin::ParsePlugReqImpl; use crate::error::ProjectResult; use crate::name::VName; use crate::parse::parsed::{ - Clause, Constant, Expr, Import, Member, MemberKind, ModuleBlock, PType, Rule, - SourceLine, SourceLineKind, + Clause, Constant, Expr, Import, Member, MemberKind, ModuleBlock, PType, Rule, SourceLine, + SourceLineKind, }; +use crate::sym; /// Split the fragment at each line break outside parentheses pub fn split_lines<'a>( @@ -55,33 +57,23 @@ pub fn split_lines<'a>( }) .map(Frag::trim) .map(|s| { - s.pop(ctx) - .and_then(|(first, inner)| { - let (last, inner) = inner.pop_back(ctx)?; - match (&first.lexeme, &last.lexeme) { - (Lexeme::LP(PType::Par), Lexeme::RP(PType::Par)) => Ok(inner.trim()), - _ => Ok(s), - } - }) - .unwrap_or(s) + match s.pop(ctx).and_then(|(f, i)| i.pop_back(ctx).map(|(l, i)| (&f.lexeme, i, &l.lexeme))) { + Ok((Lexeme::LP(PType::Par), inner, Lexeme::RP(PType::Par))) => inner.trim(), + _ => s, + } }) .filter(|l| !l.data.is_empty()) } /// Parse linebreak-separated entries -pub fn parse_module_body( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult> { +pub fn parse_module_body(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> Vec { let mut lines = Vec::new(); for l in split_lines(cursor, ctx) { - let kinds = parse_line(l, ctx)?; + let kinds = ctx.reporter().fallback(parse_line(l, ctx), |_| vec![]); let r = ctx.range_loc(&l.range()); - lines.extend( - kinds.into_iter().map(|kind| SourceLine { range: r.clone(), kind }), - ); + lines.extend(kinds.into_iter().map(|kind| SourceLine { range: r.clone(), kind })); } - Ok(lines) + lines } /// Parse a single, possibly exported entry @@ -97,11 +89,10 @@ pub fn parse_line( } let head = cursor.get(0, ctx)?; match &head.lexeme { - Lexeme::Comment(cmt) => - cmt.strip_prefix('|').and_then(|c| c.strip_suffix('|')).map_or_else( - || parse_line(cursor.step(ctx)?, ctx), - |cmt| Ok(vec![SourceLineKind::Comment(cmt.to_string())]), - ), + Lexeme::Comment(cmt) => cmt.strip_prefix('|').and_then(|c| c.strip_suffix('|')).map_or_else( + || parse_line(cursor.step(ctx)?, ctx), + |cmt| Ok(vec![SourceLineKind::Comment(cmt.to_string())]), + ), Lexeme::BR => parse_line(cursor.step(ctx)?, ctx), Lexeme::Name(n) if **n == "export" => parse_export_line(cursor.step(ctx)?, ctx).map(|k| vec![k]), @@ -116,10 +107,7 @@ pub fn parse_line( }, lexeme => { let lexeme = lexeme.clone(); - Err( - BadTokenInRegion { lexeme, region: "start of line" } - .pack(ctx.range_loc(&head.range)), - ) + Err(BadTokenInRegion { lexeme, region: "start of line" }.pack(ctx.range_loc(&head.range))) }, } } @@ -135,19 +123,16 @@ fn parse_export_line( let (names, cont) = parse_multiname(cursor.step(ctx)?, ctx)?; cont.expect_empty(ctx)?; let names = (names.into_iter()) - .map(|Import { name, path, range }| match (name, &path[..]) { - (Some(n), []) => Ok((n, range)), - (None, _) => Err(GlobExport.pack(range)), - _ => Err(NamespacedExport.pack(range)), + .map(|Import { name, path, range }| match name { + Some(n) if path.is_empty() => Ok((n, range)), + Some(_) => Err(NamespacedExport.pack(range)), + None => Err(GlobExport.pack(range)), }) .collect::, _>>()?; Ok(SourceLineKind::Export(names)) }, Lexeme::Name(n) if ["const", "macro", "module"].contains(&n.as_str()) => - Ok(SourceLineKind::Member(Member { - kind: parse_member(cursor, ctx)?, - exported: true, - })), + Ok(SourceLineKind::Member(Member { kind: parse_member(cursor, ctx)?, exported: true })), lexeme => { let lexeme = lexeme.clone(); let err = BadTokenInRegion { lexeme, region: "exported line" }; @@ -156,10 +141,7 @@ fn parse_export_line( } } -fn parse_member( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { +fn parse_member(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { let (typemark, cursor) = cursor.trim().pop(ctx)?; match &typemark.lexeme { Lexeme::Name(n) if **n == "const" => { @@ -183,31 +165,27 @@ fn parse_member( } /// Parse a macro rule -pub fn parse_rule( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { - let (pattern, prio, template) = - cursor.find_map("arrow", ctx, |a| match a { - Lexeme::Arrow(p) => Some(*p), - _ => None, - })?; +pub fn parse_rule(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { + let (pattern, prio, template) = cursor.find_map("arrow", ctx, |a| match a { + Lexeme::Arrow(p) => Some(*p), + _ => None, + })?; let (pattern, _) = parse_exprv(pattern, None, ctx)?; let (template, _) = parse_exprv(template, None, ctx)?; Ok(Rule { pattern, prio, template }) } /// Parse a constant declaration -pub fn parse_const( - cursor: Frag<'_>, - ctx: &(impl ParseCtx + ?Sized), -) -> ProjectResult { +pub fn parse_const(cursor: Frag<'_>, ctx: &(impl ParseCtx + ?Sized)) -> ProjectResult { let (name_ent, cursor) = cursor.trim().pop(ctx)?; let name = expect_name(name_ent, ctx)?; let (walrus_ent, cursor) = cursor.trim().pop(ctx)?; expect(Lexeme::Walrus, walrus_ent, ctx)?; - let (body, _) = parse_exprv(cursor, None, ctx)?; - Ok(Constant { name, value: vec_to_single(walrus_ent, body, ctx)? }) + let value = ctx.reporter().fallback( + parse_exprv(cursor, None, ctx).and_then(|(body, _)| exprv_to_single(walrus_ent, body, ctx)), + |_| Clause::Name(sym!(__syntax_error__)).into_expr(ctx.range_loc(&cursor.range())), + ); + Ok(Constant { name, value }) } /// Parse a namespaced name. TODO: use this for modules @@ -234,7 +212,7 @@ pub fn parse_module( let (name_ent, cursor) = cursor.trim().pop(ctx)?; let name = expect_name(name_ent, ctx)?; let body = expect_block(cursor, PType::Par, ctx)?; - Ok(ModuleBlock { name, body: parse_module_body(body, ctx)? }) + Ok(ModuleBlock { name, body: parse_module_body(body, ctx) }) } /// Parse a sequence of expressions @@ -258,18 +236,17 @@ pub fn parse_exprv<'a>( cursor = cursor.step(ctx)?; }, Lexeme::Placeh(ph) => { - output.push(Expr { - value: Clause::Placeh(ph.clone()), - range: ctx.range_loc(¤t.range), - }); + output + .push(Expr { value: Clause::Placeh(ph.clone()), range: ctx.range_loc(¤t.range) }); cursor = cursor.step(ctx)?; }, Lexeme::Name(n) => { - let range = ctx.range_loc(&cursor.range()); + let mut range = ctx.range_loc(¤t.range); let mut fullname = VName::new([n.clone()]).unwrap(); - while cursor.get(1, ctx).is_ok_and(|e| e.lexeme.strict_eq(&Lexeme::NS)) - { - fullname = fullname.suffix([expect_name(cursor.get(2, ctx)?, ctx)?]); + while cursor.get(1, ctx).is_ok_and(|e| e.lexeme.strict_eq(&Lexeme::NS)) { + let next_seg = cursor.get(2, ctx)?; + range.range.end = next_seg.range.end; + fullname = fullname.suffix([expect_name(next_seg, ctx)?]); cursor = cursor.step(ctx)?.step(ctx)?; } let clause = Clause::Name(fullname.to_sym()); @@ -292,9 +269,9 @@ pub fn parse_exprv<'a>( cursor = leftover; }, Lexeme::BS => { - let dot = i("."); - let (arg, body) = (cursor.step(ctx))? - .find("A '.'", ctx, |l| l.strict_eq(&Lexeme::Name(dot.clone())))?; + let dot = i!(str: "."); + let (arg, body) = + (cursor.step(ctx))?.find("A '.'", ctx, |l| l.strict_eq(&Lexeme::Name(dot.clone())))?; let (arg, _) = parse_exprv(arg, None, ctx)?; let (body, leftover) = parse_exprv(body, paren, ctx)?; output.push(Expr { @@ -315,7 +292,7 @@ pub fn parse_exprv<'a>( } /// Wrap an expression list in parentheses if necessary -pub fn vec_to_single( +pub fn exprv_to_single( fallback: &Entry, v: Vec, ctx: &(impl ParseCtx + ?Sized), diff --git a/src/parse/string.rs b/src/parse/string.rs deleted file mode 100644 index 746aff2..0000000 --- a/src/parse/string.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Parser for a string literal - -use intern_all::i; -use itertools::Itertools; - -use super::context::ParseCtx; -use super::errors::{BadCodePoint, BadEscapeSequence, NoStringEnd, NotHex, ParseErrorKind}; -#[allow(unused)] // for doc -use super::lex_plugin::LexerPlugin; -use super::lexer::{Entry, LexRes, Lexeme}; -use crate::error::{ProjectErrorObj, ProjectResult}; -use crate::foreign::atom::AtomGenerator; -use crate::foreign::inert::Inert; -use crate::libs::std::string::OrcString; - -/// Reasons why [parse_string] might fail. See [StringError] -pub enum StringErrorKind { - /// A unicode escape sequence wasn't followed by 4 hex digits - NotHex, - /// A unicode escape sequence contained an unassigned code point - BadCodePoint, - /// An unrecognized escape sequence was found - BadEscSeq, -} - -/// Error produced by [parse_string] -pub struct StringError { - /// Character where the error occured - pos: usize, - /// Reason for the error - kind: StringErrorKind, -} - -impl StringError { - /// Convert into project error for reporting - pub fn to_proj(self, ctx: &dyn ParseCtx, pos: usize) -> ProjectErrorObj { - let start = pos + self.pos; - let location = ctx.range_loc(&(start..start + 1)); - match self.kind { - StringErrorKind::NotHex => NotHex.pack(location), - StringErrorKind::BadCodePoint => BadCodePoint.pack(location), - StringErrorKind::BadEscSeq => BadEscapeSequence.pack(location), - } - } -} - -/// Process escape sequences in a string literal -pub fn parse_string(str: &str) -> Result { - let mut target = String::new(); - let mut iter = str.char_indices(); - while let Some((_, c)) = iter.next() { - if c != '\\' { - target.push(c); - continue; - } - let (mut pos, code) = iter.next().expect("lexer would have continued"); - let next = match code { - c @ ('\\' | '/' | '"') => c, - 'b' => '\x08', - 'f' => '\x0f', - 'n' => '\n', - 'r' => '\r', - 't' => '\t', - '\n' => 'skipws: loop { - match iter.next() { - None => return Ok(target), - Some((_, c)) => - if !c.is_whitespace() { - break 'skipws c; - }, - } - }, - 'u' => { - let acc = ((0..4).rev()) - .map(|radical| { - let (j, c) = (iter.next()).ok_or(StringError { pos, kind: StringErrorKind::NotHex })?; - pos = j; - let b = u32::from_str_radix(&String::from(c), 16) - .map_err(|_| StringError { pos, kind: StringErrorKind::NotHex })?; - Ok(16u32.pow(radical) + b) - }) - .fold_ok(0, u32::wrapping_add)?; - char::from_u32(acc).ok_or(StringError { pos, kind: StringErrorKind::BadCodePoint })? - }, - _ => return Err(StringError { pos, kind: StringErrorKind::BadEscSeq }), - }; - target.push(next); - } - Ok(target) -} - -/// [LexerPlugin] for a string literal. -pub struct StringLexer; -impl LexerPlugin for StringLexer { - fn lex<'b>( - &self, - req: &'_ dyn super::lex_plugin::LexPluginReq<'b>, - ) -> Option>> { - req.tail().strip_prefix('"').map(|data| { - let mut leftover = data; - return loop { - let (inside, outside) = (leftover.split_once('"')) - .ok_or_else(|| NoStringEnd.pack(req.ctx().source_range(data.len(), "")))?; - let backslashes = inside.chars().rev().take_while(|c| *c == '\\').count(); - if backslashes % 2 == 0 { - // cut form tail to recoup what string_content doesn't have - let (string_data, tail) = data.split_at(data.len() - outside.len() - 1); - let tail = &tail[1..]; // push the tail past the end quote - let string = - parse_string(string_data).map_err(|e| e.to_proj(req.ctx(), req.ctx().pos(data)))?; - let output = Inert(OrcString::from(i(&string))); - let ag = AtomGenerator::cloner(output); - let range = req.ctx().range(string_data.len(), tail); - let entry = Entry { lexeme: Lexeme::Atom(ag), range }; - break Ok(LexRes { tokens: vec![entry], tail }); - } else { - leftover = outside; - } - }; - }) - } -} - -#[cfg(test)] -mod test { - use super::StringLexer; - use crate::foreign::inert::Inert; - use crate::libs::std::string::OrcString; - use crate::parse::context::MockContext; - use crate::parse::lex_plugin::{LexPlugReqImpl, LexerPlugin}; - use crate::parse::lexer::{Entry, Lexeme}; - - #[test] - fn plain_string() { - let source = r#""hello world!" - says the programmer"#; - let req = LexPlugReqImpl { ctx: &MockContext, tail: source }; - let res = (StringLexer.lex(&req)) - .expect("the snippet starts with a quote") - .expect("it contains a valid string"); - let ag = match &res.tokens[..] { - [Entry { lexeme: Lexeme::Atom(ag), .. }] => ag, - _ => panic!("Expected a single atom"), - }; - let atom = ag.run().try_downcast::>().expect("Lexed to inert"); - assert_eq!(atom.0.as_str(), "hello world!"); - assert_eq!(res.tail, " - says the programmer"); - } -} diff --git a/src/pipeline/dealias/resolve_aliases.rs b/src/pipeline/dealias/resolve_aliases.rs index f77137b..604c4c4 100644 --- a/src/pipeline/dealias/resolve_aliases.rs +++ b/src/pipeline/dealias/resolve_aliases.rs @@ -5,8 +5,8 @@ use intern_all::Tok; use itertools::Itertools; use super::walk_with_links::walk_with_links; -use crate::error::{ErrorPosition, ProjectError, ProjectResult}; -use crate::location::CodeLocation; +use crate::error::{ErrorPosition, ErrorSansOrigin, ProjectError, Reporter}; +use crate::location::SourceRange; use crate::name::{Sym, VPath}; use crate::parse::parsed::Expr; use crate::pipeline::project::{ @@ -17,26 +17,19 @@ use crate::utils::pure_seq::with_pushed; #[derive(Clone)] struct NotFound { - location: CodeLocation, last_stop: VPath, bad_step: Tok, } -impl ProjectError for NotFound { +impl ErrorSansOrigin for NotFound { const DESCRIPTION: &'static str = "A path pointed out of the tree"; - fn message(&self) -> String { - format!("{} doesn't contain {}", self.last_stop, self.bad_step) - } - fn one_position(&self) -> CodeLocation { self.location.clone() } + fn message(&self) -> String { format!("{} doesn't contain {}", self.last_stop, self.bad_step) } } -struct NameErrors(Vec); +struct NameErrors(Vec<(NotFound, SourceRange)>); impl ProjectError for NameErrors { const DESCRIPTION: &'static str = "Some symbols were missing"; fn positions(&self) -> impl IntoIterator { - self.0.clone().into_iter().map(|nf| ErrorPosition { - location: nf.one_position(), - message: Some(nf.message()), - }) + self.0.iter().map(|(nf, sr)| ErrorPosition { origin: sr.origin(), message: Some(nf.message()) }) } } @@ -44,17 +37,16 @@ fn resolve_name( name: Sym, root: &ProjectMod, path: &[Tok], - location: CodeLocation, env: &Module, -) -> Result, NotFound> { - let full_name = path.iter().chain(&name[..]).cloned().collect_vec(); +) -> Result { + let full_name = path.iter().cloned().chain(&name[..]).collect_vec(); match walk_with_links(root, full_name.clone().into_iter()) { - Ok(rep) => Ok(Some(rep.abs_path.to_sym())), + Ok(rep) => Ok(rep.abs_path.to_sym()), Err(mut e) => match e.tail.next() { // If it got stuck on the very last step, allow it through for // now in case it is a binding. If the name doesn't get bound, by // macros it will be raised at the postmacro check. - None => Ok(Some(e.consumed_path().to_sym())), + None => Ok(e.consumed_path().to_sym()), Some(step) => { // If there's more, rebuild the last full path after redirects and // try to resolve it on the env tree. The env tree doesn't contain @@ -67,14 +59,8 @@ fn resolve_name( .collect_vec(); let valid_in_env = env.walk1_ref(&[], &fallback_path, |_| true).is_ok(); match valid_in_env { - false => Err(NotFound { - location, - last_stop: VPath(e.abs_path), - bad_step: e.name, - }), - true => Ok(e.aliased.then(|| { - Sym::new(fallback_path).expect("Not empty by construction") - })), + false => Err(NotFound { last_stop: VPath(e.abs_path), bad_step: e.name }), + true => Ok(Sym::new(fallback_path).expect("Not empty by construction")), } }, }, @@ -85,16 +71,14 @@ fn process_expr( expr: &Expr, root: &ProjectMod, path: &[Tok], - errors: &mut Vec, env: &Module, + reporter: &Reporter, ) -> Expr { - let location = CodeLocation::Source(expr.range.clone()); expr .map_names(&mut |n| { - resolve_name(n, root, path, location.clone(), env).unwrap_or_else(|e| { - errors.push(e); - None - }) + resolve_name(n, root, path, env) + .inspect_err(|e| reporter.report(e.clone().bundle(&expr.range.origin()))) + .ok() }) .unwrap_or_else(|| expr.clone()) } @@ -104,20 +88,16 @@ fn resolve_aliases_rec( path: &mut Vec>, module: &ProjectMod, env: &Module, -) -> ProjectResult { - let mut errors = Vec::new(); + reporter: &Reporter, +) -> ProjectMod { let module = Module { x: ProjXMod { src: module.x.src.as_ref().map(|s| SourceModule { range: s.range.clone(), rules: (s.rules.iter()) .map(|ProjRule { pattern, prio, template, comments }| ProjRule { - pattern: (pattern.iter()) - .map(|e| process_expr(e, root, path, &mut errors, env)) - .collect(), - template: (template.iter()) - .map(|e| process_expr(e, root, path, &mut errors, env)) - .collect(), + pattern: pattern.iter().map(|e| process_expr(e, root, path, env, reporter)).collect(), + template: template.iter().map(|e| process_expr(e, root, path, env, reporter)).collect(), comments: comments.clone(), prio: *prio, }) @@ -125,8 +105,8 @@ fn resolve_aliases_rec( }), }, entries: (module.entries.iter()) - .map(|(k, v)| -> ProjectResult<_> { - Ok((k.clone(), ModEntry { + .map(|(k, v)| { + (k.clone(), ModEntry { x: ProjXEnt { exported: v.x.exported, comments: v.x.comments.clone(), @@ -134,41 +114,41 @@ fn resolve_aliases_rec( }, member: match &v.member { ModMember::Sub(module) => { - let m = with_pushed(path, k.clone(), |p| { - resolve_aliases_rec(root, p, module, env) + let (_, m) = with_pushed(path, k.clone(), |p| { + resolve_aliases_rec(root, p, module, env, reporter) }); - ModMember::Sub(m.1?) + ModMember::Sub(m) }, ModMember::Item(item) => ModMember::Item(ProjItem { kind: match &item.kind { ItemKind::Const(v) => { - let v = process_expr(v, root, path, &mut errors, env); + let v = process_expr(v, root, path, env, reporter); ItemKind::Const(v) }, - ItemKind::Alias(n) => { - let location = (v.x.locations.first().cloned()) - .expect("Aliases are never created without source code"); - // this is an absolute path so we set the path to empty - match resolve_name(n.clone(), root, &[], location, env) { - Ok(Some(n)) => ItemKind::Alias(n), - Ok(None) => ItemKind::Alias(n.clone()), - Err(e) => return Err(e.pack()), - } + // this is an absolute path so we set the path to empty + ItemKind::Alias(n) => match resolve_name(n.clone(), root, &[], env) { + Ok(n) => ItemKind::Alias(n), + Err(e) => { + let location = v.x.locations.first().expect("Aliases always have a location"); + reporter.report(e.bundle(&location.origin)); + ItemKind::Alias(n.clone()) + }, }, _ => item.kind.clone(), }, }), }, - })) + }) }) - .collect::, _>>()?, + .collect::>(), }; - errors.is_empty().then_some(module).ok_or_else(|| NameErrors(errors).pack()) + module } pub fn resolve_aliases( project: ProjectMod, env: &Module, -) -> ProjectResult { - resolve_aliases_rec(&project, &mut Vec::new(), &project, env) + reporter: &Reporter, +) -> ProjectMod { + resolve_aliases_rec(&project, &mut Vec::new(), &project, env, reporter) } diff --git a/src/pipeline/dealias/walk_with_links.rs b/src/pipeline/dealias/walk_with_links.rs index ec72188..16bb878 100644 --- a/src/pipeline/dealias/walk_with_links.rs +++ b/src/pipeline/dealias/walk_with_links.rs @@ -1,15 +1,14 @@ -use intern_all::Tok; +use intern_all::{i, Tok}; use crate::name::{VName, VPath}; use crate::pipeline::project::{ItemKind, ProjectMemberRef, ProjectMod}; use crate::tree::ModMember; -use crate::utils::boxed_iter::BoxedIter; +use crate::utils::boxed_iter::{box_chain, BoxedIter}; use crate::utils::unwrap_or::unwrap_or; pub struct WalkReport<'a> { pub target: ProjectMemberRef<'a>, pub abs_path: VName, - pub aliased: bool, } pub struct LinkWalkError<'a> { @@ -19,52 +18,57 @@ pub struct LinkWalkError<'a> { pub name: Tok, /// Leftover steps pub tail: BoxedIter<'a, Tok>, - /// Whether an alias was ever encountered - pub aliased: bool, } impl<'a> LinkWalkError<'a> { - pub fn consumed_path(self) -> VName { - VPath::new(self.abs_path).as_prefix_of(self.name) - } + pub fn consumed_path(self) -> VName { VPath::new(self.abs_path).name_with_prefix(self.name) } } -fn walk_with_links_rec<'a, 'b>( +fn walk_with_links_rec<'a: 'b, 'b>( mut abs_path: Vec>, root: &'a ProjectMod, cur: &'a ProjectMod, prev_tgt: ProjectMemberRef<'a>, - aliased: bool, mut path: impl Iterator> + 'b, ) -> Result, LinkWalkError<'b>> { let name = match path.next() { + Some(sup) if sup == i!(str: "super") => { + if abs_path.pop().is_none() { + return Err(LinkWalkError { abs_path, name: sup, tail: Box::new(path) }); + } + let path_acc = Vec::with_capacity(abs_path.len()); + let new_path = box_chain!(abs_path.into_iter(), path); + let tgt = ProjectMemberRef::Mod(root); + return walk_with_links_rec(path_acc, root, root, tgt, new_path); + }, + Some(sup) if sup == i!(str: "self") => { + let tgt = ProjectMemberRef::Mod(cur); + return walk_with_links_rec(abs_path, root, cur, tgt, path); + }, Some(name) => name, // ends on this module None => { let abs_path = VName::new(abs_path).expect("Aliases are never empty"); - return Ok(WalkReport { target: prev_tgt, abs_path, aliased }); + return Ok(WalkReport { target: prev_tgt, abs_path }); }, }; let entry = unwrap_or! {cur.entries.get(&name); { // leads into a missing branch - return Err(LinkWalkError{ abs_path, aliased, name, tail: Box::new(path) }) + return Err(LinkWalkError{ abs_path, name, tail: Box::new(path) }) }}; match &entry.member { ModMember::Sub(m) => { // leads into submodule abs_path.push(name); let tgt = ProjectMemberRef::Mod(m); - walk_with_links_rec(abs_path, root, m, tgt, aliased, path) + walk_with_links_rec(abs_path, root, m, tgt, path) }, ModMember::Item(item) => match &item.kind { ItemKind::Alias(alias) => { // leads into alias (reset acc, cur, cur_entry) - abs_path.clear(); - abs_path.extend_from_slice(&alias[..]); - abs_path.extend(path); - let path_acc = Vec::with_capacity(abs_path.len()); - let new_path = abs_path.into_iter(); + let path_acc = Vec::with_capacity(alias.len()); + let new_path = box_chain!(alias.iter(), path); let tgt = ProjectMemberRef::Mod(root); - walk_with_links_rec(path_acc, root, root, tgt, true, new_path) + walk_with_links_rec(path_acc, root, root, tgt, new_path) }, ItemKind::Const(_) | ItemKind::None => { abs_path.push(name); @@ -72,13 +76,13 @@ fn walk_with_links_rec<'a, 'b>( Some(name) => { // leads into leaf let tail = Box::new(path); - Err(LinkWalkError { abs_path, aliased, name, tail }) + Err(LinkWalkError { abs_path, name, tail }) }, None => { // ends on leaf let target = ProjectMemberRef::Item(item); let abs_path = VName::new(abs_path).expect("pushed just above"); - Ok(WalkReport { target, abs_path, aliased }) + Ok(WalkReport { target, abs_path }) }, } }, @@ -89,13 +93,13 @@ fn walk_with_links_rec<'a, 'b>( /// Execute a walk down the tree, following aliases. /// If the path ends on an alias, that alias is also resolved. /// If the path leads out of the tree, the shortest failing path is returned -pub fn walk_with_links<'a>( - root: &ProjectMod, - path: impl Iterator> + 'a, -) -> Result, LinkWalkError<'a>> { +pub fn walk_with_links<'a: 'b, 'b>( + root: &'a ProjectMod, + path: impl Iterator> + 'b, +) -> Result, LinkWalkError<'b>> { let path_acc = path.size_hint().1.map_or_else(Vec::new, Vec::with_capacity); let tgt = ProjectMemberRef::Mod(root); - let mut result = walk_with_links_rec(path_acc, root, root, tgt, false, path); + let mut result = walk_with_links_rec(path_acc, root, root, tgt, path); // cut off excess preallocated space within normal vector growth policy let abs_path = match &mut result { Ok(rep) => rep.abs_path.vec_mut(), diff --git a/src/pipeline/load_solution.rs b/src/pipeline/load_project.rs similarity index 67% rename from src/pipeline/load_solution.rs rename to src/pipeline/load_project.rs index 2dffe87..52915de 100644 --- a/src/pipeline/load_solution.rs +++ b/src/pipeline/load_project.rs @@ -2,7 +2,6 @@ //! following the imports use std::collections::VecDeque; -use std::sync::Arc; use hashbrown::{HashMap, HashSet}; use intern_all::{sweep_t, Tok}; @@ -10,9 +9,9 @@ use intern_all::{sweep_t, Tok}; use super::dealias::resolve_aliases::resolve_aliases; use super::process_source::{process_ns, resolve_globs, GlobImports}; use super::project::{ItemKind, ProjItem, ProjXEnt, ProjectMod, ProjectTree}; -use crate::error::{bundle_location, ErrorPosition, ProjectError, ProjectResult}; -use crate::location::{CodeGenInfo, CodeLocation, SourceCode, SourceRange}; -use crate::name::{PathSlice, Sym, VName, VPath}; +use crate::error::{ErrorPosition, ProjectError, Reporter}; +use crate::location::{CodeGenInfo, CodeOrigin, SourceCode, SourceRange}; +use crate::name::{NameLike, PathSlice, Sym, VName, VPath}; use crate::parse::context::ParseCtxImpl; use crate::parse::facade::parse_file; use crate::parse::lex_plugin::LexerPlugin; @@ -20,7 +19,7 @@ use crate::parse::parse_plugin::ParseLinePlugin; use crate::tree::{ModEntry, ModMember, Module}; use crate::utils::combine::Combine; use crate::utils::sequence::Sequence; -use crate::virt_fs::{Loaded, VirtFS}; +use crate::virt_fs::{DeclTree, Loaded, VirtFS}; // apply layer: // 1. build trees @@ -36,11 +35,14 @@ use crate::virt_fs::{Loaded, VirtFS}; // would also solve some weird accidental private member aliasing issues /// Split off the longest prefix accepted by the validator -fn split_max_prefix<'a, T>( +fn split_max_prefix<'a, T, U>( path: &'a [T], - is_valid: &impl Fn(&[T]) -> bool, -) -> Option<(&'a [T], &'a [T])> { - (0..=path.len()).rev().map(|i| path.split_at(i)).find(|(file, _)| is_valid(file)) + is_valid: &impl Fn(&[T]) -> Option, +) -> Option<(&'a [T], &'a [T], U)> { + (0..=path.len()) + .rev() + .map(|i| path.split_at(i)) + .find_map(|(file, dir)| Some((file, dir, is_valid(file)?))) } /// Represents a prelude / implicit import requested by a library. @@ -58,19 +60,22 @@ pub struct Prelude { /// Hooks and extensions to the source loading process #[derive(Clone)] -pub struct SolutionContext<'a> { +pub struct ProjectContext<'a, 'b> { /// Callbacks from the lexer to support literals of custom datatypes pub lexer_plugins: Sequence<'a, &'a (dyn LexerPlugin + 'a)>, /// Callbacks from the parser to support custom module tree elements pub line_parsers: Sequence<'a, &'a (dyn ParseLinePlugin + 'a)>, /// Lines prepended to various modules to import "global" values pub preludes: Sequence<'a, &'a Prelude>, + /// Error aggregator + pub reporter: &'b Reporter, } -impl<'a> SolutionContext<'a> { +impl<'a, 'b> ProjectContext<'a, 'b> { /// Derive context for the parser - pub fn parsing(&self, code: SourceCode) -> ParseCtxImpl<'a> { + pub fn parsing<'c>(&'c self, code: SourceCode) -> ParseCtxImpl<'a, 'b> { ParseCtxImpl { code, + reporter: self.reporter, lexers: self.lexer_plugins.clone(), line_parsers: self.line_parsers.clone(), } @@ -81,49 +86,40 @@ impl<'a> SolutionContext<'a> { /// specified targets and following imports. An in-memory environment tree is /// used to allow imports from modules that are defined by other loading steps /// and later merged into this source code. -pub fn load_solution( - ctx: SolutionContext, - targets: impl IntoIterator, +pub fn load_project( + ctx: &ProjectContext<'_, '_>, + targets: impl IntoIterator, env: &Module, - fs: &impl VirtFS, -) -> ProjectResult { - let mut target_queue = VecDeque::<(Sym, CodeLocation)>::new(); - target_queue.extend(targets.into_iter()); - target_queue - .extend((ctx.preludes.iter()).map(|p| (p.target.to_sym(), CodeLocation::Gen(p.owner.clone())))); + fs: &DeclTree, +) -> ProjectTree { + let mut queue = VecDeque::<(Sym, CodeOrigin)>::new(); + queue.extend(targets.into_iter()); + queue.extend(ctx.preludes.iter().map(|p| (p.target.to_sym(), CodeOrigin::Gen(p.owner.clone())))); let mut known_files = HashSet::new(); let mut tree_acc: ProjectMod = Module::wrap([]); let mut glob_acc: GlobImports = Module::wrap([]); - while let Some((target, referrer)) = target_queue.pop_front() { - let path_parts = split_max_prefix(&target[..], &|p| { - fs.read(PathSlice(p)).map(|l| l.is_code()).unwrap_or(false) + while let Some((target, referrer)) = queue.pop_front() { + let path_parts = split_max_prefix(&target, &|p| match fs.read(PathSlice::new(p)) { + Ok(Loaded::Code(c)) => Some(c), + _ => None, }); - if let Some((filename, _)) = path_parts { - if known_files.contains(filename) { + 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(filename.to_vec()); - let path = VPath(filename.to_vec()); - let loaded = fs.read(PathSlice(filename)).map_err(|e| bundle_location(&referrer, &*e))?; - let code = match loaded { - Loaded::Collection(_) => return Err(UnexpectedDirectory { path }.pack()), - Loaded::Code(source) => SourceCode { source, path: Arc::new(path) }, - }; - let full_range = SourceRange { range: 0..code.source.len(), code: code.clone() }; - let lines = parse_file(&ctx.parsing(code.clone()))?; - let report = process_ns(code.path, lines, full_range)?; - target_queue.extend( - (report.external_references.into_iter()).map(|(k, v)| (k, CodeLocation::Source(v))), - ); - if !report.comments.is_empty() && filename.is_empty() { - todo!("panic - module op comments on root are lost") - } + known_files.insert(path); + 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); + 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; let mut glob = report.glob_imports; - for i in (0..filename.len()).rev() { + for i in (0..file.len()).rev() { // i over valid indices of filename - let key = filename[i].clone(); // last segment + let key = file[i].clone(); // last segment let comments = comments.take().into_iter().flatten().collect(); glob = Module::wrap([(key.clone(), ModEntry::wrap(ModMember::Sub(glob)))]); module = Module::wrap([(key, ModEntry { @@ -134,30 +130,23 @@ pub fn load_solution( glob_acc = (glob_acc.combine(glob)).expect("source code loaded for two nested paths"); tree_acc = (tree_acc.combine(module)).expect("source code loaded for two nested paths"); } else { - known_files.insert(target[..].to_vec()); + known_files.insert(target.clone()); // If the path is not within a file, load it as directory - match fs.read(target.as_path_slice()) { - Ok(Loaded::Collection(c)) => target_queue.extend(c.iter().map(|e| { - let name = VPath::new(target.iter()).as_prefix_of(e.clone()).to_sym(); - (name, referrer.clone()) + match fs.read(&target) { + Ok(Loaded::Collection(c)) => queue.extend(c.iter().map(|e| { + (VPath::new(target.iter()).name_with_prefix(e.clone()).to_sym(), referrer.clone()) })), Ok(Loaded::Code(_)) => unreachable!("Should have split to self and []"), // Ignore error if the path is walkable in the const tree Err(_) if env.walk1_ref(&[], &target[..], |_| true).is_ok() => (), - Err(e) => return Err(bundle_location(&referrer, &*e)), + // Otherwise raise error + Err(e) => ctx.reporter.report(e.bundle(&referrer)), } } } let mut contention = HashMap::new(); - resolve_globs( - VPath(vec![]), - glob_acc, - ctx.preludes.clone(), - &mut tree_acc, - env, - &mut contention, - )?; - let ret = resolve_aliases(tree_acc, env)?; + resolve_globs(glob_acc, ctx, &mut tree_acc, env, &mut contention); + let ret = resolve_aliases(tree_acc, env, ctx.reporter); for ((glob, original), locations) in contention { let (glob_val, _) = ret.walk1_ref(&[], &glob[..], |_| true).expect("Should've emerged in dealias"); @@ -174,13 +163,13 @@ pub fn load_solution( if glob_real != original_real { let real = original_real.clone(); let glob_real = glob_real.clone(); - let err = ConflictingGlobs { real, glob_real, original, glob, locations }; - return Err(err.pack()); + let err = ConflictingGlobs { real, glob_real, original, glob, origins: locations }; + ctx.reporter.report(err.pack()); } } sweep_t::(); sweep_t::>>(); - Ok(ProjectTree(ret)) + ProjectTree(ret) } /// Produced when a stage that deals specifically with code encounters @@ -203,7 +192,7 @@ struct ConflictingGlobs { real: Sym, glob: Sym, glob_real: Sym, - locations: Vec, + origins: Vec, } impl ProjectError for ConflictingGlobs { const DESCRIPTION: &'static str = "A symbol from a glob import conflicts with an existing name"; @@ -215,6 +204,6 @@ impl ProjectError for ConflictingGlobs { ) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.origins.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) } } diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index e6244ff..6ef2006 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -1,6 +1,6 @@ //! Loading Orchid projects from source mod dealias; -pub mod load_solution; +pub mod load_project; mod path; mod process_source; pub mod project; diff --git a/src/pipeline/path.rs b/src/pipeline/path.rs index a0ffee2..1abbcf0 100644 --- a/src/pipeline/path.rs +++ b/src/pipeline/path.rs @@ -1,7 +1,6 @@ use intern_all::{i, Tok}; -use crate::error::{ProjectError, ProjectResult}; -use crate::location::CodeLocation; +use crate::error::{ErrorSansOrigin, ResultSansOrigin}; use crate::name::VName; /// Turn a relative (import) path into an absolute path. @@ -11,42 +10,31 @@ use crate::name::VName; /// /// if the relative path contains as many or more `super` segments than the /// length of the absolute path. -pub(super) fn absolute_path( - abs_location: &[Tok], - rel_path: &[Tok], - location: CodeLocation, -) -> ProjectResult { - match absolute_path_rec(abs_location, rel_path) { - Some(v) => VName::new(v).map_err(|_| ImportAll { location }.pack()), - None => { - let path = rel_path.try_into().expect("At least one super"); - Err(TooManySupers { path, location }.pack()) - }, - } +pub(super) fn absolute_path(cwd: &[Tok], rel: &[Tok]) -> ResultSansOrigin { + absolute_path_rec(cwd, rel) + .ok_or_else(|| TooManySupers { path: rel.try_into().expect("At least one super") }.pack()) + .and_then(|v| VName::new(v).map_err(|_| ImportAll.pack())) } #[must_use = "this could be None which means that there are too many supers"] -fn absolute_path_rec( - mut abs_location: &[Tok], - mut rel_path: &[Tok], -) -> Option>> { +fn absolute_path_rec(mut cwd: &[Tok], mut rel: &[Tok]) -> Option>> { let mut relative = false; - if rel_path.first().cloned() == Some(i("self")) { + if rel.first().cloned() == Some(i!(str: "self")) { relative = true; - rel_path = rel_path.split_first().expect("checked above").1; + rel = rel.split_first().expect("checked above").1; } else { - while rel_path.first().cloned() == Some(i("super")) { - match abs_location.split_last() { - Some((_, torso)) => abs_location = torso, + while rel.first().cloned() == Some(i!(str: "super")) { + match cwd.split_last() { + Some((_, torso)) => cwd = torso, None => return None, }; - rel_path = rel_path.split_first().expect("checked above").1; + rel = rel.split_first().expect("checked above").1; relative = true; } } match relative { - true => Some(abs_location.iter().chain(rel_path).cloned().collect()), - false => Some(rel_path.to_vec()), + true => Some(cwd.iter().chain(rel).cloned().collect()), + false => Some(rel.to_vec()), } } @@ -56,27 +44,16 @@ fn absolute_path_rec( struct TooManySupers { /// The offending import path pub path: VName, - /// The faulty import statement - pub location: CodeLocation, } -impl ProjectError for TooManySupers { +impl ErrorSansOrigin for TooManySupers { const DESCRIPTION: &'static str = "an import path starts with more \ `super` segments than the current module's absolute path"; - fn message(&self) -> String { - format!("path {} contains too many `super` steps.", self.path) - } - - fn one_position(&self) -> CodeLocation { self.location.clone() } + fn message(&self) -> String { format!("path {} contains too many `super` steps.", self.path) } } /// Error produced for the statement `import *` #[derive(Clone, Debug, PartialEq, Eq, Hash)] -struct ImportAll { - /// The file containing the offending import - pub location: CodeLocation, -} -impl ProjectError for ImportAll { - const DESCRIPTION: &'static str = "a top-level glob import was used"; - fn message(&self) -> String { format!("{} imports *", self.location) } - fn one_position(&self) -> CodeLocation { self.location.clone() } +struct ImportAll; +impl ErrorSansOrigin for ImportAll { + const DESCRIPTION: &'static str = "`import *` is forbidden"; } diff --git a/src/pipeline/process_source.rs b/src/pipeline/process_source.rs index 9af4d54..dae5606 100644 --- a/src/pipeline/process_source.rs +++ b/src/pipeline/process_source.rs @@ -6,13 +6,13 @@ use intern_all::Tok; use itertools::Itertools; use never::Never; -use super::load_solution::Prelude; +use super::load_project::{Prelude, ProjectContext}; use super::path::absolute_path; use super::project::{ ItemKind, ProjItem, ProjRule, ProjXEnt, ProjXMod, ProjectEntry, ProjectMod, SourceModule, }; -use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, ProjectResult}; -use crate::location::{CodeLocation, SourceRange}; +use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj, Reporter}; +use crate::location::{CodeLocation, CodeOrigin, SourceRange}; use crate::name::{Sym, VName, VPath}; use crate::parse::parsed::{ Constant, Member, MemberKind, ModuleBlock, Rule, SourceLine, SourceLineKind, @@ -20,9 +20,7 @@ use crate::parse::parsed::{ use crate::tree::{ModEntry, ModMember, Module, WalkError}; use crate::utils::combine::Combine; use crate::utils::get_or::get_or_make; -use crate::utils::pure_seq::pushed_ref; use crate::utils::sequence::Sequence; -use crate::utils::unwrap_or::unwrap_or; // Problem: import normalization // @@ -54,7 +52,7 @@ pub(super) type GlobImports = Module; pub(super) struct FileReport { /// Absolute path of values outside the file - pub external_references: HashMap, + pub ext_refs: HashMap, pub comments: Vec>, pub module: ProjectMod, pub glob_imports: GlobImports, @@ -68,10 +66,11 @@ fn default_entry() -> ProjectEntry { } pub(super) fn process_ns( - path: Arc, + path: Sym, lines: Vec, ns_location: SourceRange, -) -> ProjectResult { + reporter: &Reporter, +) -> FileReport { let mut file_comments = Vec::new(); let mut new_comments = Vec::new(); let mut entries = HashMap::new(); @@ -84,44 +83,40 @@ pub(super) fn process_ns( SourceLineKind::Comment(comment) => new_comments.push(Arc::new(comment)), SourceLineKind::Export(names) => { let comments = (names.len() == 1).then(|| mem::take(&mut new_comments)); - for (name, name_location) in names { + for (name, name_loc) in names { let entry = get_or_make(&mut entries, &name, default_entry); - let location = CodeLocation::Source(name_location); + entry.x.locations.push(CodeLocation::new_src(name_loc, path.clone())); if entry.x.exported { - let err = MultipleExports { - path: (*path).clone().as_prefix_of(name).to_sym(), - locations: pushed_ref(&entry.x.locations, location), - }; - return Err(err.pack()); + reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); } entry.x.exported = true; entry.x.comments.extend(comments.iter().flatten().cloned()); - entry.x.locations.push(location); } }, SourceLineKind::Import(imports) => { file_comments.append(&mut new_comments); for import in imports { let nonglob_path = import.nonglob_path(); - let location = CodeLocation::Source(range.clone()); - let abs = absolute_path(&path[..], &nonglob_path[..], location)?; - if !abs[..].starts_with(&path[..]) { - external_references.insert(abs.to_sym(), import.range.clone()); - } - match import.name { - None => (glob_imports.x.0).push(GlobImpReport { target: abs, location: import.range }), - Some(key) => { - let entry = get_or_make(&mut entries, &key, default_entry); - entry.x.locations.push(CodeLocation::Source(import.range)); - match &mut entry.member { - ModMember::Item(ProjItem { kind: old @ ItemKind::None }) => - *old = ItemKind::Alias(abs.to_sym()), - _ => { - let err = MultipleDefinitions { - path: (*path).clone().as_prefix_of(key).to_sym(), - locations: entry.x.locations.clone(), - }; - return Err(err.pack()); + let origin = CodeOrigin::Source(range.clone()); + match absolute_path(&path[..], &nonglob_path[..]) { + Err(e) => reporter.report(e.bundle(&origin)), + Ok(abs) => { + if !abs[..].starts_with(&path[..]) { + external_references.insert(abs.to_sym(), import.range.clone()); + } + match import.name { + None => + (glob_imports.x.0).push(GlobImpReport { target: abs, location: import.range }), + Some(name) => { + let entry = get_or_make(&mut entries, &name, default_entry); + entry.x.locations.push(CodeLocation::new_src(import.range, path.clone())); + if let ModMember::Item(ProjItem { kind: old @ ItemKind::None }) = + &mut entry.member + { + *old = ItemKind::Alias(abs.to_sym()) + } else { + reporter.report(MultipleDefinitions::new(path.clone(), name, entry).pack()); + } }, } }, @@ -131,17 +126,11 @@ pub(super) fn process_ns( SourceLineKind::Member(Member { exported, kind }) => match kind { MemberKind::Constant(Constant { name, value }) => { let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::Source(range)); - match &mut entry.member { - ModMember::Item(ProjItem { kind: old @ ItemKind::None }) => - *old = ItemKind::Const(value), - _ => { - let err = MultipleDefinitions { - path: (*path).clone().as_prefix_of(name).to_sym(), - locations: entry.x.locations.clone(), - }; - return Err(err.pack()); - }, + entry.x.locations.push(CodeLocation::new_src(range, path.clone())); + if let ModMember::Item(ProjItem { kind: old @ ItemKind::None }) = &mut entry.member { + *old = ItemKind::Const(value) + } else { + reporter.report(MultipleDefinitions::new(path.clone(), name.clone(), entry).pack()); } entry.x.exported |= exported; entry.x.comments.append(&mut new_comments); @@ -151,13 +140,9 @@ pub(super) fn process_ns( new_comments = Vec::new(); for name in prule.collect_root_names() { let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::Source(range.clone())); + entry.x.locations.push(CodeLocation::new_src(range.clone(), path.clone())); if entry.x.exported && exported { - let err = MultipleExports { - path: (*path).clone().as_prefix_of(name).to_sym(), - locations: entry.x.locations.clone(), - }; - return Err(err.pack()); + reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); } entry.x.exported |= exported; } @@ -165,31 +150,26 @@ pub(super) fn process_ns( }, MemberKind::Module(ModuleBlock { name, body }) => { let entry = get_or_make(&mut entries, &name, default_entry); - entry.x.locations.push(CodeLocation::Source(range.clone())); + (entry.x.locations).push(CodeLocation::new_src(range.clone(), path.clone())); if !matches!(entry.member, ModMember::Item(ProjItem { kind: ItemKind::None })) { - let err = MultipleDefinitions { - path: (*path).clone().as_prefix_of(name).to_sym(), - locations: entry.x.locations.clone(), - }; - return Err(err.pack()); + reporter.report(MultipleDefinitions::new(path.clone(), name.clone(), entry).pack()); } if entry.x.exported && exported { - let err = MultipleExports { - path: (*path).clone().as_prefix_of(name).to_sym(), - locations: entry.x.locations.clone(), - }; - return Err(err.pack()); + reporter.report(MultipleExports::new(path.clone(), name.clone(), entry).pack()); } - let subpath = Arc::new(VPath(pushed_ref(&path.0, name.clone()))); - let report = process_ns(subpath, body, range)?; + let subpath = path.to_vname().suffix([name.clone()]).to_sym(); + let mut report = process_ns(subpath, body, range, reporter); entry.x.comments.append(&mut new_comments); entry.x.comments.extend(report.comments); entry.x.exported |= exported; + if let ModMember::Sub(module) = &entry.member { + // This is an error state. + report.module.entries.extend(module.entries.clone()); + } entry.member = ModMember::Sub(report.module); // record new external references - external_references.extend( - (report.external_references.into_iter()).filter(|(r, _)| !r[..].starts_with(&path.0)), - ); + external_references + .extend(report.ext_refs.into_iter().filter(|(r, _)| !r[..].starts_with(&path[..]))); // add glob_imports subtree to own tree glob_imports .entries @@ -198,22 +178,104 @@ pub(super) fn process_ns( }, } } - Ok(FileReport { - external_references, + FileReport { + ext_refs: external_references, comments: file_comments, glob_imports, module: Module { entries, x: ProjXMod { src: Some(SourceModule { range: ns_location, rules }) }, }, - }) + } } fn walk_at_path(e: WalkError, root: &ProjectMod, path: &[Tok]) -> ProjectErrorObj { let submod = (root.walk_ref(&[], path, |_| true)).expect("Invalid source path in walk error populator"); let src = submod.x.src.as_ref().expect("Import cannot appear in implied module"); - e.at(&CodeLocation::Source(src.range.clone())) + e.at(&src.range.origin()) +} + +pub fn resolve_globs_rec( + // must exist in project_root + path: VPath, + globtree: GlobImports, + preludes: Sequence<&Prelude>, + project_root: &mut ProjectMod, + env: &Module, + contention: &mut HashMap<(Sym, Sym), Vec>, + reporter: &Reporter, +) { + // All glob imports in this module + let all = + (globtree.x.0.into_iter()).map(|gir| (gir.target, CodeOrigin::Source(gir.location))).chain( + preludes + .iter() + .filter(|&pre| !path.0.starts_with(&pre.exclude[..])) + .map(|Prelude { target, owner, .. }| (target.clone(), CodeOrigin::Gen(owner.clone()))), + ); + if !path[..].is_empty() { + for (target, imp_loc) in all { + let pub_keys = match project_root.inner_walk(&path.0, &target[..], |e| e.x.exported) { + Err(e) => { + reporter.report(walk_at_path(e, project_root, &path.0)); + continue; + }, + Ok((ModEntry { member: ModMember::Item(_), .. }, parent)) => { + use crate::tree::ErrKind::NotModule; + let options = Sequence::new(|| parent.keys(|e| e.x.exported)); + let e = WalkError::last(&target[..], NotModule, options); + reporter.report(walk_at_path(e, project_root, &path.0)); + continue; + }, + // All public keys in this module and, if walkable, the environment. + Ok((ModEntry { member: ModMember::Sub(module), .. }, _)) => + (env.walk_ref(&[], &target[..], |_| true).into_iter()) + .flat_map(|m| m.keys(|_| true)) + .chain(module.keys(|e| e.x.exported)) + .collect_vec(), + }; + // Reference to the module to be modified + let mut_mod = path.0.iter().fold(&mut *project_root, |m, k| { + let entry = m.entries.get_mut(k).expect("this is a source path"); + if let ModMember::Sub(s) = &mut entry.member { s } else { panic!("This is a source path") } + }); + // Walk errors for the environment are suppressed because leaf-node + // conflicts will emerge when merging modules, and walking off the tree + // is valid. + for key in pub_keys { + let entry = get_or_make(&mut mut_mod.entries, &key, default_entry); + entry.x.locations.push(CodeLocation { + origin: imp_loc.clone(), + module: path.clone().into_name().expect("Checked above").to_sym(), + }); + let alias_tgt = target.clone().suffix([key.clone()]).to_sym(); + if let ModMember::Item(ProjItem { kind: kref @ ItemKind::None }) = &mut entry.member { + *kref = ItemKind::Alias(alias_tgt) + } else { + let local_name = path.clone().name_with_prefix(key.clone()).to_sym(); + contention.insert((alias_tgt, local_name), entry.x.origins().collect()); + } + } + } + } + for (key, entry) in globtree.entries { + match entry.member { + ModMember::Item(n) => match n {}, + ModMember::Sub(module) => { + resolve_globs_rec( + // Submodules in globtree must correspond to submodules in project + path.clone().suffix([key]), + module, + preludes.clone(), + project_root, + env, + contention, + reporter, + ) + }, + } + } } /// Resolve the glob tree separately produced by [process_ns] by looking up the @@ -221,95 +283,46 @@ fn walk_at_path(e: WalkError, root: &ProjectMod, path: &[Tok]) -> Projec /// them. Supports a prelude table which is applied to each module, and an /// environment whose keys are combined with those from within the [ProjectMod]. pub fn resolve_globs( - globtree_prefix: VPath, globtree: GlobImports, - preludes: Sequence<&Prelude>, + ctx: &ProjectContext, project_root: &mut ProjectMod, env: &Module, - contention: &mut HashMap<(Sym, Sym), Vec>, -) -> ProjectResult<()> { - // All glob imports in this module - let all = - (globtree.x.0.into_iter()).map(|gir| (gir.target, CodeLocation::Source(gir.location))).chain( - preludes - .iter() - .filter(|&pre| !globtree_prefix.0.starts_with(&pre.exclude[..])) - .map(|Prelude { target, owner, .. }| (target.clone(), CodeLocation::Gen(owner.clone()))), - ); - for (target, location) in all { - let (tgt, parent) = project_root - .inner_walk(&globtree_prefix.0, &target[..], |e| e.x.exported) - .map_err(|e| walk_at_path(e, project_root, &globtree_prefix.0))?; - match &tgt.member { - ModMember::Item(_) => { - use crate::tree::ErrKind::NotModule; - let options = Sequence::new(|| parent.keys(|e| e.x.exported)); - let e = WalkError::last(&target[..], NotModule, options); - return Err(walk_at_path(e, project_root, &globtree_prefix.0)); - }, - ModMember::Sub(module) => { - // All public keys in this module and, if walkable, the environment. - let pub_keys = (env.walk_ref(&[], &target[..], |_| true).into_iter()) - .flat_map(|m| m.keys(|_| true)) - .chain(module.keys(|e| e.x.exported)) - .collect_vec(); - // Reference to the module to be modified - let mut_mod = globtree_prefix.0.iter().fold(&mut *project_root, |m, k| { - let entry = m.entries.get_mut(k).expect("this is a source path"); - unwrap_or!(&mut entry.member => ModMember::Sub; { - panic!("This is also a source path") - }) - }); - // Walk errors for the environment are suppressed because leaf-node - // conflicts will emerge when merging modules, and walking off the tree - // is valid. - for key in pub_keys { - let entry = get_or_make(&mut mut_mod.entries, &key, default_entry); - entry.x.locations.push(location.clone()); - let alias_tgt = target.clone().suffix([key.clone()]).to_sym(); - if let ModMember::Item(ProjItem { kind: kref @ ItemKind::None }) = &mut entry.member { - *kref = ItemKind::Alias(alias_tgt) - } else { - let local_name = globtree_prefix.clone().as_prefix_of(key.clone()).to_sym(); - let locs = pushed_ref(&entry.x.locations, location.clone()); - contention.insert((alias_tgt, local_name), locs); - } - } - }, - } - } - for (key, entry) in globtree.entries { - match entry.member { - ModMember::Item(n) => match n {}, - ModMember::Sub(module) => { - let subpath = VPath(pushed_ref(&globtree_prefix.0, key)); - resolve_globs(subpath, module, preludes.clone(), project_root, env, contention)?; - }, - } - } - Ok(()) + contentions: &mut HashMap<(Sym, Sym), Vec>, +) { + let preludes = ctx.preludes.clone(); + resolve_globs_rec(VPath(vec![]), globtree, preludes, project_root, env, contentions, ctx.reporter) } struct MultipleExports { path: Sym, - locations: Vec, + locations: Vec, +} +impl MultipleExports { + fn new(mpath: Sym, name: Tok, entry: &'_ ProjectEntry) -> Self { + Self { path: mpath.to_vname().suffix([name]).to_sym(), locations: entry.x.origins().collect() } + } } impl ProjectError for MultipleExports { const DESCRIPTION: &'static str = "A symbol was exported in multiple places"; fn message(&self) -> String { format!("{} exported multiple times", self.path) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.locations.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) } } pub(super) struct MultipleDefinitions { pub(super) path: Sym, - pub(super) locations: Vec, + pub(super) locations: Vec, +} +impl MultipleDefinitions { + fn new(mpath: Sym, name: Tok, entry: &'_ ProjectEntry) -> Self { + Self { path: mpath.to_vname().suffix([name]).to_sym(), locations: entry.x.origins().collect() } + } } impl ProjectError for MultipleDefinitions { const DESCRIPTION: &'static str = "Symbol defined twice"; fn message(&self) -> String { format!("{} refers to multiple conflicting items", self.path) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).map(|l| ErrorPosition { location: l.clone(), message: None }) + (self.locations.iter()).map(|l| ErrorPosition { origin: l.clone(), message: None }) } } diff --git a/src/pipeline/project.rs b/src/pipeline/project.rs index 74fbd8f..33e1bb0 100644 --- a/src/pipeline/project.rs +++ b/src/pipeline/project.rs @@ -1,6 +1,6 @@ //! Datastructures used to define an Orchid project -use std::fmt::Display; +use std::fmt; use std::sync::Arc; use hashbrown::{HashMap, HashSet}; @@ -8,15 +8,13 @@ use intern_all::Tok; use itertools::Itertools; use never::Never; use ordered_float::NotNan; -use substack::Substack; -use crate::location::{CodeLocation, SourceRange}; -use crate::name::Sym; +use crate::location::{CodeLocation, CodeOrigin, SourceRange}; +use crate::name::{Sym, VPath}; use crate::parse::numeric::print_nat16; use crate::parse::parsed::{Clause, Expr}; -use crate::tree::{ModEntry, ModMember, Module, ModMemberRef}; +use crate::tree::{ModEntry, ModMember, ModMemberRef, Module, TreeTransforms}; use crate::utils::combine::Combine; -use crate::utils::unwrap_or::unwrap_or; /// Different elements that can appear in a module other than submodules #[derive(Debug, Clone)] @@ -45,8 +43,8 @@ impl Default for ProjItem { fn default() -> Self { Self { kind: ItemKind::None } } } -impl Display for ProjItem { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ProjItem { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.kind { ItemKind::None => write!(f, "keyword"), ItemKind::Const(c) => write!(f, "constant {c}"), @@ -78,15 +76,9 @@ pub struct ProjRule { impl ProjRule { /// Namespace all tokens in the rule #[must_use] - pub fn prefix( - self, - prefix: &[Tok], - except: &impl Fn(Tok) -> bool, - ) -> Self { + pub fn prefix(self, prefix: &[Tok], except: &impl Fn(Tok) -> bool) -> Self { let Self { comments, prio, mut pattern, mut template } = self; - (pattern.iter_mut()) - .chain(template.iter_mut()) - .for_each(|e| *e = e.prefix(prefix, except)); + (pattern.iter_mut()).chain(template.iter_mut()).for_each(|e| *e = e.prefix(prefix, except)); Self { prio, comments, pattern, template } } @@ -98,7 +90,7 @@ impl ProjRule { for e in self.pattern.iter() { e.search_all(&mut |e| { if let Clause::Name(ns_name) = &e.value { - names.extend(ns_name[..].iter().exactly_one().ok().cloned()) + names.extend(ns_name[..].iter().exactly_one().ok()) } None::<()> }); @@ -107,8 +99,8 @@ impl ProjRule { } } -impl Display for ProjRule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ProjRule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{}rule {} ={}=> {}", @@ -158,22 +150,23 @@ pub struct ProjXEnt { pub locations: Vec, } impl Default for ProjXEnt { - fn default() -> Self { - Self { comments: vec![], exported: true, locations: vec![] } - } + fn default() -> Self { Self { comments: vec![], exported: true, locations: vec![] } } } impl ProjXEnt { /// Implied modules can be merged easily. It's difficult to detect whether /// a module is implied so we just assert that it doesn't have an associated /// source location pub fn is_default(&self) -> bool { self.locations.is_empty() } + /// Take only the syntactic locations from the recorded location list for + /// error reporting + pub fn origins(&self) -> impl Iterator + '_ { + self.locations.iter().map(|l| l.origin.clone()) + } } impl Combine for ProjXEnt { type Error = MergingFiles; fn combine(self, other: Self) -> Result { - (self.is_default() && other.is_default()) - .then_some(self) - .ok_or(MergingFiles) + (self.is_default() && other.is_default()).then_some(self).ok_or(MergingFiles) } } @@ -183,8 +176,8 @@ impl Combine for ProjXEnt { #[derive(Debug, Clone, Copy, Default, Hash, PartialEq, Eq)] pub struct MergingFiles; -impl Display for ProjXMod { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ProjXMod { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut dbg = f.debug_struct("ProjectExt"); match &self.src { None => dbg.finish_non_exhaustive(), @@ -209,36 +202,6 @@ fn collect_rules_rec(bag: &mut Vec, module: &ProjectMod) { } } } -fn collect_consts_rec( - path: Substack>, - bag: &mut HashMap, - module: &ProjectMod, -) { - for (key, entry) in module.entries.iter() { - match &entry.member { - ModMember::Item(it) => - if let ItemKind::Const(expr) = &it.kind { - let name = path.push(key.clone()).iter().unreverse(); - let name = Sym::new(name).expect("pushed above"); - let location = - entry.x.locations.first().cloned().unwrap_or_else(|| { - panic!("{name} is a const in source, yet it has no location") - }); - let range = unwrap_or!(location => CodeLocation::Source; { - panic!("{name} is a const in source, yet its \ - location is generated") - }); - bag.insert(name, ConstReport { - comments: entry.x.comments.clone(), - value: expr.clone(), - range, - }); - }, - ModMember::Sub(module) => - collect_consts_rec(path.push(key.clone()), bag, module), - } - } -} /// Module corresponding to the root of a project #[derive(Debug, Clone)] @@ -256,7 +219,21 @@ impl ProjectTree { #[must_use] pub fn all_consts(&self) -> HashMap { let mut consts = HashMap::new(); - collect_consts_rec(Substack::Bottom, &mut consts, &self.0); + self.0.search_all((), |path, mem, ()| { + if let ModMemberRef::Mod(m) = mem { + for (name, ent) in m.entries.iter() { + if let ModMember::Item(ProjItem { kind: ItemKind::Const(c) }) = &ent.member { + let name = VPath::new(path.unreverse()).name_with_prefix(name.clone()).to_sym(); + let comments = ent.x.comments.clone(); + let range = match ent.x.locations.first() { + Some(CodeLocation { origin: CodeOrigin::Source(sr), .. }) => sr.clone(), + _ => panic!("Constants must have a location and must only have range locations"), + }; + consts.insert(name.clone(), ConstReport { comments, name, range, value: c.clone() }); + } + } + } + }); consts } } @@ -270,4 +247,6 @@ pub struct ConstReport { pub value: Expr, /// Source location this constant was parsed from pub range: SourceRange, + /// Name of the constant. + pub name: Sym, } diff --git a/src/rule/matcher.rs b/src/rule/matcher.rs index 9d8ae0d..92473d1 100644 --- a/src/rule/matcher.rs +++ b/src/rule/matcher.rs @@ -1,3 +1,6 @@ +//! Abstract definition of a rule matcher, so that the implementation can +//! eventually be swapped out for a different one. + use std::rc::Rc; use super::state::State; @@ -15,9 +18,6 @@ pub trait Matcher { fn new(pattern: Rc>) -> Self; /// Apply matcher to a token sequence #[must_use] - fn apply<'a>( - &self, - source: &'a [RuleExpr], - save_loc: &impl Fn(Sym) -> bool, - ) -> Option>; + fn apply<'a>(&self, source: &'a [RuleExpr], save_loc: &impl Fn(Sym) -> bool) + -> Option>; } diff --git a/src/rule/matcher_vectree/build.rs b/src/rule/matcher_vectree/build.rs index bb5431c..ca5c3d7 100644 --- a/src/rule/matcher_vectree/build.rs +++ b/src/rule/matcher_vectree/build.rs @@ -7,20 +7,18 @@ use crate::rule::matcher::RuleExpr; use crate::rule::vec_attrs::vec_attrs; use crate::utils::side::Side; -pub type MaxVecSplit<'a> = - (&'a [RuleExpr], (Tok, usize, bool), &'a [RuleExpr]); +pub type MaxVecSplit<'a> = (&'a [RuleExpr], (Tok, usize, bool), &'a [RuleExpr]); /// Derive the details of the central vectorial and the two sides from a /// slice of Expr's #[must_use] fn split_at_max_vec(pattern: &[RuleExpr]) -> Option { - let rngidx = pattern.iter().position_max_by_key(|expr| { - vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1) - })?; + let rngidx = pattern + .iter() + .position_max_by_key(|expr| vec_attrs(expr).map(|attrs| attrs.1 as i64).unwrap_or(-1))?; let (left, not_left) = pattern.split_at(rngidx); - let (placeh, right) = not_left - .split_first() - .expect("The index of the greatest element must be less than the length"); + let (placeh, right) = + not_left.split_first().expect("The index of the greatest element must be less than the length"); vec_attrs(placeh).map(|attrs| (left, attrs, right)) } @@ -38,31 +36,19 @@ pub fn mk_any(pattern: &[RuleExpr]) -> AnyMatcher { let (left, not_left) = pattern.split_at(left_split); let right_split = not_left.len() - scal_cnt(pattern.iter().rev()); let (mid, right) = not_left.split_at(right_split); - AnyMatcher::Vec { - left: mk_scalv(left), - mid: mk_vec(mid), - right: mk_scalv(right), - } + AnyMatcher::Vec { left: mk_scalv(left), mid: mk_vec(mid), right: mk_scalv(right) } } /// Pattern MUST NOT contain vectorial placeholders #[must_use] -fn mk_scalv(pattern: &[RuleExpr]) -> Vec { - pattern.iter().map(mk_scalar).collect() -} +fn mk_scalv(pattern: &[RuleExpr]) -> Vec { pattern.iter().map(mk_scalar).collect() } /// Pattern MUST start and end with a vectorial placeholder #[must_use] fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher { debug_assert!(!pattern.is_empty(), "pattern cannot be empty"); - debug_assert!( - pattern.first().map(vec_attrs).is_some(), - "pattern must start with a vectorial" - ); - debug_assert!( - pattern.last().map(vec_attrs).is_some(), - "pattern must end with a vectorial" - ); + debug_assert!(pattern.first().map(vec_attrs).is_some(), "pattern must start with a vectorial"); + debug_assert!(pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial"); let (left, (key, _, nonzero), right) = split_at_max_vec(pattern) .expect("pattern must have vectorial placeholders at least at either end"); let r_sep_size = scal_cnt(right.iter()); @@ -85,11 +71,8 @@ fn mk_vec(pattern: &[RuleExpr]) -> VecMatcher { right: Box::new(main), }, (..) => { - let mut key_order = l_side - .iter() - .chain(r_side.iter()) - .filter_map(vec_attrs) - .collect::>(); + let mut key_order = + l_side.iter().chain(r_side.iter()).filter_map(vec_attrs).collect::>(); key_order.sort_by_key(|(_, prio, _)| -(*prio as i64)); VecMatcher::Middle { left: Box::new(mk_vec(l_side)), @@ -113,62 +96,51 @@ fn mk_scalar(pattern: &RuleExpr) -> ScalMatcher { PHClass::Vec { .. } => { panic!("Scalar matcher cannot be built from vector pattern") }, - PHClass::Scalar | PHClass::Name => ScalMatcher::Placeh { - key: name.clone(), - name_only: class == &PHClass::Name, - }, + PHClass::Scalar | PHClass::Name => + ScalMatcher::Placeh { key: name.clone(), name_only: class == &PHClass::Name }, }, Clause::S(c, body) => ScalMatcher::S(*c, Box::new(mk_any(body))), - Clause::Lambda(arg, body) => - ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))), + Clause::Lambda(arg, body) => ScalMatcher::Lambda(Box::new(mk_any(arg)), Box::new(mk_any(body))), } } #[cfg(test)] mod test { use std::rc::Rc; - use std::sync::Arc; use intern_all::i; use super::mk_any; - use crate::location::{SourceCode, SourceRange}; - use crate::name::{Sym, VPath}; + use crate::location::SourceRange; use crate::parse::parsed::{Clause, PHClass, PType, Placeholder}; + use crate::sym; #[test] fn test_scan() { - let range = SourceRange { - range: 0..1, - code: SourceCode { - path: Arc::new(VPath(vec![])), - source: Arc::new(String::new()), - }, - }; - let ex = |c: Clause| c.into_expr(range.clone()); + let ex = |c: Clause| c.into_expr(SourceRange::mock()); let pattern = vec![ ex(Clause::Placeh(Placeholder { class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i("::prefix"), + name: i!(str: "::prefix"), })), - ex(Clause::Name(Sym::literal("prelude::do"))), + ex(Clause::Name(sym!(prelude::do))), ex(Clause::S( PType::Par, Rc::new(vec![ ex(Clause::Placeh(Placeholder { class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i("expr"), + name: i!(str: "expr"), })), - ex(Clause::Name(Sym::literal("prelude::;"))), + ex(Clause::Name(sym!(prelude::;))), ex(Clause::Placeh(Placeholder { class: PHClass::Vec { nonzero: false, prio: 1 }, - name: i("rest"), + name: i!(str: "rest"), })), ]), )), ex(Clause::Placeh(Placeholder { class: PHClass::Vec { nonzero: false, prio: 0 }, - name: i("::suffix"), + name: i!(str: "::suffix"), })), ]; let matcher = mk_any(&pattern); diff --git a/src/rule/matcher_vectree/mod.rs b/src/rule/matcher_vectree/mod.rs index 8b054ea..5f3d73c 100644 --- a/src/rule/matcher_vectree/mod.rs +++ b/src/rule/matcher_vectree/mod.rs @@ -1,7 +1,7 @@ //! Optimized form of macro pattern that can be quickly tested against the AST. -//! +//! //! # Construction -//! +//! //! convert pattern into hierarchy of plain, scan, middle //! - plain: accept any sequence or any non-empty sequence //! - scan: a single scalar pattern moves LTR or RTL, submatchers on either @@ -10,7 +10,7 @@ //! while getting progressively closer to each other //! //! # Application -//! +//! //! walk over the current matcher's valid options and poll the submatchers //! for each of them diff --git a/src/rule/matcher_vectree/scal_match.rs b/src/rule/matcher_vectree/scal_match.rs index f98bf96..3c23f9e 100644 --- a/src/rule/matcher_vectree/scal_match.rs +++ b/src/rule/matcher_vectree/scal_match.rs @@ -12,24 +12,20 @@ pub fn scal_match<'a>( save_loc: &impl Fn(Sym) -> bool, ) -> Option> { match (matcher, &expr.value) { - (ScalMatcher::Atom(a1), Clause::Atom(a2)) - if a1.run().0.parser_eq(&a2.run().0) => + (ScalMatcher::Atom(a1), Clause::Atom(a2)) if a1.run().0.parser_eq(&*a2.run().0) => Some(State::default()), - (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => - Some(match save_loc(n1.clone()) { - true => State::from_name(n1.clone(), expr.range.clone()), - false => State::default(), - }), + (ScalMatcher::Name(n1), Clause::Name(n2)) if n1 == n2 => Some(match save_loc(n1.clone()) { + true => State::from_name(n1.clone(), expr.range.clone()), + false => State::default(), + }), (ScalMatcher::Placeh { key, name_only: true }, Clause::Name(n)) => Some(State::from_ph(key.clone(), StateEntry::Name(n, &expr.range))), (ScalMatcher::Placeh { key, name_only: false }, _) => Some(State::from_ph(key.clone(), StateEntry::Scalar(expr))), (ScalMatcher::S(c1, b_mat), Clause::S(c2, body)) if c1 == c2 => any_match(b_mat, &body[..], save_loc), - (ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => Some( - any_match(arg_mat, arg, save_loc)? - .combine(any_match(b_mat, body, save_loc)?), - ), + (ScalMatcher::Lambda(arg_mat, b_mat), Clause::Lambda(arg, body)) => + Some(any_match(arg_mat, arg, save_loc)?.combine(any_match(b_mat, body, save_loc)?)), _ => None, } } diff --git a/src/rule/matcher_vectree/shared.rs b/src/rule/matcher_vectree/shared.rs index ef84824..581f917 100644 --- a/src/rule/matcher_vectree/shared.rs +++ b/src/rule/matcher_vectree/shared.rs @@ -1,6 +1,6 @@ //! Datastructures for cached pattern -use std::fmt::{Display, Write}; +use std::fmt; use std::rc::Rc; use intern_all::Tok; @@ -46,7 +46,7 @@ pub(super) enum VecMatcher { right_sep: Vec, /// Matches the right outer region right: Box, - /// Order of significance for sorting equally good solutions based on + /// Order of significance for sorting equally good projects based on /// the length of matches on either side. /// /// Vectorial keys that appear on either side, in priority order @@ -72,8 +72,8 @@ impl Matcher for AnyMatcher { // ################ Display ################ -impl Display for ScalMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for ScalMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Atom(a) => write!(f, "{a:?}"), Self::Placeh { key, name_only } => match name_only { @@ -87,22 +87,14 @@ impl Display for ScalMatcher { } } -impl Display for VecMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for VecMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::Placeh { key, nonzero } => { - if *nonzero { - f.write_char('.')?; - }; - write!(f, "..${key}") - }, - Self::Scan { left, sep, right, direction } => match direction { - Side::Left => { - write!(f, "Scan{{{left} <== {} <== {right}}}", sep.iter().join(" ")) - }, - Side::Right => { - write!(f, "Scan{{{left} ==> {} ==> {right}}}", sep.iter().join(" ")) - }, + Self::Placeh { key, nonzero: true } => write!(f, "...${key}"), + Self::Placeh { key, nonzero: false } => write!(f, "..${key}"), + Self::Scan { left, sep, right, direction } => { + let arrow = if direction == &Side::Left { "<==" } else { "==>" }; + write!(f, "Scan{{{left} {arrow} {} {arrow} {right}}}", sep.iter().join(" ")) }, Self::Middle { left, left_sep, mid, right_sep, right, .. } => { let left_sep_s = left_sep.iter().join(" "); @@ -113,8 +105,8 @@ impl Display for VecMatcher { } } -impl Display for AnyMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for AnyMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Scalar(s) => { write!(f, "({})", s.iter().join(" ")) @@ -144,8 +136,6 @@ impl Matcher for VectreeMatcher { self.0.apply(source, save_loc) } } -impl Display for VectreeMatcher { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } +impl fmt::Display for VectreeMatcher { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } diff --git a/src/rule/matcher_vectree/vec_match.rs b/src/rule/matcher_vectree/vec_match.rs index 1bbf0de..984b664 100644 --- a/src/rule/matcher_vectree/vec_match.rs +++ b/src/rule/matcher_vectree/vec_match.rs @@ -28,12 +28,8 @@ pub fn vec_match<'a>( for lpos in direction.walk(0..=seq.len() - sep.len()) { let rpos = lpos + sep.len(); let state = vec_match(left, &seq[..lpos], save_loc) - .and_then(|s| { - Some(s.combine(scalv_match(sep, &seq[lpos..rpos], save_loc)?)) - }) - .and_then(|s| { - Some(s.combine(vec_match(right, &seq[rpos..], save_loc)?)) - }); + .and_then(|s| Some(s.combine(scalv_match(sep, &seq[lpos..rpos], save_loc)?))) + .and_then(|s| Some(s.combine(vec_match(right, &seq[rpos..], save_loc)?))); if let Some(s) = state { return Some(s); } @@ -49,26 +45,20 @@ pub fn vec_match<'a>( let lposv = seq[..seq.len() - right_sep.len()] .windows(left_sep.len()) .enumerate() - .filter_map(|(i, window)| { - scalv_match(left_sep, window, save_loc).map(|s| (i, s)) - }) + .filter_map(|(i, window)| scalv_match(left_sep, window, save_loc).map(|s| (i, s))) .collect::>(); // Valid locations for the right separator let rposv = seq[left_sep.len()..] .windows(right_sep.len()) .enumerate() - .filter_map(|(i, window)| { - scalv_match(right_sep, window, save_loc).map(|s| (i, s)) - }) + .filter_map(|(i, window)| scalv_match(right_sep, window, save_loc).map(|s| (i, s))) .collect::>(); // Valid combinations of locations for the separators let mut pos_pairs = lposv .into_iter() .cartesian_product(rposv) .filter(|((lpos, _), (rpos, _))| lpos + left_sep.len() <= *rpos) - .map(|((lpos, lstate), (rpos, rstate))| { - (lpos, rpos, lstate.combine(rstate)) - }) + .map(|((lpos, lstate), (rpos, rstate))| (lpos, rpos, lstate.combine(rstate))) .collect::>(); // In descending order of size pos_pairs.sort_by_key(|(l, r, _)| -((r - l) as i64)); @@ -80,24 +70,14 @@ pub fn vec_match<'a>( Some( state .combine(vec_match(left, &seq[..lpos], save_loc)?) - .combine(vec_match( - mid, - &seq[lpos + left_sep.len()..rpos], - save_loc, - )?) - .combine(vec_match( - right, - &seq[rpos + right_sep.len()..], - save_loc, - )?), + .combine(vec_match(mid, &seq[lpos + left_sep.len()..rpos], save_loc)?) + .combine(vec_match(right, &seq[rpos + right_sep.len()..], save_loc)?), ) }) .max_by(|a, b| { for key in key_order { - let alen = - a.ph_len(key).expect("key_order references scalar or missing"); - let blen = - b.ph_len(key).expect("key_order references scalar or missing"); + let alen = a.ph_len(key).expect("key_order references scalar or missing"); + let blen = b.ph_len(key).expect("key_order references scalar or missing"); match alen.cmp(&blen) { Ordering::Equal => (), any => return any, diff --git a/src/rule/prepare_rule.rs b/src/rule/prepare_rule.rs index 6053473..05a3db1 100644 --- a/src/rule/prepare_rule.rs +++ b/src/rule/prepare_rule.rs @@ -12,8 +12,8 @@ use crate::pipeline::project::ProjRule; /// changing its meaning #[must_use] fn pad(rule: ProjRule) -> ProjRule { - let prefix_name = i("__gen__orchid__rule__prefix"); - let suffix_name = i("__gen__orchid__rule__suffix"); + let prefix_name = i!(str: "__gen__orchid__rule__prefix"); + let suffix_name = i!(str: "__gen__orchid__rule__suffix"); let class: PHClass = PHClass::Vec { nonzero: false, prio: 0 }; let ProjRule { comments, pattern, prio, template } = rule; let rule_head = pattern.first().expect("Pattern can never be empty!"); @@ -26,8 +26,7 @@ fn pad(rule: ProjRule) -> ProjRule { Clause::Placeh(Placeholder { name: suffix_name, class }) .into_expr(rule_tail.range.map_range(|r| r.start..r.start)) }); - let pattern = - prefix.iter().cloned().chain(pattern).chain(suffix.clone()).collect(); + let pattern = prefix.iter().cloned().chain(pattern).chain(suffix.clone()).collect(); let template = prefix.into_iter().chain(template).chain(suffix).collect(); ProjRule { comments, prio, pattern, template } } @@ -95,11 +94,7 @@ fn check_rec_exprv( } } } - if let Some(e) = exprv.last() { - check_rec_expr(e, types, in_template) - } else { - Ok(()) - } + if let Some(e) = exprv.last() { check_rec_expr(e, types, in_template) } else { Ok(()) } } pub fn prepare_rule(rule: ProjRule) -> Result { diff --git a/src/rule/repository.rs b/src/rule/repository.rs index 034f302..e5c59d6 100644 --- a/src/rule/repository.rs +++ b/src/rule/repository.rs @@ -1,4 +1,6 @@ -use std::fmt::{Debug, Display}; +//! Collects, prioritizes and executes rules. + +use std::fmt; use std::rc::Rc; use hashbrown::HashSet; @@ -8,9 +10,9 @@ use ordered_float::NotNan; use super::matcher::{Matcher, RuleExpr}; use super::matcher_vectree::shared::VectreeMatcher; use super::prepare_rule::prepare_rule; -use super::rule_error::RuleError; use super::state::apply_exprv; use super::update_first_seq; +use crate::error::Reporter; use crate::name::Sym; use crate::parse::numeric::print_nat16; use crate::pipeline::project::ProjRule; @@ -24,8 +26,8 @@ pub(super) struct CachedRule { save_location: HashSet, } -impl Display for CachedRule { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for CachedRule { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let patterns = self.pattern.iter().join(" "); write!( f, @@ -48,35 +50,25 @@ pub struct Repository { } impl Repository { /// Build a new repository to hold the given set of rules - pub fn new(mut rules: Vec) -> Result { + pub fn new(mut rules: Vec, reporter: &Reporter) -> Self { rules.sort_by_key(|r| -r.prio); let cache = rules .into_iter() - .map(|r| { - let ProjRule { pattern, prio, template, comments: _ } = - prepare_rule(r.clone()).map_err(|e| (r, e))?; + .filter_map(|r| { + let ProjRule { pattern, prio, template, comments: _ } = prepare_rule(r.clone()) + .inspect_err(|e| reporter.report(e.clone().into_project(&r))) + .ok()?; let mut pat_glossary = HashSet::new(); - pat_glossary.extend( - pattern.iter().flat_map(|e| e.value.collect_names().into_iter()), - ); + pat_glossary.extend(pattern.iter().flat_map(|e| e.value.collect_names().into_iter())); let mut tpl_glossary = HashSet::new(); - tpl_glossary.extend( - template.iter().flat_map(|e| e.value.collect_names().into_iter()), - ); - let save_location = - pat_glossary.intersection(&tpl_glossary).cloned().collect(); + tpl_glossary.extend(template.iter().flat_map(|e| e.value.collect_names().into_iter())); + let save_location = pat_glossary.intersection(&tpl_glossary).cloned().collect(); let matcher = M::new(Rc::new(pattern.clone())); - let prep = CachedRule { - matcher, - pattern, - template, - pat_glossary, - save_location, - }; - Ok((prep, prio)) + let prep = CachedRule { matcher, pattern, template, pat_glossary, save_location }; + Some((prep, prio)) }) - .collect::, _>>()?; - Ok(Self { cache }) + .collect::>(); + Self { cache } } /// Attempt to run each rule in priority order once @@ -117,11 +109,7 @@ impl Repository { /// Attempt to run each rule in priority order `limit` times. Returns /// the final tree and the number of iterations left to the limit. #[must_use] - pub fn long_step( - &self, - code: &RuleExpr, - mut limit: usize, - ) -> (RuleExpr, usize) { + pub fn long_step(&self, code: &RuleExpr, mut limit: usize) -> (RuleExpr, usize) { if limit == 0 { return (code.clone(), 0); } @@ -144,8 +132,8 @@ impl Repository { } } -impl Debug for Repository { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Debug for Repository { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for rule in self.cache.iter() { writeln!(f, "{rule:?}")? } @@ -153,8 +141,8 @@ impl Debug for Repository { } } -impl Display for Repository { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Repository { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Repository[")?; for (rule, p) in self.cache.iter() { let prio = print_nat16(*p); diff --git a/src/rule/rule_error.rs b/src/rule/rule_error.rs index f22a3a0..88d6c94 100644 --- a/src/rule/rule_error.rs +++ b/src/rule/rule_error.rs @@ -1,10 +1,12 @@ -use std::fmt::{self, Display}; +//! Error conditions encountered by the rule processor + +use std::fmt; use hashbrown::HashSet; use intern_all::Tok; use crate::error::{ErrorPosition, ProjectError, ProjectErrorObj}; -use crate::location::{CodeLocation, SourceRange}; +use crate::location::{CodeOrigin, SourceRange}; use crate::parse::parsed::{search_all_slcs, Clause, PHClass, Placeholder}; use crate::pipeline::project::ProjRule; @@ -23,7 +25,7 @@ pub enum RuleError { impl RuleError { /// Convert into a unified error trait object shared by all Orchid errors #[must_use] - pub fn to_project(self, rule: &ProjRule) -> ProjectErrorObj { + pub fn into_project(self, rule: &ProjRule) -> ProjectErrorObj { match self { Self::Missing(name) => Missing::new(rule, name).pack(), Self::Multiple(name) => Multiple::new(rule, name).pack(), @@ -33,7 +35,7 @@ impl RuleError { } } -impl Display for RuleError { +impl fmt::Display for RuleError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Missing(key) => write!(f, "Key {key} not in match pattern"), @@ -74,19 +76,12 @@ impl Missing { } } impl ProjectError for Missing { - const DESCRIPTION: &'static str = - "A key appears in the template but not the pattern of a rule"; + const DESCRIPTION: &'static str = "A key appears in the template but not the pattern of a rule"; fn message(&self) -> String { - format!( - "The key {} appears in the template but not the pattern of this rule", - self.name - ) + format!("The key {} appears in the template but not the pattern of this rule", self.name) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).cloned().map(|range| ErrorPosition { - location: CodeLocation::Source(range), - message: None, - }) + self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) } } @@ -114,16 +109,12 @@ impl Multiple { } } impl ProjectError for Multiple { - const DESCRIPTION: &'static str = - "A key appears multiple times in the pattern of a rule"; + const DESCRIPTION: &'static str = "A key appears multiple times in the pattern of a rule"; fn message(&self) -> String { format!("The key {} appears multiple times in this pattern", self.name) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).cloned().map(|range| ErrorPosition { - location: CodeLocation::Source(range), - message: None, - }) + self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) } } @@ -151,17 +142,13 @@ impl ArityMismatch { } } impl ProjectError for ArityMismatch { - const DESCRIPTION: &'static str = - "A key appears with different arities in a rule"; + const DESCRIPTION: &'static str = "A key appears with different arities in a rule"; fn message(&self) -> String { - format!( - "The key {} appears multiple times with different arities in this rule", - self.name - ) + format!("The key {} appears multiple times with different arities in this rule", self.name) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).cloned().map(|(location, class)| ErrorPosition { - location: CodeLocation::Source(location), + self.locations.iter().map(|(origin, class)| ErrorPosition { + origin: CodeOrigin::Source(origin.clone()), message: Some( "This instance represents ".to_string() + match class { @@ -206,18 +193,11 @@ impl VecNeighbors { } } impl ProjectError for VecNeighbors { - const DESCRIPTION: &'static str = - "Two vectorial placeholders appear next to each other"; + const DESCRIPTION: &'static str = "Two vectorial placeholders appear next to each other"; fn message(&self) -> String { - format!( - "The keys {} and {} appear next to each other with a vectorial arity", - self.n1, self.n2 - ) + format!("The keys {} and {} appear next to each other with a vectorial arity", self.n1, self.n2) } fn positions(&self) -> impl IntoIterator { - (self.locations.iter()).cloned().map(|location| ErrorPosition { - location: CodeLocation::Source(location), - message: None, - }) + self.locations.iter().map(|range| CodeOrigin::Source(range.clone()).into()) } } diff --git a/src/rule/state.rs b/src/rule/state.rs index 1b99376..aa2c375 100644 --- a/src/rule/state.rs +++ b/src/rule/state.rs @@ -2,12 +2,12 @@ use std::rc::Rc; use hashbrown::HashMap; use intern_all::Tok; -use itertools::{EitherOrBoth, Itertools}; use super::matcher::RuleExpr; use crate::location::SourceRange; use crate::name::Sym; use crate::parse::parsed::{Clause, Expr, PHClass, Placeholder}; +use crate::utils::join::join_maps; use crate::utils::unwrap_or::unwrap_or; #[derive(Clone, Copy, Debug)] @@ -23,31 +23,14 @@ pub struct State<'a> { } impl<'a> State<'a> { pub fn from_ph(key: Tok, entry: StateEntry<'a>) -> Self { - Self { - placeholders: HashMap::from([(key, entry)]), - name_locations: HashMap::new(), - } + Self { placeholders: HashMap::from([(key, entry)]), name_locations: HashMap::new() } } pub fn combine(self, s: Self) -> Self { Self { - placeholders: self - .placeholders - .into_iter() - .chain(s.placeholders) - .collect(), - name_locations: (self.name_locations.into_iter()) - .sorted_unstable_by_key(|(k, _)| k.id()) - .merge_join_by( - (s.name_locations.into_iter()) - .sorted_unstable_by_key(|(k, _)| k.id()), - |(k, _), (k2, _)| k.id().cmp(&k2.id()), - ) - .map(|ent| match ent { - EitherOrBoth::Left(i) | EitherOrBoth::Right(i) => i, - EitherOrBoth::Both((k, l), (_, r)) => - (k, l.into_iter().chain(r).collect()), - }) - .collect(), + placeholders: self.placeholders.into_iter().chain(s.placeholders).collect(), + name_locations: join_maps(self.name_locations, s.name_locations, |_, l, r| { + l.into_iter().chain(r).collect() + }), } } pub fn ph_len(&self, key: &Tok) -> Option { @@ -57,32 +40,27 @@ impl<'a> State<'a> { } } pub fn from_name(name: Sym, location: SourceRange) -> Self { - Self { - name_locations: HashMap::from([(name, vec![location])]), - placeholders: HashMap::new(), - } + Self { name_locations: HashMap::from([(name, vec![location])]), placeholders: HashMap::new() } } } impl Default for State<'static> { - fn default() -> Self { - Self { name_locations: HashMap::new(), placeholders: HashMap::new() } - } + fn default() -> Self { Self { name_locations: HashMap::new(), placeholders: HashMap::new() } } } #[must_use] pub fn apply_exprv(template: &[RuleExpr], state: &State) -> Vec { - template - .iter() - .map(|e| apply_expr(e, state)) - .flat_map(Vec::into_iter) - .collect() + template.iter().map(|e| apply_expr(e, state)).flat_map(Vec::into_iter).collect() } #[must_use] pub fn apply_expr(template: &RuleExpr, state: &State) -> Vec { let Expr { range, value } = template; match value { - Clause::Atom(_) | Clause::Name(_) => vec![template.clone()], + Clause::Name(n) => match state.name_locations.get(n) { + None => vec![template.clone()], + Some(locs) => vec![Expr { value: value.clone(), range: locs[0].clone() }], + }, + Clause::Atom(_) => vec![template.clone()], Clause::S(c, body) => vec![Expr { range: range.clone(), value: Clause::S(*c, Rc::new(apply_exprv(body.as_slice(), state))), diff --git a/src/rule/update_first_seq.rs b/src/rule/update_first_seq.rs index aab677a..312599e 100644 --- a/src/rule/update_first_seq.rs +++ b/src/rule/update_first_seq.rs @@ -15,8 +15,7 @@ pub fn exprv>) -> Option>>>( if let Some(v) = pred(input.clone()) { return Some(v); } - replace_first(input.as_ref(), |ex| expr(ex, pred)) - .map(|i| Rc::new(i.collect())) + replace_first(input.as_ref(), |ex| expr(ex, pred)).map(|i| Rc::new(i.collect())) } #[must_use] @@ -24,8 +23,7 @@ pub fn expr>) -> Option>>>( input: &RuleExpr, pred: &mut F, ) -> Option { - clause(&input.value, pred) - .map(|value| Expr { value, range: input.range.clone() }) + clause(&input.value, pred).map(|value| Expr { value, range: input.range.clone() }) } #[must_use] @@ -53,11 +51,8 @@ pub fn replace_first Option>( ) -> Option + '_> { for i in 0..slice.len() { if let Some(new) = f(&slice[i]) { - let subbed_iter = slice[0..i] - .iter() - .cloned() - .chain(iter::once(new)) - .chain(slice[i + 1..].iter().cloned()); + let subbed_iter = + slice[0..i].iter().cloned().chain(iter::once(new)).chain(slice[i + 1..].iter().cloned()); return Some(subbed_iter); } } diff --git a/src/rule/vec_attrs.rs b/src/rule/vec_attrs.rs index 2c9a3f9..4b38328 100644 --- a/src/rule/vec_attrs.rs +++ b/src/rule/vec_attrs.rs @@ -8,10 +8,8 @@ use crate::parse::parsed::{Clause, PHClass, Placeholder}; #[must_use] pub fn vec_attrs(expr: &RuleExpr) -> Option<(Tok, usize, bool)> { match expr.value.clone() { - Clause::Placeh(Placeholder { - class: PHClass::Vec { prio, nonzero }, - name, - }) => Some((name, prio, nonzero)), + Clause::Placeh(Placeholder { class: PHClass::Vec { prio, nonzero }, name }) => + Some((name, prio, nonzero)), _ => None, } } diff --git a/src/tree.rs b/src/tree.rs index 20685c3..6a65594 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,7 +1,7 @@ //! Generic module tree structure //! //! Used by various stages of the pipeline with different parameters -use std::fmt::{Debug, Display}; +use std::fmt; use hashbrown::HashMap; use intern_all::{ev, i, Tok}; @@ -10,7 +10,7 @@ use substack::Substack; use trait_set::trait_set; use crate::error::{ProjectError, ProjectErrorObj}; -use crate::location::CodeLocation; +use crate::location::CodeOrigin; use crate::name::{VName, VPath}; use crate::utils::boxed_iter::BoxedIter; use crate::utils::combine::Combine; @@ -19,7 +19,7 @@ use crate::utils::sequence::Sequence; /// An umbrella trait for operations you can carry out on any part of the tree /// structure -pub trait TreeTransforms { +pub trait TreeTransforms: Sized { /// Data held at the leaves of the tree type Item; /// Data associated with modules @@ -30,23 +30,33 @@ pub trait TreeTransforms { /// 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, - item: &impl Fn(Substack>, Self::Item) -> T, - module: &impl Fn(Substack>, Self::XMod) -> U, - entry: &impl Fn(Substack>, Self::XEnt) -> V, - path: Substack>, - ) -> Self::SelfType; + 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 + /// * [`Substack>`] - the walked path /// * [Module] - the current module - /// * T - data for reduce. + /// * `T` - data for reduce. fn search_all<'a, T>( &'a self, init: T, @@ -56,9 +66,8 @@ pub trait TreeTransforms { T, ) -> T, ) -> T { - let res = self.search(init, |stack, member, state| { - Ok::(callback(stack, member, state)) - }); + let res = + self.search(init, |stack, member, state| Ok::(callback(stack, member, state))); res.unwrap_or_else(|e| match e {}) } @@ -67,9 +76,9 @@ pub trait TreeTransforms { /// * 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 + /// * [`Substack>`] - the walked path /// * [Module] - the current module - /// * T - data for reduce. + /// * `T` - data for reduce. fn search<'a, T, E>( &'a self, init: T, @@ -110,16 +119,16 @@ impl TreeTransforms for ModMember { type XMod = XMod; type SelfType = ModMember; - fn map_data( + fn map_data_rec( self, - item: &impl Fn(Substack>, Item) -> T, - module: &impl Fn(Substack>, XMod) -> U, - entry: &impl Fn(Substack>, XEnt) -> V, + 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(item, module, entry, path)), + Self::Sub(sub) => ModMember::Sub(sub.map_data_rec(item, module, entry, path)), } } @@ -153,18 +162,18 @@ pub enum ConflictKind { } macro_rules! impl_for_conflict { - ($target:ty, $deps:tt, $for:ty, $body:tt) => { + ($target:ty, ($($deps:tt)*), $for:ty, $body:tt) => { impl $target for $for where - Item::Error: $deps, - XMod::Error: $deps, - XEnt::Error: $deps, + Item::Error: $($deps)*, + XMod::Error: $($deps)*, + XEnt::Error: $($deps)*, $body }; } -impl_for_conflict!(Clone, Clone, ConflictKind, { +impl_for_conflict!(Clone, (Clone), ConflictKind, { fn clone(&self) -> Self { match self { ConflictKind::Item(it_e) => ConflictKind::Item(it_e.clone()), @@ -175,8 +184,8 @@ impl_for_conflict!(Clone, Clone, ConflictKind, { } }); -impl_for_conflict!(Debug, Debug, ConflictKind, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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(), @@ -196,26 +205,22 @@ pub struct TreeConflict { /// What type of failure occurred pub kind: ConflictKind, } -impl - TreeConflict -{ - fn new(kind: ConflictKind) -> Self { - Self { path: VPath::new([]), kind } - } +impl TreeConflict { + fn new(kind: ConflictKind) -> Self { Self { path: VPath::new([]), kind } } fn push(self, seg: Tok) -> Self { Self { path: self.path.prefix([seg]), kind: self.kind } } } -impl_for_conflict!(Clone, Clone, TreeConflict, { +impl_for_conflict!(Clone, (Clone), TreeConflict, { fn clone(&self) -> Self { Self { path: self.path.clone(), kind: self.kind.clone() } } }); -impl_for_conflict!(Debug, Debug, TreeConflict, { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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) @@ -223,9 +228,7 @@ impl_for_conflict!(Debug, Debug, TreeConflict, { } }); -impl Combine - for ModMember -{ +impl Combine for ModMember { type Error = TreeConflict; fn combine(self, other: Self) -> Result { @@ -248,9 +251,7 @@ pub struct ModEntry { /// Additional fields pub x: XEnt, } -impl Combine - for ModEntry -{ +impl Combine for ModEntry { type Error = TreeConflict; fn combine(self, other: Self) -> Result { match self.x.combine(other.x) { @@ -276,15 +277,15 @@ impl TreeTransforms for ModEntry { type XMod = XMod; type SelfType = ModEntry; - fn map_data( + fn map_data_rec( self, - item: &impl Fn(Substack>, Item) -> T, - module: &impl Fn(Substack>, XMod) -> U, - entry: &impl Fn(Substack>, XEnt) -> V, + 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(item, module, entry, path.clone()), + member: self.member.map_data_rec(item, module, entry, path.clone()), x: entry(path, self.x), } } @@ -304,9 +305,7 @@ impl TreeTransforms for ModEntry { } impl ModEntry { /// Wrap a member directly with trivial metadata - pub fn wrap(member: ModMember) -> Self { - Self { member, x: XEnt::default() } - } + 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)) } } @@ -317,18 +316,13 @@ impl ModEntry { /// Create a module #[must_use] pub fn tree>(arr: impl IntoIterator) -> Self { - Self::wrap(ModMember::Sub(Module::wrap( - arr.into_iter().map(|(k, v)| (i(k.as_ref()), v)), - ))) + Self::wrap(ModMember::Sub(Module::wrap(arr.into_iter().map(|(k, v)| (i(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) { + pub fn tree_ent>(key: K, arr: impl IntoIterator) -> (K, Self) { (key, Self::tree(arr)) } @@ -383,11 +377,7 @@ impl Module { &'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()), - ) + Box::new((self.entries.iter()).filter(move |(_, v)| filter(v)).map(|(k, _)| k.clone())) } /// Return the module at the end of the given path @@ -447,12 +437,9 @@ impl Module { &'a self, origin: &[Tok], target: &'b [Tok], - is_exported: impl for<'c> Fn(&'c ModEntry) -> bool - + Clone - + 'b, + 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(); + 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); } @@ -462,9 +449,7 @@ impl Module { } /// Wrap entry table in a module with trivial metadata - pub fn wrap( - entries: impl IntoIterator>, - ) -> Self + pub fn wrap(entries: impl IntoIterator>) -> Self where XMod: Default { Self { entries: entries.into_iter().collect(), x: XMod::default() } } @@ -476,19 +461,17 @@ impl TreeTransforms for Module { type XMod = XMod; type SelfType = Module; - fn map_data( + fn map_data_rec( self, - item: &impl Fn(Substack>, Item) -> T, - module: &impl Fn(Substack>, XMod) -> U, - entry: &impl Fn(Substack>, XEnt) -> V, + 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(item, module, entry, path.push(k))) - }) + .map(|(k, e)| (k.clone(), e.map_data_rec(item, module, entry, path.push(k)))) .collect(), } } @@ -511,24 +494,20 @@ impl TreeTransforms for Module { } } -impl Combine - for Module -{ +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)))?; + 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 Display +impl fmt::Display for Module { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "module {{")?; for (name, ModEntry { member, x: extra }) in &self.entries { match member { @@ -580,15 +559,11 @@ impl<'a> WalkError<'a> { /// Attach a location to the error and convert into trait object for reporting #[must_use] - pub fn at(self, location: &CodeLocation) -> ProjectErrorObj { + pub fn at(self, origin: &CodeOrigin) -> ProjectErrorObj { let details = WalkErrorDetails { - location: location.clone(), - path: VName::new( - (self.prefix.iter()) - .chain(self.path.iter().take(self.pos + 1)) - .cloned(), - ) - .expect("empty paths don't cause an error"), + 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 { @@ -600,16 +575,12 @@ impl<'a> WalkError<'a> { /// 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 [Tok], - kind: ErrKind, - options: Sequence<'a, Tok>, - ) -> Self { + pub fn last(path: &'a [Tok], kind: ErrKind, options: Sequence<'a, Tok>) -> Self { WalkError { kind, path, options, pos: path.len() - 1, prefix: &[] } } } -impl<'a> Debug for WalkError<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +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) @@ -622,27 +593,23 @@ impl<'a> Debug for WalkError<'a> { struct WalkErrorDetails { path: VName, options: Vec>, - location: CodeLocation, + origin: CodeOrigin, } impl WalkErrorDetails { - fn print_options(&self) -> String { - format!("options are {}", ev(&self.options).join(", ")) - } + fn print_options(&self) -> String { format!("options are {}", ev(&self.options).join(", ")) } } struct FilteredError(WalkErrorDetails); impl ProjectError for FilteredError { const DESCRIPTION: &'static str = "The path leads into a private module"; - fn one_position(&self) -> CodeLocation { self.0.location.clone() } - fn message(&self) -> String { - format!("{} is private, {}", self.0.path, self.0.print_options()) - } + 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) -> CodeLocation { self.0.location.clone() } + fn one_position(&self) -> CodeOrigin { self.0.origin.clone() } fn message(&self) -> String { format!("{} does not exist, {}", self.0.path, self.0.print_options()) } @@ -651,7 +618,7 @@ impl ProjectError for MissingError { struct NotModuleError(WalkErrorDetails); impl ProjectError for NotModuleError { const DESCRIPTION: &'static str = "The path leads into a leaf"; - fn one_position(&self) -> CodeLocation { self.0.location.clone() } + 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/utils/clonable_iter.rs b/src/utils/clonable_iter.rs index 49d2c7f..0f334d7 100644 --- a/src/utils/clonable_iter.rs +++ b/src/utils/clonable_iter.rs @@ -44,8 +44,7 @@ where }, Err(arc) => take_with_output(&mut *arc.lock().unwrap(), |s| match s { State::End => (State::End, (Self::wrap(State::End), None)), - State::Cont(next, data) => - (State::Cont(next.clone(), data.clone()), (next, Some(data))), + State::Cont(next, data) => (State::Cont(next.clone(), data.clone()), (next, Some(data))), State::Head(mut iter) => match iter.next() { None => (State::End, (Self::wrap(State::End), None)), Some(data) => { diff --git a/src/utils/combine.rs b/src/utils/combine.rs index d11aca4..fd344e1 100644 --- a/src/utils/combine.rs +++ b/src/utils/combine.rs @@ -1,9 +1,15 @@ +//! The concept of a fallible merger + use never::Never; -/// Fallible variant of [Add] +/// 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; } diff --git a/src/utils/ddispatch.rs b/src/utils/ddispatch.rs index 3049e8a..febc9ec 100644 --- a/src/utils/ddispatch.rs +++ b/src/utils/ddispatch.rs @@ -1,4 +1,4 @@ -//! A variant of [std::any::Provider] +//! A simplified, stable variant of `std::any::Provider`. use std::any::Any; @@ -12,9 +12,7 @@ impl<'a> Request<'a> { } /// Serve a value if it's the correct type - pub fn serve(&mut self, value: T) { - self.serve_with::(|| value) - } + pub fn serve(&mut self, value: T) { self.serve_with::(|| value) } /// Invoke the callback to serve the request only if the return type matches pub fn serve_with(&mut self, provider: impl FnOnce() -> T) { diff --git a/src/utils/join.rs b/src/utils/join.rs index a145414..16ec461 100644 --- a/src/utils/join.rs +++ b/src/utils/join.rs @@ -24,7 +24,7 @@ pub fn try_join_maps( let mut mixed = HashMap::with_capacity(left.len() + right.len()); for (key, lval) in left { let val = match right.remove(&key) { - None => lval, + None => lval, Some(rval) => merge(&key, lval, rval)?, }; mixed.insert(key, val); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 6bd684d..3ba572c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,19 +1,19 @@ //! Utilities that don't necessarily have a well-defined role in the //! problem-domain of Orchid but are rather designed to fulfill abstract -//! solution-domain tasks. +//! project-domain tasks. //! //! An unreferenced util should be either moved out to a package or deleted pub(crate) mod boxed_iter; pub(crate) mod clonable_iter; -pub(crate) mod combine; +pub mod combine; pub mod ddispatch; pub(crate) mod get_or; pub(crate) mod iter_find; +pub mod join; pub mod pure_seq; pub mod sequence; pub mod side; pub mod string_from_charset; pub mod take_with_output; pub(crate) mod unwrap_or; -pub mod join; diff --git a/src/utils/pure_seq.rs b/src/utils/pure_seq.rs index 4f0ba44..1717c94 100644 --- a/src/utils/pure_seq.rs +++ b/src/utils/pure_seq.rs @@ -6,10 +6,7 @@ use std::iter; /// /// Create a new vector consisting of the provided vector with the /// element appended. See [pushed_ref] to use it with a slice -pub fn pushed>( - vec: I, - t: I::Item, -) -> C { +pub fn pushed>(vec: I, t: I::Item) -> C { vec.into_iter().chain(iter::once(t)).collect() } diff --git a/src/utils/sequence.rs b/src/utils/sequence.rs index f62bc99..4fe162b 100644 --- a/src/utils/sequence.rs +++ b/src/utils/sequence.rs @@ -1,28 +1,27 @@ //! An alternative to `Iterable` in many languages, a [Fn] that returns an //! iterator. -use dyn_clone::{clone_box, DynClone}; +use std::rc::Rc; + use trait_set::trait_set; use super::boxed_iter::BoxedIter; trait_set! { - trait Payload<'a, T> = DynClone + Fn() -> BoxedIter<'a, T> + 'a; + 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>(Box>); +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 + Clone, - ) -> Self { - Self(Box::new(move || Box::new(f().into_iter()))) + 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(clone_box(&*self.0)) } + fn clone(&self) -> Self { Self(self.0.clone()) } } diff --git a/src/utils/side.rs b/src/utils/side.rs index 3b655bc..808a275 100644 --- a/src/utils/side.rs +++ b/src/utils/side.rs @@ -1,7 +1,7 @@ //! Named left/right. I tried bools, I couldn't consistently remember which one //! is left, so I made an enum. Rust should optimize this into a bool anyway. -use std::fmt::Display; +use std::fmt; use std::ops::Not; use super::boxed_iter::BoxedIter; @@ -16,8 +16,8 @@ pub enum Side { Right, } -impl Display for Side { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for Side { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Left => write!(f, "Left"), Self::Right => write!(f, "Right"), @@ -48,12 +48,7 @@ impl Side { } /// ignore N elements from this end and M elements from the other end /// of a slice - pub fn crop_both<'a, T>( - &self, - margin: usize, - opposite: usize, - slice: &'a [T], - ) -> &'a [T] { + pub fn crop_both<'a, T>(&self, margin: usize, opposite: usize, slice: &'a [T]) -> &'a [T] { self.crop(margin, self.opposite().crop(opposite, slice)) } /// Pick this side from a pair of things @@ -72,10 +67,7 @@ impl Side { } /// Walk a double ended iterator (assumed to be left-to-right) in this /// direction - pub fn walk<'a, I: DoubleEndedIterator + 'a>( - &self, - iter: I, - ) -> BoxedIter<'a, I::Item> { + pub fn walk<'a, I: DoubleEndedIterator + 'a>(&self, iter: I) -> BoxedIter<'a, I::Item> { match self { Side::Right => Box::new(iter) as BoxedIter, Side::Left => Box::new(iter.rev()), @@ -99,15 +91,7 @@ mod test { /// the sides are explicitly stated #[test] fn test_walk() { - assert_eq!( - Side::Right.walk(0..4).collect_vec(), - vec![0, 1, 2, 3], - "can walk a range" - ); - assert_eq!( - Side::Left.walk(0..4).collect_vec(), - vec![3, 2, 1, 0], - "can walk a range backwards" - ) + assert_eq!(Side::Right.walk(0..4).collect_vec(), vec![0, 1, 2, 3], "can walk a range"); + assert_eq!(Side::Left.walk(0..4).collect_vec(), vec![3, 2, 1, 0], "can walk a range backwards") } } diff --git a/src/utils/string_from_charset.rs b/src/utils/string_from_charset.rs index c1e195e..53809b5 100644 --- a/src/utils/string_from_charset.rs +++ b/src/utils/string_from_charset.rs @@ -3,14 +3,12 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String { let radix = digits.len() as u64; - let mut prefix = if val > radix { - string_from_charset_rec(val / radix, digits) - } else { - String::new() - }; - let digit = digits.chars().nth(val as usize - 1).unwrap_or_else(|| { - panic!("Overindexed digit set \"{}\" with {}", digits, val - 1) - }); + let mut prefix = + if val > radix { string_from_charset_rec(val / radix, digits) } else { String::new() }; + let digit = digits + .chars() + .nth(val as usize - 1) + .unwrap_or_else(|| panic!("Overindexed digit set \"{}\" with {}", digits, val - 1)); prefix.push(digit); prefix } diff --git a/src/virt_fs/common.rs b/src/virt_fs/common.rs index 0256b5a..c902ad6 100644 --- a/src/virt_fs/common.rs +++ b/src/virt_fs/common.rs @@ -1,10 +1,9 @@ use std::rc::Rc; use std::sync::Arc; -use intern_all::{i, Tok}; -use itertools::Itertools; +use intern_all::Tok; -use crate::error::{ErrorSansLocation, ErrorSansLocationObj}; +use crate::error::{ErrorSansOrigin, ErrorSansOriginObj}; use crate::name::{PathSlice, VPath}; /// Represents the result of loading code from a string-tree form such @@ -27,17 +26,27 @@ impl Loaded { } /// Returned by any source loading callback -pub type FSResult = Result; +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 { - pub fn new(path: VPath) -> Self { - Self(path) - } + /// Instantiate error + pub fn new(path: VPath) -> Self { Self(path) } } -impl ErrorSansLocation for CodeNotFound { +impl ErrorSansOrigin for CodeNotFound { const DESCRIPTION: &'static str = "No source code for path"; fn message(&self) -> String { format!("{} not found", self.0) } } @@ -47,7 +56,17 @@ impl ErrorSansLocation for CodeNotFound { /// formats and other sources for libraries and dependencies. pub trait VirtFS { /// Implementation of [VirtFS::read] - fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult; + fn get(&self, path: &[Tok], 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: &[Tok]) -> Option; @@ -59,12 +78,19 @@ pub trait VirtFS { } /// 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.0, path) } + fn read(&self, path: &PathSlice) -> FSResult { self.get(path, path) } } impl VirtFS for &dyn VirtFS { - fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult { + fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { (*self).get(path, full_path) } fn display(&self, path: &[Tok]) -> Option { (*self).display(path) } } + +impl VirtFS for Rc { + fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { + (**self).get(path, full_path) + } + fn display(&self, path: &[Tok]) -> Option { (**self).display(path) } +} diff --git a/src/virt_fs/decl.rs b/src/virt_fs/decl.rs index fd3f5ac..69daf1a 100644 --- a/src/virt_fs/decl.rs +++ b/src/virt_fs/decl.rs @@ -1,10 +1,11 @@ use std::rc::Rc; +use std::sync::Arc; use intern_all::Tok; use super::common::CodeNotFound; use super::{FSResult, Loaded, VirtFS}; -use crate::error::ErrorSansLocation; +use crate::error::ErrorSansOrigin; use crate::name::PathSlice; use crate::tree::{ModEntry, ModMember}; use crate::utils::combine::Combine; @@ -14,9 +15,17 @@ pub struct ConflictingTrees; impl Combine for Rc { type Error = ConflictingTrees; - fn combine(self, _: Self) -> Result { - Err(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 @@ -24,7 +33,7 @@ impl Combine for Rc { pub type DeclTree = ModEntry, (), ()>; impl VirtFS for DeclTree { - fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult { + fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { match &self.member { ModMember::Item(it) => it.get(path, full_path), ModMember::Sub(module) => match path.split_first() { @@ -44,3 +53,22 @@ impl VirtFS for DeclTree { } } } + +impl VirtFS for String { + fn display(&self, _: &[Tok]) -> Option { None } + fn get(&self, path: &[Tok], 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, _: &[Tok]) -> Option { None } + fn get(&self, path: &[Tok], 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/src/virt_fs/dir.rs b/src/virt_fs/dir.rs index d5b67f1..f67a7b5 100644 --- a/src/virt_fs/dir.rs +++ b/src/virt_fs/dir.rs @@ -10,7 +10,7 @@ use intern_all::{i, Tok}; use super::common::CodeNotFound; use super::{FSResult, Loaded, VirtFS}; -use crate::error::{ErrorSansLocation, ErrorSansLocationObj}; +use crate::error::{ErrorSansOrigin, ErrorSansOriginObj}; use crate::name::PathSlice; #[derive(Clone)] @@ -19,12 +19,11 @@ struct OpenError { dir: Arc>, } impl OpenError { - pub fn wrap(file: io::Error, dir: io::Error) -> ErrorSansLocationObj { - Self { dir: Arc::new(Mutex::new(dir)), file: Arc::new(Mutex::new(file)) } - .pack() + 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 ErrorSansLocation for OpenError { +impl ErrorSansOrigin for OpenError { const DESCRIPTION: &'static str = "A file system error occurred"; fn message(&self) -> String { let Self { dir, file } = self; @@ -40,25 +39,19 @@ impl ErrorSansLocation for OpenError { #[derive(Clone)] struct IOError(Arc>); impl IOError { - pub fn wrap(inner: io::Error) -> ErrorSansLocationObj { - Self(Arc::new(Mutex::new(inner))).pack() - } + pub fn wrap(inner: io::Error) -> ErrorSansOriginObj { Self(Arc::new(Mutex::new(inner))).pack() } } -impl ErrorSansLocation for IOError { +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()) - } + fn message(&self) -> String { format!("File read error: {}", self.0.lock().unwrap()) } } #[derive(Clone)] struct NotUtf8(PathBuf); impl NotUtf8 { - pub fn wrap(path: &Path) -> ErrorSansLocationObj { - Self(path.to_owned()).pack() - } + pub fn wrap(path: &Path) -> ErrorSansOriginObj { Self(path.to_owned()).pack() } } -impl ErrorSansLocation for NotUtf8 { +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()) @@ -78,21 +71,18 @@ impl DirNode { Self { cached: RefCell::default(), root, suffix } } - fn ext(&self) -> &str { - self.suffix.strip_prefix('.').expect("Checked in constructor") - } + fn ext(&self) -> &str { self.suffix.strip_prefix('.').expect("Checked in constructor") } - fn load_file(&self, fpath: &Path, orig_path: PathSlice) -> FSResult { + 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()) { + 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))?; @@ -116,7 +106,7 @@ impl DirNode { } } impl VirtFS for DirNode { - fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult { + fn get(&self, path: &[Tok], 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)) diff --git a/src/virt_fs/embed.rs b/src/virt_fs/embed.rs index 270ef9d..7f9e0c5 100644 --- a/src/virt_fs/embed.rs +++ b/src/virt_fs/embed.rs @@ -5,7 +5,7 @@ use rust_embed::RustEmbed; use super::common::CodeNotFound; use super::{FSResult, Loaded, VirtFS}; -use crate::error::ErrorSansLocation; +use crate::error::ErrorSansOrigin; use crate::location::CodeGenInfo; use crate::name::PathSlice; use crate::tree::{ModEntry, ModMember, Module}; @@ -25,8 +25,7 @@ impl EmbeddedFS { 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 path_no_suffix = path.strip_suffix(suffix).expect("embed filtered for suffix"); let mut segments = path_no_suffix.split('/').map(i); let mut cur_seg = segments.next().expect("Embed is a directory"); for next_seg in segments { @@ -56,7 +55,7 @@ impl EmbeddedFS { } impl VirtFS for EmbeddedFS { - fn get(&self, path: &[Tok], full_path: PathSlice) -> FSResult { + fn get(&self, path: &[Tok], full_path: &PathSlice) -> FSResult { if path.is_empty() { return Ok(Loaded::collection(self.tree.keys(|_| true))); } diff --git a/src/virt_fs/mod.rs b/src/virt_fs/mod.rs index b652eac..fc3d537 100644 --- a/src/virt_fs/mod.rs +++ b/src/virt_fs/mod.rs @@ -11,8 +11,8 @@ mod dir; mod embed; mod prefix; -pub use common::{CodeNotFound, FSResult, Loaded, VirtFS}; -pub use decl::DeclTree; +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/src/virt_fs/prefix.rs index 843e3de..93bdaba 100644 --- a/src/virt_fs/prefix.rs +++ b/src/virt_fs/prefix.rs @@ -3,22 +3,18 @@ use itertools::Itertools; use super::common::CodeNotFound; use super::VirtFS; -use crate::error::ErrorSansLocation; +use crate::error::ErrorSansOrigin; use crate::name::{PathSlice, VPath}; /// Modify the prefix of a nested file tree -pub struct PrefixFS { +pub struct PrefixFS<'a> { remove: VPath, add: VPath, - wrapped: Box, + wrapped: Box, } -impl PrefixFS { +impl<'a> PrefixFS<'a> { /// Modify the prefix of a file tree - pub fn new( - wrapped: impl VirtFS + 'static, - remove: impl AsRef, - add: impl AsRef, - ) -> Self { + 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()), @@ -26,14 +22,14 @@ impl PrefixFS { } } fn proc_path(&self, path: &[Tok]) -> Option>> { - let path = path.strip_prefix(&self.remove[..])?; + let path = path.strip_prefix(self.remove.as_slice())?; Some(self.add.0.iter().chain(path).cloned().collect_vec()) } } -impl VirtFS for PrefixFS { - fn get(&self, path: &[Tok], full_path: PathSlice) -> super::FSResult { - let path = (self.proc_path(path)) - .ok_or_else(|| CodeNotFound::new(full_path.to_vpath()).pack())?; +impl<'a> VirtFS for PrefixFS<'a> { + fn get(&self, path: &[Tok], 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: &[Tok]) -> Option {