From aebbf51228b35cbf2115873cf38776ea1f066fac Mon Sep 17 00:00:00 2001 From: Lawrence Bethlenfalvy Date: Sat, 17 Jun 2023 21:12:23 +0100 Subject: [PATCH] STL rework - fixed lots of bugs - overlay libraries work correctly and reliably - the STL is an overlay library - examples updated --- Cargo.lock | 225 ++++++++++++++- Cargo.toml | 15 +- examples/calculator/main.orc | 7 +- examples/hello-world/main.orc | 2 +- examples/list-processing/list.orc | 41 --- examples/list-processing/main.orc | 4 +- examples/list-processing/option.orc | 6 - examples/maps/fn.orc | 15 - examples/maps/main.orc | 6 +- orchid.code-workspace | 26 +- rustfmt.toml | 1 - src/bin/cli/mod.rs | 2 + src/bin/cli/prompt.rs | 11 + src/bin/main.rs | 269 +++++++++--------- src/foreign_macros/atomic_impl.rs | 56 ++-- src/foreign_macros/define_fn.rs | 79 ++++- src/foreign_macros/write_fn_step.rs | 48 +++- src/interner/monotype.rs | 4 +- src/lib.rs | 1 + src/parse/import.rs | 6 +- src/parse/lexer.rs | 6 +- src/pipeline/file_loader.rs | 46 ++- .../import_resolution/apply_aliases.rs | 66 +++-- .../import_resolution/collect_aliases.rs | 94 +++--- src/pipeline/import_resolution/decls.rs | 1 + .../import_resolution/resolve_imports.rs | 22 +- src/pipeline/mod.rs | 1 - src/pipeline/parse_layer.rs | 9 +- src/pipeline/project_tree/build_tree.rs | 11 +- .../project_tree/collect_ops/exported_ops.rs | 61 ++-- .../project_tree/collect_ops/ops_for.rs | 7 - src/pipeline/project_tree/tree.rs | 1 + src/pipeline/source_loader/load_source.rs | 118 +++++--- src/pipeline/split_name.rs | 16 -- src/representations/ast.rs | 13 +- src/representations/interpreted.rs | 6 + src/representations/sourcefile.rs | 6 +- src/representations/tree.rs | 30 +- src/rule/matcher_vectree/build.rs | 5 +- src/rule/repository.rs | 16 +- src/stl/arithmetic_error.rs | 28 ++ src/stl/bool.orc | 4 + src/stl/bool.rs | 92 ++++++ src/stl/bool/boolean.rs | 29 -- src/stl/bool/equals.rs | 54 ---- src/stl/bool/ifthenelse.rs | 46 --- src/stl/bool/mod.rs | 16 -- src/stl/conv.rs | 67 +++++ src/stl/conv/mod.rs | 14 - src/stl/conv/parse_float.rs | 43 --- src/stl/conv/parse_uint.rs | 47 --- src/stl/conv/to_string.rs | 32 --- src/stl/cpsio/debug.rs | 32 --- src/stl/cpsio/mod.rs | 19 -- src/stl/cpsio/panic.rs | 35 --- src/stl/cpsio/print.rs | 40 --- src/stl/cpsio/readline.rs | 27 -- {examples/list-processing => src/stl}/fn.orc | 2 +- src/stl/{cpsio/io.rs => io/command.rs} | 0 src/stl/io/inspect.rs | 29 ++ src/stl/io/mod.rs | 30 ++ src/stl/io/panic.rs | 21 ++ src/stl/io/print.rs | 31 ++ src/stl/io/readline.rs | 25 ++ src/stl/known.orc | 1 + {examples/maps => src/stl}/list.orc | 3 +- {examples/maps => src/stl}/map.orc | 7 +- src/stl/mk_stl.rs | 65 ++++- src/stl/mod.rs | 8 +- src/stl/num.orc | 5 + src/stl/num.rs | 149 ++++++++++ src/stl/num/mod.rs | 16 -- src/stl/num/numeric.rs | 135 --------- src/stl/num/operators/add.rs | 31 -- src/stl/num/operators/divide.rs | 37 --- src/stl/num/operators/mod.rs | 5 - src/stl/num/operators/multiply.rs | 36 --- src/stl/num/operators/remainder.rs | 36 --- src/stl/num/operators/subtract.rs | 36 --- {examples/maps => src/stl}/option.orc | 2 +- src/stl/prelude.orc | 15 + src/stl/proc.orc | 12 + src/stl/str.orc | 1 + src/stl/str.rs | 39 +++ src/stl/str/char_at.rs | 24 -- src/stl/str/concatenate.rs | 14 - src/stl/str/mod.rs | 12 - src/utils/mod.rs | 2 + src/utils/split_max_prefix.rs | 14 + src/utils/substack.rs | 8 +- src/utils/unwrap_or.rs | 4 + 91 files changed, 1444 insertions(+), 1395 deletions(-) delete mode 100644 examples/list-processing/list.orc delete mode 100644 examples/list-processing/option.orc delete mode 100644 examples/maps/fn.orc create mode 100644 src/bin/cli/mod.rs create mode 100644 src/bin/cli/prompt.rs delete mode 100644 src/pipeline/split_name.rs create mode 100644 src/stl/arithmetic_error.rs create mode 100644 src/stl/bool.orc create mode 100644 src/stl/bool.rs delete mode 100644 src/stl/bool/boolean.rs delete mode 100644 src/stl/bool/equals.rs delete mode 100644 src/stl/bool/ifthenelse.rs delete mode 100644 src/stl/bool/mod.rs create mode 100644 src/stl/conv.rs delete mode 100644 src/stl/conv/mod.rs delete mode 100644 src/stl/conv/parse_float.rs delete mode 100644 src/stl/conv/parse_uint.rs delete mode 100644 src/stl/conv/to_string.rs delete mode 100644 src/stl/cpsio/debug.rs delete mode 100644 src/stl/cpsio/mod.rs delete mode 100644 src/stl/cpsio/panic.rs delete mode 100644 src/stl/cpsio/print.rs delete mode 100644 src/stl/cpsio/readline.rs rename {examples/list-processing => src/stl}/fn.orc (90%) rename src/stl/{cpsio/io.rs => io/command.rs} (100%) create mode 100644 src/stl/io/inspect.rs create mode 100644 src/stl/io/mod.rs create mode 100644 src/stl/io/panic.rs create mode 100644 src/stl/io/print.rs create mode 100644 src/stl/io/readline.rs create mode 100644 src/stl/known.orc rename {examples/maps => src/stl}/list.orc (94%) rename {examples/maps => src/stl}/map.orc (94%) create mode 100644 src/stl/num.orc create mode 100644 src/stl/num.rs delete mode 100644 src/stl/num/mod.rs delete mode 100644 src/stl/num/numeric.rs delete mode 100644 src/stl/num/operators/add.rs delete mode 100644 src/stl/num/operators/divide.rs delete mode 100644 src/stl/num/operators/mod.rs delete mode 100644 src/stl/num/operators/multiply.rs delete mode 100644 src/stl/num/operators/remainder.rs delete mode 100644 src/stl/num/operators/subtract.rs rename {examples/maps => src/stl}/option.orc (92%) create mode 100644 src/stl/prelude.orc create mode 100644 src/stl/proc.orc create mode 100644 src/stl/str.orc create mode 100644 src/stl/str.rs delete mode 100644 src/stl/str/char_at.rs delete mode 100644 src/stl/str/concatenate.rs delete mode 100644 src/stl/str/mod.rs create mode 100644 src/utils/split_max_prefix.rs diff --git a/Cargo.lock b/Cargo.lock index 38c8d17..a4969a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "anstream" version = "0.3.2" @@ -85,6 +94,25 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "cc" version = "1.0.79" @@ -109,9 +137,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.7" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" +checksum = "80672091db20273a15cf9fdd4e47ed43b5091ec9841bf4c6145c9dfbbcae09ed" dependencies = [ "clap_builder", "clap_derive", @@ -120,9 +148,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.2.7" +version = "4.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "914c8c79fb560f238ef6429439a30023c862f7a28e688c58f7203f12b29970bd" +checksum = "c1458a1df40e1e2afebb7ab60ce55c1fa8f431146205aa5f4887e0b111c27636" dependencies = [ "anstream", "anstyle", @@ -133,9 +161,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.2.0" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", "proc-macro2", @@ -145,9 +173,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" [[package]] name = "colorchoice" @@ -155,6 +183,35 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "cpufeatures" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "dyn-clone" version = "1.0.11" @@ -188,6 +245,22 @@ dependencies = [ "libc", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -199,6 +272,19 @@ dependencies = [ "wasi", ] +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -273,6 +359,21 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece97ea872ece730aed82664c424eb4c8291e1ff2480247ccf7409044bc6479f" +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "num-traits" version = "0.2.15" @@ -299,15 +400,16 @@ dependencies = [ "itertools", "ordered-float", "paste", + "rust-embed", "thiserror", "trait-set", ] [[package]] name = "ordered-float" -version = "3.6.0" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a384337e997e6860ffbaa83708b2ef329fd8c54cb67a5f64d421e0f943254f" +checksum = "2fc2dbde8f8a79f2102cc474ceb0ad68e3b80b85289ea62389b60e66777e4213" dependencies = [ "num-traits", ] @@ -345,6 +447,58 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rust-embed" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b68543d5527e158213414a92832d2aab11a84d2571a5eb021ebe22c43aab066" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "6.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4e0f0ced47ded9a68374ac145edd65a6c1fa13a96447b873660b2a568a0fd7" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 1.0.109", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "7.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512b0ab6853f7e14e3c8754acb43d6f748bb9ced66aa5915a6553ac8213f7731" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + [[package]] name = "rustix" version = "0.37.19" @@ -359,6 +513,32 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.160" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "stacker" version = "0.1.15" @@ -431,6 +611,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "unicode-ident" version = "1.0.8" @@ -449,6 +635,16 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -471,6 +667,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 9c377d4..7e05313 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,11 +23,12 @@ doc = false [dependencies] thiserror = "1.0" -chumsky = "0.9.2" -hashbrown = "0.13.2" -ordered-float = "3.0" +chumsky = "0.9" +hashbrown = "0.13" +ordered-float = "3.7" itertools = "0.10" -dyn-clone = "1.0.11" -clap = { version = "4.2.4", features = ["derive"] } -trait-set = "0.3.0" -paste = "1.0.12" +dyn-clone = "1.0" +clap = { version = "4.3", features = ["derive"] } +trait-set = "0.3" +paste = "1.0" +rust-embed = { version = "6.6", features = ["include-exclude"] } diff --git a/examples/calculator/main.orc b/examples/calculator/main.orc index 48b0fee..99da9f2 100644 --- a/examples/calculator/main.orc +++ b/examples/calculator/main.orc @@ -1,16 +1,15 @@ -import std::(parse_float, to_string) -import std::(readline, print) +import std::(proc::*, to_float, to_string, io::(readline, print)) export main := do{ cps print "left operand: "; cps data = readline; - let a = parse_float data; + let a = to_float data; cps print "operator: "; cps op = readline; cps print ("you selected \"" ++ op ++ "\"\n"); cps print "right operand: "; cps data = readline; - let b = parse_float data; + let b = to_float data; let result = ( if op == "+" then a + b else if op == "-" then a - b diff --git a/examples/hello-world/main.orc b/examples/hello-world/main.orc index 3c42e51..4141ef2 100644 --- a/examples/hello-world/main.orc +++ b/examples/hello-world/main.orc @@ -1,3 +1,3 @@ -import std::print +import std::io::print main := print "Hello, world!\n" "goodbye" \ No newline at end of file diff --git a/examples/list-processing/list.orc b/examples/list-processing/list.orc deleted file mode 100644 index b6a3b1b..0000000 --- a/examples/list-processing/list.orc +++ /dev/null @@ -1,41 +0,0 @@ -import option -import super::fn::* - -pair := \a.\b. \f. f a b - --- Constructors - -export cons := \hd.\tl. option::some (pair hd tl) -export end := option::none - -export pop := \list.\default.\f.list default \cons.cons f - --- Operators - -export reduce := \list.\acc.\f. ( - loop r on (list acc) with - pop list acc \head.\tail. r tail (f acc head) -) - -export map := \list.\f. ( - loop r on (list) with - pop list end \head.\tail. cons (f head) (r tail) -) - -export skip := \list.\n. ( - loop r on (list n) with - if n == 0 then list - else pop list end \head.\tail. r tail (n - 1) -) - -export take := \list.\n. ( - loop r on (list n) with - if n == 0 then end - else pop list end \head.\tail. cons head $ r tail $ n - 1 -) - -new[...$item, ...$rest:1] =0x2p84=> (cons (...$item) new[...$rest]) -new[...$end] =0x1p84=> (cons (...$end) end) -new[] =0x1p84=> end - -export ::(new) diff --git a/examples/list-processing/main.orc b/examples/list-processing/main.orc index 968c5bf..90cba9d 100644 --- a/examples/list-processing/main.orc +++ b/examples/list-processing/main.orc @@ -1,6 +1,4 @@ -import std::(to_string, print) -import super::list -import fn::* +import std::(proc::*, io::print, to_string) export main := do{ let foo = list::new[1, 2, 3, 4, 5, 6]; diff --git a/examples/list-processing/option.orc b/examples/list-processing/option.orc deleted file mode 100644 index 840e24c..0000000 --- a/examples/list-processing/option.orc +++ /dev/null @@ -1,6 +0,0 @@ -export some := \v. \d.\f. f v -export none := \d.\f. d - -export map := \option.\f. option none f -export flatten := \option. option none \opt. opt -export flatmap := \option.\f. option none \opt. map opt f diff --git a/examples/maps/fn.orc b/examples/maps/fn.orc deleted file mode 100644 index 57e4bd9..0000000 --- a/examples/maps/fn.orc +++ /dev/null @@ -1,15 +0,0 @@ -export Y := \f.(\x.f (x x))(\x.f (x x)) - -export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r. - bind_names (..$parameters) (...$tail) -) ..$parameters - --- bind each of the names in the first argument as a parameter for the second argument -bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload -bind_names () (...$payload) =0x1p250=> ...$payload - -export ...$prefix $ ...$suffix:1 =0x1p34=> ...$prefix (...$suffix) -export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix - -export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body)) -$name => ...$body =0x1p129=> (\$name. ...$body) \ No newline at end of file diff --git a/examples/maps/main.orc b/examples/maps/main.orc index 39eedf2..8e3b5ae 100644 --- a/examples/maps/main.orc +++ b/examples/maps/main.orc @@ -1,8 +1,4 @@ -import list -import map -import option -import fn::* -import std::(print, to_string) +import std::(proc::*, io::print, to_string) export main := do{ let foo = map::new[ diff --git a/orchid.code-workspace b/orchid.code-workspace index e18e705..dd9e8ad 100644 --- a/orchid.code-workspace +++ b/orchid.code-workspace @@ -36,35 +36,13 @@ }, "extensions": { "recommendations": [ - "tomoki1207.pdf", - "james-yu.latex-workshop", "bungcip.better-toml", "maptz.regionfolder", "serayuzgur.crates", "tamasfe.even-better-toml", - "haskell.haskell", - "justusadam.language-haskell", "yzhang.markdown-all-in-one", - "goessner.mdmath", - "gruntfuggly.todo-tree" + "gruntfuggly.todo-tree", + "vadimcn.vscode-lldb" ] }, - "launch": { - "version": "0.2.0", - "configurations": [ - { - "type": "lldb", - "request": "launch", - "name": "Cargo launch", - "cwd": "${workspaceFolder:orchid}", - "program": "${workspaceFolder}/target/debug/orchid", - "cargo": { - "args": [ - "run", - ] - }, - "args": [] - } - ] - } } \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml index 62f1d15..f434b9d 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -12,7 +12,6 @@ newline_style = "Unix" normalize_comments = true wrap_comments = true overflow_delimited_expr = true -single_line_if_else_max_width = 50 use_small_heuristics = "Max" # literals diff --git a/src/bin/cli/mod.rs b/src/bin/cli/mod.rs new file mode 100644 index 0000000..4453d13 --- /dev/null +++ b/src/bin/cli/mod.rs @@ -0,0 +1,2 @@ +mod prompt; +pub use prompt::cmd_prompt; diff --git a/src/bin/cli/prompt.rs b/src/bin/cli/prompt.rs new file mode 100644 index 0000000..66837bc --- /dev/null +++ b/src/bin/cli/prompt.rs @@ -0,0 +1,11 @@ +use std::io::{self, Error, Write}; + +pub fn cmd_prompt(prompt: &str) -> Result<(String, Vec), Error> { + print!("{}", prompt); + io::stdout().flush()?; + let mut cmdln = String::new(); + io::stdin().read_line(&mut cmdln)?; + let mut segments = cmdln.split(' '); + let cmd = if let Some(cmd) = segments.next() { cmd } else { "" }; + Ok((cmd.to_string(), segments.map(str::to_string).collect())) +} diff --git a/src/bin/main.rs b/src/bin/main.rs index 7d9fc58..68a94f9 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,170 +1,166 @@ -use std::borrow::Borrow; +mod cli; + use std::fs::File; use std::path::{Path, PathBuf}; -use std::rc::Rc; +use std::process; use clap::Parser; use hashbrown::HashMap; use itertools::Itertools; use orchidlang::interner::{InternedDisplay, Interner, Sym}; -use orchidlang::pipeline::file_loader::{mk_cache, Loaded}; -use orchidlang::pipeline::{ - collect_consts, collect_rules, from_const_tree, parse_layer, ProjectTree, -}; -use orchidlang::rule::Repo; -use orchidlang::sourcefile::{FileEntry, Import}; -use orchidlang::{ast_to_interpreted, interpreter, stl}; +use orchidlang::{ast, ast_to_interpreted, interpreter, pipeline, rule, stl}; + +use crate::cli::cmd_prompt; /// Orchid interpreter #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - /// Folder containing main.orc + /// Folder containing main.orc or the manually specified entry module #[arg(short, long, default_value = ".")] - pub project: String, + pub dir: String, + /// Entrypoint for the interpreter + #[arg(short, long, default_value = "main::main")] + pub main: String, + /// Maximum number of steps taken by the macro executor + #[arg(long, default_value_t = 10_000)] + pub macro_limit: usize, + /// Print the parsed ruleset and exit + #[arg(long)] + pub dump_repo: bool, + /// Step through the macro execution process in the specified symbol + #[arg(long, default_value = "")] + pub macro_debug: String, } impl Args { + /// Validate the project directory and the + pub fn chk_dir_main(&self) -> Result<(), String> { + let dir_path = PathBuf::from(&self.dir); + if !dir_path.is_dir() { + return Err(format!("{} is not a directory", dir_path.display())); + } + let segs = self.main.split("::").collect::>(); + if segs.len() < 2 { + return Err("Entry point too short".to_string()); + } + let (pathsegs, _) = segs.split_at(segs.len() - 1); + let mut possible_files = pathsegs.iter().scan(dir_path, |path, seg| { + path.push(seg); + Some(path.with_extension("orc")) + }); + if possible_files.all(|p| File::open(p).is_err()) { + return Err(format!( + "{} not found in {}", + pathsegs.join("::"), + PathBuf::from(&self.dir).display() + )); + } + Ok(()) + } + pub fn chk_proj(&self) -> Result<(), String> { - let mut path = PathBuf::from(&self.project); - path.push(PathBuf::from("main.orc")); - if File::open(&path).is_ok() { - Ok(()) - } else { - Err(format!("{} not found", path.display())) + self.chk_dir_main() + } +} + +/// Load and parse all source related to the symbol `target` or all symbols +/// in the namespace `target` in the context of the STL. All sourcefiles must +/// reside within `dir`. +fn load_dir(dir: &Path, target: Sym, i: &Interner) -> pipeline::ProjectTree { + let file_cache = pipeline::file_loader::mk_dir_cache(dir.to_path_buf(), i); + let library = stl::mk_stl(i, stl::StlOptions::default()); + pipeline::parse_layer( + &[target], + &|path| file_cache.find(&path), + &library, + &stl::mk_prelude(i), + i, + ) + .expect("Failed to load source code") +} + +pub fn to_sym(data: &str, i: &Interner) -> Sym { + i.i(&data.split("::").map(|s| i.i(s)).collect::>()[..]) +} + +/// A little utility to step through the resolution of a macro set +pub fn macro_debug(repo: rule::Repo, mut code: ast::Expr, i: &Interner) { + let mut idx = 0; + println!("Macro debugger working on {}", code.bundle(i)); + loop { + let (cmd, _) = cmd_prompt("cmd> ").unwrap(); + match cmd.trim() { + "" | "n" | "next" => + if let Some(c) = repo.step(&code) { + idx += 1; + code = c; + println!("Step {idx}: {}", code.bundle(i)); + }, + "p" | "print" => println!("Step {idx}: {}", code.bundle(i)), + "d" | "dump" => println!("Rules: {}", repo.bundle(i)), + "q" | "quit" => return, + "h" | "help" => println!( + "Available commands: + \t, n, next\t\ttake a step + \tp, print\t\tprint the current state + \tq, quit\t\texit + \th, help\t\tprint this text" + ), + _ => { + println!("unrecognized command \"{}\", try \"help\"", cmd); + continue; + }, } } } -fn main() { +pub fn main() { let args = Args::parse(); args.chk_proj().unwrap_or_else(|e| panic!("{e}")); - run_dir(PathBuf::try_from(args.project).unwrap().borrow()); -} - -static PRELUDE_TXT: &str = r#" -import std::( - add, subtract, multiply, remainder, divide, - equals, ifthenelse, - concatenate -) - -export ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) -export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b)) -export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) -export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b)) -export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b)) -export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) -export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b)) - -export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest } -export do { ...$return } =0x1p130=> ...$return - -export statement (let $name = ...$value) ...$next =0x1p230=> ( - (\$name. ...$next) (...$value) -) -export statement (cps $name = ...$operation) ...$next =0x2p230=> ( - (...$operation) \$name. ...$next -) -export statement (cps ...$operation) ...$next =0x1p230=> ( - (...$operation) (...$next) -) - -export if ...$cond then ...$true else ...$false:1 =0x1p84=> ( - ifthenelse (...$cond) (...$true) (...$false) -) - -export ::(,) -"#; - -fn prelude_path(i: &Interner) -> Sym { - i.i(&[i.i("prelude")][..]) -} -fn mainmod_path(i: &Interner) -> Sym { - i.i(&[i.i("main")][..]) -} -fn entrypoint(i: &Interner) -> Sym { - i.i(&[i.i("main"), i.i("main")][..]) -} - -fn load_environment(i: &Interner) -> ProjectTree { - let env = from_const_tree( - HashMap::from([(i.i("std"), stl::mk_stl(i))]), - &[i.i("std")], - i, - ); - let loader = |path: Sym| { - if path == prelude_path(i) { - Ok(Loaded::Code(Rc::new(PRELUDE_TXT.to_string()))) - } else { - panic!( - "Prelude pointed to non-std path {}", - i.extern_vec(path).join("::") - ) - } - }; - parse_layer(&[prelude_path(i)], &loader, &env, &[], i).expect("prelude error") -} - -fn load_dir(i: &Interner, dir: &Path) -> ProjectTree { - let environment = load_environment(i); - let file_cache = mk_cache(dir.to_path_buf(), i); - let loader = |path| file_cache.find(&path); - let prelude = - [FileEntry::Import(vec![Import { path: prelude_path(i), name: None }])]; - parse_layer(&[mainmod_path(i)], &loader, &environment, &prelude, i) - .expect("Failed to load source code") -} - -pub fn run_dir(dir: &Path) { + let dir = PathBuf::try_from(args.dir).unwrap(); let i = Interner::new(); - let project = load_dir(&i, dir); - let rules = collect_rules(&project); - let consts = collect_consts(&project, &i); - println!("Initializing rule repository with {} rules", rules.len()); - let repo = Repo::new(rules, &i).unwrap_or_else(|(rule, error)| { + let main = to_sym(&args.main, &i); + let project = load_dir(&dir, main, &i); + let rules = pipeline::collect_rules(&project); + let consts = pipeline::collect_consts(&project, &i); + let repo = rule::Repo::new(rules, &i).unwrap_or_else(|(rule, error)| { panic!( "Rule error: {} - Offending rule: {}", + Offending rule: {}", error.bundle(&i), rule.bundle(&i) ) }); - println!("Repo dump: {}", repo.bundle(&i)); + if args.dump_repo { + println!("Parsed rules: {}", repo.bundle(&i)); + return; + } else if !args.macro_debug.is_empty() { + let name = to_sym(&args.macro_debug, &i); + let code = consts + .get(&name) + .unwrap_or_else(|| panic!("Constant {} not found", args.macro_debug)); + return macro_debug(repo, code.clone(), &i); + } let mut exec_table = HashMap::new(); for (name, source) in consts.iter() { - // let nval = entrypoint(&i); let name = &nval; let source = &consts[name]; - let mut tree = source.clone(); let displayname = i.extern_vec(*name).join("::"); - let macro_timeout = 100; - println!("Executing macros in {displayname}...",); - let mut idx = 0; - let unmatched = loop { - if idx == macro_timeout { - panic!("Macro execution in {displayname} didn't halt") - } - match repo.step(&tree) { - None => break tree, - Some(phase) => { - println!("Step {idx}/{macro_timeout}: {}", phase.bundle(&i)); - tree = phase; - }, - } - idx += 1; - }; - let runtree = ast_to_interpreted(&unmatched) - .unwrap_or_else(|e| panic!("Postmacro conversion error: {e}")); + let (unmatched, steps_left) = repo.long_step(source, args.macro_limit + 1); + assert!(steps_left > 0, "Macro execution in {displayname} did not halt"); + let runtree = ast_to_interpreted(&unmatched).unwrap_or_else(|e| { + panic!("Postmacro conversion error in {displayname}: {e}") + }); exec_table.insert(*name, runtree); } - println!("macro execution complete"); let ctx = interpreter::Context { symbols: &exec_table, interner: &i, gas: None }; - let entrypoint = exec_table.get(&entrypoint(&i)).unwrap_or_else(|| { + let entrypoint = exec_table.get(&main).unwrap_or_else(|| { + let main = args.main; + let symbols = + exec_table.keys().map(|t| i.extern_vec(*t).join("::")).join(", "); panic!( - "entrypoint not found, known keys are: {}", - exec_table - .keys() - .map(|t| i.r(*t).iter().map(|t| i.r(*t)).join("::")) - .join(", ") + "Entrypoint not found! + Entrypoint was {main} + known keys are {symbols}" ) }); let io_handler = orchidlang::stl::handleIO; @@ -173,12 +169,11 @@ pub fn run_dir(dir: &Path) { ret.unwrap_or_else(|e| panic!("Runtime error: {}", e)); if inert { println!("Settled at {}", state.expr().clause.bundle(&i)); - println!( - "Remaining gas: {}", - gas.map(|g| g.to_string()).unwrap_or(String::from("∞")) - ); - } - if gas == Some(0) { - println!("Ran out of gas!") + if let Some(g) = gas { + println!("Remaining gas: {g}") + } + } else if gas == Some(0) { + eprintln!("Ran out of gas!"); + process::exit(-1); } } diff --git a/src/foreign_macros/atomic_impl.rs b/src/foreign_macros/atomic_impl.rs index d77196d..1d538fb 100644 --- a/src/foreign_macros/atomic_impl.rs +++ b/src/foreign_macros/atomic_impl.rs @@ -6,14 +6,21 @@ use std::fmt::Debug; #[allow(unused)] // for the doc comments use dyn_clone::DynClone; +#[allow(unused)] // for the doc comments +use crate::define_fn; #[allow(unused)] // for the doc comments use crate::foreign::{Atomic, ExternFn}; #[allow(unused)] // for the doc comments +use crate::write_fn_step; +#[allow(unused)] // for the doc comments use crate::Primitive; /// A macro that generates implementations of [Atomic] to simplify the /// development of external bindings for Orchid. /// +/// Most use cases are fulfilled by [define_fn], pathological cases can combine +/// [write_fn_step] with manual [Atomic] implementations. +/// /// The macro depends on implementations of [`AsRef`] and /// [`From<(&Self, Clause)>`] for extracting the clause to be processed and then /// reconstructing the [Atomic]. Naturally, supertraits of [Atomic] are also @@ -32,34 +39,35 @@ use crate::Primitive; /// /// _definition of the `add` function in the STL_ /// ``` +/// use orchidlang::{Literal}; /// use orchidlang::interpreted::ExprInst; -/// use orchidlang::stl::Numeric; +/// use orchidlang::stl::litconv::with_lit; /// use orchidlang::{atomic_impl, atomic_redirect, externfn_impl}; /// +/// /// Convert a literal to a string using Rust's conversions for floats, chars and +/// /// uints respectively /// #[derive(Clone)] -/// pub struct Add2; -/// externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x })); +/// struct ToString; /// -/// #[derive(Debug, Clone)] -/// pub struct Add1 { -/// x: ExprInst, +/// externfn_impl!{ +/// ToString, |_: &Self, expr_inst: ExprInst|{ +/// Ok(InternalToString { +/// expr_inst +/// }) +/// } /// } -/// atomic_redirect!(Add1, x); -/// atomic_impl!(Add1); -/// externfn_impl!(Add1, |this: &Self, x: ExprInst| { -/// let a: Numeric = this.x.clone().try_into()?; -/// Ok(Add0 { a, x }) -/// }); -/// -/// #[derive(Debug, Clone)] -/// pub struct Add0 { -/// a: Numeric, -/// x: ExprInst, +/// #[derive(std::fmt::Debug,Clone)] +/// struct InternalToString { +/// expr_inst: ExprInst, /// } -/// atomic_redirect!(Add0, x); -/// atomic_impl!(Add0, |Self { a, x }: &Self, _| { -/// let b: Numeric = x.clone().try_into()?; -/// Ok((*a + b).into()) +/// atomic_redirect!(InternalToString, expr_inst); +/// atomic_impl!(InternalToString, |Self { expr_inst }: &Self, _|{ +/// with_lit(expr_inst, |l| Ok(match l { +/// Literal::Char(c) => c.to_string(), +/// Literal::Uint(i) => i.to_string(), +/// Literal::Num(n) => n.to_string(), +/// Literal::Str(s) => s.clone(), +/// })).map(|s| Literal::Str(s).into()) /// }); /// ``` #[macro_export] @@ -92,7 +100,11 @@ macro_rules! atomic_impl { // branch off or wrap up let clause = if inert { let closure = $next_phase; - match closure(&next_self, ctx) { + let res: Result< + $crate::interpreted::Clause, + std::rc::Rc, + > = closure(&next_self, ctx); + match res { Ok(r) => r, Err(e) => return Err($crate::interpreter::RuntimeError::Extern(e)), } diff --git a/src/foreign_macros/define_fn.rs b/src/foreign_macros/define_fn.rs index f8bbef0..ac54aef 100644 --- a/src/foreign_macros/define_fn.rs +++ b/src/foreign_macros/define_fn.rs @@ -26,6 +26,15 @@ use crate::write_fn_step; /// defined in the first step and returns a [Result] of the success type or /// `Rc`. /// +/// To avoid typing the same expression a lot, the conversion is optional. +/// If it is omitted, the field is initialized with a [TryInto::try_into] call +/// from `&ExprInst` to the target type. In this case, the error is +/// short-circuited using `?` so conversions through `FromResidual` are allowed. +/// The optional syntax starts with `as`. +/// +/// If all conversions are omitted, the alias definition (`expr=$ident in`) has +/// no effect and is therefore optional. +/// /// Finally, the body of the function is provided as an expression which can /// reference all of the arguments by their names, each bound to a ref of the /// specified type. @@ -45,13 +54,59 @@ use crate::write_fn_step; /// } /// } /// ``` +/// +/// A simpler format is also offered for unary functions: +/// +/// ``` +/// use orchidlang::stl::litconv::with_lit; +/// use orchidlang::{define_fn, Literal}; +/// +/// define_fn! { +/// /// Convert a literal to a string using Rust's conversions for floats, +/// /// chars and uints respectively +/// ToString = |x| with_lit(x, |l| Ok(match l { +/// Literal::Char(c) => c.to_string(), +/// Literal::Uint(i) => i.to_string(), +/// Literal::Num(n) => n.to_string(), +/// Literal::Str(s) => s.clone(), +/// })).map(|s| Literal::Str(s).into()) +/// } +/// ``` #[macro_export] macro_rules! define_fn { + // Unary function entry + ($( #[ $attr:meta ] )* $qual:vis $name:ident = $body:expr) => {paste::paste!{ + $crate::write_fn_step!( + $( #[ $attr ] )* $qual $name + > + [< Internal $name >] + ); + $crate::write_fn_step!( + [< Internal $name >] + {} + out = expr => Ok(expr); + { + let lambda = $body; + lambda(out) + } + ); + }}; + // xname is optional only if every conversion is implicit + ($( #[ $attr:meta ] )* $qual:vis $name:ident { + $( $arg:ident: $typ:ty ),+ + } => $body:expr) => { + $crate::define_fn!{expr=expr in + $( #[ $attr ] )* $qual $name { + $( $arg: $typ ),* + } => $body + } + }; + // multi-parameter function entry (expr=$xname:ident in $( #[ $attr:meta ] )* $qual:vis $name:ident { - $arg0:ident: $typ0:ty as $parse0:expr - $(, $arg:ident: $typ:ty as $parse:expr )* + $arg0:ident: $typ0:ty $( as $parse0:expr )? + $(, $arg:ident: $typ:ty $( as $parse:expr )? )* } => $body:expr ) => {paste::paste!{ // Generate initial state @@ -64,8 +119,10 @@ macro_rules! define_fn { $crate::define_fn!(@MIDDLE $xname [< Internal $name >] ($body) () ( - ($arg0: $typ0 as $parse0) - $( ($arg: $typ as $parse) )* + ( $arg0: $typ0 $( as $parse0)? ) + $( + ( $arg: $typ $( as $parse)? ) + )* ) ); }}; @@ -80,10 +137,10 @@ macro_rules! define_fn { // later fields ( // field that should be processed by this step - ( $arg0:ident: $typ0:ty as $parse0:expr ) + ( $arg0:ident: $typ0:ty $( as $parse0:expr )? ) // ensure that we have a next stage $( - ( $arg:ident: $typ:ty as $parse:expr ) + ( $arg:ident: $typ:ty $( as $parse:expr )? ) )+ ) ) => {paste::paste!{ @@ -93,7 +150,7 @@ macro_rules! define_fn { $( $arg_prev:ident : $typ_prev:ty ),* } [< $name $arg0:upper >] - where $arg0:$typ0 = $xname => $parse0; + where $arg0:$typ0 $( = $xname => $parse0 )? ; ); $crate::define_fn!(@MIDDLE $xname [< $name $arg0:upper >] ($body) ( @@ -101,7 +158,9 @@ macro_rules! define_fn { ($arg0: $typ0) ) ( - $( ($arg: $typ as $parse) )+ + $( + ( $arg: $typ $( as $parse)? ) + )+ ) ); }}; @@ -113,7 +172,7 @@ macro_rules! define_fn { ) // the last one is initialized before the body runs ( - ($arg0:ident: $typ0:ty as $parse0:expr) + ($arg0:ident: $typ0:ty $( as $parse0:expr )? ) ) ) => { $crate::write_fn_step!( @@ -121,7 +180,7 @@ macro_rules! define_fn { { $( $arg_prev: $typ_prev ),* } - $arg0:$typ0 = $xname => $parse0; + $arg0:$typ0 $( = $xname => $parse0 )? ; $body ); }; diff --git a/src/foreign_macros/write_fn_step.rs b/src/foreign_macros/write_fn_step.rs index bddf6b3..82e1420 100644 --- a/src/foreign_macros/write_fn_step.rs +++ b/src/foreign_macros/write_fn_step.rs @@ -1,13 +1,17 @@ #[allow(unused)] // for doc use crate::define_fn; #[allow(unused)] // for doc +use crate::foreign::Atomic; +#[allow(unused)] // for doc use crate::foreign::ExternFn; #[allow(unused)] // for doc use crate::interpreted::ExprInst; /// Write one step in the state machine representing a simple n-ary non-variadic -/// Orchid function. There are no known use cases for it that aren't expressed -/// better with [define_fn] which generates calls to this macro. +/// Orchid function. Most use cases are better covered by [define_fn] which +/// generates calls to this macro. This macro can be used in combination with +/// manual [Atomic] implementations to define a function that only behaves like +/// a simple n-ary non-variadic function with respect to some of its arguments. /// /// There are three ways to call this macro for the initial state, internal /// state, and exit state. All of them are demonstrated in one example and @@ -25,14 +29,14 @@ use crate::interpreted::ExprInst; /// // Middle state /// write_fn_step!( /// CharAt1 {} -/// CharAt0 where s = x => with_str(x, |s| Ok(s.clone())); +/// CharAt0 where s: String = x => with_str(x, |s| Ok(s.clone())); /// ); /// // Exit state /// write_fn_step!( /// CharAt0 { s: String } /// i = x => with_uint(x, Ok); /// { -/// if let Some(c) = s.chars().nth(i as usize) { +/// if let Some(c) = s.chars().nth(*i as usize) { /// Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) /// } else { /// RuntimeError::fail( @@ -52,7 +56,7 @@ use crate::interpreted::ExprInst; /// struct definition. A field called `expr_inst` of type [ExprInst] is added /// implicitly, so the first middle state has an empty field list. The next /// state is also provided, alongside the name and conversion of the next -/// parameter from a [&ExprInst] under the provided alias to a +/// parameter from a `&ExprInst` under the provided alias to a /// `Result<_, Rc>`. The success type is inferred from the /// type of the field at the place of its actual definition. This conversion is /// done in the implementation of [ExternFn] which also places the new @@ -67,6 +71,12 @@ use crate::interpreted::ExprInst; /// argument names bound. The arguments here are all references to their actual /// types except for the last one which is converted from [ExprInst] immediately /// before the body is evaluated. +/// +/// To avoid typing the same parsing process a lot, the conversion is optional. +/// If it is omitted, the field is initialized with a [TryInto::try_into] call +/// from `&ExprInst` to the target type. In this case, the error is +/// short-circuited using `?` so conversions through `FromResidual` are allowed. +/// The optional syntax starts with the `=` sign and ends before the semicolon. #[macro_export] macro_rules! write_fn_step { // write entry stage @@ -87,7 +97,7 @@ macro_rules! write_fn_step { $( $arg:ident : $typ:ty ),* } $next:ident where - $added:ident $( : $added_typ:ty )? = $xname:ident => $extract:expr ; + $added:ident $( : $added_typ:ty )? $( = $xname:ident => $extract:expr )? ; ) => { $( #[ $attr ] )* #[derive(std::fmt::Debug, Clone)] @@ -100,8 +110,8 @@ macro_rules! write_fn_step { $crate::externfn_impl!( $name, |this: &Self, expr_inst: $crate::interpreted::ExprInst| { - let $xname = &this.expr_inst; - let $added $( :$added_typ )? = $extract?; + let $added $( :$added_typ )? = + $crate::write_fn_step!(@CONV &this.expr_inst $(, $xname $extract )?); Ok($next{ $( $arg: this.$arg.clone(), )* $added, expr_inst @@ -114,23 +124,37 @@ macro_rules! write_fn_step { $( #[ $attr:meta ] )* $quant:vis $name:ident { $( $arg:ident: $typ:ty ),* } - $added:ident $(: $added_typ:ty )? = $xname:ident => $extract:expr ; + $added:ident $(: $added_typ:ty )? $( = $xname:ident => $extract:expr )? ; $process:expr ) => { $( #[ $attr ] )* #[derive(std::fmt::Debug, Clone)] $quant struct $name { - $( $arg: $typ, )+ + $( $arg: $typ, )* expr_inst: $crate::interpreted::ExprInst, } $crate::atomic_redirect!($name, expr_inst); $crate::atomic_impl!( $name, |Self{ $($arg, )* expr_inst }: &Self, _| { - let $xname = expr_inst; - let $added $(: $added_typ )? = $extract?; + let added $(: $added_typ )? = + $crate::write_fn_step!(@CONV expr_inst $(, $xname $extract )?); + let $added = &added; $process } ); }; + // Write conversion expression for an ExprInst + (@CONV $locxname:expr, $xname:ident $extract:expr) => { + { + let $xname = $locxname; + match $extract { + Err(e) => return Err(e), + Ok(r) => r, + } + } + }; + (@CONV $locxname:expr) => { + ($locxname).try_into()? + }; } diff --git a/src/interner/monotype.rs b/src/interner/monotype.rs index 39eaa76..535c1a0 100644 --- a/src/interner/monotype.rs +++ b/src/interner/monotype.rs @@ -52,7 +52,7 @@ impl TypedInterner { pub fn r(&self, t: Tok) -> &T { let values = self.values.borrow(); let key = t.into_usize() - 1; - values[key].0 + values[key].0.borrow() } /// Intern a static reference without allocating the data on the heap @@ -91,7 +91,7 @@ impl Drop for TypedInterner { if !owned { continue; } - unsafe { Box::from_raw((item as *const T).cast_mut()) }; + let _ = unsafe { Box::from_raw((item as *const T).cast_mut()) }; } } } diff --git a/src/lib.rs b/src/lib.rs index 84706a5..4dd8e12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,7 @@ pub mod rule; pub mod stl; mod utils; +pub use interner::Sym; pub use representations::ast_to_interpreted::ast_to_interpreted; pub use representations::{ ast, interpreted, sourcefile, tree, Literal, Location, PathSet, Primitive, diff --git a/src/parse/import.rs b/src/parse/import.rs index 3c5daff..a7200ea 100644 --- a/src/parse/import.rs +++ b/src/parse/import.rs @@ -83,11 +83,7 @@ pub fn import_parser<'a>( Some(Import { path: ctx.interner().i(&path), name: { - if name == ctx.interner().i("*") { - None - } else { - Some(name) - } + if name == ctx.interner().i("*") { None } else { Some(name) } }, }) }) diff --git a/src/parse/lexer.rs b/src/parse/lexer.rs index 56d4349..c1e7728 100644 --- a/src/parse/lexer.rs +++ b/src/parse/lexer.rs @@ -115,11 +115,7 @@ impl InternedDisplay for Lexeme { Self::PH(Placeholder { name, class }) => match *class { PHClass::Scalar => write!(f, "${}", i.r(*name)), PHClass::Vec { nonzero, prio } => { - if nonzero { - write!(f, "...") - } else { - write!(f, "..") - }?; + if nonzero { write!(f, "...") } else { write!(f, "..") }?; write!(f, "${}", i.r(*name))?; if prio != 0 { write!(f, ":{}", prio)?; diff --git a/src/pipeline/file_loader.rs b/src/pipeline/file_loader.rs index f782d7a..a964be2 100644 --- a/src/pipeline/file_loader.rs +++ b/src/pipeline/file_loader.rs @@ -1,8 +1,11 @@ -//! File system implementation of the source loader callback +//! Source loader callback definition and builtin implementations use std::path::{Path, PathBuf}; use std::rc::Rc; use std::{fs, io}; +use chumsky::text::Character; +use rust_embed::RustEmbed; + use crate::interner::{Interner, Sym}; use crate::pipeline::error::{ ErrorPosition, ProjectError, UnexpectedDirectory, @@ -86,13 +89,52 @@ pub fn load_file(root: &Path, path: &[impl AsRef]) -> IOResult { } /// Generates a cached file loader for a directory -pub fn mk_cache(root: PathBuf, i: &Interner) -> Cache { +pub fn mk_dir_cache(root: PathBuf, i: &Interner) -> Cache { Cache::new(move |token: Sym, _this| -> IOResult { let path = i.r(token).iter().map(|t| i.r(*t).as_str()).collect::>(); load_file(&root, &path) }) } +/// Load a file from the specified path from an embed table +pub fn load_embed(path: &str, ext: &str) -> IOResult { + let file_path = path.to_string() + ext; + if let Some(file) = T::get(&file_path) { + let s = file.data.iter().map(|c| c.to_char()).collect::(); + Ok(Loaded::Code(Rc::new(s))) + } else { + let entries = T::iter() + .map(|c| c.to_string()) + .filter_map(|path: String| { + let item_prefix = path.to_string() + "/"; + path.strip_prefix(&item_prefix).map(|subpath| { + let item_name = subpath + .split_inclusive('/') + .next() + .expect("Exact match excluded earlier"); + item_name + .strip_suffix('/') // subdirectory + .or_else(|| item_name.strip_suffix(ext)) // file + .expect("embed should be filtered to extension") + .to_string() + }) + }) + .collect::>(); + Ok(Loaded::Collection(Rc::new(entries))) + } +} + +/// Generates a cached file loader for a [RustEmbed] +pub fn mk_embed_cache<'a, T: 'static + RustEmbed>( + ext: &'a str, + i: &'a Interner, +) -> Cache<'a, Sym, IOResult> { + Cache::new(move |token: Sym, _this| -> IOResult { + let path = i.extern_vec(token).join("/"); + load_embed::(&path, ext) + }) +} + /// Loads the string contents of a file at the given location. /// If the path points to a directory, raises an error. pub fn load_text( diff --git a/src/pipeline/import_resolution/apply_aliases.rs b/src/pipeline/import_resolution/apply_aliases.rs index 7535f54..15e5e78 100644 --- a/src/pipeline/import_resolution/apply_aliases.rs +++ b/src/pipeline/import_resolution/apply_aliases.rs @@ -3,14 +3,14 @@ use std::rc::Rc; use hashbrown::HashMap; use super::alias_map::AliasMap; -use super::decls::InjectedAsFn; +use super::decls::{InjectedAsFn, UpdatedFn}; use crate::ast::{Expr, Rule}; use crate::interner::{Interner, Sym, Tok}; use crate::pipeline::{ProjectExt, ProjectModule}; use crate::representations::tree::{ModEntry, ModMember}; use crate::utils::Substack; -fn resolve( +fn resolve_rec( token: Sym, alias_map: &AliasMap, i: &Interner, @@ -18,7 +18,7 @@ fn resolve( if let Some(alias) = alias_map.resolve(token) { Some(i.r(alias).clone()) } else if let Some((foot, body)) = i.r(token).split_last() { - let mut new_beginning = resolve(i.i(body), alias_map, i)?; + let mut new_beginning = resolve_rec(i.i(body), alias_map, i)?; new_beginning.push(*foot); Some(new_beginning) } else { @@ -26,6 +26,18 @@ fn resolve( } } +fn resolve( + token: Sym, + alias_map: &AliasMap, + injected_as: &impl InjectedAsFn, + i: &Interner, +) -> Option { + injected_as(&i.r(token)[..]).or_else(|| { + let next_v = resolve_rec(token, alias_map, i)?; + Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v))) + }) +} + fn process_expr( expr: &Expr, alias_map: &AliasMap, @@ -33,16 +45,7 @@ fn process_expr( i: &Interner, ) -> Expr { expr - .map_names(&|n| { - injected_as(&i.r(n)[..]).or_else(|| { - let next_v = resolve(n, alias_map, i)?; - // println!("Resolved alias {} to {}", - // i.extern_vec(n).join("::"), - // i.extern_all(&next_v).join("::") - // ); - Some(injected_as(&next_v).unwrap_or_else(|| i.i(&next_v))) - }) - }) + .map_names(&|n| resolve(n, alias_map, injected_as, i)) .unwrap_or_else(|| expr.clone()) } @@ -54,10 +57,9 @@ fn apply_aliases_rec( alias_map: &AliasMap, i: &Interner, injected_as: &impl InjectedAsFn, + updated: &impl UpdatedFn, ) -> ProjectModule { - let items = module - .items - .iter() + let items = (module.items.iter()) .map(|(name, ent)| { let ModEntry { exported, member } = ent; let member = match member { @@ -65,9 +67,7 @@ fn apply_aliases_rec( ModMember::Item(process_expr(expr, alias_map, injected_as, i)), ModMember::Sub(module) => { let subpath = path.push(*name); - let is_ignored = - injected_as(&subpath.iter().rev_vec_clone()).is_some(); - let new_mod = if is_ignored { + let new_mod = if !updated(&subpath.iter().rev_vec_clone()) { module.clone() } else { let module = module.as_ref(); @@ -77,6 +77,7 @@ fn apply_aliases_rec( alias_map, i, injected_as, + updated, )) }; ModMember::Sub(new_mod) @@ -85,23 +86,18 @@ fn apply_aliases_rec( (*name, ModEntry { exported: *exported, member }) }) .collect::>(); - let rules = module - .extra - .rules - .iter() + let rules = (module.extra.rules.iter()) .map(|rule| { let Rule { pattern, prio, template } = rule; Rule { prio: *prio, pattern: Rc::new( - pattern - .iter() + (pattern.iter()) .map(|expr| process_expr(expr, alias_map, injected_as, i)) .collect::>(), ), template: Rc::new( - template - .iter() + (template.iter()) .map(|expr| process_expr(expr, alias_map, injected_as, i)) .collect::>(), ), @@ -113,7 +109,11 @@ fn apply_aliases_rec( imports: module.imports.clone(), extra: ProjectExt { rules, - exports: module.extra.exports.clone(), + exports: (module.extra.exports.iter()) + .map(|(k, v)| { + (*k, resolve(*v, alias_map, injected_as, i).unwrap_or(*v)) + }) + .collect(), file: module.extra.file.clone(), imports_from: module.extra.imports_from.clone(), }, @@ -125,6 +125,14 @@ pub fn apply_aliases( alias_map: &AliasMap, i: &Interner, injected_as: &impl InjectedAsFn, + updated: &impl UpdatedFn, ) -> ProjectModule { - apply_aliases_rec(Substack::Bottom, module, alias_map, i, injected_as) + apply_aliases_rec( + Substack::Bottom, + module, + alias_map, + i, + injected_as, + updated, + ) } diff --git a/src/pipeline/import_resolution/collect_aliases.rs b/src/pipeline/import_resolution/collect_aliases.rs index 4e9ca1b..d9e554c 100644 --- a/src/pipeline/import_resolution/collect_aliases.rs +++ b/src/pipeline/import_resolution/collect_aliases.rs @@ -1,12 +1,13 @@ +use core::panic; use std::rc::Rc; use super::alias_map::AliasMap; -use super::decls::InjectedAsFn; +use super::decls::UpdatedFn; use crate::interner::{Interner, Tok}; use crate::pipeline::error::{NotExported, ProjectError}; use crate::pipeline::project_tree::{split_path, ProjectModule, ProjectTree}; use crate::representations::tree::{ModMember, WalkErrorKind}; -use crate::utils::{pushed, Substack}; +use crate::utils::{pushed, unwrap_or, Substack}; /// Assert that a module identified by a path can see a given symbol fn assert_visible( @@ -15,36 +16,40 @@ fn assert_visible( project: &ProjectTree, i: &Interner, ) -> Result<(), Rc> { - let (tgt_item, tgt_path) = if let Some(s) = target.split_last() { - s - } else { - return Ok(()); - }; + let (tgt_item, tgt_path) = unwrap_or!(target.split_last(); return Ok(())); let shared_len = source.iter().zip(tgt_path.iter()).take_while(|(a, b)| a == b).count(); - let shared_root = - project.0.walk(&tgt_path[..shared_len], false).expect("checked in parsing"); - let direct_parent = - shared_root.walk(&tgt_path[shared_len..], true).map_err(|e| { - match e.kind { - WalkErrorKind::Missing => panic!("checked in parsing"), - WalkErrorKind::Private => { - let full_path = &tgt_path[..shared_len + e.pos]; - let (file, sub) = split_path(full_path, project); - let (ref_file, ref_sub) = split_path(source, project); - NotExported { - file: i.extern_all(file), - subpath: i.extern_all(sub), - referrer_file: i.extern_all(ref_file), - referrer_subpath: i.extern_all(ref_sub), - } - .rc() - }, - } + let vis_ignored_len = usize::min(tgt_path.len(), shared_len + 1); + let private_root = + (project.0).walk(&tgt_path[..vis_ignored_len], false).unwrap_or_else(|e| { + let path_slc = &tgt_path[..vis_ignored_len]; + let bad_path = i.extern_all(path_slc).join("::"); + eprintln!( + "Error while walking {bad_path}; {:?} on step {}", + e.kind, e.pos + ); + eprintln!("looking from {}", i.extern_all(source).join("::")); + panic!("") + }); + let direct_parent = private_root + .walk(&tgt_path[vis_ignored_len..], true) + .map_err(|e| match e.kind { + WalkErrorKind::Missing => panic!("checked in parsing"), + WalkErrorKind::Private => { + let full_path = &tgt_path[..shared_len + e.pos]; + let (file, sub) = split_path(full_path, project); + let (ref_file, ref_sub) = split_path(source, project); + NotExported { + file: i.extern_all(file), + subpath: i.extern_all(sub), + referrer_file: i.extern_all(ref_file), + referrer_subpath: i.extern_all(ref_sub), + } + .rc() + }, })?; let tgt_item_exported = direct_parent.extra.exports.contains_key(tgt_item); - let target_prefixes_source = - shared_len == tgt_path.len() && source.get(shared_len) == Some(tgt_item); + let target_prefixes_source = shared_len == tgt_path.len(); if !tgt_item_exported && !target_prefixes_source { let (file, sub) = split_path(target, project); let (ref_file, ref_sub) = split_path(source, project); @@ -69,20 +74,30 @@ fn collect_aliases_rec( project: &ProjectTree, alias_map: &mut AliasMap, i: &Interner, - injected_as: &impl InjectedAsFn, + updated: &impl UpdatedFn, ) -> Result<(), Rc> { // Assume injected module has been alias-resolved let mod_path_v = path.iter().rev_vec_clone(); - if injected_as(&mod_path_v).is_some() { + if !updated(&mod_path_v) { return Ok(()); }; - for (&name, &target_mod) in module.extra.imports_from.iter() { - let target_mod_v = i.r(target_mod); + for (&name, &target_mod_name) in module.extra.imports_from.iter() { + let target_mod_v = i.r(target_mod_name); let target_sym_v = pushed(target_mod_v, name); assert_visible(&mod_path_v, &target_sym_v, project, i)?; let sym_path_v = pushed(&mod_path_v, name); let sym_path = i.i(&sym_path_v); - let target_sym = i.i(&target_sym_v); + let target_mod = (project.0.walk(target_mod_v, false)) + .expect("checked above in assert_visible"); + let target_sym = + *target_mod.extra.exports.get(&name).unwrap_or_else(|| { + panic!( + "error in {}, {} has no member {}", + i.extern_all(&mod_path_v).join("::"), + i.extern_all(target_mod_v).join("::"), + i.r(name) + ) + }); alias_map.link(sym_path, target_sym); } for (&name, entry) in module.items.iter() { @@ -97,7 +112,7 @@ fn collect_aliases_rec( project, alias_map, i, - injected_as, + updated, )? } Ok(()) @@ -109,14 +124,7 @@ pub fn collect_aliases( project: &ProjectTree, alias_map: &mut AliasMap, i: &Interner, - injected_as: &impl InjectedAsFn, + updated: &impl UpdatedFn, ) -> Result<(), Rc> { - collect_aliases_rec( - Substack::Bottom, - module, - project, - alias_map, - i, - injected_as, - ) + collect_aliases_rec(Substack::Bottom, module, project, alias_map, i, updated) } diff --git a/src/pipeline/import_resolution/decls.rs b/src/pipeline/import_resolution/decls.rs index 27fc3c2..bd735b1 100644 --- a/src/pipeline/import_resolution/decls.rs +++ b/src/pipeline/import_resolution/decls.rs @@ -4,4 +4,5 @@ use crate::interner::{Sym, Tok}; trait_set! { pub trait InjectedAsFn = Fn(&[Tok]) -> Option; + pub trait UpdatedFn = Fn(&[Tok]) -> bool; } diff --git a/src/pipeline/import_resolution/resolve_imports.rs b/src/pipeline/import_resolution/resolve_imports.rs index 7f93f05..c9bcd14 100644 --- a/src/pipeline/import_resolution/resolve_imports.rs +++ b/src/pipeline/import_resolution/resolve_imports.rs @@ -1,11 +1,9 @@ use std::rc::Rc; -use itertools::Itertools; - use super::alias_map::AliasMap; use super::apply_aliases::apply_aliases; use super::collect_aliases::collect_aliases; -use super::decls::InjectedAsFn; +use super::decls::{InjectedAsFn, UpdatedFn}; use crate::interner::Interner; use crate::pipeline::error::ProjectError; use crate::pipeline::project_tree::ProjectTree; @@ -16,21 +14,11 @@ pub fn resolve_imports( project: ProjectTree, i: &Interner, injected_as: &impl InjectedAsFn, + updated: &impl UpdatedFn, ) -> Result> { let mut map = AliasMap::new(); - collect_aliases(project.0.as_ref(), &project, &mut map, i, injected_as)?; - println!( - "Aliases: {{{:?}}}", - map - .targets - .iter() - .map(|(kt, vt)| format!( - "{} => {}", - i.extern_vec(*kt).join("::"), - i.extern_vec(*vt).join("::") - )) - .join(", ") - ); - let new_mod = apply_aliases(project.0.as_ref(), &map, i, injected_as); + collect_aliases(project.0.as_ref(), &project, &mut map, i, updated)?; + let new_mod = + apply_aliases(project.0.as_ref(), &map, i, injected_as, updated); Ok(ProjectTree(Rc::new(new_mod))) } diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs index 6c68d5b..3b06847 100644 --- a/src/pipeline/mod.rs +++ b/src/pipeline/mod.rs @@ -6,7 +6,6 @@ mod import_resolution; mod parse_layer; mod project_tree; mod source_loader; -mod split_name; pub use parse_layer::parse_layer; pub use project_tree::{ diff --git a/src/pipeline/parse_layer.rs b/src/pipeline/parse_layer.rs index 44b9608..23efedb 100644 --- a/src/pipeline/parse_layer.rs +++ b/src/pipeline/parse_layer.rs @@ -34,13 +34,16 @@ pub fn parse_layer( }; let source = source_loader::load_source(targets, prelude, i, loader, &|path| { - injected_as(path).is_some() + environment.0.walk(&i.r(path)[..], false).is_ok() })?; let tree = project_tree::build_tree(source, i, prelude, &injected_names)?; let sum = ProjectTree(Rc::new( - environment.0.as_ref().clone() + tree.0.as_ref().clone(), + environment.0.as_ref().clone().overlay(tree.0.as_ref().clone()), )); - let resolvd = import_resolution::resolve_imports(sum, i, &injected_as)?; + let resolvd = + import_resolution::resolve_imports(sum, i, &injected_as, &|path| { + tree.0.walk(path, false).is_ok() + })?; // Addition among modules favours the left hand side. Ok(resolvd) } diff --git a/src/pipeline/project_tree/build_tree.rs b/src/pipeline/project_tree/build_tree.rs index ff58ce6..62e722e 100644 --- a/src/pipeline/project_tree/build_tree.rs +++ b/src/pipeline/project_tree/build_tree.rs @@ -57,11 +57,7 @@ fn source_to_module( let imports = data .iter() .filter_map(|ent| { - if let FileEntry::Import(impv) = ent { - Some(impv.iter()) - } else { - None - } + if let FileEntry::Import(impv) = ent { Some(impv.iter()) } else { None } }) .flatten() .cloned() @@ -182,6 +178,10 @@ fn files_to_module( i: &Interner, ) -> Rc> { let lvl = path.len(); + debug_assert!( + files.iter().map(|f| f.path.len()).max().unwrap() >= lvl, + "path is longer than any of the considered file paths" + ); let path_v = path.iter().rev_vec_clone(); if files.len() == 1 && files[0].path.len() == lvl { return source_to_module( @@ -227,6 +227,7 @@ pub fn build_tree( prelude: &[FileEntry], injected: &impl InjectedOperatorsFn, ) -> Result> { + assert!(!files.is_empty(), "A tree requires at least one module"); let ops_cache = collect_ops::mk_cache(&files, i, injected); let mut entries = files .iter() diff --git a/src/pipeline/project_tree/collect_ops/exported_ops.rs b/src/pipeline/project_tree/collect_ops/exported_ops.rs index fbeac5f..827dcb7 100644 --- a/src/pipeline/project_tree/collect_ops/exported_ops.rs +++ b/src/pipeline/project_tree/collect_ops/exported_ops.rs @@ -1,16 +1,13 @@ -use std::println; use std::rc::Rc; use hashbrown::HashSet; -use itertools::Itertools; use trait_set::trait_set; use crate::interner::{Interner, Sym, Tok}; use crate::pipeline::error::{ModuleNotFound, ProjectError}; use crate::pipeline::source_loader::LoadedSourceTable; -use crate::pipeline::split_name::split_name; use crate::representations::tree::WalkErrorKind; -use crate::utils::Cache; +use crate::utils::{split_max_prefix, unwrap_or, Cache}; pub type OpsResult = Result>>, Rc>; pub type ExportedOpsCache<'a> = Cache<'a, Sym, OpsResult>; @@ -33,34 +30,24 @@ pub fn collect_exported_ops( i: &Interner, injected: &impl InjectedOperatorsFn, ) -> OpsResult { - if let Some(ops) = injected(path) { - if path == i.i(&[i.i("prelude")][..]) { - println!("%%% Prelude exported ops %%%"); - println!("{}", ops.iter().map(|t| i.r(*t)).join(", ")); - } - return Ok(ops); - } + let injected = injected(path).unwrap_or_else(|| Rc::new(HashSet::new())); let is_file = |n: &[Tok]| loaded.contains_key(&i.i(n)); let path_s = &i.r(path)[..]; - let name_split = split_name(path_s, &is_file); - let (fpath_v, subpath_v) = if let Some(f) = name_split { - f - } else { - return Ok(Rc::new( - loaded - .keys() - .copied() - .filter_map(|modname| { - let modname_s = i.r(modname); - if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) { - Some(modname_s[path_s.len()]) - } else { - None - } - }) - .collect::>(), - )); - }; + let name_split = split_max_prefix(path_s, &is_file); + let (fpath_v, subpath_v) = unwrap_or!(name_split; return Ok(Rc::new( + (loaded.keys()) + .copied() + .filter_map(|modname| { + let modname_s = i.r(modname); + if path_s.len() == coprefix(path_s.iter(), modname_s.iter()) { + Some(modname_s[path_s.len()]) + } else { + None + } + }) + .chain(injected.iter().copied()) + .collect::>(), + ))); let fpath = i.i(fpath_v); let preparsed = &loaded[&fpath].preparsed; let module = preparsed.0.walk(subpath_v, false).map_err(|walk_err| { @@ -70,8 +57,7 @@ pub fn collect_exported_ops( }, WalkErrorKind::Missing => ModuleNotFound { file: i.extern_vec(fpath), - subpath: subpath_v - .iter() + subpath: (subpath_v.iter()) .take(walk_err.pos) .map(|t| i.r(*t)) .cloned() @@ -80,12 +66,11 @@ pub fn collect_exported_ops( .rc(), } })?; - let out: HashSet<_> = - module.items.iter().filter(|(_, v)| v.exported).map(|(k, _)| *k).collect(); - if path == i.i(&[i.i("prelude")][..]) { - println!("%%% Prelude exported ops %%%"); - println!("{}", out.iter().map(|t| i.r(*t)).join(", ")); - } + let out = (module.items.iter()) + .filter(|(_, v)| v.exported) + .map(|(k, _)| *k) + .chain(injected.iter().copied()) + .collect::>(); Ok(Rc::new(out)) } diff --git a/src/pipeline/project_tree/collect_ops/ops_for.rs b/src/pipeline/project_tree/collect_ops/ops_for.rs index f2ae1be..8b97c8f 100644 --- a/src/pipeline/project_tree/collect_ops/ops_for.rs +++ b/src/pipeline/project_tree/collect_ops/ops_for.rs @@ -1,7 +1,6 @@ use std::rc::Rc; use hashbrown::HashSet; -use itertools::Itertools; use super::exported_ops::{ExportedOpsCache, OpsResult}; use crate::interner::{Interner, Tok}; @@ -34,13 +33,11 @@ pub fn collect_ops_for( ) -> OpsResult { let tree = &loaded[&i.i(file)].preparsed.0; let mut ret = HashSet::new(); - println!("collecting ops for {}", i.extern_all(file).join("::")); tree_all_ops(tree.as_ref(), &mut ret); tree.visit_all_imports(&mut |modpath, _module, import| { if let Some(n) = import.name { ret.insert(n); } else { - println!("\tglob import from {}", i.extern_vec(import.path).join("::")); let path = import_abs_path(file, modpath, &i.r(import.path)[..], i) .expect("This error should have been caught during loading"); ret.extend(ops_cache.find(&i.i(&path))?.iter().copied()); @@ -48,9 +45,5 @@ pub fn collect_ops_for( Ok::<_, Rc>(()) })?; ret.drain_filter(|t| !is_op(i.r(*t))); - if file == &[i.i("map")][..] { - println!(" %%% ops in map %%% "); - println!("{}", ret.iter().map(|t| i.r(*t)).join(", ")) - } Ok(Rc::new(ret)) } diff --git a/src/pipeline/project_tree/tree.rs b/src/pipeline/project_tree/tree.rs index f1d40dc..98b2c3e 100644 --- a/src/pipeline/project_tree/tree.rs +++ b/src/pipeline/project_tree/tree.rs @@ -41,6 +41,7 @@ impl Add for ProjectExt { pub type ProjectModule = Module; /// Module corresponding to the root of a project +#[derive(Debug, Clone)] pub struct ProjectTree(pub Rc); fn collect_rules_rec(bag: &mut Vec, module: &ProjectModule) { diff --git a/src/pipeline/source_loader/load_source.rs b/src/pipeline/source_loader/load_source.rs index e0d4193..115ee10 100644 --- a/src/pipeline/source_loader/load_source.rs +++ b/src/pipeline/source_loader/load_source.rs @@ -3,12 +3,12 @@ use std::rc::Rc; use super::loaded_source::{LoadedSource, LoadedSourceTable}; use super::preparse::preparse; -use crate::interner::{Interner, Sym, Tok}; +use crate::interner::{Interner, Sym}; use crate::pipeline::error::ProjectError; use crate::pipeline::file_loader::{load_text, IOResult, Loaded}; use crate::pipeline::import_abs_path::import_abs_path; -use crate::pipeline::split_name::split_name; use crate::representations::sourcefile::FileEntry; +use crate::utils::split_max_prefix; /// Load the source at the given path or all within if it's a collection, /// and all sources imported from these. @@ -18,31 +18,67 @@ fn load_abs_path_rec( prelude: &[FileEntry], i: &Interner, get_source: &impl Fn(Sym) -> IOResult, - is_injected: &impl Fn(&[Tok]) -> bool, + is_injected_module: &impl Fn(Sym) -> bool, ) -> Result<(), Rc> { - let abs_pathv = i.r(abs_path); - // short-circuit if this import is defined externally or already known - if is_injected(abs_pathv) | table.contains_key(&abs_path) { + // # Termination + // + // Every recursion of this function either + // - adds one of the files in the source directory to `table` or + // - recursively traverses a directory tree + // therefore eventually the function exits, assuming that the directory tree + // contains no cycles. + + // Termination: exit if entry already visited + if table.contains_key(&abs_path) { return Ok(()); } + // try splitting the path to file, swallowing any IO errors - let is_file = |p| (get_source)(p).map(|l| l.is_code()).unwrap_or(false); - let name_split = split_name(abs_pathv, &|p| is_file(i.i(p))); - let filename = if let Some((f, _)) = name_split { - f + let is_file = |sym| get_source(sym).map(|l| l.is_code()).unwrap_or(false); + let name_split = split_max_prefix(&i.r(abs_path)[..], &|p| is_file(i.i(p))); + if let Some((filename, _)) = name_split { + // if the filename is valid, load, preparse and record this file + let text = load_text(i.i(filename), &get_source, i)?; + let preparsed = preparse( + filename.iter().map(|t| i.r(*t)).cloned().collect(), + text.as_str(), + prelude, + i, + )?; + table.insert(i.i(filename), LoadedSource { + text, + preparsed: preparsed.clone(), + }); + // recurse on all imported modules + preparsed.0.visit_all_imports(&mut |modpath, _module, import| { + let abs_pathv = + import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; + // recurse on imported module + load_abs_path_rec( + i.i(&abs_pathv), + table, + prelude, + i, + get_source, + is_injected_module, + ) + }) } else { - // If the path could not be split to file, load it as directory - let coll = if let Loaded::Collection(c) = (get_source)(abs_path)? { - c - } - // ^^ raise any IO error that was previously swallowed - else { - panic!("split_name returned None but the path is a file") + // If the path is not within a file, load it as directory + let coll = match get_source(abs_path) { + Ok(Loaded::Collection(coll)) => coll, + Ok(Loaded::Code(_)) => + unreachable!("split_name returned None but the path is a file"), + Err(e) => { + let parent = i.r(abs_path).split_last().expect("import path nonzero").1; + // exit without error if it was injected, or raise any IO error that was + // previously swallowed + return if is_injected_module(i.i(parent)) { Ok(()) } else { Err(e) }; + }, }; // recurse on all files and folders within for item in coll.iter() { - let abs_subpath = abs_pathv - .iter() + let abs_subpath = (i.r(abs_path).iter()) .copied() .chain(iter::once(i.i(item))) .collect::>(); @@ -52,48 +88,36 @@ fn load_abs_path_rec( prelude, i, get_source, - is_injected, + is_injected_module, )? } - return Ok(()); - }; - // otherwise load, preparse and record this file - let text = load_text(i.i(filename), &get_source, i)?; - let preparsed = preparse( - filename.iter().map(|t| i.r(*t)).cloned().collect(), - text.as_str(), - prelude, - i, - )?; - table.insert(abs_path, LoadedSource { text, preparsed: preparsed.clone() }); - // recurse on all imported modules - preparsed.0.visit_all_imports(&mut |modpath, _module, import| { - let abs_pathv = - import_abs_path(filename, modpath, &import.nonglob_path(i), i)?; - // recurse on imported module - load_abs_path_rec( - i.i(&abs_pathv), - table, - prelude, - i, - get_source, - is_injected, - ) - }) + Ok(()) + } } /// Load and preparse all files reachable from the load targets via /// imports that aren't injected. +/// +/// is_injected_module must return false for injected symbols, but may return +/// true for parents of injected modules that are not directly part of the +/// injected data (the ProjectTree doesn't make a distinction between the two) pub fn load_source( targets: &[Sym], prelude: &[FileEntry], i: &Interner, get_source: &impl Fn(Sym) -> IOResult, - is_injected: &impl Fn(&[Tok]) -> bool, + is_injected_module: &impl Fn(Sym) -> bool, ) -> Result> { let mut table = LoadedSourceTable::new(); for target in targets { - load_abs_path_rec(*target, &mut table, prelude, i, get_source, is_injected)? + load_abs_path_rec( + *target, + &mut table, + prelude, + i, + get_source, + is_injected_module, + )? } Ok(table) } diff --git a/src/pipeline/split_name.rs b/src/pipeline/split_name.rs deleted file mode 100644 index d22ba1d..0000000 --- a/src/pipeline/split_name.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::interner::Tok; - -#[allow(clippy::type_complexity)] -// FIXME couldn't find a good factoring -pub fn split_name<'a>( - path: &'a [Tok], - is_valid: &impl Fn(&[Tok]) -> bool, -) -> Option<(&'a [Tok], &'a [Tok])> { - for split in (0..=path.len()).rev() { - let (filename, subpath) = path.split_at(split); - if is_valid(filename) { - return Some((filename, subpath)); - } - } - None -} diff --git a/src/representations/ast.rs b/src/representations/ast.rs index 4505f74..451bc58 100644 --- a/src/representations/ast.rs +++ b/src/representations/ast.rs @@ -179,11 +179,8 @@ impl Clause { match self { Clause::Lambda(arg, body) => { arg.visit_names(binds, cb); - let new_binds = if let Clause::Name(n) = arg.value { - binds.push(n) - } else { - binds - }; + let new_binds = + if let Clause::Name(n) = arg.value { binds.push(n) } else { binds }; for x in body.iter() { x.visit_names(new_binds, cb) } @@ -216,11 +213,7 @@ impl Clause { val.unwrap_or_else(|| e.clone()) }) .collect(); - if any_some { - Some(Clause::S(*c, Rc::new(new_body))) - } else { - None - } + if any_some { Some(Clause::S(*c, Rc::new(new_body))) } else { None } }, Clause::Lambda(arg, body) => { let new_arg = arg.map_names(pred); diff --git a/src/representations/interpreted.rs b/src/representations/interpreted.rs index cc2e090..b75db9b 100644 --- a/src/representations/interpreted.rs +++ b/src/representations/interpreted.rs @@ -219,3 +219,9 @@ impl> From for Clause { Self::P(Primitive::Literal(value.into())) } } + +impl> From for ExprInst { + fn from(value: T) -> Self { + value.into().wrap() + } +} diff --git a/src/representations/sourcefile.rs b/src/representations/sourcefile.rs index 8f74e2b..451bb55 100644 --- a/src/representations/sourcefile.rs +++ b/src/representations/sourcefile.rs @@ -1,4 +1,6 @@ //! Building blocks of a source file +use std::iter; + use itertools::{Either, Itertools}; use crate::ast::{Constant, Rule}; @@ -157,7 +159,9 @@ pub fn absolute_path( if tail.is_empty() { Ok(new_abs.to_vec()) } else { - absolute_path(new_abs, tail, i) + let new_rel = + iter::once(i.i("self")).chain(tail.iter().copied()).collect::>(); + absolute_path(new_abs, &new_rel, i) } } else if *head == i.i("self") { Ok(abs_location.iter().chain(tail.iter()).copied().collect()) diff --git a/src/representations/tree.rs b/src/representations/tree.rs index 89f4561..b59665d 100644 --- a/src/representations/tree.rs +++ b/src/representations/tree.rs @@ -107,23 +107,23 @@ impl Module { ) -> Result<(), E> { self.visit_all_imports_rec(Substack::Bottom, callback) } -} -impl> Add - for Module -{ - type Output = Self; - - fn add(mut self, rhs: Self) -> Self::Output { - let Module { extra, imports, items } = rhs; + /// Combine two module trees; wherever they conflict, the overlay is + /// preferred. + pub fn overlay(mut self, overlay: Self) -> Self + where + TExt: Add, + { + let Module { extra, imports, items } = overlay; + let mut new_items = HashMap::new(); for (key, right) in items { // if both contain a submodule if let Some(left) = self.items.remove(&key) { if let ModMember::Sub(rsub) = &right.member { if let ModMember::Sub(lsub) = &left.member { // merge them with rhs exportedness - let new_mod = lsub.as_ref().clone() + rsub.as_ref().clone(); - self.items.insert(key, ModEntry { + let new_mod = lsub.as_ref().clone().overlay(rsub.as_ref().clone()); + new_items.insert(key, ModEntry { exported: right.exported, member: ModMember::Sub(Rc::new(new_mod)), }); @@ -132,10 +132,14 @@ impl> Add } } // otherwise right shadows left - self.items.insert(key, right); + new_items.insert(key, right); } + new_items.extend(self.items.into_iter()); self.imports.extend(imports.into_iter()); - self.extra = self.extra + extra; - self + Module { + items: new_items, + imports: self.imports, + extra: self.extra + extra, + } } } diff --git a/src/rule/matcher_vectree/build.rs b/src/rule/matcher_vectree/build.rs index da1dfc4..39b8adf 100644 --- a/src/rule/matcher_vectree/build.rs +++ b/src/rule/matcher_vectree/build.rs @@ -56,11 +56,8 @@ fn mk_vec(pattern: &[Expr]) -> VecMatcher { pattern.last().map(vec_attrs).is_some(), "pattern must end with a vectorial" ); - let (left, (key, prio, nonzero), right) = split_at_max_vec(pattern) + let (left, (key, _, nonzero), right) = split_at_max_vec(pattern) .expect("pattern must have vectorial placeholders at least at either end"); - if prio >= 1 { - println!("Nondefault priority {} found", prio) - } let r_sep_size = scal_cnt(right.iter()); let (r_sep, r_side) = right.split_at(r_sep_size); let l_sep_size = scal_cnt(left.iter().rev()); diff --git a/src/rule/repository.rs b/src/rule/repository.rs index 8e87ca1..5a3aed5 100644 --- a/src/rule/repository.rs +++ b/src/rule/repository.rs @@ -114,29 +114,25 @@ 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. #[allow(unused)] - pub fn long_step( - &self, - code: &Expr, - mut limit: usize, - ) -> Result<(Expr, usize), RuleError> { + pub fn long_step(&self, code: &Expr, mut limit: usize) -> (Expr, usize) { if limit == 0 { - return Ok((code.clone(), 0)); + return (code.clone(), 0); } if let Some(mut processed) = self.step(code) { limit -= 1; if limit == 0 { - return Ok((processed, 0)); + return (processed, 0); } while let Some(out) = self.step(&processed) { limit -= 1; if limit == 0 { - return Ok((out, 0)); + return (out, 0); } processed = out; } - Ok((processed, limit)) + (processed, limit) } else { - Ok((code.clone(), limit)) + (code.clone(), limit) } } } diff --git a/src/stl/arithmetic_error.rs b/src/stl/arithmetic_error.rs new file mode 100644 index 0000000..47d40a9 --- /dev/null +++ b/src/stl/arithmetic_error.rs @@ -0,0 +1,28 @@ +use std::fmt::Display; + +use crate::foreign::ExternError; + +/// Various errors produced by arithmetic operations +pub enum ArithmeticError { + /// Integer overflow + Overflow, + /// Float overflow + Infinity, + /// Division or modulo by zero + DivByZero, + /// Other, unexpected operation produced NaN + NaN, +} + +impl Display for ArithmeticError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::NaN => write!(f, "Operation resulted in NaN"), + Self::Overflow => write!(f, "Integer overflow"), + Self::Infinity => write!(f, "Operation resulted in Infinity"), + Self::DivByZero => write!(f, "A division by zero was attempted"), + } + } +} + +impl ExternError for ArithmeticError {} diff --git a/src/stl/bool.orc b/src/stl/bool.orc new file mode 100644 index 0000000..1b1c88d --- /dev/null +++ b/src/stl/bool.orc @@ -0,0 +1,4 @@ +export ...$a == ...$b =0x3p36=> (equals (...$a) (...$b)) +export if ...$cond then ...$true else ...$false:1 =0x1p84=> ( + ifthenelse (...$cond) (...$true) (...$false) +) \ No newline at end of file diff --git a/src/stl/bool.rs b/src/stl/bool.rs new file mode 100644 index 0000000..2836ffb --- /dev/null +++ b/src/stl/bool.rs @@ -0,0 +1,92 @@ +use std::rc::Rc; + +use crate::foreign::Atom; +use crate::interner::Interner; +use crate::pipeline::ConstTree; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::Primitive; +use crate::stl::litconv::with_lit; +use crate::stl::AssertionError; +use crate::{atomic_inert, define_fn, Literal, PathSet}; + +/// Booleans exposed to Orchid +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Boolean(pub bool); +atomic_inert!(Boolean); + +impl From for Boolean { + fn from(value: bool) -> Self { + Self(value) + } +} + +impl TryFrom<&ExprInst> for Boolean { + type Error = (); + + fn try_from(value: &ExprInst) -> Result { + let expr = value.expr(); + if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause { + if let Some(b) = a.as_any().downcast_ref::() { + return Ok(*b); + } + } + Err(()) + } +} + +define_fn! {expr=x in + /// Compares the inner values if + /// + /// - both values are char, + /// - both are string, + /// - both are either uint or num + Equals { + a: Literal as with_lit(x, |l| Ok(l.clone())), + b: Literal as with_lit(x, |l| Ok(l.clone())) + } => Ok(Boolean::from(match (a, b) { + (Literal::Char(c1), Literal::Char(c2)) => c1 == c2, + (Literal::Num(n1), Literal::Num(n2)) => n1 == n2, + (Literal::Str(s1), Literal::Str(s2)) => s1 == s2, + (Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, + (Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64), + (Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64), + (..) => AssertionError::fail( + b.clone().into(), + "the expected type" + )?, + }).to_atom_cls()) +} + +// Even though it's a ternary function, IfThenElse is implemented as an unary +// foreign function, as the rest of the logic can be defined in Orchid. +define_fn! { + /// Takes a boolean and two branches, runs the first if the bool is true, the + /// second if it's false. + IfThenElse = |x: &ExprInst| x.try_into() + .map_err(|_| AssertionError::ext(x.clone(), "a boolean")) + .map(|b: Boolean| if b.0 {Clause::Lambda { + args: Some(PathSet { steps: Rc::new(vec![]), next: None }), + body: Clause::Lambda { + args: None, + body: Clause::LambdaArg.wrap() + }.wrap(), + }} else {Clause::Lambda { + args: None, + body: Clause::Lambda { + args: Some(PathSet { steps: Rc::new(vec![]), next: None }), + body: Clause::LambdaArg.wrap(), + }.wrap(), + }}) +} + +pub fn bool(i: &Interner) -> ConstTree { + ConstTree::tree([( + i.i("bool"), + ConstTree::tree([ + (i.i("ifthenelse"), ConstTree::xfn(IfThenElse)), + (i.i("equals"), ConstTree::xfn(Equals)), + (i.i("true"), ConstTree::atom(Boolean(true))), + (i.i("false"), ConstTree::atom(Boolean(false))), + ]), + )]) +} diff --git a/src/stl/bool/boolean.rs b/src/stl/bool/boolean.rs deleted file mode 100644 index 8726db9..0000000 --- a/src/stl/bool/boolean.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::atomic_inert; -use crate::foreign::Atom; -use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::Primitive; - -/// Booleans exposed to Orchid -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct Boolean(pub bool); -atomic_inert!(Boolean); - -impl From for Boolean { - fn from(value: bool) -> Self { - Self(value) - } -} - -impl TryFrom for Boolean { - type Error = (); - - fn try_from(value: ExprInst) -> Result { - let expr = value.expr(); - if let Clause::P(Primitive::Atom(Atom(a))) = &expr.clause { - if let Some(b) = a.as_any().downcast_ref::() { - return Ok(*b); - } - } - Err(()) - } -} diff --git a/src/stl/bool/equals.rs b/src/stl/bool/equals.rs deleted file mode 100644 index 4d53712..0000000 --- a/src/stl/bool/equals.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::fmt::Debug; - -use super::super::assertion_error::AssertionError; -use super::super::litconv::with_lit; -use super::boolean::Boolean; -use crate::representations::interpreted::ExprInst; -use crate::representations::Literal; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Compares the inner values if -/// -/// - both values are char, -/// - both are string, -/// - both are either uint or num -/// -/// Next state: [Equals1] - -#[derive(Clone)] -pub struct Equals2; -externfn_impl!(Equals2, |_: &Self, x: ExprInst| Ok(Equals1 { x })); - -/// Prev state: [Equals2]; Next state: [Equals0] - -#[derive(Debug, Clone)] -pub struct Equals1 { - x: ExprInst, -} -atomic_redirect!(Equals1, x); -atomic_impl!(Equals1); -externfn_impl!(Equals1, |this: &Self, x: ExprInst| { - with_lit(&this.x, |l| Ok(Equals0 { a: l.clone(), x })) -}); - -/// Prev state: [Equals1] -#[derive(Debug, Clone)] -pub struct Equals0 { - a: Literal, - x: ExprInst, -} -atomic_redirect!(Equals0, x); -atomic_impl!(Equals0, |Self { a, x }: &Self, _| { - let eqls = with_lit(x, |l| { - Ok(match (a, l) { - (Literal::Char(c1), Literal::Char(c2)) => c1 == c2, - (Literal::Num(n1), Literal::Num(n2)) => n1 == n2, - (Literal::Str(s1), Literal::Str(s2)) => s1 == s2, - (Literal::Uint(i1), Literal::Uint(i2)) => i1 == i2, - (Literal::Num(n1), Literal::Uint(u1)) => *n1 == (*u1 as f64), - (Literal::Uint(u1), Literal::Num(n1)) => *n1 == (*u1 as f64), - (..) => AssertionError::fail(x.clone(), "the expected type")?, - }) - })?; - Ok(Boolean::from(eqls).to_atom_cls()) -}); diff --git a/src/stl/bool/ifthenelse.rs b/src/stl/bool/ifthenelse.rs deleted file mode 100644 index 2960120..0000000 --- a/src/stl/bool/ifthenelse.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::fmt::Debug; -use std::rc::Rc; - -use super::super::assertion_error::AssertionError; -use super::Boolean; -use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::PathSet; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Takes a boolean and two branches, runs the first if the bool is true, the -/// second if it's false. -/// -/// Next state: [IfThenElse0] -#[derive(Clone)] -pub struct IfThenElse1; -externfn_impl!(IfThenElse1, |_: &Self, x: ExprInst| Ok(IfThenElse0 { x })); - -/// Prev state: [IfThenElse1] -#[derive(Debug, Clone)] -pub struct IfThenElse0 { - x: ExprInst, -} -atomic_redirect!(IfThenElse0, x); -atomic_impl!(IfThenElse0, |this: &Self, _| { - let Boolean(b) = this - .x - .clone() - .try_into() - .map_err(|_| AssertionError::ext(this.x.clone(), "a boolean"))?; - Ok(if b { - Clause::Lambda { - args: Some(PathSet { steps: Rc::new(vec![]), next: None }), - body: Clause::Lambda { args: None, body: Clause::LambdaArg.wrap() } - .wrap(), - } - } else { - Clause::Lambda { - args: None, - body: Clause::Lambda { - args: Some(PathSet { steps: Rc::new(vec![]), next: None }), - body: Clause::LambdaArg.wrap(), - } - .wrap(), - } - }) -}); diff --git a/src/stl/bool/mod.rs b/src/stl/bool/mod.rs deleted file mode 100644 index 13e288b..0000000 --- a/src/stl/bool/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod boolean; -mod equals; -mod ifthenelse; -pub use boolean::Boolean; - -use crate::interner::Interner; -use crate::pipeline::ConstTree; - -pub fn bool(i: &Interner) -> ConstTree { - ConstTree::tree([ - (i.i("ifthenelse"), ConstTree::xfn(ifthenelse::IfThenElse1)), - (i.i("equals"), ConstTree::xfn(equals::Equals2)), - (i.i("true"), ConstTree::atom(Boolean(true))), - (i.i("false"), ConstTree::atom(Boolean(false))), - ]) -} diff --git a/src/stl/conv.rs b/src/stl/conv.rs new file mode 100644 index 0000000..d0c88c0 --- /dev/null +++ b/src/stl/conv.rs @@ -0,0 +1,67 @@ +use chumsky::Parser; +use ordered_float::NotNan; + +use super::litconv::with_lit; +use super::{ArithmeticError, AssertionError}; +use crate::foreign::ExternError; +use crate::interner::Interner; +use crate::parse::{float_parser, int_parser}; +use crate::pipeline::ConstTree; +use crate::{define_fn, Literal}; + +define_fn! { + /// parse a number. Accepts the same syntax Orchid does. + ToFloat = |x| with_lit(x, |l| match l { + Literal::Str(s) => float_parser() + .parse(s.as_str()) + .map_err(|_| AssertionError::ext( + x.clone(), + "cannot be parsed into a float" + )), + Literal::Num(n) => Ok(*n), + Literal::Uint(i) => NotNan::new(*i as f64) + .map_err(|_| ArithmeticError::NaN.into_extern()), + Literal::Char(char) => char + .to_digit(10) + .map(|i| NotNan::new(i as f64).expect("u32 to f64 yielded NaN")) + .ok_or_else(|| AssertionError::ext(x.clone(), "is not a decimal digit")), + }).map(|nn| Literal::Num(nn).into()) +} + +define_fn! { + /// Parse an unsigned integer. Accepts the same formats Orchid does. If the + /// input is a number, floors it. + ToUint = |x| with_lit(x, |l| match l { + Literal::Str(s) => int_parser() + .parse(s.as_str()) + .map_err(|_| AssertionError::ext( + x.clone(), + "cannot be parsed into an unsigned int", + )), + Literal::Num(n) => Ok(n.floor() as u64), + Literal::Uint(i) => Ok(*i), + Literal::Char(char) => char + .to_digit(10) + .map(u64::from) + .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit")), + }).map(|u| Literal::Uint(u).into()) +} + +define_fn! { + /// Convert a literal to a string using Rust's conversions for floats, chars and + /// uints respectively + ToString = |x| with_lit(x, |l| Ok(match l { + Literal::Char(c) => c.to_string(), + Literal::Uint(i) => i.to_string(), + Literal::Num(n) => n.to_string(), + Literal::Str(s) => s.clone(), + })).map(|s| Literal::Str(s).into()) +} + +pub fn conv(i: &Interner) -> ConstTree { + ConstTree::tree([ + (i.i("to_float"), ConstTree::xfn(ToFloat)), + (i.i("to_uint"), ConstTree::xfn(ToUint)), + (i.i("to_string"), ConstTree::xfn(ToString)), + ]) +} diff --git a/src/stl/conv/mod.rs b/src/stl/conv/mod.rs deleted file mode 100644 index e11f76a..0000000 --- a/src/stl/conv/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -use crate::interner::Interner; -use crate::pipeline::ConstTree; - -mod parse_float; -mod parse_uint; -mod to_string; - -pub fn conv(i: &Interner) -> ConstTree { - ConstTree::tree([ - (i.i("parse_float"), ConstTree::xfn(parse_float::ParseFloat1)), - (i.i("parse_uint"), ConstTree::xfn(parse_uint::ParseUint1)), - (i.i("to_string"), ConstTree::xfn(to_string::ToString1)), - ]) -} diff --git a/src/stl/conv/parse_float.rs b/src/stl/conv/parse_float.rs deleted file mode 100644 index d0e1cb7..0000000 --- a/src/stl/conv/parse_float.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::fmt::Debug; - -use chumsky::Parser; - -use super::super::assertion_error::AssertionError; -use super::super::litconv::with_lit; -use crate::parse::float_parser; -use crate::representations::interpreted::ExprInst; -use crate::representations::Literal; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// parse a number. Accepts the same syntax Orchid does -/// -/// Next state: [ParseFloat0] -#[derive(Clone)] -pub struct ParseFloat1; -externfn_impl!(ParseFloat1, |_: &Self, x: ExprInst| Ok(ParseFloat0 { x })); - -/// Prev state: [ParseFloat1] -#[derive(Debug, Clone)] -pub struct ParseFloat0 { - x: ExprInst, -} -atomic_redirect!(ParseFloat0, x); -atomic_impl!(ParseFloat0, |Self { x }: &Self, _| { - let number = with_lit(x, |l| { - Ok(match l { - Literal::Str(s) => { - let parser = float_parser(); - parser.parse(s.as_str()).map_err(|_| { - AssertionError::ext(x.clone(), "cannot be parsed into a float") - })? - }, - Literal::Num(n) => *n, - Literal::Uint(i) => (*i as u32).into(), - Literal::Char(char) => char - .to_digit(10) - .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? - .into(), - }) - })?; - Ok(number.into()) -}); diff --git a/src/stl/conv/parse_uint.rs b/src/stl/conv/parse_uint.rs deleted file mode 100644 index 336a45b..0000000 --- a/src/stl/conv/parse_uint.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::fmt::Debug; - -use chumsky::Parser; - -use super::super::assertion_error::AssertionError; -use super::super::litconv::with_lit; -use crate::parse::int_parser; -use crate::representations::interpreted::ExprInst; -use crate::representations::Literal; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Parse an unsigned integer. Accepts the same formats Orchid does. If the -/// input is a number, floors it. -/// -/// Next state: [ParseUint0] -#[derive(Clone)] -pub struct ParseUint1; -externfn_impl!(ParseUint1, |_: &Self, x: ExprInst| Ok(ParseUint0 { x })); - -/// Prev state: [ParseUint1] -#[derive(Debug, Clone)] -pub struct ParseUint0 { - x: ExprInst, -} -atomic_redirect!(ParseUint0, x); -atomic_impl!(ParseUint0, |Self { x }: &Self, _| { - let uint = with_lit(x, |l| { - Ok(match l { - Literal::Str(s) => { - let parser = int_parser(); - parser.parse(s.as_str()).map_err(|_| { - AssertionError::ext( - x.clone(), - "cannot be parsed into an unsigned int", - ) - })? - }, - Literal::Num(n) => n.floor() as u64, - Literal::Uint(i) => *i, - Literal::Char(char) => char - .to_digit(10) - .ok_or(AssertionError::ext(x.clone(), "is not a decimal digit"))? - .into(), - }) - })?; - Ok(uint.into()) -}); diff --git a/src/stl/conv/to_string.rs b/src/stl/conv/to_string.rs deleted file mode 100644 index c1cab36..0000000 --- a/src/stl/conv/to_string.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::fmt::Debug; - -use super::super::litconv::with_lit; -use crate::representations::interpreted::ExprInst; -use crate::representations::Literal; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Convert a literal to a string using Rust's conversions for floats, chars and -/// uints respectively -/// -/// Next state: [ToString0] -#[derive(Clone)] -pub struct ToString1; -externfn_impl!(ToString1, |_: &Self, x: ExprInst| Ok(ToString0 { x })); - -/// Prev state: [ToString1] -#[derive(Debug, Clone)] -pub struct ToString0 { - x: ExprInst, -} -atomic_redirect!(ToString0, x); -atomic_impl!(ToString0, |Self { x }: &Self, _| { - let string = with_lit(x, |l| { - Ok(match l { - Literal::Char(c) => c.to_string(), - Literal::Uint(i) => i.to_string(), - Literal::Num(n) => n.to_string(), - Literal::Str(s) => s.clone(), - }) - })?; - Ok(string.into()) -}); diff --git a/src/stl/cpsio/debug.rs b/src/stl/cpsio/debug.rs deleted file mode 100644 index b309fa2..0000000 --- a/src/stl/cpsio/debug.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::fmt::Debug; - -use crate::foreign::{Atomic, AtomicReturn}; -use crate::interner::InternedDisplay; -use crate::interpreter::Context; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_defaults, externfn_impl}; - -/// Print and return whatever expression is in the argument without normalizing -/// it. -/// -/// Next state: [Debug1] -#[derive(Clone)] -pub struct Debug2; -externfn_impl!(Debug2, |_: &Self, x: ExprInst| Ok(Debug1 { x })); - -/// Prev state: [Debug2] -#[derive(Debug, Clone)] -pub struct Debug1 { - x: ExprInst, -} -impl Atomic for Debug1 { - atomic_defaults!(); - fn run(&self, ctx: Context) -> crate::foreign::AtomicResult { - println!("{}", self.x.bundle(ctx.interner)); - Ok(AtomicReturn { - clause: self.x.expr().clause.clone(), - gas: ctx.gas.map(|g| g - 1), - inert: false, - }) - } -} diff --git a/src/stl/cpsio/mod.rs b/src/stl/cpsio/mod.rs deleted file mode 100644 index 25123ce..0000000 --- a/src/stl/cpsio/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::interner::Interner; -use crate::pipeline::ConstTree; - -mod debug; -mod io; -mod panic; -mod print; -mod readline; - -pub use io::{handle, IO}; - -pub fn cpsio(i: &Interner) -> ConstTree { - ConstTree::tree([ - (i.i("print"), ConstTree::xfn(print::Print2)), - (i.i("readline"), ConstTree::xfn(readline::Readln2)), - (i.i("debug"), ConstTree::xfn(debug::Debug2)), - (i.i("panic"), ConstTree::xfn(panic::Panic1)), - ]) -} diff --git a/src/stl/cpsio/panic.rs b/src/stl/cpsio/panic.rs deleted file mode 100644 index 39f3fcd..0000000 --- a/src/stl/cpsio/panic.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::fmt::Display; - -use super::super::litconv::with_str; -use crate::foreign::ExternError; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Takes a message, returns an [ExternError] unconditionally. -/// -/// Next state: [Panic0] -#[derive(Clone)] -pub struct Panic1; -externfn_impl!(Panic1, |_: &Self, x: ExprInst| Ok(Panic0 { x })); - -/// Prev state: [Panic1] -#[derive(Debug, Clone)] -pub struct Panic0 { - x: ExprInst, -} -atomic_redirect!(Panic0, x); -atomic_impl!(Panic0, |Self { x }: &Self, _| { - with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern())) -}); - -/// An unrecoverable error in Orchid land. Of course, because Orchid is lazy, it -/// only applies to the expressions that use the one that generated it. -pub struct OrchidPanic(String); - -impl Display for OrchidPanic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Orchid code panicked: {}", self.0) - } -} - -impl ExternError for OrchidPanic {} diff --git a/src/stl/cpsio/print.rs b/src/stl/cpsio/print.rs deleted file mode 100644 index ed56cd9..0000000 --- a/src/stl/cpsio/print.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::fmt::Debug; - -use super::super::litconv::with_str; -use super::io::IO; -use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; -use crate::interpreter::Context; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_defaults, atomic_impl, atomic_redirect, externfn_impl}; - -/// Wrap a string and the continuation into an [IO] event to be evaluated by the -/// embedder. -/// -/// Next state: [Print1] -#[derive(Clone)] -pub struct Print2; -externfn_impl!(Print2, |_: &Self, x: ExprInst| Ok(Print1 { x })); - -/// Prev state: [Print2]; Next state: [Print0] -#[derive(Debug, Clone)] -pub struct Print1 { - x: ExprInst, -} -atomic_redirect!(Print1, x); -atomic_impl!(Print1); -externfn_impl!(Print1, |this: &Self, x: ExprInst| { - with_str(&this.x, |s| Ok(Print0 { s: s.clone(), x })) -}); - -/// Prev state: [Print1] -#[derive(Debug, Clone)] -pub struct Print0 { - s: String, - x: ExprInst, -} -impl Atomic for Print0 { - atomic_defaults!(); - fn run(&self, ctx: Context) -> AtomicResult { - Ok(AtomicReturn::from_data(IO::Print(self.s.clone(), self.x.clone()), ctx)) - } -} diff --git a/src/stl/cpsio/readline.rs b/src/stl/cpsio/readline.rs deleted file mode 100644 index 83ad57f..0000000 --- a/src/stl/cpsio/readline.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::fmt::Debug; - -use super::io::IO; -use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; -use crate::interpreter::Context; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_defaults, externfn_impl}; - -/// Create an [IO] event that reads a line form standard input and calls the -/// continuation with it. -/// -/// Next state: [Readln1] -#[derive(Clone)] -pub struct Readln2; -externfn_impl!(Readln2, |_: &Self, x: ExprInst| Ok(Readln1 { x })); - -/// Prev state: [Readln2] -#[derive(Debug, Clone)] -pub struct Readln1 { - x: ExprInst, -} -impl Atomic for Readln1 { - atomic_defaults!(); - fn run(&self, ctx: Context) -> AtomicResult { - Ok(AtomicReturn::from_data(IO::Readline(self.x.clone()), ctx)) - } -} diff --git a/examples/list-processing/fn.orc b/src/stl/fn.orc similarity index 90% rename from examples/list-processing/fn.orc rename to src/stl/fn.orc index ef27dbd..d76aaed 100644 --- a/examples/list-processing/fn.orc +++ b/src/stl/fn.orc @@ -8,7 +8,7 @@ export loop $r on (..$parameters) with ...$tail =0x5p129=> Y (\$r. bind_names ($name ..$rest) $payload =0x1p250=> \$name. bind_names (..$rest) $payload bind_names () (...$payload) =0x1p250=> ...$payload -export ...$prefix $ ...$suffix:1 =0x1p39=> ...$prefix (...$suffix) +export ...$prefix $ ...$suffix:1 =0x1p38=> ...$prefix (...$suffix) export ...$prefix |> $fn ..$suffix:1 =0x2p32=> $fn (...$prefix) ..$suffix export (...$argv) => ...$body =0x2p129=> (bind_names (...$argv) (...$body)) diff --git a/src/stl/cpsio/io.rs b/src/stl/io/command.rs similarity index 100% rename from src/stl/cpsio/io.rs rename to src/stl/io/command.rs diff --git a/src/stl/io/inspect.rs b/src/stl/io/inspect.rs new file mode 100644 index 0000000..521e1aa --- /dev/null +++ b/src/stl/io/inspect.rs @@ -0,0 +1,29 @@ +use std::fmt::Debug; + +use crate::foreign::{Atomic, AtomicReturn}; +use crate::interner::InternedDisplay; +use crate::interpreter::Context; +use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, write_fn_step}; + +write_fn_step! { + /// Print and return whatever expression is in the argument without + /// normalizing it. + pub Inspect > Inspect1 +} + +#[derive(Debug, Clone)] +struct Inspect1 { + expr_inst: ExprInst, +} +impl Atomic for Inspect1 { + atomic_defaults!(); + fn run(&self, ctx: Context) -> crate::foreign::AtomicResult { + println!("{}", self.expr_inst.bundle(ctx.interner)); + Ok(AtomicReturn { + clause: self.expr_inst.expr().clause.clone(), + gas: ctx.gas.map(|g| g - 1), + inert: false, + }) + } +} diff --git a/src/stl/io/mod.rs b/src/stl/io/mod.rs new file mode 100644 index 0000000..9a6a9fa --- /dev/null +++ b/src/stl/io/mod.rs @@ -0,0 +1,30 @@ +use crate::interner::Interner; +use crate::pipeline::ConstTree; + +mod command; +mod inspect; +mod panic; +mod print; +mod readline; + +pub use command::{handle, IO}; + +pub fn io(i: &Interner, allow_impure: bool) -> ConstTree { + let pure = ConstTree::tree([( + i.i("io"), + ConstTree::tree([ + (i.i("print"), ConstTree::xfn(print::Print)), + (i.i("readline"), ConstTree::xfn(readline::ReadLn)), + (i.i("panic"), ConstTree::xfn(panic::Panic)), + ]), + )]); + if !allow_impure { + pure + } else { + pure + + ConstTree::tree([( + i.i("io"), + ConstTree::tree([(i.i("debug"), ConstTree::xfn(inspect::Inspect))]), + )]) + } +} diff --git a/src/stl/io/panic.rs b/src/stl/io/panic.rs new file mode 100644 index 0000000..f8aab8e --- /dev/null +++ b/src/stl/io/panic.rs @@ -0,0 +1,21 @@ +use std::fmt::Display; + +use super::super::litconv::with_str; +use crate::define_fn; +use crate::foreign::ExternError; + +define_fn! { + /// Takes a message, returns an [ExternError] unconditionally. + pub Panic = |x| with_str(x, |s| Err(OrchidPanic(s.clone()).into_extern())) +} +/// An unrecoverable error in Orchid land. Because Orchid is lazy, this only +/// invalidates expressions that reference the one that generated it. +pub struct OrchidPanic(String); + +impl Display for OrchidPanic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Orchid code panicked: {}", self.0) + } +} + +impl ExternError for OrchidPanic {} diff --git a/src/stl/io/print.rs b/src/stl/io/print.rs new file mode 100644 index 0000000..35620a3 --- /dev/null +++ b/src/stl/io/print.rs @@ -0,0 +1,31 @@ +use std::fmt::Debug; + +use super::super::litconv::with_str; +use super::command::IO; +use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; +use crate::interpreter::Context; +use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, write_fn_step}; + +write_fn_step! { + /// Wrap a string and the continuation into an [IO] event to be evaluated by + /// the embedder. + pub Print > Print1 +} +write_fn_step! { + Print1 {} + Print0 where message = x => with_str(x, |s| Ok(s.clone())); +} + +#[derive(Debug, Clone)] +struct Print0 { + message: String, + expr_inst: ExprInst, +} +impl Atomic for Print0 { + atomic_defaults!(); + fn run(&self, ctx: Context) -> AtomicResult { + let command = IO::Print(self.message.clone(), self.expr_inst.clone()); + Ok(AtomicReturn::from_data(command, ctx)) + } +} diff --git a/src/stl/io/readline.rs b/src/stl/io/readline.rs new file mode 100644 index 0000000..78b4d41 --- /dev/null +++ b/src/stl/io/readline.rs @@ -0,0 +1,25 @@ +use std::fmt::Debug; + +use super::command::IO; +use crate::foreign::{Atomic, AtomicResult, AtomicReturn}; +use crate::interpreter::Context; +use crate::representations::interpreted::ExprInst; +use crate::{atomic_defaults, write_fn_step}; + +write_fn_step! { + /// Create an [IO] event that reads a line form standard input and calls the + /// continuation with it. + pub ReadLn > ReadLn1 +} + +#[derive(Debug, Clone)] +struct ReadLn1 { + expr_inst: ExprInst, +} +impl Atomic for ReadLn1 { + atomic_defaults!(); + fn run(&self, ctx: Context) -> AtomicResult { + let command = IO::Readline(self.expr_inst.clone()); + Ok(AtomicReturn::from_data(command, ctx)) + } +} diff --git a/src/stl/known.orc b/src/stl/known.orc new file mode 100644 index 0000000..f162235 --- /dev/null +++ b/src/stl/known.orc @@ -0,0 +1 @@ +export ::(,) \ No newline at end of file diff --git a/examples/maps/list.orc b/src/stl/list.orc similarity index 94% rename from examples/maps/list.orc rename to src/stl/list.orc index b01eb9e..f7a62d0 100644 --- a/examples/maps/list.orc +++ b/src/stl/list.orc @@ -1,5 +1,4 @@ -import option -import super::fn::* +import super::(option, fn::*, bool::*, known::*, num::*,) pair := \a.\b. \f. f a b diff --git a/examples/maps/map.orc b/src/stl/map.orc similarity index 94% rename from examples/maps/map.orc rename to src/stl/map.orc index bc5f23d..c44e6fb 100644 --- a/examples/maps/map.orc +++ b/src/stl/map.orc @@ -1,8 +1,5 @@ -import list -import option -import fn::* -import std::to_string -import std::debug +import super::(bool::*, fn::*, known::*, list, option, proc::*) +import std::io::panic -- utilities for using lists as pairs diff --git a/src/stl/mk_stl.rs b/src/stl/mk_stl.rs index 7ab5f6e..7eb630f 100644 --- a/src/stl/mk_stl.rs +++ b/src/stl/mk_stl.rs @@ -1,13 +1,68 @@ +use hashbrown::HashMap; +use rust_embed::RustEmbed; + use super::bool::bool; use super::conv::conv; -use super::cpsio::cpsio; +use super::io::io; use super::num::num; use super::str::str; -use crate::interner::Interner; -use crate::pipeline::ConstTree; +use crate::interner::{Interner, Sym}; +use crate::pipeline::file_loader::mk_embed_cache; +use crate::pipeline::{from_const_tree, parse_layer, ProjectTree}; +use crate::sourcefile::{FileEntry, Import}; + +/// Feature flags for the STL. +#[derive(Default)] +pub struct StlOptions { + /// Whether impure functions (such as io::debug) are allowed. An embedder + /// would typically disable this flag + pub impure: bool, +} + +#[derive(RustEmbed)] +#[folder = "src/stl"] +#[prefix = "std/"] +#[include = "*.orc"] +struct StlEmbed; + +// TODO: fix all orc modules to not rely on prelude /// Build the standard library used by the interpreter by combining the other /// libraries -pub fn mk_stl(i: &Interner) -> ConstTree { - cpsio(i) + conv(i) + bool(i) + str(i) + num(i) +pub fn mk_stl(i: &Interner, options: StlOptions) -> ProjectTree { + let const_tree = from_const_tree( + HashMap::from([( + i.i("std"), + io(i, options.impure) + conv(i) + bool(i) + str(i) + num(i), + )]), + &[i.i("std")], + i, + ); + let ld_cache = mk_embed_cache::(".orc", i); + parse_layer( + &StlEmbed::iter() + .map(|path| -> Sym { + let segtoks = path + .strip_suffix(".orc") + .expect("the embed is filtered for suffix") + .split('/') + .map(|segment| i.i(segment)) + .collect::>(); + i.i(&segtoks[..]) + }) + .collect::>()[..], + &|p| ld_cache.find(&p), + &const_tree, + &[], + i, + ) + .expect("Parse error in STL") +} + +/// Generate prelude lines to be injected to every module compiled with the STL +pub fn mk_prelude(i: &Interner) -> Vec { + vec![FileEntry::Import(vec![Import { + path: i.i(&[i.i("std"), i.i("prelude")][..]), + name: None, + }])] } diff --git a/src/stl/mod.rs b/src/stl/mod.rs index 2233af6..4ab7137 100644 --- a/src/stl/mod.rs +++ b/src/stl/mod.rs @@ -1,17 +1,19 @@ //! Constants exposed to usercode by the interpreter +mod arithmetic_error; mod assertion_error; mod bool; mod conv; -mod cpsio; +mod io; pub mod litconv; mod mk_stl; mod num; mod runtime_error; mod str; +pub use arithmetic_error::ArithmeticError; pub use assertion_error::AssertionError; -pub use cpsio::{handle as handleIO, IO}; -pub use mk_stl::mk_stl; +pub use io::{handle as handleIO, IO}; +pub use mk_stl::{mk_prelude, mk_stl, StlOptions}; pub use runtime_error::RuntimeError; pub use self::bool::Boolean; diff --git a/src/stl/num.orc b/src/stl/num.orc new file mode 100644 index 0000000..5ef82d3 --- /dev/null +++ b/src/stl/num.orc @@ -0,0 +1,5 @@ +export ...$a + ...$b =0x2p36=> (add (...$a) (...$b)) +export ...$a - ...$b:1 =0x2p36=> (subtract (...$a) (...$b)) +export ...$a * ...$b =0x1p36=> (multiply (...$a) (...$b)) +export ...$a % ...$b:1 =0x1p36=> (remainder (...$a) (...$b)) +export ...$a / ...$b:1 =0x1p36=> (divide (...$a) (...$b)) diff --git a/src/stl/num.rs b/src/stl/num.rs new file mode 100644 index 0000000..6ee6ac0 --- /dev/null +++ b/src/stl/num.rs @@ -0,0 +1,149 @@ +use std::rc::Rc; + +use ordered_float::NotNan; + +use super::litconv::with_lit; +use super::{ArithmeticError, AssertionError}; +use crate::define_fn; +use crate::foreign::ExternError; +use crate::interner::Interner; +use crate::pipeline::ConstTree; +use crate::representations::interpreted::{Clause, ExprInst}; +use crate::representations::{Literal, Primitive}; + +// region: Numeric, type to handle floats and uints together + +/// A number, either floating point or unsigned int, visible to Orchid. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Numeric { + /// A nonnegative integer such as a size, index or count + Uint(u64), + /// A float other than NaN. Orchid has no silent errors + Num(NotNan), +} + +impl Numeric { + fn as_f64(&self) -> f64 { + match self { + Numeric::Num(n) => **n, + Numeric::Uint(i) => *i as f64, + } + } + + /// Wrap a f64 in a Numeric + fn num(value: f64) -> Result> { + if value.is_finite() { + NotNan::new(value) + .map(Self::Num) + .map_err(|_| ArithmeticError::NaN.into_extern()) + } else { + Err(ArithmeticError::Infinity.into_extern()) + } + } +} + +impl TryFrom<&ExprInst> for Numeric { + type Error = Rc; + fn try_from(value: &ExprInst) -> Result { + with_lit(value, |l| match l { + Literal::Uint(i) => Ok(Numeric::Uint(*i)), + Literal::Num(n) => Ok(Numeric::Num(*n)), + _ => AssertionError::fail(value.clone(), "an integer or number")?, + }) + } +} + +impl From for Clause { + fn from(value: Numeric) -> Self { + Clause::P(Primitive::Literal(match value { + Numeric::Uint(i) => Literal::Uint(i), + Numeric::Num(n) => Literal::Num(n), + })) + } +} + +// endregion + +// region: operations + +define_fn! { + /// Add two numbers. If they're both uint, the output is uint. If either is + /// number, the output is number. + Add { a: Numeric, b: Numeric } => match (a, b) { + (Numeric::Uint(a), Numeric::Uint(b)) => { + a.checked_add(*b) + .map(Numeric::Uint) + .ok_or_else(|| ArithmeticError::Overflow.into_extern()) + } + (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a + b)), + (Numeric::Num(a), Numeric::Uint(b)) | (Numeric::Uint(b), Numeric::Num(a)) + => Numeric::num(a.into_inner() + *b as f64), + }.map(Numeric::into) +} + +define_fn! { + /// Subtract a number from another. Always returns Number. + Subtract { a: Numeric, b: Numeric } => match (a, b) { + (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(*a as f64 - *b as f64), + (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a - b)), + (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a - *b as f64), + (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 - **b), + }.map(Numeric::into) +} + +define_fn! { + /// Multiply two numbers. If they're both uint, the output is uint. If either + /// is number, the output is number. + Multiply { a: Numeric, b: Numeric } => match (a, b) { + (Numeric::Uint(a), Numeric::Uint(b)) => { + a.checked_mul(*b) + .map(Numeric::Uint) + .ok_or_else(|| ArithmeticError::Overflow.into_extern()) + } + (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a * b)), + (Numeric::Uint(a), Numeric::Num(b)) | (Numeric::Num(b), Numeric::Uint(a)) + => Numeric::num(*a as f64 * **b), + }.map(Numeric::into) +} + +define_fn! { + /// Divide a number by another. Always returns Number. + Divide { a: Numeric, b: Numeric } => { + let a: f64 = a.as_f64(); + let b: f64 = b.as_f64(); + if b == 0.0 { + return Err(ArithmeticError::DivByZero.into_extern()) + } + Numeric::num(a / b).map(Numeric::into) + } +} + +define_fn! { + /// Take the remainder of two numbers. If they're both uint, the output is + /// uint. If either is number, the output is number. + Remainder { a: Numeric, b: Numeric } => match (a, b) { + (Numeric::Uint(a), Numeric::Uint(b)) => { + a.checked_rem(*b) + .map(Numeric::Uint) + .ok_or_else(|| ArithmeticError::DivByZero.into_extern()) + } + (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(*(a % b)), + (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(*a as f64 % **b), + (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(**a % *b as f64), + }.map(Numeric::into) +} + +// endregion + +pub fn num(i: &Interner) -> ConstTree { + ConstTree::tree([( + i.i("num"), + ConstTree::tree([ + (i.i("add"), ConstTree::xfn(Add)), + (i.i("subtract"), ConstTree::xfn(Subtract)), + (i.i("multiply"), ConstTree::xfn(Multiply)), + (i.i("divide"), ConstTree::xfn(Divide)), + (i.i("remainder"), ConstTree::xfn(Remainder)), + ]), + )]) +} diff --git a/src/stl/num/mod.rs b/src/stl/num/mod.rs deleted file mode 100644 index 74d80e3..0000000 --- a/src/stl/num/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -mod numeric; -pub mod operators; -pub use numeric::Numeric; - -use crate::interner::Interner; -use crate::pipeline::ConstTree; - -pub fn num(i: &Interner) -> ConstTree { - ConstTree::tree([ - (i.i("add"), ConstTree::xfn(operators::add::Add2)), - (i.i("subtract"), ConstTree::xfn(operators::subtract::Subtract2)), - (i.i("multiply"), ConstTree::xfn(operators::multiply::Multiply2)), - (i.i("divide"), ConstTree::xfn(operators::divide::Divide2)), - (i.i("remainder"), ConstTree::xfn(operators::remainder::Remainder2)), - ]) -} diff --git a/src/stl/num/numeric.rs b/src/stl/num/numeric.rs deleted file mode 100644 index fdee860..0000000 --- a/src/stl/num/numeric.rs +++ /dev/null @@ -1,135 +0,0 @@ -use std::ops::{Add, Div, Mul, Rem, Sub}; -use std::rc::Rc; - -use ordered_float::NotNan; - -use super::super::assertion_error::AssertionError; -use super::super::litconv::with_lit; -use crate::foreign::ExternError; -use crate::representations::interpreted::{Clause, ExprInst}; -use crate::representations::{Literal, Primitive}; - -/// A number, either floating point or unsigned int, visible to Orchid. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum Numeric { - /// A nonnegative integer such as a size, index or count - Uint(u64), - /// A float other than NaN. Orchid has no silent errors - Num(NotNan), -} - -impl Numeric { - /// Wrap a f64 in a Numeric - /// - /// # Panics - /// - /// if the value is NaN or Infinity.try_into() - fn num>(value: T) -> Self { - let f = value.into(); - assert!(f.is_finite(), "unrepresentable number"); - NotNan::try_from(f).map(Self::Num).expect("not a number") - } -} - -impl Add for Numeric { - type Output = Numeric; - - fn add(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a + b), - (Numeric::Uint(a), Numeric::Num(b)) - | (Numeric::Num(b), Numeric::Uint(a)) => - Numeric::num::(a as f64 + *b), - } - } -} - -impl Sub for Numeric { - type Output = Numeric; - - fn sub(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Numeric::Uint(a), Numeric::Uint(b)) if b <= a => Numeric::Uint(a - b), - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::num(a as f64 - b as f64), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a - b), - (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64), - } - } -} - -impl Mul for Numeric { - type Output = Numeric; - - fn mul(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a * b), - (Numeric::Uint(a), Numeric::Num(b)) - | (Numeric::Num(b), Numeric::Uint(a)) => - Numeric::Num(NotNan::new(a as f64).unwrap() * b), - } - } -} - -impl Div for Numeric { - type Output = Numeric; - - fn div(self, rhs: Self) -> Self::Output { - let a: f64 = self.into(); - let b: f64 = rhs.into(); - Numeric::num(a / b) - } -} - -impl Rem for Numeric { - type Output = Numeric; - - fn rem(self, rhs: Self) -> Self::Output { - match (self, rhs) { - (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b), - (Numeric::Num(a), Numeric::Num(b)) => Numeric::num(a % b), - (Numeric::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b), - (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64), - } - } -} - -impl TryFrom for Numeric { - type Error = Rc; - fn try_from(value: ExprInst) -> Result { - with_lit(&value.clone(), |l| match l { - Literal::Uint(i) => Ok(Numeric::Uint(*i)), - Literal::Num(n) => Ok(Numeric::Num(*n)), - _ => AssertionError::fail(value, "an integer or number")?, - }) - } -} - -impl From for Clause { - fn from(value: Numeric) -> Self { - Clause::P(Primitive::Literal(match value { - Numeric::Uint(i) => Literal::Uint(i), - Numeric::Num(n) => Literal::Num(n), - })) - } -} - -impl From for String { - fn from(value: Numeric) -> Self { - match value { - Numeric::Uint(i) => i.to_string(), - Numeric::Num(n) => n.to_string(), - } - } -} - -impl From for f64 { - fn from(val: Numeric) -> Self { - match val { - Numeric::Num(n) => *n, - Numeric::Uint(i) => i as f64, - } - } -} diff --git a/src/stl/num/operators/add.rs b/src/stl/num/operators/add.rs deleted file mode 100644 index 4a37b09..0000000 --- a/src/stl/num/operators/add.rs +++ /dev/null @@ -1,31 +0,0 @@ -use std::fmt::Debug; - -use super::super::Numeric; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -#[derive(Clone)] -pub struct Add2; -externfn_impl!(Add2, |_: &Self, x: ExprInst| Ok(Add1 { x })); - -#[derive(Debug, Clone)] -pub struct Add1 { - x: ExprInst, -} -atomic_redirect!(Add1, x); -atomic_impl!(Add1); -externfn_impl!(Add1, |this: &Self, x: ExprInst| { - let a: Numeric = this.x.clone().try_into()?; - Ok(Add0 { a, x }) -}); - -#[derive(Debug, Clone)] -pub struct Add0 { - a: Numeric, - x: ExprInst, -} -atomic_redirect!(Add0, x); -atomic_impl!(Add0, |Self { a, x }: &Self, _| { - let b: Numeric = x.clone().try_into()?; - Ok((*a + b).into()) -}); diff --git a/src/stl/num/operators/divide.rs b/src/stl/num/operators/divide.rs deleted file mode 100644 index c8cc703..0000000 --- a/src/stl/num/operators/divide.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::fmt::Debug; - -use super::super::Numeric; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Divides two numbers -/// -/// Next state: [Divide1] - -#[derive(Clone)] -pub struct Divide2; -externfn_impl!(Divide2, |_: &Self, x: ExprInst| Ok(Divide1 { x })); - -/// Prev state: [Divide2]; Next state: [Divide0] -#[derive(Debug, Clone)] -pub struct Divide1 { - x: ExprInst, -} -atomic_redirect!(Divide1, x); -atomic_impl!(Divide1); -externfn_impl!(Divide1, |this: &Self, x: ExprInst| { - let a: Numeric = this.x.clone().try_into()?; - Ok(Divide0 { a, x }) -}); - -/// Prev state: [Divide1] -#[derive(Debug, Clone)] -pub struct Divide0 { - a: Numeric, - x: ExprInst, -} -atomic_redirect!(Divide0, x); -atomic_impl!(Divide0, |Self { a, x }: &Self, _| { - let b: Numeric = x.clone().try_into()?; - Ok((*a / b).into()) -}); diff --git a/src/stl/num/operators/mod.rs b/src/stl/num/operators/mod.rs deleted file mode 100644 index ee7fb4f..0000000 --- a/src/stl/num/operators/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod add; -pub mod divide; -pub mod multiply; -pub mod remainder; -pub mod subtract; diff --git a/src/stl/num/operators/multiply.rs b/src/stl/num/operators/multiply.rs deleted file mode 100644 index ba4aefe..0000000 --- a/src/stl/num/operators/multiply.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::fmt::Debug; - -use super::super::Numeric; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Multiplies two numbers -/// -/// Next state: [Multiply1] -#[derive(Clone)] -pub struct Multiply2; -externfn_impl!(Multiply2, |_: &Self, x: ExprInst| Ok(Multiply1 { x })); - -/// Prev state: [Multiply2]; Next state: [Multiply0] -#[derive(Debug, Clone)] -pub struct Multiply1 { - x: ExprInst, -} -atomic_redirect!(Multiply1, x); -atomic_impl!(Multiply1); -externfn_impl!(Multiply1, |this: &Self, x: ExprInst| { - let a: Numeric = this.x.clone().try_into()?; - Ok(Multiply0 { a, x }) -}); - -/// Prev state: [Multiply1] -#[derive(Debug, Clone)] -pub struct Multiply0 { - a: Numeric, - x: ExprInst, -} -atomic_redirect!(Multiply0, x); -atomic_impl!(Multiply0, |Self { a, x }: &Self, _| { - let b: Numeric = x.clone().try_into()?; - Ok((*a * b).into()) -}); diff --git a/src/stl/num/operators/remainder.rs b/src/stl/num/operators/remainder.rs deleted file mode 100644 index 736bd43..0000000 --- a/src/stl/num/operators/remainder.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::fmt::Debug; - -use super::super::Numeric; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Takes the modulus of two numbers. -/// -/// Next state: [Remainder1] -#[derive(Clone)] -pub struct Remainder2; -externfn_impl!(Remainder2, |_: &Self, x: ExprInst| Ok(Remainder1 { x })); - -/// Prev state: [Remainder2]; Next state: [Remainder0] -#[derive(Debug, Clone)] -pub struct Remainder1 { - x: ExprInst, -} -atomic_redirect!(Remainder1, x); -atomic_impl!(Remainder1); -externfn_impl!(Remainder1, |this: &Self, x: ExprInst| { - let a: Numeric = this.x.clone().try_into()?; - Ok(Remainder0 { a, x }) -}); - -/// Prev state: [Remainder1] -#[derive(Debug, Clone)] -pub struct Remainder0 { - a: Numeric, - x: ExprInst, -} -atomic_redirect!(Remainder0, x); -atomic_impl!(Remainder0, |Self { a, x }: &Self, _| { - let b: Numeric = x.clone().try_into()?; - Ok((*a % b).into()) -}); diff --git a/src/stl/num/operators/subtract.rs b/src/stl/num/operators/subtract.rs deleted file mode 100644 index 191ffc4..0000000 --- a/src/stl/num/operators/subtract.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::fmt::Debug; - -use super::super::Numeric; -use crate::representations::interpreted::ExprInst; -use crate::{atomic_impl, atomic_redirect, externfn_impl}; - -/// Subtracts two numbers -/// -/// Next state: [Subtract1] -#[derive(Clone)] -pub struct Subtract2; -externfn_impl!(Subtract2, |_: &Self, x: ExprInst| Ok(Subtract1 { x })); - -/// Prev state: [Subtract2]; Next state: [Subtract0] -#[derive(Debug, Clone)] -pub struct Subtract1 { - x: ExprInst, -} -atomic_redirect!(Subtract1, x); -atomic_impl!(Subtract1); -externfn_impl!(Subtract1, |this: &Self, x: ExprInst| { - let a: Numeric = this.x.clone().try_into()?; - Ok(Subtract0 { a, x }) -}); - -/// Prev state: [Subtract1] -#[derive(Debug, Clone)] -pub struct Subtract0 { - a: Numeric, - x: ExprInst, -} -atomic_redirect!(Subtract0, x); -atomic_impl!(Subtract0, |Self { a, x }: &Self, _| { - let b: Numeric = x.clone().try_into()?; - Ok((*a - b).into()) -}); diff --git a/examples/maps/option.orc b/src/stl/option.orc similarity index 92% rename from examples/maps/option.orc rename to src/stl/option.orc index 6cd8719..c09534f 100644 --- a/examples/maps/option.orc +++ b/src/stl/option.orc @@ -1,4 +1,4 @@ -import std::panic +import std::io::panic export some := \v. \d.\f. f v export none := \d.\f. d diff --git a/src/stl/prelude.orc b/src/stl/prelude.orc new file mode 100644 index 0000000..9feccb4 --- /dev/null +++ b/src/stl/prelude.orc @@ -0,0 +1,15 @@ +import std::num::* +export ::(+, -, *, /, %) +import std::str::* +export ::(++) +import std::bool::* +export ::(==, if, then, else) +import std::fn::* +export ::(loop, on, with, $, |>, =>) +import std::list +import std::map +import std::option +export ::(list, map, option) + +import std::known::* +export ::(,) \ No newline at end of file diff --git a/src/stl/proc.orc b/src/stl/proc.orc new file mode 100644 index 0000000..8219124 --- /dev/null +++ b/src/stl/proc.orc @@ -0,0 +1,12 @@ +export do { ...$statement ; ...$rest:1 } =0x2p130=> statement (...$statement) do { ...$rest } +export do { ...$return } =0x1p130=> ...$return + +export statement (let $name = ...$value) ...$next =0x1p230=> ( + (\$name. ...$next) (...$value) +) +export statement (cps $name = ...$operation) ...$next =0x2p230=> ( + (...$operation) \$name. ...$next +) +export statement (cps ...$operation) ...$next =0x1p230=> ( + (...$operation) (...$next) +) \ No newline at end of file diff --git a/src/stl/str.orc b/src/stl/str.orc new file mode 100644 index 0000000..5dfc4df --- /dev/null +++ b/src/stl/str.orc @@ -0,0 +1 @@ +export ...$a ++ ...$b =0x4p36=> (concatenate (...$a) (...$b)) \ No newline at end of file diff --git a/src/stl/str.rs b/src/stl/str.rs new file mode 100644 index 0000000..6de545d --- /dev/null +++ b/src/stl/str.rs @@ -0,0 +1,39 @@ +use super::litconv::{with_str, with_uint}; +use super::RuntimeError; +use crate::interner::Interner; +use crate::pipeline::ConstTree; +use crate::{define_fn, Literal}; + +define_fn! {expr=x in + /// Append a string to another + pub Concatenate { + a: String as with_str(x, |s| Ok(s.clone())), + b: String as with_str(x, |s| Ok(s.clone())) + } => Ok(Literal::Str(a.to_owned() + b).into()) +} + +define_fn! {expr=x in + pub CharAt { + s: String as with_str(x, |s| Ok(s.clone())), + i: u64 as with_uint(x, Ok) + } => { + if let Some(c) = s.chars().nth(*i as usize) { + Ok(Literal::Char(c).into()) + } else { + RuntimeError::fail( + "Character index out of bounds".to_string(), + "indexing string", + )? + } + } +} + +pub fn str(i: &Interner) -> ConstTree { + ConstTree::tree([( + i.i("str"), + ConstTree::tree([ + (i.i("concatenate"), ConstTree::xfn(Concatenate)), + (i.i("char_at"), ConstTree::xfn(CharAt)), + ]), + )]) +} diff --git a/src/stl/str/char_at.rs b/src/stl/str/char_at.rs deleted file mode 100644 index d4780b6..0000000 --- a/src/stl/str/char_at.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::super::litconv::{with_str, with_uint}; -use super::super::runtime_error::RuntimeError; -use crate::interpreted::Clause; -use crate::{write_fn_step, Literal, Primitive}; - -write_fn_step!(pub CharAt2 > CharAt1); -write_fn_step!( - CharAt1 {} - CharAt0 where s = x => with_str(x, |s| Ok(s.clone())) ; -); -write_fn_step!( - CharAt0 { s: String } - i = x => with_uint(x, Ok); - { - if let Some(c) = s.chars().nth(i as usize) { - Ok(Clause::P(Primitive::Literal(Literal::Char(c)))) - } else { - RuntimeError::fail( - "Character index out of bounds".to_string(), - "indexing string", - )? - } - } -); diff --git a/src/stl/str/concatenate.rs b/src/stl/str/concatenate.rs deleted file mode 100644 index c9cdfc0..0000000 --- a/src/stl/str/concatenate.rs +++ /dev/null @@ -1,14 +0,0 @@ -use super::super::litconv::with_str; -use crate::define_fn; -use crate::representations::interpreted::Clause; -use crate::representations::{Literal, Primitive}; - -define_fn! {expr=x in - /// Append a string to another - pub Concatenate { - a: String as with_str(x, |s| Ok(s.clone())), - b: String as with_str(x, |s| Ok(s.clone())) - } => { - Ok(Clause::P(Primitive::Literal(Literal::Str(a.to_owned() + &b)))) - } -} diff --git a/src/stl/str/mod.rs b/src/stl/str/mod.rs deleted file mode 100644 index 90ff695..0000000 --- a/src/stl/str/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -mod char_at; -mod concatenate; - -use crate::interner::Interner; -use crate::pipeline::ConstTree; - -pub fn str(i: &Interner) -> ConstTree { - ConstTree::tree([( - i.i("concatenate"), - ConstTree::xfn(concatenate::Concatenate), - )]) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index d6ad685..1c01525 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,6 +3,7 @@ mod print_nname; mod pushed; mod replace_first; mod side; +mod split_max_prefix; mod string_from_charset; mod substack; mod unwrap_or; @@ -12,6 +13,7 @@ pub use print_nname::sym2string; pub use pushed::pushed; pub use replace_first::replace_first; pub use side::Side; +pub use split_max_prefix::split_max_prefix; pub use substack::{Stackframe, Substack, SubstackIterator}; pub(crate) use unwrap_or::unwrap_or; pub mod iter; diff --git a/src/utils/split_max_prefix.rs b/src/utils/split_max_prefix.rs new file mode 100644 index 0000000..d5c01c8 --- /dev/null +++ b/src/utils/split_max_prefix.rs @@ -0,0 +1,14 @@ +/// Split off the longest prefix accepted by the validator +#[allow(clippy::type_complexity)] // FIXME couldn't find a good factoring +pub fn split_max_prefix<'a, T>( + path: &'a [T], + is_valid: &impl Fn(&[T]) -> bool, +) -> Option<(&'a [T], &'a [T])> { + for split in (0..=path.len()).rev() { + let (filename, subpath) = path.split_at(split); + if is_valid(filename) { + return Some((filename, subpath)); + } + } + None +} diff --git a/src/utils/substack.rs b/src/utils/substack.rs index 40983b5..3f807c3 100644 --- a/src/utils/substack.rs +++ b/src/utils/substack.rs @@ -41,17 +41,13 @@ impl<'a, T> Substack<'a, T> { } /// Create a new frame on top of this substack pub fn new_frame(&'a self, item: T) -> Stackframe<'a, T> { - Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len) } + Stackframe { item, prev: self, len: self.opt().map_or(1, |s| s.len + 1) } } /// obtain the previous stackframe if one exists /// TODO: this should return a [Substack] pub fn pop(&'a self, count: usize) -> Option<&'a Stackframe<'a, T>> { if let Self::Frame(p) = self { - if count == 0 { - Some(p) - } else { - p.prev.pop(count - 1) - } + if count == 0 { Some(p) } else { p.prev.pop(count - 1) } } else { None } diff --git a/src/utils/unwrap_or.rs b/src/utils/unwrap_or.rs index 0a24a12..62b0c06 100644 --- a/src/utils/unwrap_or.rs +++ b/src/utils/unwrap_or.rs @@ -1,9 +1,13 @@ /// A macro version of [Option::unwrap_or_else] which supports flow /// control statements such as `return` and `break` in the "else" branch. +/// It also supports unwrapping concrete variants of other enums macro_rules! unwrap_or { ($m:expr; $fail:expr) => {{ if let Some(res) = ($m) { res } else { $fail } }}; + ($m:expr => $pattern:path; $fail:expr) => {{ + if let $pattern(res) = ($m) { res } else { $fail } + }}; } pub(crate) use unwrap_or;