Transfer commit

This commit is contained in:
2023-03-21 19:36:40 +00:00
parent 180ebb56fa
commit f3ce910f66
63 changed files with 1410 additions and 1023 deletions

2
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,2 @@
{
}

225
Cargo.lock generated
View File

@@ -2,15 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "ahash"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217"
dependencies = [
"const-random",
]
[[package]] [[package]]
name = "ahash" name = "ahash"
version = "0.7.6" version = "0.7.6"
@@ -22,6 +13,17 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "ahash"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
]
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.1.0" version = "1.1.0"
@@ -29,16 +31,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "bitvec" name = "cc"
version = "1.0.1" version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -48,50 +44,22 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chumsky" name = "chumsky"
version = "0.8.0" version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" checksum = "23170228b96236b5a7299057ac284a321457700bc8c41a4476052f0f4ba5349d"
dependencies = [ dependencies = [
"ahash 0.3.8", "hashbrown 0.12.3",
"stacker",
] ]
[[package]] [[package]]
name = "const-random" name = "dashmap"
version = "0.1.13" version = "4.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f590d95d011aa80b063ffe3253422ed5aa462af4e9867d43ce8337562bac77c4" checksum = "e77a43b28d0668df09411cb0bc9a8c2adc40f9a048afe863e05fd43251e8e39c"
dependencies = [ dependencies = [
"const-random-macro", "cfg-if",
"proc-macro-hack", "num_cpus",
]
[[package]]
name = "const-random-macro"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615f6e27d000a2bffbc7f2f6a8669179378fa27ee4d0a509e985dfc0a7defb40"
dependencies = [
"getrandom",
"lazy_static",
"proc-macro-hack",
"tiny-keccak",
]
[[package]]
name = "crunchy"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@@ -106,12 +74,6 @@ version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.2.6" version = "0.2.6"
@@ -125,18 +87,39 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.1" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
dependencies = [ dependencies = [
"ahash 0.7.6", "ahash 0.7.6",
] ]
[[package]] [[package]]
name = "implicit-clone" name = "hashbrown"
version = "0.3.5" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40fc102e70475c320b185cd18c1e48bba2d7210b63970a4d581ef903e4368ef7" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash 0.7.6",
]
[[package]]
name = "hashbrown"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
dependencies = [
"ahash 0.8.3",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
@@ -148,10 +131,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "lazy_static" name = "lasso"
version = "1.4.0" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" checksum = "aeb7b21a526375c5ca55f1a6dfd4e1fad9fa4edd750f530252a718a44b2608f0"
dependencies = [
"dashmap",
"hashbrown 0.11.2",
]
[[package]] [[package]]
name = "libc" name = "libc"
@@ -175,26 +162,32 @@ dependencies = [
] ]
[[package]] [[package]]
name = "once_cell" name = "num_cpus"
version = "1.12.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]] [[package]]
name = "orchid" name = "orchid"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bitvec",
"chumsky", "chumsky",
"derivative",
"dyn-clone", "dyn-clone",
"hashbrown", "hashbrown 0.13.2",
"implicit-clone",
"itertools", "itertools",
"lazy_static", "lasso",
"mappable-rc", "mappable-rc",
"ordered-float", "ordered-float",
"paste",
"smallvec", "smallvec",
"thiserror", "thiserror",
] ]
@@ -208,18 +201,6 @@ dependencies = [
"num-traits", "num-traits",
] ]
[[package]]
name = "paste"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "proc-macro-hack"
version = "0.5.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.39" version = "1.0.39"
@@ -229,6 +210,15 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "psm"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.18" version = "1.0.18"
@@ -238,18 +228,25 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.10.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "stacker"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
dependencies = [
"cc",
"cfg-if",
"libc",
"psm",
"winapi",
]
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.95" version = "1.0.95"
@@ -261,12 +258,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.31" version = "1.0.31"
@@ -287,15 +278,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.0" version = "1.0.0"
@@ -315,10 +297,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]] [[package]]
name = "wyz" name = "winapi"
version = "0.5.1" version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [ dependencies = [
"tap", "winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
] ]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

View File

@@ -7,15 +7,11 @@ edition = "2021"
[dependencies] [dependencies]
thiserror = "1.0" thiserror = "1.0"
chumsky = "0.8" chumsky = "0.9.2"
derivative = "2.2" hashbrown = "0.13.2"
hashbrown = "0.12"
mappable-rc = "0.1" mappable-rc = "0.1"
ordered-float = "3.0" ordered-float = "3.0"
itertools = "0.10" itertools = "0.10"
smallvec = { version = "1.10.0", features = ['const_generics'] } smallvec = { version = "1.10.0", features = ['const_generics'] }
lazy_static = "1.4.0"
implicit-clone = "0.3.5"
bitvec = "1.0.1"
dyn-clone = "1.0.11" dyn-clone = "1.0.11"
paste = "1.0.11" lasso = { version = "0.6.0", features = ['multi-threaded'] }

View File

@@ -1,3 +1,4 @@
import prelude::*
import std::conv::(parse_float, to_string) import std::conv::(parse_float, to_string)
import std::cpsio::(readline, print) import std::cpsio::(readline, print)
import std::str::(concatenate) import std::str::(concatenate)
@@ -10,10 +11,10 @@ export main := do{
cps data = readline; cps data = readline;
let b = parse_float data; let b = parse_float data;
let result = ( let result = (
if op = "+" then a + b if op == "+" then a + b
else if op = "-" then a - b else if op == "-" then a - b
else if op = "*" then a * b else if op == "*" then a * b
else if op = "/" then a / b else if op == "/" then a / b
else "Unsupported operation" -- dynamically typed shenanigans else "Unsupported operation" -- dynamically typed shenanigans
); );
cps print (to_string result ++ "\n"); cps print (to_string result ++ "\n");

View File

@@ -0,0 +1,49 @@
# List of open-source packages I used
## [thiserror](https://github.com/dtolnay/thiserror)
_License: Apache 2.0 or MIT_
Helps derive `Error` for aggregate errors, although I eventually stopped trying to do so as it was simpler to just treat error types as bags of data about the failure.
## [chumsky](https://github.com/zesterer/chumsky)
_License: MIT_
A fantastic parser combinator that allowed me to specify things like the nuanced conditions under which a float token can be promoted to an uint token in a declarative way. In hindsight passes after tokenization could have been written by hand, tokenized Orchid is not that hard to parse into an AST and it would have probably made some tasks such as allowing `.` (dot) as a token considerably easier.
## [hashbrown](https://github.com/rust-lang/hashbrown)
_License: Apache 2.0 or MIT_
Google's swisstable. Almost perfectly identical to `HashMap` in std, with a couple additional APIs. I use it for the raw entry API which the generic processing step cache requires to avoid unnecessary clones of potentially very large trees.
## [mappable-rc](https://github.com/JakobDegen/mappable-rc)
_License: Apache 2.0 or MIT_
A refcounting pointer which can be updated to dereference to some part of the value it holds similarly to C++'s `shared_ptr`. Using this crate was ultimately a mistake on my part, in early stages of development (early stages of my Rust journey) I wanted to store arbitrary subsections of an expression during macro execution without dealing with lifetimes. Removing all uses of this crate and instead just dealing with lifetimes is on the roadmap.
## [ordered-float](https://github.com/reem/rust-ordered-float)
_License: MIT_
A wrapper around floating point numbers that removes `NaN` from the set of possible values, promoting `<` and `>` to total orderings and `==` to an equivalence relation. Orchid does not have `NaN` because it's a silent error. All operations that would produce `NaN` either abort or indicate the failure in their return type.
## [itertools](https://github.com/rust-itertools/itertools)
_License: Apache 2.0 or MIT_
A utility crate, I use it everywhere.
## [smallvec](https://github.com/servo/references-smallvec)
_License: Apache 2.0 or MIT_
small vector optimization - allocates space for a statically known number of elements on the stack to save heap allocations. This is a gamble since the stack space is wasted if the data does spill to the heap, but it can improve performance massively in hot paths.
## [dyn-clone](https://github.com/dtolnay/dyn-clone)
_License: Apache 2.0 or MIT_
All expressions in Orchid are clonable, and to allow for optimizations, Atoms have control over their own cloning logic, so this object-safe version of `Clone` is used.

View File

@@ -45,10 +45,10 @@ $1 [ 0 ] a equals a < $1 ] b 0
Some global rules are also needed, also instantiated for all possible characters in the templated positions Some global rules are also needed, also instantiated for all possible characters in the templated positions
``` ```
$1 $2 < equals $2 < $1 unless $1 is | $1 $2 < equals $2 < $1 unless $1 is |
| $1 < equals $1 | > | $1 < equals $1 | >
> $1 $2 equals $1 > $2 unless $2 is ] > $1 $2 equals $1 > $2 unless $2 is ]
> $1 ] equals [ $1 ] > $1 ] equals [ $1 ]
``` ```
What I really appreciate in this proof is how visual it is; based on this, it's easy to imagine how one would go about encoding a pushdown automaton, lambda calculus or other interesting tree-walking procedures. This is exactly why I based my preprocessor on this system. What I really appreciate in this proof is how visual it is; based on this, it's easy to imagine how one would go about encoding a pushdown automaton, lambda calculus or other interesting tree-walking procedures. This is exactly why I based my preprocessor on this system.
@@ -57,10 +57,41 @@ What I really appreciate in this proof is how visual it is; based on this, it's
I found two major problems with C and Rust macros which vastly limit their potential. They're relatively closed systems, and prone to aliasing. Every other item in Rust follows a rigorous namespacing scheme, but the macros break this seal, I presume the reason is that macro execution happens before namespace resolution. I found two major problems with C and Rust macros which vastly limit their potential. They're relatively closed systems, and prone to aliasing. Every other item in Rust follows a rigorous namespacing scheme, but the macros break this seal, I presume the reason is that macro execution happens before namespace resolution.
Orchid's macros - substitution rules - operate on namespaced tokens. This means that users can safely give their macros short and intuitive names, but it also means that the macros can hook into each other. Consider for example the following hypothetical example. Orchid's macros - substitution rules - operate on namespaced tokens. This means that users can safely give their macros short and intuitive names, but it also means that the macros can hook into each other. Consider for example the following example, which is a slightly modified version of a
real rule included in the prelude:
a widely known module implements a unique way of transforming iterators using an SQL-like syntax.
in _procedural.or_
```orchid ```orchid
select ...$collist from ...$ export do { ...$statement ; ...$rest:1 } =10_001=> (
statement (...$statement) do { ...$rest }
)
export do { ...$return } =10_000=> (...$return)
export statement (let $_name = ...$value) ...$next =10_000=> (
(\$_name. ...$next) (...$value)
)
``` ```
in _cpsio.or_
```orchid
import procedural::statement
export statement (cps $_name = ...$operation) ...$next =10_001=> (
(...$operation) \$_name. ...$next
)
export statement (cps ...$operation) ...$next =10_000=> (
(...$operation) (...$next)
)
```
in _main.or_
```orchid
import procedural::(do, let, ;)
import cpsio::cps
export main := do{
cps data = readline;
let a = parse_float data * 2;
cps print (data ++ " doubled is " ++ stringify a)
}
```

View File

@@ -0,0 +1,101 @@
# Parsing
Orchid expressions are similar in nature to lambda calculus or haskell, except whitespace is mostly irrelevant.
## Names
`name` and `ns_name` tokens appear all over the place in this spec. They represent operators, function names, arguments, modules. A `name` is
1. the universally recognized operators `,`, `.`, `..` and `...` (comma and single, double and triple dot)
2. any C identifier
3. any sequence of name-safe characters starting with a character that cannot begin a C identifier. A name-safe character is any non-whitespace Unicode character other than
- digits
- the namespace separator `:`,
- the parametric expression starters `\` and `@`,
- the string and char delimiters `"` and `'`,
- the various brackets`(`, `)`, `[`, `]`, `{` and `}`,
- `,`, `.` and `$`
This means that, in absence of a known list of names, `!importatn!` is a single name but `importatn!` is two names, as a name that starts as a C identifier cannot contain special characters. It also means that using non-English characters in Orchid variables is a really bad idea. This is intentional, identifiers that need to be repeated verbatim should only contain characters that appear on all latin keyboards.
There are also reserved words that cannot be used as names; `export` and `import`.
A `ns_name` is a sequence of one or more `name` tokens separated by the namespace separator `::`.
All tokens that do not contain `::` in the code may be `name` or `ns_name` depending on their context.
## Clauses
Clauses are the building blocks of Orchid's syntax. They belong to one of a couple categories:
- S-expressions are a parenthesized sequence of space-delimited `clause`s. All three types of brackets `()`, `[]` and `{}` are supported.
- Lambdas start with `\<name>.`, followed by a sequence of `clause`s where `<name>` is a single `name` or `$_` followed by a C identifier. This is a greedy pattern that ends at the end of an enclosing S-expression, or the end of input.
- numbers can be in decimal, binary with the `0b` prefix, hexadecimal with the `0x` prefix, or octal with the `0` prefix. All bases support the decimal point, exponential notation or both. The exponent is prefixed with `p`, always written in decimal, may be negative, and it represents a power of the base rather than a power of 10. For example, `0xf0.4p-2` is `0xf04 / 16 ^ 3` or ~0.9385.
- Strings are delimited with `"`, support `\` escapes and four digit unicode escapes of the form `\uXXXX`. They may contain line breaks.
- Chars are a single character or escape from the above description of a string delimited by `'`.
- Placeholders are either of three styles; `$name`, `..$name`, `...$name`, `..$name:p`, `...$name:p`. the name is always a C identifier, p is an integer growth priority.
- Names are a single `ns_name`
## Files
Files are separated into lines. A line is delimited by newlines and only contains newlines within brackets. A line may be an import, rule, exported rule, or explicit export.
### Rules
Rules have the following form
```
pattern =priority=> template
```
The pattern is able to define new operators implicitly by referencing them, so all tokens must be delimited by spaces. The template is inserted in place of the pattern without parentheses, so unless it's meant to be part of a pattern matched by another rule which expects a particular parenthesization, when more than one token is produced the output should be wrapped in parentheses.
A shorthand syntax is available for functions:
```
name := value
```
name in this case must be a single `name`. Value is automatically parenthesized, and the priority of these rules is always zero.
### Explicit exports and exported rules
An explicit export consists of `export :: ( <names> )` where `<names>` is a comma-separated list of `name`s.
An exported rule consists of the keyword `export` followed by a regular rule. It both counts as a rule and an export of all the `name`s within the pattern.
### Imports
An import is a line starting with the keyword `import`, followed by a tree of imported names.
```
import_tree = name
| name :: import_tree
| name :: *
| ( import_tree [, import_tree]+ )
```
Some examples of valid imports:
```
import std::cpsio
import std::(conv::parse_float, cpsio, str::*)
import std
```
Some examples of invalid imports:
```
import std::()
import std::cpsio::(print, *)
import std::(cpsio)
```
> **info**
>
> while none of these are guaranteed to work currently, there's little reason they would have to be invalid, so future specifications may allow them.
An import can be normalized into a list of independent imports ending either with a `*` called wildcard imports or with a `name`. wildcard imports are normalized to imports for all the `name`s exported from the parent module. All Name clauses in the file starting with the same `name` one of these imports ended with are prefixed with the full import path. The rest of the Name clauses are prefixed with the full path of the current module.
Reference cycles in Orchid modules are never allowed, so the dependency of a module's exports on its imports and a wildcard's import's value on the referenced module's exports does not introduce the risk of circular dependencies, it just specifies the order of processing for files.

View File

@@ -0,0 +1,45 @@
# Macros
After parsing, what remains is a set of macro rules, each with a pattern, priority and template. Modules aren't tracked in this stage, their purpose was to namespace the tokens within the rules.
By employing custom import logic, it's also possible to add rules bypassing the parser. Starting with the macro phase, `clause`s may also be `atom`s or `externfn`s. The role of these is detailed in the [[03-runtime]] section.
Macros are executed in reverse priority order, each macro is checked against each subsection of each clause sequence. When a match is found, the substitution is performed and all macros are executed again.
## Placeholders
Patterns fall into two categories
- scalar placeholders
- `$name` matches exactly one clause
- `$_name` matches exactly one Name clause
- vectorial placeholders
- `..$name` matches zero or more clauses
- `...$name` matches one or more clauses
`$_name` is uniquely valid in the position of an argument name within a lambda.
Vectorial placeholders may also have a positive decimal integer growth priority specified after the name, separated with a `:` like so: `...$cond:2`. If it isn't specified, the growth priority defaults to 0.
The template may only include placeholders referenced in the pattern. All occurences of a placeholder within a rule must match the same things.
## Execution
Each clause in the pattern matches clauses as follows:
- Name matches name with the same full path.
- Lambda matches a lambda with matching argument name and matching body. If the argument name in the pattern is a name-placeholder (as in `\$_phname.`), the argument name in the source is treated as a module-local Name clause.
- Parenthesized expressions match each other if the contained sequences match and both use the same kind of parentheses.
- Placeholders' matched sets are as listed in [Placeholders].
If a pattern contains the same placeholder name more than once, matches where they don't match perfectly identical clauses, names or clause sequences are discarded.
### Order of preference
The growth order of vectorial placeholders is
- Outside before inside parentheses
- descending growth priority
- left-to-right by occurrence in the pattern.
If a pattern matches a sequence in more than one way, whichever match allocates more clauses to the first vectorial placeholder in growth order is preferred.

View File

@@ -0,0 +1,32 @@
# Runtime
Orchid is evaluated lazily. This means that everything operates on unevaluated expressions. This has the advantage that unused values never need to be computed, but it also introduces a great deal of complexity in interoperability.
## Execution mode
The executor supports step-by-step execution, multiple steps at once, and running an expression to completion. Once an Orchid program reaches a nonreducible state, it is either an external item, a literal, or a lambda function.
## external API
In order to do anything useful, Orchid provides an API for defining clauses that have additional behaviour implemented in Rust. Basic arithmetic is defined using these.
### Atomic
atomics are opaque units of foreign data, with the following operations:
- functions for the same three execution modes the language itself supports
- downcasting to a concrete type
Atomics can be used to represent processes. Given enough processing cycles, these return a different clause.
They can also be used to wrap data addressed to other external code. This category of atomics reports nonreducible at all times, and relies on the downcasting API to interact with ExternFn-s.
It's possible to use a combination of these for conditional optimizations - for instance, to recognize chains of processes that can be more efficiently expressed as a single task.
### ExternFn
external functions can be combined with another clause to form a new clause. Most of the time, this new clause would be an Atomic which forwards processing to the arguments until they can't be normalized any further, at which point it either returns an ExternFn to take another argument or executes the operation associated with the function and returns.
Because this combination of operations is so common, several macros are provided to streamline it.
Sometimes, eg. when encoding effectful functions in continuation passing style, an ExternFn returns its argument without modification. It is always a logic error to run expressions outside a run call, or to expect an expression to be of any particular shape without ensuring that run returned nonreducible in the past.

View File

@@ -10,12 +10,19 @@
"editor.unicodeHighlight.invisibleCharacters": false, "editor.unicodeHighlight.invisibleCharacters": false,
"diffEditor.ignoreTrimWhitespace": false, "diffEditor.ignoreTrimWhitespace": false,
"editor.wordWrap": "bounded", "editor.wordWrap": "bounded",
"editor.wordWrapColumn": 100, "editor.wordWrapColumn": 80,
"editor.quickSuggestions": { "editor.quickSuggestions": {
"comments": "off", "comments": "off",
"strings": "off", "strings": "off",
"other": "off" "other": "off"
} },
"editor.lineNumbers": "off",
"editor.glyphMargin": false,
"editor.rulers": [],
"editor.guides.indentation": false,
},
"[rust]": {
"editor.rulers": [74]
} }
}, },
"extensions": { "extensions": {

View File

@@ -3,10 +3,10 @@ mod boolean;
mod ifthenelse; mod ifthenelse;
pub use boolean::Boolean; pub use boolean::Boolean;
use crate::project::{Loader, fnlib_loader}; use crate::project::{Loader, extlib_loader};
pub fn bool() -> impl Loader { pub fn bool() -> impl Loader {
fnlib_loader(vec![ extlib_loader(vec![
("ifthenelse", Box::new(ifthenelse::IfThenElse1)), ("ifthenelse", Box::new(ifthenelse::IfThenElse1)),
("equals", Box::new(equals::Equals2)) ("equals", Box::new(equals::Equals2))
]) ])

View File

@@ -1,11 +1,11 @@
use crate::project::{fnlib_loader, Loader}; use crate::project::{extlib_loader, Loader};
mod to_string; mod to_string;
mod parse_float; mod parse_float;
mod parse_uint; mod parse_uint;
pub fn conv() -> impl Loader { pub fn conv() -> impl Loader {
fnlib_loader(vec![ extlib_loader(vec![
("parse_float", Box::new(parse_float::ParseFloat1)), ("parse_float", Box::new(parse_float::ParseFloat1)),
("parse_uint", Box::new(parse_uint::ParseUint1)), ("parse_uint", Box::new(parse_uint::ParseUint1)),
("to_string", Box::new(to_string::ToString1)) ("to_string", Box::new(to_string::ToString1))

View File

@@ -1,10 +1,10 @@
use crate::project::{Loader, fnlib_loader}; use crate::project::{Loader, extlib_loader};
mod print; mod print;
mod readline; mod readline;
pub fn cpsio() -> impl Loader { pub fn cpsio() -> impl Loader {
fnlib_loader(vec![ extlib_loader(vec![
("print", Box::new(print::Print2)), ("print", Box::new(print::Print2)),
("readline", Box::new(readline::Readln2)) ("readline", Box::new(readline::Readln2))
]) ])

View File

@@ -2,10 +2,10 @@ mod numeric;
pub mod operators; pub mod operators;
pub use numeric::Numeric; pub use numeric::Numeric;
use crate::project::{fnlib_loader, Loader}; use crate::project::{extlib_loader, Loader};
pub fn num() -> impl Loader { pub fn num() -> impl Loader {
fnlib_loader(vec![ extlib_loader(vec![
("add", Box::new(operators::add::Add2)), ("add", Box::new(operators::add::Add2)),
("subtract", Box::new(operators::subtract::Subtract2)), ("subtract", Box::new(operators::subtract::Subtract2)),
("multiply", Box::new(operators::multiply::Multiply2)), ("multiply", Box::new(operators::multiply::Multiply2)),

View File

@@ -14,15 +14,29 @@ pub enum Numeric {
Num(NotNan<f64>) Num(NotNan<f64>)
} }
impl Numeric {
/// Wrap a f64 in a Numeric
///
/// # Panics
///
/// if the value is NaN or Infinity.try_into()
fn num<T>(value: T) -> Self where T: Into<f64> {
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 { impl Add for Numeric {
type Output = Numeric; type Output = Numeric;
fn add(self, rhs: Self) -> Self::Output { fn add(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a + b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(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::Uint(a), Numeric::Num(b)) |
=> Numeric::Num(NotNan::new(a as f64).unwrap() + b) (Numeric::Num(b), Numeric::Uint(a))
=> Numeric::num::<f64>(a as f64 + *b)
} }
} }
} }
@@ -34,10 +48,10 @@ impl Sub for Numeric {
match (self, rhs) { match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) if b < a => Numeric::Uint(a - b), (Numeric::Uint(a), Numeric::Uint(b)) if b < a => Numeric::Uint(a - b),
(Numeric::Uint(a), Numeric::Uint(b)) (Numeric::Uint(a), Numeric::Uint(b))
=> Numeric::Num(NotNan::new(a as f64 - b as f64).unwrap()), => Numeric::num(a as f64 - b as f64),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(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::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 - *b),
=> Numeric::Num(NotNan::new(a as f64).unwrap() - b) (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a - b as f64)
} }
} }
} }
@@ -48,8 +62,9 @@ impl Mul for Numeric {
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a * b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(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::Uint(a), Numeric::Num(b)) |
(Numeric::Num(b), Numeric::Uint(a))
=> Numeric::Num(NotNan::new(a as f64).unwrap() * b) => Numeric::Num(NotNan::new(a as f64).unwrap() * b)
} }
} }
@@ -59,9 +74,9 @@ impl Div for Numeric {
type Output = Numeric; type Output = Numeric;
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
let a = match self { Numeric::Uint(i) => i as f64, Numeric::Num(f) => *f }; let a: f64 = self.into();
let b = match rhs { Numeric::Uint(i) => i as f64, Numeric::Num(f) => *f }; let b: f64 = rhs.into();
Numeric::Num(NotNan::new(a / b).unwrap()) Numeric::num(a / b)
} }
} }
@@ -71,9 +86,9 @@ impl Rem for Numeric {
fn rem(self, rhs: Self) -> Self::Output { fn rem(self, rhs: Self) -> Self::Output {
match (self, rhs) { match (self, rhs) {
(Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b), (Numeric::Uint(a), Numeric::Uint(b)) => Numeric::Uint(a % b),
(Numeric::Num(a), Numeric::Num(b)) => Numeric::Num(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::Uint(a), Numeric::Num(b)) => Numeric::num(a as f64 % *b),
=> Numeric::Num(NotNan::new(a as f64).unwrap() % b) (Numeric::Num(a), Numeric::Uint(b)) => Numeric::num(*a % b as f64)
} }
} }
} }
@@ -109,3 +124,12 @@ impl From<Numeric> for String {
} }
} }
} }
impl Into<f64> for Numeric {
fn into(self) -> f64 {
match self {
Numeric::Num(n) => *n,
Numeric::Uint(i) => i as f64
}
}
}

View File

@@ -2,10 +2,10 @@ mod concatenate;
mod cls2str; mod cls2str;
mod char_at; mod char_at;
pub use cls2str::cls2str; pub use cls2str::cls2str;
use crate::project::{Loader, fnlib_loader}; use crate::project::{Loader, extlib_loader};
pub fn str() -> impl Loader { pub fn str() -> impl Loader {
fnlib_loader(vec![ extlib_loader(vec![
("concatenate", Box::new(concatenate::Concatenate2)) ("concatenate", Box::new(concatenate::Concatenate2))
]) ])
} }

View File

@@ -5,20 +5,26 @@ use std::rc::Rc;
use dyn_clone::DynClone; use dyn_clone::DynClone;
use crate::representations::interpreted::{Clause, RuntimeError, InternalError}; use crate::representations::interpreted::{
Clause, RuntimeError, InternalError
};
pub trait ExternError: Display { pub trait ExternError: Display {
fn into_extern(self) -> Rc<dyn ExternError> where Self: 'static + Sized { fn into_extern(self) -> Rc<dyn ExternError>
where Self: 'static + Sized {
Rc::new(self) Rc::new(self)
} }
} }
/// Represents an externally defined function from the perspective of the executor /// Represents an externally defined function from the perspective of
/// Since Orchid lacks basic numerical operations, these are also external functions. /// the executor. Since Orchid lacks basic numerical operations,
/// these are also external functions.
pub trait ExternFn: DynClone { pub trait ExternFn: DynClone {
fn name(&self) -> &str; fn name(&self) -> &str;
fn apply(&self, arg: Clause) -> Result<Clause, Rc<dyn ExternError>>; fn apply(&self, arg: Clause) -> Result<Clause, Rc<dyn ExternError>>;
fn hash(&self, state: &mut dyn std::hash::Hasher) { state.write_str(self.name()) } fn hash(&self, state: &mut dyn std::hash::Hasher) {
state.write_str(self.name())
}
} }
impl Eq for dyn ExternFn {} impl Eq for dyn ExternFn {}
@@ -26,7 +32,9 @@ impl PartialEq for dyn ExternFn {
fn eq(&self, other: &Self) -> bool { self.name() == other.name() } fn eq(&self, other: &Self) -> bool { self.name() == other.name() }
} }
impl Hash for dyn ExternFn { impl Hash for dyn ExternFn {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.name().hash(state) } fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state)
}
} }
impl Debug for dyn ExternFn { impl Debug for dyn ExternFn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -41,27 +49,31 @@ pub trait Atomic: Any + Debug + DynClone where Self: 'static {
fn run_once(&self) -> Result<Clause, InternalError>; fn run_once(&self) -> Result<Clause, InternalError>;
fn run_n_times(&self, n: usize) -> Result<(Clause, usize), RuntimeError>; fn run_n_times(&self, n: usize) -> Result<(Clause, usize), RuntimeError>;
fn run_to_completion(&self) -> Result<Clause, RuntimeError>; fn run_to_completion(&self) -> Result<Clause, RuntimeError>;
fn typestr(&self) -> &str { "clause" }
} }
/// Represents a black box unit of code with its own normalization steps. Typically [ExternFn] /// Represents a black box unit of code with its own normalization steps.
/// will produce an [Atom] when applied to a [Clause], this [Atom] will then forward `run_*` calls /// Typically [ExternFn] will produce an [Atom] when applied to a [Clause],
/// to the argument until it yields [InternalError::NonReducible] at which point the [Atom] will /// this [Atom] will then forward `run_*` calls to the argument until it
/// validate and process the argument, returning a different [Atom] intended for processing by /// yields [InternalError::NonReducible] at which point the [Atom] will
/// external code, a new [ExternFn] to capture an additional argument, or an Orchid expression /// validate and process the argument, returning a different [Atom]
/// intended for processing by external code, a new [ExternFn] to capture
/// an additional argument, or an Orchid expression
/// to pass control back to the interpreter. /// to pass control back to the interpreter.
pub struct Atom(pub Box<dyn Atomic>); pub struct Atom(pub Box<dyn Atomic>);
impl Atom { impl Atom {
pub fn new<T: 'static + Atomic>(data: T) -> Self { pub fn new<T: 'static + Atomic>(data: T) -> Self {
Self(Box::new(data) as Box<dyn Atomic>) Self(Box::new(data) as Box<dyn Atomic>)
} }
pub fn data(&self) -> &dyn Atomic { self.0.as_ref() as &dyn Atomic } pub fn data(&self) -> &dyn Atomic {
self.0.as_ref() as &dyn Atomic
}
pub fn try_cast<T: Atomic>(&self) -> Result<&T, ()> { pub fn try_cast<T: Atomic>(&self) -> Result<&T, ()> {
self.data().as_any().downcast_ref().ok_or(()) self.data().as_any().downcast_ref().ok_or(())
} }
pub fn is<T: 'static>(&self) -> bool { self.data().as_any().is::<T>() } pub fn is<T: 'static>(&self) -> bool { self.data().as_any().is::<T>() }
pub fn cast<T: 'static>(&self) -> &T { pub fn cast<T: 'static>(&self) -> &T {
self.data().as_any().downcast_ref().expect("Type mismatch on Atom::cast") self.data().as_any().downcast_ref()
.expect("Type mismatch on Atom::cast")
} }
} }
@@ -78,7 +90,7 @@ impl Hash for Atom {
} }
impl Debug for Atom { impl Debug for Atom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "##ATOM[{:?}]:{:?}##", self.data(), self.data().typestr()) write!(f, "##ATOM[{:?}]##", self.data())
} }
} }
impl Eq for Atom {} impl Eq for Atom {}

View File

@@ -1,5 +1,4 @@
#![feature(specialization)] #![feature(specialization)]
#![feature(core_intrinsics)]
#![feature(adt_const_params)] #![feature(adt_const_params)]
#![feature(generic_const_exprs)] #![feature(generic_const_exprs)]
#![feature(generators, generator_trait)] #![feature(generators, generator_trait)]
@@ -9,7 +8,6 @@
#![feature(hasher_prefixfree_extras)] #![feature(hasher_prefixfree_extras)]
#![feature(closure_lifetime_binder)] #![feature(closure_lifetime_binder)]
#![feature(generic_arg_infer)] #![feature(generic_arg_infer)]
use std::{env::current_dir, collections::HashMap}; use std::{env::current_dir, collections::HashMap};
// mod executor; // mod executor;
@@ -22,6 +20,7 @@ mod scheduler;
pub(crate) mod foreign; pub(crate) mod foreign;
mod external; mod external;
mod foreign_macros; mod foreign_macros;
use lasso::Rodeo;
pub use representations::ast; pub use representations::ast;
use ast::{Expr, Clause}; use ast::{Expr, Clause};
// use representations::typed as t; // use representations::typed as t;
@@ -54,13 +53,13 @@ export (...$a - ...$b:1) =1001=> (subtract (...$a) (...$b))
export (...$a * ...$b) =1000=> (multiply (...$a) (...$b)) export (...$a * ...$b) =1000=> (multiply (...$a) (...$b))
export (...$a % ...$b:1) =1000=> (remainder (...$a) (...$b)) export (...$a % ...$b:1) =1000=> (remainder (...$a) (...$b))
export (...$a / ...$b:1) =1000=> (divide (...$a) (...$b)) export (...$a / ...$b:1) =1000=> (divide (...$a) (...$b))
export (...$a = ...$b) =1002=> (equals (...$a) (...$b)) export (...$a == ...$b) =1002=> (equals (...$a) (...$b))
export (...$a ++ ...$b) =1003=> (concatenate (...$a) (...$b)) export (...$a ++ ...$b) =1003=> (concatenate (...$a) (...$b))
export do { ...$statement ; ...$rest:1 } =10_001=> ( export do { ...$statement ; ...$rest:1 } =10_001=> (
statement (...$statement) do { ...$rest } statement (...$statement) do { ...$rest }
) )
export do { ...$statement } =10_000=> (...$statement) export do { ...$return } =10_000=> (...$return)
export statement (let $_name = ...$value) ...$next =10_000=> ( export statement (let $_name = ...$value) ...$next =10_000=> (
(\$_name. ...$next) (...$value) (\$_name. ...$next) (...$value)
@@ -86,11 +85,15 @@ fn initial_tree() -> Mrc<[Expr]> {
#[allow(unused)] #[allow(unused)]
fn load_project() { fn load_project() {
let collect_rules = rule_collector(map_loader(HashMap::from([ let mut rodeo = Rodeo::default();
("std", std().boxed()), let collect_rules = rule_collector(
("prelude", string_loader(PRELUDE).boxed()), rodeo,
("mod", file_loader(current_dir().expect("Missing CWD!")).boxed()) map_loader(HashMap::from([
]))); ("std", std().boxed()),
("prelude", string_loader(PRELUDE).boxed()),
("mod", file_loader(current_dir().expect("Missing CWD!")).boxed())
]))
);
let rules = match collect_rules.try_find(&literal(&["mod", "main"])) { let rules = match collect_rules.try_find(&literal(&["mod", "main"])) {
Ok(rules) => rules, Ok(rules) => rules,
Err(err) => if let ModuleError::Syntax(pe) = err { Err(err) => if let ModuleError::Syntax(pe) = err {
@@ -124,11 +127,5 @@ fn load_project() {
} }
fn main() { fn main() {
// lambda_notation_debug();
load_project(); load_project();
// let mut std = std();
// match std.load(&["parse_float"]) {
// Ok(_) => println!("wtf"),
// Err(e) => panic!("{:?}", e)
// }
} }

View File

@@ -1,9 +1,10 @@
use std::rc::Rc;
use chumsky::{self, prelude::*, Parser}; use chumsky::{self, prelude::*, Parser};
use mappable_rc::Mrc; use lasso::Spur;
use crate::enum_parser; use crate::enum_parser;
use crate::representations::Primitive; use crate::representations::Primitive;
use crate::representations::{Literal, ast::{Clause, Expr}}; use crate::representations::{Literal, ast::{Clause, Expr}};
use crate::utils::to_mrc_slice;
use super::lexer::Lexeme; use super::lexer::Lexeme;
@@ -12,18 +13,22 @@ fn sexpr_parser<P>(
expr: P expr: P
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone ) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone { where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
Lexeme::paren_parser(expr.repeated()).map(|(del, b)| Clause::S(del, to_mrc_slice(b))) Lexeme::paren_parser(expr.repeated())
.map(|(del, b)| Clause::S(del, Rc::new(b)))
} }
/// Parses `\name.body` or `\name:type.body` where name is any valid name and type and body are /// Parses `\name.body` or `\name:type.body` where name is any valid name
/// both expressions. Comments are allowed and ignored everywhere in between the tokens /// and type and body are both expressions. Comments are allowed
fn lambda_parser<P>( /// and ignored everywhere in between the tokens
expr: P fn lambda_parser<'a, P, F>(
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone expr: P, intern: &'a F
where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone { ) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
just(Lexeme::BS) just(Lexeme::BS)
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(enum_parser!(Lexeme::Name)) .ignore_then(namelike_parser(intern))
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then( .then(
just(Lexeme::Type) just(Lexeme::Type)
@@ -35,20 +40,21 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
.then_ignore(just(Lexeme::name("."))) .then_ignore(just(Lexeme::name(".")))
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then(expr.repeated().at_least(1)) .then(expr.repeated().at_least(1))
.map(|((name, typ), body): ((String, Vec<Expr>), Vec<Expr>)| { .map(|((name, typ), body): ((Clause, Vec<Expr>), Vec<Expr>)| {
// for ent in &mut body { ent.bind_parameter(&name) }; Clause::Lambda(Rc::new(name), Rc::new(typ), Rc::new(body))
Clause::Lambda(name, to_mrc_slice(typ), to_mrc_slice(body))
}) })
} }
/// see [lambda_parser] but `@` instead of `\` and the name is optional /// see [lambda_parser] but `@` instead of `\` and the name is optional
fn auto_parser<P>( fn auto_parser<'a, P, F>(
expr: P expr: P, intern: &'a F
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone ) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone { where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
just(Lexeme::At) just(Lexeme::At)
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.ignore_then(enum_parser!(Lexeme::Name).or_not()) .ignore_then(namelike_parser(intern).or_not())
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then( .then(
just(Lexeme::Type) just(Lexeme::Type)
@@ -60,23 +66,27 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
.then_ignore(just(Lexeme::name("."))) .then_ignore(just(Lexeme::name(".")))
.then_ignore(enum_parser!(Lexeme::Comment).repeated()) .then_ignore(enum_parser!(Lexeme::Comment).repeated())
.then(expr.repeated().at_least(1)) .then(expr.repeated().at_least(1))
.try_map(|((name, typ), body): ((Option<String>, Vec<Expr>), Vec<Expr>), s| { .try_map(|((name, typ), body): ((Option<Clause>, Vec<Expr>), Vec<Expr>), s| {
if name.is_none() && typ.is_empty() { if name.is_none() && typ.is_empty() {
Err(Simple::custom(s, "Auto without name or type has no effect")) Err(Simple::custom(s, "Auto without name or type has no effect"))
} else { } else {
Ok(Clause::Auto(name, to_mrc_slice(typ), to_mrc_slice(body))) Ok(Clause::Auto(name.map(Rc::new), Rc::new(typ), Rc::new(body)))
} }
}) })
} }
/// Parses a sequence of names separated by :: <br/> /// Parses a sequence of names separated by :: <br/>
/// Comments are allowed and ignored in between /// Comments are allowed and ignored in between
fn name_parser() -> impl Parser<Lexeme, Vec<String>, Error = Simple<Lexeme>> + Clone { pub fn ns_name_parser<'a, F>(intern: &'a F)
enum_parser!(Lexeme::Name).separated_by( -> impl Parser<Lexeme, Vec<Spur>, Error = Simple<Lexeme>> + Clone + 'a
enum_parser!(Lexeme::Comment).repeated() where F: Fn(&str) -> Spur + 'a {
.then(just(Lexeme::NS)) enum_parser!(Lexeme::Name)
.then(enum_parser!(Lexeme::Comment).repeated()) .map(|s| intern(&s))
).at_least(1) .separated_by(
enum_parser!(Lexeme::Comment).repeated()
.then(just(Lexeme::NS))
.then(enum_parser!(Lexeme::Comment).repeated())
).at_least(1)
} }
/// Parse any legal argument name starting with a `$` /// Parse any legal argument name starting with a `$`
@@ -87,42 +97,59 @@ fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> +
}) })
} }
pub fn namelike_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where F: Fn(&str) -> Spur + 'a {
choice((
just(Lexeme::name("...")).to(true)
.or(just(Lexeme::name("..")).to(false))
.then(placeholder_parser())
.then(
just(Lexeme::Type)
.ignore_then(enum_parser!(Lexeme::Uint))
.or_not().map(Option::unwrap_or_default)
)
.map(|((nonzero, key), prio)| Clause::Placeh{key, vec: Some((
prio.try_into().unwrap(),
nonzero
))}),
ns_name_parser(intern)
.map(|qualified| Clause::Name(Rc::new(qualified))),
))
}
pub fn clause_parser<'a, P, F>(
expr: P, intern: &'a F
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone + 'a
where
P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone + 'a,
F: Fn(&str) -> Spur + 'a {
enum_parser!(Lexeme::Comment).repeated()
.ignore_then(choice((
enum_parser!(Lexeme >> Literal; Uint, Num, Char, Str)
.map(Primitive::Literal).map(Clause::P),
placeholder_parser().map(|key| Clause::Placeh{key, vec: None}),
namelike_parser(intern),
sexpr_parser(expr.clone()),
lambda_parser(expr.clone(), intern),
auto_parser(expr.clone(), intern),
just(Lexeme::At).ignore_then(expr.clone()).map(|arg| {
Clause::Explicit(Rc::new(arg))
})
))).then_ignore(enum_parser!(Lexeme::Comment).repeated())
}
/// Parse an expression /// Parse an expression
pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> { pub fn xpr_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
recursive(|expr| { recursive(|expr| {
let clause = let clause = clause_parser(expr, intern);
enum_parser!(Lexeme::Comment).repeated()
.ignore_then(choice((
enum_parser!(Lexeme >> Literal; Uint, Num, Char, Str).map(Primitive::Literal).map(Clause::P),
placeholder_parser().map(|key| Clause::Placeh{key, vec: None}),
just(Lexeme::name("...")).to(true)
.or(just(Lexeme::name("..")).to(false))
.then(placeholder_parser())
.then(
just(Lexeme::Type)
.ignore_then(enum_parser!(Lexeme::Uint))
.or_not().map(Option::unwrap_or_default)
)
.map(|((nonzero, key), prio)| Clause::Placeh{key, vec: Some((
prio.try_into().unwrap(),
nonzero
))}),
name_parser().map(|qualified| Clause::Name {
local: if qualified.len() == 1 {Some(qualified[0].clone())} else {None},
qualified: to_mrc_slice(qualified)
}),
sexpr_parser(expr.clone()),
lambda_parser(expr.clone()),
auto_parser(expr.clone()),
just(Lexeme::At).ignore_then(expr.clone()).map(|arg| {
Clause::Explicit(Mrc::new(arg))
})
))).then_ignore(enum_parser!(Lexeme::Comment).repeated());
clause.clone().then( clause.clone().then(
just(Lexeme::Type) just(Lexeme::Type)
.ignore_then(clause.clone()) .ignore_then(clause.clone())
.repeated() .repeated()
) )
.map(|(val, typ)| Expr(val, to_mrc_slice(typ))) .map(|(val, typ)| Expr(val, Rc::new(typ)))
}).labelled("Expression") }).labelled("Expression")
} }

View File

@@ -1,34 +1,33 @@
use std::rc::Rc;
use chumsky::{Parser, prelude::*}; use chumsky::{Parser, prelude::*};
use itertools::Itertools; use itertools::Itertools;
use mappable_rc::Mrc; use lasso::Spur;
use crate::representations::sourcefile::Import;
use crate::utils::iter::{box_once, box_flatten, into_boxed_iter, BoxedIterIter}; use crate::utils::iter::{box_once, box_flatten, into_boxed_iter, BoxedIterIter};
use crate::utils::{to_mrc_slice, mrc_derive};
use crate::{enum_parser, box_chain}; use crate::{enum_parser, box_chain};
use super::lexer::Lexeme; use super::lexer::Lexeme;
#[derive(Debug, Clone)]
pub struct Import {
pub path: Mrc<[String]>,
/// If name is None, this is a wildcard import
pub name: Option<String>
}
/// initialize a BoxedIter<BoxedIter<String>> with a single element. /// initialize a BoxedIter<BoxedIter<String>> with a single element.
fn init_table(name: String) -> BoxedIterIter<'static, String> { fn init_table(name: Spur) -> BoxedIterIter<'static, Spur> {
// I'm not at all confident that this is a good approach. // I'm not at all confident that this is a good approach.
box_once(box_once(name)) box_once(box_once(name))
} }
/// Parse an import command /// Parse an import command
/// Syntax is same as Rust's `use` except the verb is import, no trailing semi /// Syntax is same as Rust's `use` except the verb is import, no trailing
/// and the delimiters are plain parentheses. Namespaces should preferably contain /// semi and the delimiters are plain parentheses. Namespaces should
/// crossplatform filename-legal characters but the symbols are explicitly allowed /// preferably contain crossplatform filename-legal characters but the
/// to go wild. There's a blacklist in [name] /// symbols are explicitly allowed to go wild.
pub fn import_parser() -> impl Parser<Lexeme, Vec<Import>, Error = Simple<Lexeme>> { /// There's a blacklist in [name]
// TODO: this algorithm isn't cache friendly, copies a lot and is generally pretty bad. pub fn import_parser<'a, F>(intern: &'a F)
recursive(|expr: Recursive<Lexeme, BoxedIterIter<String>, Simple<Lexeme>>| { -> impl Parser<Lexeme, Vec<Import>, Error = Simple<Lexeme>> + 'a
enum_parser!(Lexeme::Name) where F: Fn(&str) -> Spur + 'a {
let globstar = intern("*");
// TODO: this algorithm isn't cache friendly and copies a lot
recursive(move |expr:Recursive<Lexeme, BoxedIterIter<Spur>, Simple<Lexeme>>| {
enum_parser!(Lexeme::Name).map(|s| intern(s.as_str()))
.separated_by(just(Lexeme::NS)) .separated_by(just(Lexeme::NS))
.then( .then(
just(Lexeme::NS) just(Lexeme::NS)
@@ -39,15 +38,17 @@ pub fn import_parser() -> impl Parser<Lexeme, Vec<Import>, Error = Simple<Lexeme
.delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('('))) .delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
.map(|v| box_flatten(v.into_iter())) .map(|v| box_flatten(v.into_iter()))
.labelled("import group"), .labelled("import group"),
// Each expr returns a list of imports, flatten those into a common list // Each expr returns a list of imports, flatten into common list
just(Lexeme::name("*")).map(|_| init_table("*".to_string())) just(Lexeme::name("*")).map(move |_| init_table(globstar))
.labelled("wildcard import"), // Just a *, wrapped .labelled("wildcard import"), // Just a *, wrapped
enum_parser!(Lexeme::Name).map(init_table) enum_parser!(Lexeme::Name)
.map(|s| init_table(intern(s.as_str())))
.labelled("import terminal") // Just a name, wrapped .labelled("import terminal") // Just a name, wrapped
)) ))
).or_not() ).or_not()
) )
.map(|(name, opt_post): (Vec<String>, Option<BoxedIterIter<String>>)| -> BoxedIterIter<String> { .map(|(name, opt_post): (Vec<Spur>, Option<BoxedIterIter<Spur>>)|
-> BoxedIterIter<Spur> {
if let Some(post) = opt_post { if let Some(post) = opt_post {
Box::new(post.map(move |el| { Box::new(post.map(move |el| {
box_chain!(name.clone().into_iter(), el) box_chain!(name.clone().into_iter(), el)
@@ -56,14 +57,17 @@ pub fn import_parser() -> impl Parser<Lexeme, Vec<Import>, Error = Simple<Lexeme
box_once(into_boxed_iter(name)) box_once(into_boxed_iter(name))
} }
}) })
}).map(|paths| { }).map(move |paths| {
paths.filter_map(|namespaces| { paths.filter_map(|namespaces| {
let path = to_mrc_slice(namespaces.collect_vec()); let mut path = namespaces.collect_vec();
let path_prefix = mrc_derive(&path, |p| &p[..p.len() - 1]); let name = path.pop()?;
match path.last()?.as_str() { Some(Import {
"*" => Some(Import { path: path_prefix, name: None }), path: Rc::new(path),
name => Some(Import { path: path_prefix, name: Some(name.to_owned()) }) name: {
} if name == globstar { None }
else { Some(name.to_owned()) }
}
})
}).collect() }).collect()
}).labelled("import") }).labelled("import")
} }

View File

@@ -9,12 +9,8 @@ mod import;
mod enum_parser; mod enum_parser;
mod parse; mod parse;
pub use sourcefile::FileEntry;
pub use sourcefile::line_parser; pub use sourcefile::line_parser;
pub use sourcefile::imports;
pub use sourcefile::exported_names;
pub use lexer::{lexer, Lexeme, Entry as LexerEntry}; pub use lexer::{lexer, Lexeme, Entry as LexerEntry};
pub use name::is_op; pub use name::is_op;
pub use parse::{parse, reparse, ParseError}; pub use parse::{parse, reparse, ParseError};
pub use import::Import;
pub use number::{float_parser, int_parser}; pub use number::{float_parser, int_parser};

View File

@@ -2,11 +2,12 @@ use std::{ops::Range, fmt::Debug};
use chumsky::{prelude::{Simple, end}, Stream, Parser}; use chumsky::{prelude::{Simple, end}, Stream, Parser};
use itertools::Itertools; use itertools::Itertools;
use lasso::Spur;
use thiserror::Error; use thiserror::Error;
use crate::{ast::Rule, parse::{lexer::LexedText, sourcefile::split_lines}}; use crate::{ast::Rule, parse::{lexer::LexedText, sourcefile::split_lines}, representations::sourcefile::FileEntry};
use super::{Lexeme, FileEntry, lexer, line_parser, LexerEntry}; use super::{Lexeme, lexer, line_parser, LexerEntry};
#[derive(Error, Debug, Clone)] #[derive(Error, Debug, Clone)]
@@ -17,14 +18,19 @@ pub enum ParseError {
Ast(Vec<Simple<Lexeme>>) Ast(Vec<Simple<Lexeme>>)
} }
pub fn parse<'a, Op>(ops: &[Op], data: &str) -> Result<Vec<FileEntry>, ParseError> pub fn parse<'a, Op, F>(
where Op: 'a + AsRef<str> + Clone { ops: &[Op], data: &str, intern: &F
) -> Result<Vec<FileEntry>, ParseError>
where
Op: 'a + AsRef<str> + Clone,
F: Fn(&str) -> Spur
{
let lexie = lexer(ops); let lexie = lexer(ops);
let token_batchv = split_lines(data).map(|line| { let token_batchv = split_lines(data).map(|line| {
lexie.parse(line).map_err(ParseError::Lex) lexie.parse(line).map_err(ParseError::Lex)
}).collect::<Result<Vec<_>, _>>()?; }).collect::<Result<Vec<_>, _>>()?;
println!("Lexed:\n{:?}", LexedText(token_batchv.clone())); println!("Lexed:\n{:?}", LexedText(token_batchv.clone()));
let parsr = line_parser().then_ignore(end()); let parsr = line_parser(intern).then_ignore(end());
let (parsed_lines, errors_per_line) = token_batchv.into_iter().filter(|v| { let (parsed_lines, errors_per_line) = token_batchv.into_iter().filter(|v| {
!v.is_empty() !v.is_empty()
}).map(|v| { }).map(|v| {
@@ -47,10 +53,15 @@ where Op: 'a + AsRef<str> + Clone {
else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) } else { Ok(parsed_lines.into_iter().map(Option::unwrap).collect()) }
} }
pub fn reparse<'a, Op>(ops: &[Op], data: &str, pre: &[FileEntry]) pub fn reparse<'a, Op, F>(
ops: &[Op], data: &str, pre: &[FileEntry], intern: &F
)
-> Result<Vec<FileEntry>, ParseError> -> Result<Vec<FileEntry>, ParseError>
where Op: 'a + AsRef<str> + Clone { where
let result = parse(ops, data)?; Op: 'a + AsRef<str> + Clone,
F: Fn(&str) -> Spur
{
let result = parse(ops, data, intern)?;
Ok(result.into_iter().zip(pre.iter()).map(|(mut output, donor)| { Ok(result.into_iter().zip(pre.iter()).map(|(mut output, donor)| {
if let FileEntry::Rule(Rule{source, ..}, _) = &mut output { if let FileEntry::Rule(Rule{source, ..}, _) = &mut output {
if let FileEntry::Rule(Rule{source: s2, ..}, _) = donor { if let FileEntry::Rule(Rule{source: s2, ..}, _) = donor {

View File

@@ -1,164 +1,64 @@
use std::collections::HashSet;
use std::iter; use std::iter;
use std::rc::Rc;
use crate::{enum_parser, box_chain}; use crate::representations::sourcefile::FileEntry;
use crate::ast::{Expr, Clause, Rule}; use crate::enum_parser;
use crate::utils::{to_mrc_slice, one_mrc_slice}; use crate::ast::{Expr, Rule};
use crate::utils::Stackframe;
use crate::utils::iter::box_empty;
use super::expression::xpr_parser; use super::expression::{xpr_parser, ns_name_parser};
use super::import::{self, Import};
use super::import::import_parser; use super::import::import_parser;
use super::lexer::Lexeme; use super::lexer::Lexeme;
use chumsky::{Parser, prelude::*}; use chumsky::{Parser, prelude::*};
use lasso::Spur;
use ordered_float::NotNan; use ordered_float::NotNan;
use lazy_static::lazy_static;
/// Anything we might encounter in a file fn rule_parser<'a, F>(intern: &'a F) -> impl Parser<Lexeme, (
#[derive(Debug, Clone)] Vec<Expr>, NotNan<f64>, Vec<Expr>
pub enum FileEntry { ), Error = Simple<Lexeme>> + 'a
Import(Vec<import::Import>), where F: Fn(&str) -> Spur + 'a {
Comment(String), xpr_parser(intern).repeated()
/// The bool indicates whether the rule is exported - whether tokens uniquely defined inside it
/// should be exported
Rule(Rule, bool),
Export(Vec<Vec<String>>)
}
fn visit_all_names_clause_recur<'a, F>(
clause: &'a Clause,
binds: Stackframe<String>,
cb: &mut F
) where F: FnMut(&'a [String]) {
match clause {
Clause::Auto(name, typ, body) => {
for x in typ.iter() {
visit_all_names_expr_recur(x, binds.clone(), cb)
}
let binds_dup = binds.clone();
let new_binds = if let Some(n) = name {
binds_dup.push(n.to_owned())
} else {
binds
};
for x in body.iter() {
visit_all_names_expr_recur(x, new_binds.clone(), cb)
}
},
Clause::Lambda(name, typ, body) => {
for x in typ.iter() {
visit_all_names_expr_recur(x, binds.clone(), cb)
}
for x in body.iter() {
visit_all_names_expr_recur(x, binds.push(name.to_owned()), cb)
}
},
Clause::S(_, body) => for x in body.iter() {
visit_all_names_expr_recur(x, binds.clone(), cb)
},
Clause::Name{ local: Some(name), qualified } => {
if binds.iter().all(|x| x != name) {
cb(qualified)
}
}
_ => (),
}
}
/// Recursively iterate through all "names" in an expression. It also finds a lot of things that
/// aren't names, such as all bound parameters. Generally speaking, this is not a very
/// sophisticated search.
///
/// TODO: find a way to exclude parameters
fn visit_all_names_expr_recur<'a, F>(
expr: &'a Expr,
binds: Stackframe<String>,
cb: &mut F
) where F: FnMut(&'a [String]) {
let Expr(val, typ) = expr;
visit_all_names_clause_recur(val, binds.clone(), cb);
for typ in typ.as_ref() {
visit_all_names_clause_recur(typ, binds.clone(), cb);
}
}
/// Collect all names that occur in an expression
fn find_all_names(expr: &Expr) -> HashSet<&[String]> {
let mut ret = HashSet::new();
visit_all_names_expr_recur(expr, Stackframe::new(String::new()), &mut |n| {
if !n.last().unwrap().starts_with('$') {
ret.insert(n);
}
});
ret
}
fn rule_parser() -> impl Parser<Lexeme, (Vec<Expr>, NotNan<f64>, Vec<Expr>), Error = Simple<Lexeme>> {
xpr_parser().repeated()
.then(enum_parser!(Lexeme::Rule)) .then(enum_parser!(Lexeme::Rule))
.then(xpr_parser().repeated()) .then(xpr_parser(intern).repeated())
// .map(|((lhs, prio), rhs)| )
.map(|((a, b), c)| (a, b, c)) .map(|((a, b), c)| (a, b, c))
.labelled("Rule") .labelled("Rule")
} }
pub fn line_parser() -> impl Parser<Lexeme, FileEntry, Error = Simple<Lexeme>> { pub fn line_parser<'a, F>(intern: &'a F)
-> impl Parser<Lexeme, FileEntry, Error = Simple<Lexeme>> + 'a
where F: Fn(&str) -> Spur + 'a {
choice(( choice((
// In case the usercode wants to parse doc // In case the usercode wants to parse doc
enum_parser!(Lexeme >> FileEntry; Comment), enum_parser!(Lexeme >> FileEntry; Comment),
just(Lexeme::Import) just(Lexeme::Import)
.ignore_then(import_parser().map(FileEntry::Import)) .ignore_then(import_parser(intern).map(FileEntry::Import))
.then_ignore(enum_parser!(Lexeme::Comment).or_not()), .then_ignore(enum_parser!(Lexeme::Comment).or_not()),
just(Lexeme::Export).map_err_with_span(|e, s| { just(Lexeme::Export).map_err_with_span(|e, s| {
println!("{:?} could not yield an export", s); e println!("{:?} could not yield an export", s); e
}).ignore_then( }).ignore_then(
just(Lexeme::NS).ignore_then( just(Lexeme::NS).ignore_then(
enum_parser!(Lexeme::Name).map(|n| vec![n]) ns_name_parser(intern).map(Rc::new)
.separated_by(just(Lexeme::name(","))) .separated_by(just(Lexeme::name(",")))
.delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('('))) .delimited_by(just(Lexeme::LP('(')), just(Lexeme::RP('(')))
).map(FileEntry::Export) ).map(FileEntry::Export)
.or(rule_parser().map(|(source, prio, target)| { .or(rule_parser(intern).map(|(source, prio, target)| {
FileEntry::Rule(Rule { FileEntry::Rule(Rule {
source: to_mrc_slice(source), source: Rc::new(source),
prio, prio,
target: to_mrc_slice(target) target: Rc::new(target)
}, true) }, true)
})) }))
), ),
// This could match almost anything so it has to go last // This could match almost anything so it has to go last
rule_parser().map(|(source, prio, target)| FileEntry::Rule(Rule{ rule_parser(intern).map(|(source, prio, target)| {
source: to_mrc_slice(source), FileEntry::Rule(Rule{
prio, source: Rc::new(source),
target: to_mrc_slice(target) prio,
}, false)), target: Rc::new(target)
}, false)
}),
)) ))
} }
/// Collect all exported names (and a lot of other words) from a file
pub fn exported_names(src: &[FileEntry]) -> HashSet<&[String]> {
src.iter().flat_map(|ent| match ent {
FileEntry::Rule(Rule{source, target, ..}, true) =>
box_chain!(source.iter(), target.iter()),
_ => box_empty()
}).flat_map(find_all_names).chain(
src.iter().filter_map(|ent| {
if let FileEntry::Export(names) = ent {Some(names.iter())} else {None}
}).flatten().map(Vec::as_slice)
).collect()
}
/// Summarize all imports from a file in a single list of qualified names
pub fn imports<'a, 'b, I>(
src: I
) -> impl Iterator<Item = &'b import::Import> + 'a
where I: Iterator<Item = &'b FileEntry> + 'a {
src.filter_map(|ent| match ent {
FileEntry::Import(impv) => Some(impv.iter()),
_ => None
}).flatten()
}
pub fn split_lines(data: &str) -> impl Iterator<Item = &str> { pub fn split_lines(data: &str) -> impl Iterator<Item = &str> {
let mut source = data.char_indices(); let mut source = data.char_indices();
let mut last_slice = 0; let mut last_slice = 0;

View File

@@ -1,7 +1,33 @@
use crate::parse::FileEntry; use lasso::Spur;
use super::{Loader, Loaded}; use crate::representations::sourcefile::FileEntry;
pub fn ext_loader(data: Vec<FileEntry>) -> impl Loader { use super::{Loader, Loaded, LoadingError};
move |_: &[&str]| Ok(Loaded::External(data.clone()))
pub fn ext_loader<'a, T, F>(
data: Vec<FileEntry>,
mut submods: Vec<(&'static str, T)>,
intern: &'a F
) -> impl Loader + 'a
where
T: Loader + 'a,
F: Fn(&str) -> Spur {
move |path: &[&str]| {
let (step, rest) = match path.split_first() {
None => return Ok(Loaded::AST(
data.iter().cloned().chain(
submods.iter().map(|(s, _)| FileEntry::LazyModule(intern(s)))
).collect()
)),
Some(t) => t
};
if let Some((_, l)) = submods.iter_mut().find(|(s, l)| s == step) {
l.load(rest)
} else {
let errtyp = if rest.is_empty() {
LoadingError::UnknownNode
} else {LoadingError::Missing};
Err(errtyp(step.to_string()))
}
}
} }

View File

@@ -0,0 +1,34 @@
use std::rc::Rc;
use lasso::Spur;
use ordered_float::NotNan;
use crate::representations::Primitive;
use crate::representations::sourcefile::FileEntry;
use crate::foreign::ExternFn;
use crate::ast::{Rule, Clause};
use super::{Loader, ext_loader};
pub fn extlib_loader<'a, T, F>(
fns: Vec<(&'static str, Box<dyn ExternFn>)>,
submods: Vec<(&'static str, T)>,
intern: &'a F
) -> impl Loader + 'a
where
T: Loader + 'a,
F: Fn(&str) -> Spur + 'a
{
let entries = (
fns.into_iter().map(|(name, xfn)| FileEntry::Rule(Rule {
source: Rc::new(vec![
Clause::Name(Rc::new(vec![intern(name)])).into_expr(),
]),
prio: NotNan::try_from(0.0f64).unwrap(),
target: Rc::new(vec![
Clause::P(Primitive::ExternFn(xfn)).into_expr(),
])
}, true))
).collect();
ext_loader(entries, submods, intern)
}

View File

@@ -1,22 +1,34 @@
use std::fs::read_to_string; use std::fs::read_to_string;
use std::path::PathBuf; use std::path::PathBuf;
use lasso::Spur;
use crate::representations::sourcefile::FileEntry;
use super::{Loaded, Loader, LoadingError}; use super::{Loaded, Loader, LoadingError};
pub fn file_loader(proj: PathBuf) -> impl Loader + 'static { pub fn file_loader<'a, F>(
proj: PathBuf,
intern: &'a F
) -> impl Loader + 'a
where F: Fn(&str) -> Spur + 'a {
move |path: &[&str]| { move |path: &[&str]| {
let dirpath = proj.join(path.join("/")); let dirpath = proj.join(path.join("/"));
if dirpath.is_dir() || dirpath.is_symlink() { if dirpath.is_dir() || dirpath.is_symlink() {
return Ok(Loaded::Namespace( return Ok(Loaded::AST(
dirpath.read_dir()? dirpath.read_dir()?
.filter_map(|entr| { .filter_map(|entr| {
let ent = entr.ok()?; let ent = entr.ok()?;
let typ = ent.file_type().ok()?; let typ = ent.file_type().ok()?;
let path = ent.path(); let path = ent.path();
if typ.is_dir() || typ.is_symlink() { if typ.is_dir() || typ.is_symlink() {
Some(ent.file_name().to_string_lossy().into_owned()) let name = ent.file_name();
let spur = intern(name.to_string_lossy().as_ref());
Some(FileEntry::LazyModule(spur))
} else if typ.is_file() && path.extension()? == "orc" { } else if typ.is_file() && path.extension()? == "orc" {
Some(path.file_stem()?.to_string_lossy().into_owned()) let name = path.file_stem().expect("extension tested above");
let spur = intern(name.to_string_lossy().as_ref());
Some(FileEntry::LazyModule(spur))
} else { None } } else { None }
}) })
.collect() .collect()
@@ -24,7 +36,7 @@ pub fn file_loader(proj: PathBuf) -> impl Loader + 'static {
} }
let orcfile = dirpath.with_extension("orc"); let orcfile = dirpath.with_extension("orc");
if orcfile.is_file() { if orcfile.is_file() {
read_to_string(orcfile).map(Loaded::Module).map_err(LoadingError::from) read_to_string(orcfile).map(Loaded::Source).map_err(LoadingError::from)
} else { } else {
let pathstr = dirpath.to_string_lossy().into_owned(); let pathstr = dirpath.to_string_lossy().into_owned();
Err(if dirpath.exists() { LoadingError::UnknownNode(pathstr) } Err(if dirpath.exists() { LoadingError::UnknownNode(pathstr) }

View File

@@ -1,23 +0,0 @@
use itertools::Itertools;
use ordered_float::NotNan;
use crate::parse::FileEntry;
use crate::representations::Primitive;
use crate::utils::{one_mrc_slice, mrc_empty_slice};
use crate::foreign::ExternFn;
use crate::ast::{Rule, Expr, Clause};
use super::{Loader, ext_loader};
pub fn fnlib_loader(src: Vec<(&'static str, Box<dyn ExternFn>)>) -> impl Loader {
let entries = src.into_iter().map(|(name, xfn)| FileEntry::Rule(Rule {
source: one_mrc_slice(Expr(Clause::Name{
local: Some(name.to_string()),
qualified: one_mrc_slice(name.to_string())
}, mrc_empty_slice())),
prio: NotNan::try_from(0.0f64).unwrap(),
target: one_mrc_slice(Expr(Clause::P(Primitive::ExternFn(xfn)), mrc_empty_slice()))
}, true))
.collect_vec();
ext_loader(entries)
}

View File

@@ -5,7 +5,7 @@ use super::{Loader, LoadingError, Loaded};
pub fn map_loader<'a, T: Loader + 'a>(mut map: HashMap<&'a str, T>) -> impl Loader + 'a { pub fn map_loader<'a, T: Loader + 'a>(mut map: HashMap<&'a str, T>) -> impl Loader + 'a {
move |path: &[&str]| { move |path: &[&str]| {
let (key, subpath) = if let Some(sf) = path.split_first() {sf} let (key, subpath) = if let Some(sf) = path.split_first() {sf}
else {return Ok(Loaded::Module(map.keys().cloned().collect()))}; else {return Ok(Loaded::Source(map.keys().cloned().collect()))};
let sub = if let Some(sub) = map.get_mut(key.to_string().as_str()) {sub} let sub = if let Some(sub) = map.get_mut(key.to_string().as_str()) {sub}
else {return Err( else {return Err(
if subpath.len() == 0 {LoadingError::UnknownNode(path.join("::"))} if subpath.len() == 0 {LoadingError::UnknownNode(path.join("::"))}

View File

@@ -2,21 +2,19 @@ mod file_loader;
mod ext_loader; mod ext_loader;
mod string_loader; mod string_loader;
mod map_loader; mod map_loader;
mod fnlib_loader; mod extlib_loader;
mod overlay_loader;
mod prefix_loader; mod prefix_loader;
pub use file_loader::file_loader; pub use file_loader::file_loader;
pub use ext_loader::ext_loader; pub use ext_loader::ext_loader;
pub use fnlib_loader::fnlib_loader; pub use extlib_loader::extlib_loader;
pub use string_loader::string_loader; pub use string_loader::string_loader;
pub use map_loader::map_loader; pub use map_loader::map_loader;
pub use overlay_loader::overlay_loader;
pub use prefix_loader::prefix_loader; pub use prefix_loader::prefix_loader;
use std::{rc::Rc, io}; use std::{rc::Rc, io};
use crate::parse::FileEntry; use crate::representations::sourcefile::FileEntry;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum LoadingError { pub enum LoadingError {
@@ -34,11 +32,10 @@ impl From<io::Error> for LoadingError {
} }
} }
#[derive(Debug, Clone)] #[derive(Clone)]
pub enum Loaded { pub enum Loaded {
Module(String), Source(String),
Namespace(Vec<String>), AST(Vec<FileEntry>)
External(Vec<FileEntry>)
} }
pub trait Loader { pub trait Loader {

View File

@@ -1,19 +0,0 @@
use super::{Loader, LoadingError};
pub fn overlay_loader(mut base: impl Loader, mut overlay: impl Loader) -> impl Loader {
move |path: &[&str]| match overlay.load(path) {
ok@Ok(_) => ok,
e@Err(LoadingError::IOErr(_)) => e,
Err(_) => base.load(path)
}
}
#[macro_export]
macro_rules! overlay_loader {
($left:expr, $right:expr) => {
overlay_loader($left, $right)
};
($left:expr, $mid:expr, $($rest:expr),+) => {
overlay_loader($left, overlay_loader!($mid, $($rest),+))
};
}

View File

@@ -1,5 +1,5 @@
use super::{Loader, Loaded}; use super::{Loader, Loaded};
pub fn string_loader<'a>(data: &'a str) -> impl Loader + 'a { pub fn string_loader<'a>(data: &'a str) -> impl Loader + 'a {
move |_: &[&str]| Ok(Loaded::Module(data.to_string())) move |_: &[&str]| Ok(Loaded::Source(data.to_string()))
} }

View File

@@ -8,7 +8,7 @@ pub use module_error::ModuleError;
pub use rule_collector::rule_collector; pub use rule_collector::rule_collector;
pub use loading::{ pub use loading::{
Loader, Loaded, LoadingError, Loader, Loaded, LoadingError,
ext_loader, file_loader, string_loader, map_loader, fnlib_loader, ext_loader, file_loader, string_loader, map_loader, extlib_loader,
overlay_loader, prefix_loader prefix_loader
}; };
use crate::ast::Rule; use crate::ast::Rule;

View File

@@ -1,73 +1,80 @@
use std::collections::HashMap; use std::collections::HashMap;
use mappable_rc::Mrc; use std::rc::Rc;
use itertools::Itertools;
use lasso::Spur;
use thiserror::Error; use thiserror::Error;
use crate::utils::{Stackframe, to_mrc_slice}; use crate::utils::Stackframe;
use crate::ast::{Expr, Clause}; use crate::ast::{Expr, Clause};
type ImportMap = HashMap<String, Mrc<[String]>>; type ImportMap = HashMap<Spur, Rc<Vec<Spur>>>;
#[derive(Debug, Clone, Error)] #[derive(Debug, Clone, Error)]
pub enum ResolutionError<Err> { pub enum ResolutionError<Err> {
#[error("Reference cycle at {0:?}")] #[error("Reference cycle at {0:?}")]
Cycle(Vec<Mrc<[String]>>), Cycle(Vec<Rc<Vec<Spur>>>),
#[error("No module provides {0:?}")] #[error("No module provides {0:?}")]
NoModule(Mrc<[String]>), NoModule(Rc<Vec<Spur>>),
#[error(transparent)] #[error(transparent)]
Delegate(#[from] Err) Delegate(#[from] Err)
} }
type ResolutionResult<E> = Result<Mrc<[String]>, ResolutionError<E>>; type ResolutionResult<E> = Result<Rc<Vec<Spur>>, ResolutionError<E>>;
/// Recursively resolves symbols to their original names in expressions while caching every /// Recursively resolves symbols to their original names in expressions
/// resolution. This makes the resolution process lightning fast and invalidation completely /// while caching every resolution. This makes the resolution process
/// impossible since the intermediate steps of a resolution aren't stored. /// lightning fast and invalidation completely impossible since
/// the intermediate steps of a resolution aren't stored.
pub struct NameResolver<FSplit, FImps, E> { pub struct NameResolver<FSplit, FImps, E> {
cache: HashMap<Mrc<[String]>, ResolutionResult<E>>, cache: HashMap<Rc<Vec<Spur>>, ResolutionResult<E>>,
get_modname: FSplit, split: FSplit,
get_imports: FImps get_imports: FImps
} }
impl<FSplit, FImps, E> NameResolver<FSplit, FImps, E> impl<FSplit, FImps, E> NameResolver<FSplit, FImps, E>
where where
FSplit: FnMut(Mrc<[String]>) -> Option<Mrc<[String]>>, FSplit: FnMut(Rc<Vec<Spur>>) -> Option<(Rc<Vec<Spur>>, Rc<Vec<Spur>>)>,
FImps: FnMut(Mrc<[String]>) -> Result<ImportMap, E>, FImps: FnMut(Rc<Vec<Spur>>) -> Result<ImportMap, E>,
E: Clone E: Clone
{ {
pub fn new(get_modname: FSplit, get_imports: FImps) -> Self { pub fn new(split: FSplit, get_imports: FImps) -> Self {
Self { Self {
cache: HashMap::new(), cache: HashMap::new(),
get_modname, split,
get_imports get_imports
} }
} }
fn split(&self, symbol: Rc<Vec<Spur>>)
-> Result<(Rc<Vec<Spur>>, Rc<Vec<Spur>>), ResolutionError<E>> {
let (path, name) = (self.split)(symbol.clone())
.ok_or_else(|| ResolutionError::NoModule(symbol.clone()))?;
if name.is_empty() {
panic!("get_modname matched all to module and nothing to name")
}
Ok((path, name))
}
/// Obtains a symbol's originnal name /// Obtains a symbol's originnal name
/// Uses a substack to detect loops /// Uses a substack to detect loops
fn find_origin_rec( fn find_origin_rec(
&mut self, &mut self,
symbol: Mrc<[String]>, symbol: Rc<Vec<Spur>>,
import_path: Stackframe<Mrc<[String]>> import_path: Stackframe<Rc<Vec<Spur>>>
) -> Result<Mrc<[String]>, ResolutionError<E>> { ) -> Result<Rc<Vec<Spur>>, ResolutionError<E>> {
if let Some(cached) = self.cache.get(&symbol) { if let Some(cached) = self.cache.get(&symbol) {
return cached.as_ref().map_err(|e| e.clone()).map(Mrc::clone) return cached.clone()
} }
// The imports and path of the referenced file and the local name // The imports and path of the referenced file and the local name
let path = (self.get_modname)(Mrc::clone(&symbol)).ok_or_else(|| { let (path, name) = self.split(symbol)?;
ResolutionError::NoModule(Mrc::clone(&symbol)) let imports = (self.get_imports)(path.clone())?;
})?;
let name = &symbol[path.len()..];
if name.is_empty() {
panic!("get_modname matched all to module and nothing to name in {:?}", import_path)
}
let imports = (self.get_imports)(Mrc::clone(&path))?;
let result = if let Some(source) = imports.get(&name[0]) { let result = if let Some(source) = imports.get(&name[0]) {
let new_sym: Vec<String> = source.iter().chain(name.iter()).cloned().collect(); let new_sym = source.iter().chain(name.iter()).cloned().collect_vec();
if import_path.iter().any(|el| el.as_ref() == new_sym.as_slice()) { if import_path.iter().any(|el| el.as_ref() == new_sym.as_slice()) {
Err(ResolutionError::Cycle(import_path.iter().map(Mrc::clone).collect())) Err(ResolutionError::Cycle(import_path.iter().cloned().collect()))
} else { } else {
self.find_origin_rec(to_mrc_slice(new_sym), import_path.push(Mrc::clone(&symbol))) self.find_origin_rec(Rc::new(new_sym), import_path.push(symbol.clone()))
} }
} else { } else {
Ok(symbol.clone()) // If not imported, it must be locally defined Ok(symbol.clone()) // If not imported, it must be locally defined
@@ -81,30 +88,27 @@ where
} }
fn process_exprmrcopt_rec(&mut self, fn process_exprmrcopt_rec(&mut self,
exbo: &Option<Mrc<Expr>> exbo: &Option<Rc<Expr>>
) -> Result<Option<Mrc<Expr>>, ResolutionError<E>> { ) -> Result<Option<Rc<Expr>>, ResolutionError<E>> {
exbo.iter().map(|exb| Ok(Mrc::new(self.process_expression_rec(exb.as_ref())?))) exbo.iter().map(|exb| Ok(Rc::new(self.process_expression_rec(exb)?)))
.next().transpose() .next().transpose()
} }
fn process_clause_rec(&mut self, tok: &Clause) -> Result<Clause, ResolutionError<E>> { fn process_clause_rec(&mut self, tok: &Clause) -> Result<Clause, ResolutionError<E>> {
Ok(match tok { Ok(match tok {
Clause::S(c, exv) => Clause::S(*c, to_mrc_slice( Clause::S(c, exv) => Clause::S(*c, Rc::new(
exv.as_ref().iter().map(|e| self.process_expression_rec(e)) exv.iter().map(|e| self.process_expression_rec(e))
.collect::<Result<Vec<Expr>, ResolutionError<E>>>()? .collect::<Result<_, _>>()?
)), )),
Clause::Lambda(name, typ, body) => Clause::Lambda(name.clone(), Clause::Lambda(name, typ, body) => Clause::Lambda(name.clone(),
to_mrc_slice(self.process_exprv_rec(typ.as_ref())?), Rc::new(self.process_exprv_rec(&typ)?),
to_mrc_slice(self.process_exprv_rec(body.as_ref())?) Rc::new(self.process_exprv_rec(&body)?)
), ),
Clause::Auto(name, typ, body) => Clause::Auto(name.clone(), Clause::Auto(name, typ, body) => Clause::Auto(name.clone(),
to_mrc_slice(self.process_exprv_rec(typ.as_ref())?), Rc::new(self.process_exprv_rec(&typ)?),
to_mrc_slice(self.process_exprv_rec(body.as_ref())?) Rc::new(self.process_exprv_rec(&body)?)
), ),
Clause::Name{local, qualified} => Clause::Name{ Clause::Name(name) => Clause::Name(self.find_origin(name.clone())?),
local: local.clone(),
qualified: self.find_origin(Mrc::clone(qualified))?
},
x => x.clone() x => x.clone()
}) })
} }
@@ -112,12 +116,14 @@ where
fn process_expression_rec(&mut self, Expr(token, typ): &Expr) -> Result<Expr, ResolutionError<E>> { fn process_expression_rec(&mut self, Expr(token, typ): &Expr) -> Result<Expr, ResolutionError<E>> {
Ok(Expr( Ok(Expr(
self.process_clause_rec(token)?, self.process_clause_rec(token)?,
typ.iter().map(|t| self.process_clause_rec(t)).collect::<Result<_, _>>()? Rc::new(typ.iter().map(|t| {
self.process_clause_rec(t)
}).collect::<Result<_, _>>()?)
)) ))
} }
pub fn find_origin(&mut self, symbol: Mrc<[String]>) -> Result<Mrc<[String]>, ResolutionError<E>> { pub fn find_origin(&mut self, symbol: Rc<Vec<Spur>>) -> Result<Rc<Vec<Spur>>, ResolutionError<E>> {
self.find_origin_rec(Mrc::clone(&symbol), Stackframe::new(symbol)) self.find_origin_rec(symbol.clone(), Stackframe::new(symbol))
} }
#[allow(dead_code)] #[allow(dead_code)]

View File

@@ -1,6 +1,8 @@
use mappable_rc::Mrc; use std::rc::Rc;
use crate::{ast::{Expr, Clause}, utils::{collect_to_mrc, to_mrc_slice}}; use lasso::Spur;
use crate::ast::{Expr, Clause};
/// Replaces the first element of a name with the matching prefix from a prefix map /// Replaces the first element of a name with the matching prefix from a prefix map
@@ -8,34 +10,33 @@ use crate::{ast::{Expr, Clause}, utils::{collect_to_mrc, to_mrc_slice}};
/// Called by [#prefix] which handles Typed. /// Called by [#prefix] which handles Typed.
fn prefix_clause( fn prefix_clause(
expr: &Clause, expr: &Clause,
namespace: Mrc<[String]> namespace: &[Spur]
) -> Clause { ) -> Clause {
match expr { match expr {
Clause::S(c, v) => Clause::S(*c, Clause::S(c, v) => Clause::S(*c, Rc::new(v.iter().map(|e| {
collect_to_mrc(v.iter().map(|e| prefix_expr(e, Mrc::clone(&namespace)))) prefix_expr(e, namespace)
), }).collect())),
Clause::Auto(name, typ, body) => Clause::Auto( Clause::Auto(name, typ, body) => Clause::Auto(
name.clone(), name.clone(),
collect_to_mrc(typ.iter().map(|e| prefix_expr(e, Mrc::clone(&namespace)))), Rc::new(typ.iter().map(|e| prefix_expr(e, namespace)).collect()),
collect_to_mrc(body.iter().map(|e| prefix_expr(e, Mrc::clone(&namespace)))), Rc::new(body.iter().map(|e| prefix_expr(e, namespace)).collect()),
), ),
Clause::Lambda(name, typ, body) => Clause::Lambda( Clause::Lambda(name, typ, body) => Clause::Lambda(
name.clone(), name.clone(),
collect_to_mrc(typ.iter().map(|e| prefix_expr(e, Mrc::clone(&namespace)))), Rc::new(typ.iter().map(|e| prefix_expr(e, namespace)).collect()),
collect_to_mrc(body.iter().map(|e| prefix_expr(e, Mrc::clone(&namespace)))), Rc::new(body.iter().map(|e| prefix_expr(e, namespace)).collect()),
),
Clause::Name(name) => Clause::Name(
Rc::new(namespace.iter().chain(name.iter()).cloned().collect())
), ),
Clause::Name{local, qualified} => Clause::Name{
local: local.clone(),
qualified: collect_to_mrc(namespace.iter().chain(qualified.iter()).cloned())
},
x => x.clone() x => x.clone()
} }
} }
/// Produce an Expr object for any value of Expr /// Produce an Expr object for any value of Expr
pub fn prefix_expr(Expr(clause, typ): &Expr, namespace: Mrc<[String]>) -> Expr { pub fn prefix_expr(Expr(clause, typ): &Expr, namespace: &[Spur]) -> Expr {
Expr( Expr(
prefix_clause(clause, Mrc::clone(&namespace)), prefix_clause(clause, namespace),
to_mrc_slice(typ.iter().map(|e| prefix_clause(e, Mrc::clone(&namespace))).collect()) Rc::new(typ.iter().map(|e| prefix_clause(e, namespace)).collect())
) )
} }

View File

@@ -1,85 +1,92 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::{HashMap, HashSet, VecDeque}; use std::collections::{HashMap, HashSet, VecDeque};
use std::fmt::Debug;
use std::rc::Rc; use std::rc::Rc;
use itertools::Itertools; use itertools::Itertools;
use mappable_rc::Mrc; use lasso::Spur;
use crate::ast::Rule; use crate::ast::Rule;
use crate::parse::{self, FileEntry}; use crate::parse;
use crate::utils::{Cache, mrc_derive, to_mrc_slice, one_mrc_slice}; use crate::representations::sourcefile::{FileEntry, exported_names, imports};
use crate::utils::Cache;
use super::name_resolver::NameResolver; use super::name_resolver::NameResolver;
use super::module_error::ModuleError; use super::module_error::ModuleError;
use super::prefix::prefix_expr; use super::prefix::prefix_expr;
use super::loading::{Loaded, Loader, LoadingError}; use super::loading::{Loaded, Loader, LoadingError};
use crate::parse::Import;
type ParseResult<T> = Result<T, ModuleError<LoadingError>>; type ParseResult<T> = Result<T, ModuleError<LoadingError>>;
#[derive(Debug, Clone)] #[derive(Clone)]
pub struct Module { pub struct Module {
pub rules: Vec<Rule>, pub rules: Vec<Rule>,
pub exports: Vec<String>, pub exports: Vec<Spur>,
pub references: HashSet<Mrc<[String]>> pub references: HashSet<Rc<Vec<Spur>>>
} }
pub type RuleCollectionResult = Result<Vec<super::Rule>, ModuleError<LoadingError>>; pub type RuleCollectionResult = Result<Vec<super::Rule>, ModuleError<LoadingError>>;
pub fn rule_collector<F: 'static>( pub fn rule_collector<'a, F: 'a, G: 'a, H: 'a>(
intern: &'a G, deintern: &'a H,
load_mod: F load_mod: F
) -> Cache<'static, Mrc<[String]>, RuleCollectionResult> ) -> Cache<'static, Rc<Vec<Spur>>, RuleCollectionResult>
where F: Loader where F: Loader, G: Fn(&str) -> Spur, H: Fn(Spur) -> &'a str
{ {
let load_mod_rc = RefCell::new(load_mod); let load_mod_rc = RefCell::new(load_mod);
// Map paths to a namespace with name list (folder) or module with source text (file) // Map paths to a namespace with name list (folder) or module with source text (file)
let loaded = Rc::new(Cache::new(move |path: Mrc<[String]>, _| -> ParseResult<Loaded> { let loaded = Cache::rc(move |path: Rc<Vec<Spur>>, _| -> ParseResult<Rc<Loaded>> {
load_mod_rc.borrow_mut().load(&path.iter().map(|s| s.as_str()).collect_vec()).map_err(ModuleError::Load) let load_mod = load_mod_rc.borrow_mut();
})); let spath = path.iter().cloned().map(deintern).collect_vec();
load_mod.load(&spath).map(Rc::new).map_err(ModuleError::Load)
});
// Map names to the longest prefix that points to a valid module // Map names to the longest prefix that points to a valid module
// At least one segment must be in the prefix, and the prefix must not be the whole name // At least one segment must be in the prefix, and the prefix must not be the whole name
let modname = Rc::new(Cache::new({ let modname = Cache::rc({
let loaded = loaded.clone(); let loaded = loaded.clone();
move |symbol: Mrc<[String]>, _| -> Result<Mrc<[String]>, Vec<ModuleError<LoadingError>>> { move |symbol: Rc<Vec<Spur>>, _| -> Result<Rc<Vec<Spur>>, Rc<Vec<ModuleError<LoadingError>>>> {
let mut errv: Vec<ModuleError<LoadingError>> = Vec::new(); let mut errv: Vec<ModuleError<LoadingError>> = Vec::new();
let reg_err = |e, errv: &mut Vec<ModuleError<LoadingError>>| { let reg_err = |e, errv: &mut Vec<ModuleError<LoadingError>>| {
errv.push(e); errv.push(e);
if symbol.len() == errv.len() { Err(errv.clone()) } if symbol.len() == errv.len() { Err(Rc::new(errv.clone())) }
else { Ok(()) } else { Ok(()) }
}; };
loop { loop {
let path = mrc_derive(&symbol, |s| &s[..s.len() - errv.len() - 1]); // TODO: this should not live on the heap
match loaded.try_find(&path) { let path = Rc::new(symbol.iter()
.take(symbol.len() - errv.len() - 1)
.cloned()
.collect_vec());
match loaded.find(&path).as_ref() {
Ok(imports) => match imports.as_ref() { Ok(imports) => match imports.as_ref() {
Loaded::Module(_) | Loaded::External(_) => break Ok(path), Loaded::Source(_) | Loaded::AST(_) => break Ok(path),
Loaded::Namespace(_) => reg_err(ModuleError::None, &mut errv)?
}, },
Err(err) => reg_err(err, &mut errv)? Err(err) => reg_err(err.clone(), &mut errv)?
} }
} }
} }
})); });
// Preliminarily parse a file, substitution rules and imports are valid // Preliminarily parse a file, substitution rules and imports are valid
let prelude_path = one_mrc_slice("prelude".to_string());
let preparsed = Rc::new(Cache::new({ let preparsed = Rc::new(Cache::new({
// let prelude_path = vec!["prelude".to_string()];
// let interned_prelude_path = Rc::new(
// prelude_path.iter()
// .map(|s| intern(s.as_str()))
// .collect_vec()
// );
let loaded = loaded.clone(); let loaded = loaded.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Vec<FileEntry>> {
let loaded = loaded.try_find(&path)?; let loaded = loaded.find(&path)?;
match loaded.as_ref() { match loaded.as_ref() {
Loaded::Module(source) => { Loaded::Source(source) => {
let mut entv = parse::parse(&[] as &[&str], source.as_str())?; let mut entv = parse::parse(&[] as &[&str], source.as_str(), intern)?;
if !entv.iter().any(|ent| if let FileEntry::Import(imps) = ent { // if path != interned_prelude_path {
imps.iter().any(|imp| imp.path.starts_with(&prelude_path)) // entv.push(FileEntry::Import(vec![Import{
} else {false}) && path != prelude_path { // name: None, path: prelude_path
entv.push(FileEntry::Import(vec![Import{ // }]))
name: None, path: Mrc::clone(&prelude_path) // }
}]))
}
Ok(entv) Ok(entv)
} }
Loaded::External(ast) => Ok(ast.clone()), Loaded::AST(ast) => Ok(ast.clone()),
Loaded::Namespace(_) => Err(ModuleError::None),
} }
} }
})); }));
@@ -87,13 +94,10 @@ where F: Loader
let exports = Rc::new(Cache::new({ let exports = Rc::new(Cache::new({
let loaded = loaded.clone(); let loaded = loaded.clone();
let preparsed = preparsed.clone(); let preparsed = preparsed.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<String>> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Vec<Spur>> {
let loaded = loaded.try_find(&path)?; let loaded = loaded.find(&path)?;
if let Loaded::Namespace(names) = loaded.as_ref() { let preparsed = preparsed.find(&path)?;
return Ok(names.clone()); Ok(exported_names(&preparsed)
}
let preparsed = preparsed.try_find(&path)?;
Ok(parse::exported_names(&preparsed)
.into_iter() .into_iter()
.map(|n| n[0].clone()) .map(|n| n[0].clone())
.collect()) .collect())
@@ -103,24 +107,26 @@ where F: Loader
let imports = Rc::new(Cache::new({ let imports = Rc::new(Cache::new({
let preparsed = preparsed.clone(); let preparsed = preparsed.clone();
let exports = exports.clone(); let exports = exports.clone();
move |path: Mrc<[String]>, _| -> ParseResult<HashMap<String, Mrc<[String]>>> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Rc<HashMap<Spur, Rc<Vec<Spur>>>>> {
let entv = preparsed.try_find(&path)?; let entv = preparsed.find(&path)?;
let import_entries = parse::imports(entv.iter()); let import_entries = imports(entv.iter());
let mut imported_symbols: HashMap<String, Mrc<[String]>> = HashMap::new(); let mut imported_symbols = HashMap::<Spur, Rc<Vec<Spur>>>::new();
for imp in import_entries { for imp in import_entries {
let export = exports.try_find(&imp.path)?; let export_list = exports.find(&path)?;
if let Some(ref name) = imp.name { if let Some(ref name) = imp.name {
if export.contains(name) { if export_list.contains(name) {
imported_symbols.insert(name.clone(), Mrc::clone(&imp.path)); imported_symbols.insert(name.clone(), imp.path.clone());
} else {panic!("{:?} doesn't export {}", imp.path, name)} } else {
panic!("{:?} doesn't export {}", imp.path, deintern(*name))
}
} else { } else {
for exp in export.as_ref() { for exp in export_list {
imported_symbols.insert(exp.clone(), Mrc::clone(&imp.path)); imported_symbols.insert(exp, imp.path.clone());
} }
} }
} }
println!("Imports for {:?} are {:?}", path.as_ref(), imported_symbols); // println!("Imports for {:?} are {:?}", path.as_ref(), imported_symbols);
Ok(imported_symbols) Ok(Rc::new(imported_symbols))
} }
})); }));
// Final parse, operators are correctly separated // Final parse, operators are correctly separated
@@ -128,69 +134,73 @@ where F: Loader
let preparsed = preparsed.clone(); let preparsed = preparsed.clone();
let imports = imports.clone(); let imports = imports.clone();
let loaded = loaded.clone(); let loaded = loaded.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<FileEntry>> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Vec<FileEntry>> {
let imported_ops: Vec<String> = let imported_ops: Vec<String> =
imports.try_find(&path)? imports.find(&path)?
.keys() .keys()
.filter(|s| parse::is_op(s)) .map(|s| deintern(*s).to_string())
.cloned() .filter(|s| parse::is_op(s))
.collect(); .collect();
// let parser = file_parser(&prelude, &imported_ops); let pre = preparsed.find(&path)?;
let pre = preparsed.try_find(&path)?; match loaded.find(&path)?.as_ref() {
match loaded.try_find(&path)?.as_ref() { Loaded::Source(source) => Ok(parse::reparse(
Loaded::Module(source) => Ok(parse::reparse(&imported_ops, source.as_str(), &pre)?), &imported_ops, source.as_str(), &pre, intern
Loaded::External(ast) => Ok(ast.clone()), )?),
Loaded::Namespace(_) => Err(ModuleError::None) Loaded::AST(ast) => Ok(ast.clone()),
} }
} }
})); }));
let name_resolver_rc = RefCell::new(NameResolver::new({ let name_resolver = NameResolver::new({
let modname = modname.clone(); let modname = modname.clone();
move |path| { move |path| {
Some(modname.try_find(&path).ok()?.as_ref().clone()) let modname = modname.find(&path).ok()?;
let symname = Rc::new(path[modname.len()..].to_vec());
Some((modname, symname))
} }
}, { }, {
let imports = imports.clone(); let imports = imports.clone();
move |path| { move |path| {
imports.try_find(&path).map(|f| f.as_ref().clone()) imports.find(&path).map(|f| f.as_ref().clone())
} }
})); });
// Turn parsed files into a bag of rules and a list of toplevel export names // Turn parsed files into a bag of rules and a list of toplevel export names
let resolved = Rc::new(Cache::new({ let resolved = Rc::new(Cache::new({
let parsed = parsed.clone(); let parsed = parsed.clone();
let exports = exports.clone(); let exports = exports.clone();
let imports = imports.clone(); let imports = imports.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Module> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Module> {
let mut name_resolver = name_resolver_rc.borrow_mut();
let module = Module { let module = Module {
rules: parsed.try_find(&path)? rules: parsed.find(&path)?
.iter() .iter()
.filter_map(|ent| { .filter_map(|ent| {
if let FileEntry::Rule(Rule{source, prio, target}, _) = ent { if let FileEntry::Rule(Rule{source, prio, target}, _) = ent {
Some(Rule { Some(Rule {
source: source.iter() source: Rc::new(
.map(|ex| { source.iter()
prefix_expr(ex, Mrc::clone(&path)) .map(|ex| prefix_expr(ex, &path))
}).collect(), .collect_vec()
target: target.iter().map(|ex| { ),
prefix_expr(ex, Mrc::clone(&path)) target: Rc::new(
}).collect(), target.iter()
.map(|ex| prefix_expr(ex, &path))
.collect_vec()
),
prio: *prio, prio: *prio,
}) })
} else { None } } else { None }
}) })
.map(|Rule{ source, target, prio }| Ok(super::Rule { .map(|Rule{ source, target, prio }| Ok(super::Rule {
source: to_mrc_slice(source.iter() source: Rc::new(source.iter()
.map(|ex| name_resolver.process_expression(ex)) .map(|ex| name_resolver.process_expression(ex))
.collect::<Result<Vec<_>, _>>()?), .collect::<Result<Vec<_>, _>>()?),
target: to_mrc_slice(target.iter() target: Rc::new(target.iter()
.map(|ex| name_resolver.process_expression(ex)) .map(|ex| name_resolver.process_expression(ex))
.collect::<Result<Vec<_>, _>>()?), .collect::<Result<Vec<_>, _>>()?),
prio prio
})) }))
.collect::<ParseResult<Vec<super::Rule>>>()?, .collect::<ParseResult<Vec<super::Rule>>>()?,
exports: exports.try_find(&path)?.as_ref().clone(), exports: exports.find(&path)?.clone(),
references: imports.try_find(&path)? references: imports.find(&path)?
.values().cloned().collect() .values().cloned().collect()
}; };
Ok(module) Ok(module)
@@ -198,14 +208,14 @@ where F: Loader
})); }));
Cache::new({ Cache::new({
let resolved = resolved.clone(); let resolved = resolved.clone();
move |path: Mrc<[String]>, _| -> ParseResult<Vec<super::Rule>> { move |path: Rc<Vec<Spur>>, _| -> ParseResult<Vec<super::Rule>> {
// Breadth-first search // Breadth-first search
let mut processed: HashSet<Mrc<[String]>> = HashSet::new(); let mut processed: HashSet<Rc<Vec<Spur>>> = HashSet::new();
let mut rules: Vec<super::Rule> = Vec::new(); let mut rules: Vec<super::Rule> = Vec::new();
let mut pending: VecDeque<Mrc<[String]>> = VecDeque::new(); let mut pending: VecDeque<Rc<Vec<Spur>>> = VecDeque::new();
pending.push_back(path); pending.push_back(path);
while let Some(el) = pending.pop_front() { while let Some(el) = pending.pop_front() {
let resolved = resolved.try_find(&el)?; let resolved = resolved.find(&el)?;
processed.insert(el.clone()); processed.insert(el.clone());
pending.extend( pending.extend(
resolved.references.iter() resolved.references.iter()

View File

@@ -1,133 +1,193 @@
use mappable_rc::Mrc; use lasso::RodeoResolver;
use lasso::Spur;
use itertools::Itertools; use itertools::Itertools;
use ordered_float::NotNan; use ordered_float::NotNan;
use std::{hash::Hash, intrinsics::likely}; use std::hash::Hash;
use std::fmt::Debug; use std::rc::Rc;
use crate::utils::mrc_empty_slice; use crate::utils::InternedDisplay;
use crate::utils::one_mrc_slice; use crate::utils::Stackframe;
use super::primitive::Primitive; use super::primitive::Primitive;
/// An S-expression with a type /// An S-expression with a type
#[derive(PartialEq, Eq, Hash)] #[derive(PartialEq, Eq, Hash)]
pub struct Expr(pub Clause, pub Mrc<[Clause]>); pub struct Expr(pub Clause, pub Rc<Vec<Clause>>);
impl Expr { impl Expr {
pub fn into_clause(self) -> Clause { pub fn into_clause(self) -> Clause {
if likely(self.1.len() == 0) { self.0 } if self.1.len() == 0 { self.0 }
else { Clause::S('(', one_mrc_slice(self)) } else { Clause::S('(', Rc::new(vec![self])) }
}
pub fn visit_names<F>(&self,
binds: Stackframe<Rc<Vec<Spur>>>,
cb: &mut F
) where F: FnMut(Rc<Vec<Spur>>) {
let Expr(val, typ) = self;
val.visit_names(binds.clone(), cb);
for typ in typ.as_ref() {
typ.visit_names(binds.clone(), cb);
}
} }
} }
impl Clone for Expr { impl Clone for Expr {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(self.0.clone(), Mrc::clone(&self.1)) Self(self.0.clone(), self.1.clone())
} }
} }
impl Debug for Expr { impl InternedDisplay for Expr {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self,
f: &mut std::fmt::Formatter<'_>,
rr: RodeoResolver
) -> std::fmt::Result {
let Expr(val, typ) = self; let Expr(val, typ) = self;
write!(f, "{:?}", val)?; val.fmt(f, rr)?;
for typ in typ.as_ref() { for typ in typ.as_ref() {
write!(f, ":{:?}", typ)? write!(f, ":")?;
typ.fmt(f, rr)?;
} }
Ok(()) Ok(())
} }
} }
/// An S-expression as read from a source file /// An S-expression as read from a source file
#[derive(PartialEq, Eq, Hash)] #[derive(PartialEq, Eq, Hash, Clone)]
pub enum Clause { pub enum Clause {
P(Primitive), P(Primitive),
/// A c-style name or an operator, eg. `+`, `i`, `foo::bar` /// A c-style name or an operator, eg. `+`, `i`, `foo::bar`
Name{ Name(Rc<Vec<Spur>>),
local: Option<String>, /// A parenthesized exmrc_empty_slice()pression
qualified: Mrc<[String]> /// eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}`
}, S(char, Rc<Vec<Expr>>),
/// A parenthesized expression, eg. `(print out "hello")`, `[1, 2, 3]`, `{Some(t) => t}` /// An explicit expression associated with the leftmost, outermost
S(char, Mrc<[Expr]>), /// [Clause::Auto], eg. `read @Uint`
/// An explicit expression associated with the leftmost, outermost [Clause::Auto], eg. `read @Uint` Explicit(Rc<Expr>),
Explicit(Mrc<Expr>),
/// A function expression, eg. `\x. x + 1` /// A function expression, eg. `\x. x + 1`
Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>), Lambda(Rc<Clause>, Rc<Vec<Expr>>, Rc<Vec<Expr>>),
/// A parameterized expression with type inference, eg. `@T. T -> T` /// A parameterized expression with type inference, eg. `@T. T -> T`
Auto(Option<String>, Mrc<[Expr]>, Mrc<[Expr]>), Auto(Option<Rc<Clause>>, Rc<Vec<Expr>>, Rc<Vec<Expr>>),
/// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1` /// A placeholder for macros, eg. `$name`, `...$body`, `...$lhs:1`
Placeh{ Placeh{
key: String, key: String,
/// None => matches one token /// None => matches one token
/// Some((prio, nonzero)) => /// Some((prio, nonzero)) =>
/// prio is the sizing priority for the vectorial (higher prio grows first) /// prio is the sizing priority for the vectorial
/// (higher prio grows first)
/// nonzero is whether the vectorial matches 1..n or 0..n tokens /// nonzero is whether the vectorial matches 1..n or 0..n tokens
vec: Option<(usize, bool)> vec: Option<(usize, bool)>
}, },
} }
impl Clause { impl Clause {
pub fn body(&self) -> Option<Mrc<[Expr]>> { pub fn body(&self) -> Option<Rc<Vec<Expr>>> {
match self { match self {
Self::Auto(_, _, body) | Self::Auto(_, _, body) |
Self::Lambda(_, _, body) | Self::Lambda(_, _, body) |
Self::S(_, body) => Some(Mrc::clone(body)), Self::S(_, body) => Some(body.clone()),
_ => None _ => None
} }
} }
pub fn typ(&self) -> Option<Mrc<[Expr]>> { pub fn typ(&self) -> Option<Rc<Vec<Expr>>> {
match self { match self {
Self::Auto(_, typ, _) | Self::Lambda(_, typ, _) => Some(Mrc::clone(typ)), Self::Auto(_, typ, _) | Self::Lambda(_, typ, _) => Some(typ.clone()),
_ => None _ => None
} }
} }
pub fn into_expr(self) -> Expr { pub fn into_expr(self) -> Expr {
if let Self::S('(', body) = &self { if let Self::S('(', body) = &self {
if body.len() == 1 { body[0].clone() } if body.len() == 1 { body[0].clone() }
else { Expr(self, mrc_empty_slice()) } else { Expr(self, Rc::default()) }
} else { Expr(self, mrc_empty_slice()) } } else { Expr(self, Rc::default()) }
} }
pub fn from_exprv(exprv: Mrc<[Expr]>) -> Option<Clause> { pub fn from_exprv(exprv: &[Expr]) -> Option<Clause> {
if exprv.len() == 0 { None } if exprv.len() == 0 { None }
else if exprv.len() == 1 { Some(exprv[0].clone().into_clause()) } else if exprv.len() == 1 { Some(exprv[0].clone().into_clause()) }
else { Some(Self::S('(', exprv)) } else { Some(Self::S('(', Rc::new(exprv.to_vec()))) }
} }
}
impl Clone for Clause { /// Recursively iterate through all "names" in an expression.
fn clone(&self) -> Self { /// It also finds a lot of things that aren't names, such as all
/// bound parameters. Generally speaking, this is not a very
/// sophisticated search.
pub fn visit_names<F>(&self,
binds: Stackframe<Rc<Vec<Spur>>>,
cb: &mut F
) where F: FnMut(Rc<Vec<Spur>>) {
match self { match self {
Self::S(c, b) => Self::S(*c, Mrc::clone(b)), Clause::Auto(name, typ, body) => {
Self::Auto(n, t, b) => Self::Auto( for x in typ.iter() {
n.clone(), Mrc::clone(t), Mrc::clone(b) x.visit_names(binds.clone(), cb)
), }
Self::Name { local: l, qualified: q } => Self::Name { let binds_dup = binds.clone();
local: l.clone(), qualified: Mrc::clone(q) let new_binds = if let Some(rc) = name {
if let Clause::Name(name) = rc.as_ref() {
binds_dup.push(name.clone())
} else { binds }
} else { binds };
for x in body.iter() {
x.visit_names(new_binds.clone(), cb)
}
}, },
Self::Lambda(n, t, b) => Self::Lambda( Clause::Lambda(name, typ, body) => {
n.clone(), Mrc::clone(t), Mrc::clone(b) for x in typ.iter() {
), x.visit_names(binds.clone(), cb)
Self::Placeh{key, vec} => Self::Placeh{key: key.clone(), vec: *vec}, }
Self::P(p) => Self::P(p.clone()), for x in body.iter() {
Self::Explicit(expr) => Self::Explicit(Mrc::clone(expr)) let new_binds = if let Clause::Name(name) = name.as_ref() {
binds.push(name.clone())
} else { binds };
x.visit_names(new_binds, cb)
}
},
Clause::S(_, body) => for x in body.iter() {
x.visit_names(binds.clone(), cb)
},
Clause::Name(name) => {
if binds.iter().all(|x| x != name) {
cb(name.clone())
}
}
_ => (),
} }
} }
} }
fn fmt_expr_seq(it: &mut dyn Iterator<Item = &Expr>, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt_expr_seq(
it: &mut dyn Iterator<Item = &Expr>,
f: &mut std::fmt::Formatter<'_>,
rr: RodeoResolver
) -> std::fmt::Result {
for item in Itertools::intersperse(it.map(Some), None) { match item { for item in Itertools::intersperse(it.map(Some), None) { match item {
Some(expr) => write!(f, "{:?}", expr), Some(expr) => expr.fmt(f, rr),
None => f.write_str(" "), None => f.write_str(" "),
}? } }? }
Ok(()) Ok(())
} }
impl Debug for Clause { pub fn fmt_name(
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { name: &Rc<Vec<Spur>>, f: &mut std::fmt::Formatter, rr: RodeoResolver
) -> std::fmt::Result {
for el in itertools::intersperse(
name.iter().map(|s| rr.resolve(s)),
"::"
) {
write!(f, "{}", el)?
}
Ok(())
}
impl InternedDisplay for Clause {
fn fmt(&self,
f: &mut std::fmt::Formatter<'_>,
rr: RodeoResolver
) -> std::fmt::Result {
match self { match self {
Self::P(p) => write!(f, "{:?}", p), Self::P(p) => write!(f, "{:?}", p),
Self::Name{local, qualified} => Self::Name(name) => fmt_name(name, f, rr),
if let Some(local) = local {write!(f, "{}`{}`", qualified.join("::"), local)}
else {write!(f, "{}", qualified.join("::"))},
Self::S(del, items) => { Self::S(del, items) => {
f.write_str(&del.to_string())?; f.write_str(&del.to_string())?;
fmt_expr_seq(&mut items.iter(), f)?; fmt_expr_seq(&mut items.iter(), f, rr)?;
f.write_str(match del { f.write_str(match del {
'(' => ")", '[' => "]", '{' => "}", '(' => ")", '[' => "]", '{' => "}",
_ => "CLOSING_DELIM" _ => "CLOSING_DELIM"
@@ -135,44 +195,49 @@ impl Debug for Clause {
}, },
Self::Lambda(name, argtyp, body) => { Self::Lambda(name, argtyp, body) => {
f.write_str("\\")?; f.write_str("\\")?;
f.write_str(name)?; name.fmt(f, rr)?;
f.write_str(":")?; fmt_expr_seq(&mut argtyp.iter(), f)?; f.write_str(".")?; f.write_str(":")?;
fmt_expr_seq(&mut body.iter(), f) fmt_expr_seq(&mut argtyp.iter(), f, rr)?;
f.write_str(".")?;
fmt_expr_seq(&mut body.iter(), f, rr)
}, },
Self::Auto(name, argtyp, body) => { Self::Auto(name_opt, argtyp, body) => {
f.write_str("@")?; f.write_str("@")?;
f.write_str(&name.clone().unwrap_or_default())?; if let Some(name) = name_opt { name.fmt(f, rr)? }
f.write_str(":")?; fmt_expr_seq(&mut argtyp.iter(), f)?; f.write_str(".")?; f.write_str(":")?;
fmt_expr_seq(&mut body.iter(), f) fmt_expr_seq(&mut argtyp.iter(), f, rr)?;
f.write_str(".")?;
fmt_expr_seq(&mut body.iter(), f, rr)
}, },
Self::Placeh{key, vec: None} => write!(f, "${key}"), Self::Placeh{key, vec: None} => write!(f, "${key}"),
Self::Placeh{key, vec: Some((prio, true))} => write!(f, "...${key}:{prio}"), Self::Placeh{key, vec: Some((prio, true))} =>
Self::Placeh{key, vec: Some((prio, false))} => write!(f, "..${key}:{prio}"), write!(f, "...${key}:{prio}"),
Self::Explicit(expr) => write!(f, "@{:?}", expr.as_ref()) Self::Placeh{key, vec: Some((prio, false))} =>
write!(f, "..${key}:{prio}"),
Self::Explicit(expr) => {
write!(f, "@")?;
expr.fmt(f, rr)
}
} }
} }
} }
/// A substitution rule as read from the source /// A substitution rule as read from the source
#[derive(PartialEq, Eq, Hash)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct Rule { pub struct Rule {
pub source: Mrc<[Expr]>, pub source: Rc<Vec<Expr>>,
pub prio: NotNan<f64>, pub prio: NotNan<f64>,
pub target: Mrc<[Expr]> pub target: Rc<Vec<Expr>>
} }
impl Clone for Rule { impl InternedDisplay for Rule {
fn clone(&self) -> Self { fn fmt(&self,
Self { f: &mut std::fmt::Formatter<'_>,
source: Mrc::clone(&self.source), rr: RodeoResolver
prio: self.prio, ) -> std::fmt::Result {
target: Mrc::clone(&self.target) for e in self.source.iter() { e.fmt(f, rr)?; write!(f, " ")?; }
} write!(f, "={}=>", self.prio)?;
} for e in self.target.iter() { write!(f, " ")?; e.fmt(f, rr)?; }
} Ok(())
impl Debug for Rule {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?} ={}=> {:?}", self.source, self.prio, self.target)
} }
} }

View File

@@ -1,5 +1,7 @@
use std::{rc::Rc, fmt::Display}; use std::{rc::Rc, fmt::Display};
use lasso::{Spur, RodeoResolver};
use crate::utils::Stackframe; use crate::utils::Stackframe;
use super::{ast, postmacro}; use super::{ast, postmacro};
@@ -8,27 +10,30 @@ use super::{ast, postmacro};
pub enum Error { pub enum Error {
/// `()` as a clause is meaningless in lambda calculus /// `()` as a clause is meaningless in lambda calculus
EmptyS, EmptyS,
/// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` left in the code are /// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}`
/// signs of incomplete macro execution /// left in the code are signs of incomplete macro execution
BadGroup(char), BadGroup(char),
/// `foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly specifying `foo:(bar:baz)` /// `foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly
/// is forbidden and it's also meaningless since `baz` can only ever be the kind of types /// specifying `foo:(bar:baz)` is forbidden and it's also meaningless
/// since `baz` can only ever be the kind of types
ExplicitKindOfType, ExplicitKindOfType,
/// Name never bound in an enclosing scope - indicates incomplete macro substitution /// Name never bound in an enclosing scope - indicates incomplete
Unbound(String), /// macro substitution
/// Namespaced names can never occur in the code, these are signs of incomplete macro execution Unbound(Vec<String>),
Symbol, /// Placeholders shouldn't even occur in the code during macro execution.
/// Placeholders shouldn't even occur in the code during macro execution. Something is clearly /// Something is clearly terribly wrong
/// terribly wrong
Placeholder, Placeholder,
/// It's possible to try and transform the clause `(foo:bar)` into a typed clause, /// It's possible to try and transform the clause `(foo:bar)` into a
/// however the correct value of this ast clause is a typed expression (included in the error) /// typed clause, however the correct value of this ast clause is a
/// typed expression (included in the error)
/// ///
/// [expr] handles this case, so it's only really possible to get this /// [expr] handles this case, so it's only really possible to get this
/// error if you're calling [clause] directly /// error if you're calling [clause] directly
ExprToClause(postmacro::Expr), ExprToClause(postmacro::Expr),
/// @ tokens only ever occur between a function and a parameter /// @ tokens only ever occur between a function and a parameter
NonInfixAt NonInfixAt,
/// Arguments can be either [ast::Clause::Name] or [ast::Clause::Placeh]
InvalidArg
} }
impl Display for Error { impl Display for Error {
@@ -37,44 +42,64 @@ impl Display for Error {
Error::EmptyS => write!(f, "`()` as a clause is meaningless in lambda calculus"), Error::EmptyS => write!(f, "`()` as a clause is meaningless in lambda calculus"),
Error::BadGroup(c) => write!(f, "Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` left in the code are signs of incomplete macro execution"), Error::BadGroup(c) => write!(f, "Only `(...)` may be converted to typed lambdas. `[...]` and `{{...}}` left in the code are signs of incomplete macro execution"),
Error::ExplicitKindOfType => write!(f, "`foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly specifying `foo:(bar:baz)` is forbidden and meaningless since `baz` can only ever be the kind of types"), Error::ExplicitKindOfType => write!(f, "`foo:bar:baz` will be parsed as `(foo:bar):baz`. Explicitly specifying `foo:(bar:baz)` is forbidden and meaningless since `baz` can only ever be the kind of types"),
Error::Unbound(name) => write!(f, "Name \"{name}\" never bound in an enclosing scope. This indicates incomplete macro substitution"), Error::Unbound(name) => {
Error::Symbol => write!(f, "Namespaced names not matching any macros found in the code."), write!(f, "Name \"");
for el in itertools::intersperse(
name.iter().map(String::as_str),
"::"
) { write!(f, "{}", el)? }
write!(f, "\" never bound in an enclosing scope. This indicates incomplete macro substitution")
}
Error::Placeholder => write!(f, "Placeholders shouldn't even occur in the code during macro execution, this is likely a compiler bug"), Error::Placeholder => write!(f, "Placeholders shouldn't even occur in the code during macro execution, this is likely a compiler bug"),
Error::ExprToClause(expr) => write!(f, "Attempted to transform the clause (foo:bar) into a typed clause. This is likely a compiler bug"), Error::ExprToClause(expr) => write!(f, "Attempted to transform the clause (foo:bar) into a typed clause. This is likely a compiler bug"),
Error::NonInfixAt => write!(f, "@ as a token can only ever occur between a generic and a type parameter.") Error::NonInfixAt => write!(f, "@ as a token can only ever occur between a generic and a type parameter."),
Error::InvalidArg => write!(f, "Arguments can be either Name or Placeholder nodes")
} }
} }
} }
#[derive(Clone, Copy)]
struct Init<'a>(&'a RodeoResolver);
/// Try to convert an expression from AST format to typed lambda /// Try to convert an expression from AST format to typed lambda
pub fn expr(expr: &ast::Expr) -> Result<postmacro::Expr, Error> { pub fn expr(expr: &ast::Expr, i: Init) -> Result<postmacro::Expr, Error> {
expr_rec(expr, Context::default()) expr_rec(expr, Context::new(i))
} }
/// Try and convert a single clause from AST format to typed lambda /// Try and convert a single clause from AST format to typed lambda
pub fn clause(clause: &ast::Clause) -> Result<postmacro::Clause, Error> { pub fn clause(
clause_rec(clause, Context::default()) clause: &ast::Clause, i: Init
) -> Result<postmacro::Clause, Error> {
clause_rec(clause, Context::new(i))
} }
/// Try and convert a sequence of expressions from AST format to typed lambda /// Try and convert a sequence of expressions from AST format to
pub fn exprv(exprv: &[ast::Expr]) -> Result<postmacro::Expr, Error> { /// typed lambda
exprv_rec(exprv, Context::default()) pub fn exprv(
exprv: &[ast::Expr], i: Init
) -> Result<postmacro::Expr, Error> {
exprv_rec(exprv, Context::new(i))
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct Context<'a> { struct Context<'a> {
names: Stackframe<'a, (&'a str, bool)> names: Stackframe<'a, (&'a [Spur], bool)>,
rr: &'a RodeoResolver
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
fn w_name<'b>(&'b self, name: &'b str, is_auto: bool) -> Context<'b> where 'a: 'b { fn w_name<'b>(&'b self,
Context { names: self.names.push((name, is_auto)) } name: &'b [Spur],
is_auto: bool
) -> Context<'b> where 'a: 'b {
Context {
names: self.names.push((name, is_auto)),
rr: self.rr
}
} }
}
impl Default for Context<'static> { fn new(i: Init) -> Context<'static> {
fn default() -> Self { Context { names: Stackframe::new((&[], false)), rr: i.0 }
Self { names: Stackframe::new(("", false)) }
} }
} }
@@ -138,8 +163,12 @@ fn clause_rec<'a>(
if t.len() > 0 {return Err(Error::ExplicitKindOfType)} if t.len() > 0 {return Err(Error::ExplicitKindOfType)}
else {Rc::new(vec![c])} else {Rc::new(vec![c])}
}; };
let body_ctx = if let Some(name) = no { let body_ctx = if let Some(rc) = no {
ctx.w_name(&&**name, true) match rc.as_ref() {
ast::Clause::Name(name) => ctx.w_name(&&**name, true),
ast::Clause::Placeh { .. } => return Err(Error::Placeholder),
_ => return Err(Error::InvalidArg)
}
} else {ctx}; } else {ctx};
let body = exprv_rec(b.as_ref(), body_ctx)?; let body = exprv_rec(b.as_ref(), body_ctx)?;
Ok(postmacro::Clause::Auto(typ, Rc::new(body))) Ok(postmacro::Clause::Auto(typ, Rc::new(body)))
@@ -150,14 +179,22 @@ fn clause_rec<'a>(
if t.len() > 0 {return Err(Error::ExplicitKindOfType)} if t.len() > 0 {return Err(Error::ExplicitKindOfType)}
else {Rc::new(vec![c])} else {Rc::new(vec![c])}
}; };
let body_ctx = ctx.w_name(&&**n, false); let body_ctx = match n.as_ref() {
ast::Clause::Name(name) => ctx.w_name(&&**name, true),
ast::Clause::Placeh { .. } => return Err(Error::Placeholder),
_ => return Err(Error::InvalidArg)
};
let body = exprv_rec(b.as_ref(), body_ctx)?; let body = exprv_rec(b.as_ref(), body_ctx)?;
Ok(postmacro::Clause::Lambda(typ, Rc::new(body))) Ok(postmacro::Clause::Lambda(typ, Rc::new(body)))
} }
ast::Clause::Name { local: Some(arg), .. } => { ast::Clause::Name(name) => {
let (level, (_, is_auto)) = ctx.names.iter().enumerate().find(|(_, (n, _))| n == arg) let (level, (_, is_auto)) = ctx.names.iter().enumerate()
.ok_or_else(|| Error::Unbound(arg.clone()))?; .find(|(_, (n, _))| n == &name.as_slice())
let label = if *is_auto {postmacro::Clause::AutoArg} else {postmacro::Clause::LambdaArg}; .ok_or_else(|| Error::Unbound(
name.iter().map(|s| ctx.rr.resolve(s).to_string()).collect()
))?;
let label = if *is_auto {postmacro::Clause::AutoArg}
else {postmacro::Clause::LambdaArg};
Ok(label(level)) Ok(label(level))
} }
ast::Clause::S(paren, entries) => { ast::Clause::S(paren, entries) => {
@@ -166,7 +203,6 @@ fn clause_rec<'a>(
if typ.len() == 0 {Ok(val)} if typ.len() == 0 {Ok(val)}
else {Err(Error::ExprToClause(postmacro::Expr(val, typ)))} else {Err(Error::ExprToClause(postmacro::Expr(val, typ)))}
}, },
ast::Clause::Name { local: None, .. } => Err(Error::Symbol),
ast::Clause::Placeh { .. } => Err(Error::Placeholder), ast::Clause::Placeh { .. } => Err(Error::Placeholder),
ast::Clause::Explicit(..) => Err(Error::NonInfixAt) ast::Clause::Explicit(..) => Err(Error::NonInfixAt)
} }

View File

@@ -1,10 +0,0 @@
use std::sync::atomic::AtomicU64;
use lazy_static::lazy_static;
lazy_static! {
static ref NEXT_NAME: AtomicU64 = AtomicU64::new(0);
}
pub fn get_name() -> u64 {
NEXT_NAME.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}

View File

@@ -2,11 +2,11 @@ pub mod ast;
// pub mod typed; // pub mod typed;
pub mod literal; pub mod literal;
pub mod ast_to_postmacro; pub mod ast_to_postmacro;
pub mod get_name;
pub(crate) mod interpreted; pub(crate) mod interpreted;
mod postmacro; mod postmacro;
mod primitive; mod primitive;
mod path_set; mod path_set;
pub mod sourcefile;
pub use path_set::PathSet; pub use path_set::PathSet;
pub use primitive::Primitive; pub use primitive::Primitive;
pub mod postmacro_to_interpreted; pub mod postmacro_to_interpreted;

View File

@@ -1,8 +1,6 @@
use crate::utils::string_from_charset; use crate::utils::string_from_charset;
use super::primitive::Primitive; use super::primitive::Primitive;
use super::ast_to_postmacro;
use super::ast;
use std::fmt::{Debug, Write}; use std::fmt::{Debug, Write};
use std::rc::Rc; use std::rc::Rc;
@@ -104,17 +102,3 @@ impl Debug for Clause {
self.deep_fmt(f, 0, Wrap(false, false)) self.deep_fmt(f, 0, Wrap(false, false))
} }
} }
impl TryFrom<&ast::Expr> for Expr {
type Error = ast_to_postmacro::Error;
fn try_from(value: &ast::Expr) -> Result<Self, Self::Error> {
ast_to_postmacro::expr(value)
}
}
impl TryFrom<&ast::Clause> for Clause {
type Error = ast_to_postmacro::Error;
fn try_from(value: &ast::Clause) -> Result<Self, Self::Error> {
ast_to_postmacro::clause(value)
}
}

View File

@@ -0,0 +1,76 @@
use std::rc::Rc;
use std::collections::HashSet;
use lasso::Spur;
use crate::box_chain;
use crate::utils::{Stackframe, iter::box_empty};
use crate::ast::{Rule, Expr};
#[derive(Debug, Clone)]
pub struct Import {
pub path: Rc<Vec<Spur>>,
/// If name is None, this is a wildcard import
pub name: Option<Spur>
}
/// Anything we might encounter in a file
#[derive(Clone)]
pub enum FileEntry {
Import(Vec<Import>),
Comment(String),
/// The bool indicates whether the rule is exported, that is,
/// whether tokens uniquely defined inside it should be exported
Rule(Rule, bool),
Export(Vec<Rc<Vec<Spur>>>),
LazyModule(Spur)
}
/// Collect all names that occur in an expression
fn find_all_names_expr(
expr: &Expr
) -> HashSet<Rc<Vec<Spur>>> {
let mut ret = HashSet::new();
expr.visit_names(
Stackframe::new(Rc::default()),
&mut |n| { ret.insert(n); }
);
ret
}
/// Collect all exported names (and a lot of other words) from a file
pub fn exported_names(
src: &[FileEntry]
) -> HashSet<Rc<Vec<Spur>>> {
src.iter().flat_map(|ent| match ent {
FileEntry::Rule(Rule{source, target, ..}, true) =>
box_chain!(source.iter(), target.iter()),
_ => box_empty()
}).flat_map(|e| find_all_names_expr(e))
.chain(
src.iter().filter_map(|ent| {
if let FileEntry::Export(names) = ent {
Some(names.iter())
} else {None}
}).flatten().cloned()
).chain(
src.iter().filter_map(|ent| {
if let FileEntry::LazyModule(lm) = ent {
Some(Rc::new(vec![*lm]))
} else {None}
})
).collect()
}
/// Summarize all imports from a file in a single list of qualified names
pub fn imports<'a, 'b, I>(
src: I
) -> impl Iterator<Item = &'b Import> + 'a
where I: Iterator<Item = &'b FileEntry> + 'a {
src.filter_map(|ent| match ent {
FileEntry::Import(impv) => Some(impv.iter()),
_ => None
}).flatten()
}

View File

@@ -1,10 +1,11 @@
use std::iter; use std::iter;
use std::rc::Rc;
use hashbrown::HashMap; use hashbrown::HashMap;
use mappable_rc::Mrc; use mappable_rc::Mrc;
use crate::unwrap_or; use crate::unwrap_or;
use crate::utils::{to_mrc_slice, one_mrc_slice, mrc_empty_slice, replace_first}; use crate::utils::{to_mrc_slice, one_mrc_slice, mrc_empty_slice};
use crate::utils::iter::{box_once, into_boxed_iter}; use crate::utils::iter::{box_once, into_boxed_iter};
use crate::ast::{Expr, Clause}; use crate::ast::{Expr, Clause};
use super::slice_matcher::SliceMatcherDnC; use super::slice_matcher::SliceMatcherDnC;
@@ -14,7 +15,7 @@ use super::update_first_seq_rec;
fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>) fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
-> Result<(), String> { -> Result<(), String> {
let verify_clause = |clause: &Clause, is_vec: &mut HashMap<String, bool>| -> Result<(), String> { let verify_clause = |clause: &Clause, is_vec: &mut HashMap<String, bool>| {
match clause { match clause {
Clause::Placeh{key, vec} => { Clause::Placeh{key, vec} => {
if let Some(known) = is_vec.get(key) { if let Some(known) = is_vec.get(key) {
@@ -23,16 +24,24 @@ fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
is_vec.insert(key.clone(), vec.is_some()); is_vec.insert(key.clone(), vec.is_some());
} }
} }
Clause::Auto(name, typ, body) => { Clause::Auto(name_opt, typ, body) => {
if let Some(key) = name.as_ref().and_then(|key| key.strip_prefix('$')) { if let Some(name) = name_opt.as_ref() {
if is_vec.get(key) == Some(&true) { return Err(key.to_string()) } if let Clause::Placeh { key, vec } = name.as_ref() {
if vec.is_some() || is_vec.get(key) == Some(&true) {
return Err(key.to_string())
}
is_vec.insert(key.to_owned(), false);
}
} }
typ.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?; typ.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?; body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
} }
Clause::Lambda(name, typ, body) => { Clause::Lambda(name, typ, body) => {
if let Some(key) = name.strip_prefix('$') { if let Clause::Placeh { key, vec } = name.as_ref() {
if is_vec.get(key) == Some(&true) { return Err(key.to_string()) } if vec.is_some() || is_vec.get(key) == Some(&true) {
return Err(key.to_string())
}
is_vec.insert(key.to_owned(), false);
} }
typ.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?; typ.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?; body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
@@ -52,33 +61,56 @@ fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
Ok(()) Ok(())
} }
/// Ensure that src starts and ends with a vectorial placeholder without
fn slice_to_vec(src: &mut Mrc<[Expr]>, tgt: &mut Mrc<[Expr]>) { /// modifying the meaning of the substitution rule
let prefix_expr = Expr(Clause::Placeh{key: "::prefix".to_string(), vec: Some((0, false))}, to_mrc_slice(vec![])); fn slice_to_vec(src: &mut Rc<Vec<Expr>>, tgt: &mut Rc<Vec<Expr>>) {
let postfix_expr = Expr(Clause::Placeh{key: "::postfix".to_string(), vec: Some((0, false))}, to_mrc_slice(vec![])); let prefix_expr = Expr(Clause::Placeh{
key: "::prefix".to_string(),
vec: Some((0, false))
}, Rc::default());
let postfix_expr = Expr(Clause::Placeh{
key: "::postfix".to_string(),
vec: Some((0, false))
}, Rc::default());
// Prefix or postfix to match the full vector // Prefix or postfix to match the full vector
let head_multi = matches!(src.first().expect("Src can never be empty!").0, Clause::Placeh{vec: Some(_), ..}); let head_multi = matches!(
let tail_multi = matches!(src.last().expect("Impossible branch!").0, Clause::Placeh{vec: Some(_), ..}); src.first().expect("Src can never be empty!").0,
Clause::Placeh{vec: Some(_), ..}
);
let tail_multi = matches!(
src.last().expect("Impossible branch!").0,
Clause::Placeh{vec: Some(_), ..}
);
let prefix_vec = if head_multi {vec![]} else {vec![prefix_expr]}; let prefix_vec = if head_multi {vec![]} else {vec![prefix_expr]};
let postfix_vec = if tail_multi {vec![]} else {vec![postfix_expr]}; let postfix_vec = if tail_multi {vec![]} else {vec![postfix_expr]};
*src = to_mrc_slice(prefix_vec.iter().chain(src.iter()).chain(postfix_vec.iter()).cloned().collect()); *src = Rc::new(
*tgt = to_mrc_slice(prefix_vec.iter().chain(tgt.iter()).chain(postfix_vec.iter()).cloned().collect()); prefix_vec.iter()
.chain(src.iter())
.chain(postfix_vec.iter())
.cloned().collect()
);
*tgt = Rc::new(
prefix_vec.iter()
.chain(tgt.iter())
.chain(postfix_vec.iter())
.cloned().collect()
);
} }
/// keep re-probing the input with pred until it stops matching /// keep re-probing the input with pred until it stops matching
fn update_all_seqs<F>(input: Mrc<[Expr]>, pred: &mut F) -> Option<Mrc<[Expr]>> fn update_all_seqs<F>(input: Rc<Vec<Expr>>, pred: &mut F)
where F: FnMut(Mrc<[Expr]>) -> Option<Mrc<[Expr]>> { -> Option<Rc<Vec<Expr>>>
where F: FnMut(Rc<Vec<Expr>>) -> Option<Rc<Vec<Expr>>> {
let mut tmp = update_first_seq_rec::exprv(input, pred); let mut tmp = update_first_seq_rec::exprv(input, pred);
while let Some(xv) = tmp { while let Some(xv) = tmp {
tmp = update_first_seq_rec::exprv(Mrc::clone(&xv), pred); tmp = update_first_seq_rec::exprv(xv.clone(), pred);
if tmp.is_none() {return Some(xv)} if tmp.is_none() {return Some(xv)}
} }
None None
} }
// fn write_clause_rec(state: &State, clause: &Clause) -> fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr)
-> Box<dyn Iterator<Item = Expr>> {
fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr) -> Box<dyn Iterator<Item = Expr>> {
let out_typ = tpl_typ.iter() let out_typ = tpl_typ.iter()
.flat_map(|c| write_expr_rec(state, &c.clone().into_expr())) .flat_map(|c| write_expr_rec(state, &c.clone().into_expr()))
.map(Expr::into_clause) .map(Expr::into_clause)
@@ -86,6 +118,11 @@ fn write_expr_rec(state: &State, Expr(tpl_clause, tpl_typ): &Expr) -> Box<dyn It
match tpl_clause { match tpl_clause {
Clause::Auto(name_opt, typ, body) => box_once(Expr(Clause::Auto( Clause::Auto(name_opt, typ, body) => box_once(Expr(Clause::Auto(
name_opt.as_ref().and_then(|name| { name_opt.as_ref().and_then(|name| {
if let Clause::Placeh { key, .. } = name {
match &state[key] {
Entry::NameOpt(name) => name.as_ref().map(|s| s.as_ref().to_owned())
}
}
if let Some(state_key) = name.strip_prefix('$') { if let Some(state_key) = name.strip_prefix('$') {
match &state[state_key] { match &state[state_key] {
Entry::NameOpt(name) => name.as_ref().map(|s| s.as_ref().to_owned()), Entry::NameOpt(name) => name.as_ref().map(|s| s.as_ref().to_owned()),

View File

@@ -11,14 +11,17 @@ use super::State;
use super::split_at_max_vec::split_at_max_vec; use super::split_at_max_vec::split_at_max_vec;
/// Tuple with custom cloning logic /// Tuple with custom cloning logic
#[derive(Debug, Eq, PartialEq, Hash)] // #[derive(Debug, Eq, PartialEq, Hash)]
pub struct CacheEntry<'a>(Mrc<[Expr]>, &'a SliceMatcherDnC); // pub struct CacheEntry<'a>(Mrc<[Expr]>, &'a SliceMatcherDnC);
impl<'a> Clone for CacheEntry<'a> { // impl<'a> Clone for CacheEntry<'a> {
fn clone(&self) -> Self { // fn clone(&self) -> Self {
let CacheEntry(mrc, matcher) = self; // let CacheEntry(mrc, matcher) = self;
CacheEntry(Mrc::clone(mrc), matcher) // CacheEntry(Mrc::clone(mrc), matcher)
} // }
} // }
// ^^^^
// This has been removed because the slice-based version needs no custom
// cloning logic. In the next iteration, remove the this altogether.
/// Matcher that applies a pattern to a slice via divide-and-conquer /// Matcher that applies a pattern to a slice via divide-and-conquer
@@ -66,8 +69,8 @@ impl SliceMatcherDnC {
matches!(self.clause.as_ref(), Clause::Placeh{vec: Some(..), ..}) matches!(self.clause.as_ref(), Clause::Placeh{vec: Some(..), ..})
} }
/// If clause is a name, the qualified name this can match /// If clause is a name, the qualified name this can match
pub fn clause_qual_name(&self) -> Option<Mrc<[String]>> { pub fn clause_qual_name(&self) -> Option<Rc<Vec<Spur>>> {
if let Clause::Name { qualified, .. } = self.clause.as_ref() {Some(Mrc::clone(qualified))} else {None} if let Clause::Name(name) = self.clause.as_ref() {Some(name.clone())} else {None}
} }
/// If clause is a Placeh, the key in the state the match will be stored at /// If clause is a Placeh, the key in the state the match will be stored at
pub fn state_key(&self) -> Option<&String> { pub fn state_key(&self) -> Option<&String> {
@@ -89,8 +92,8 @@ impl SliceMatcherDnC {
/// Enumerate all valid subdivisions based on the reported size constraints of self and /// Enumerate all valid subdivisions based on the reported size constraints of self and
/// the two subranges /// the two subranges
pub fn valid_subdivisions(&self, pub fn valid_subdivisions<'a>(&'a self,
range: Mrc<[Expr]> range: &'a [Expr]
) -> impl Iterator<Item = (Mrc<[Expr]>, Mrc<[Expr]>, Mrc<[Expr]>)> { ) -> impl Iterator<Item = (Mrc<[Expr]>, Mrc<[Expr]>, Mrc<[Expr]>)> {
let own_max = unwrap_or!(self.own_max_size(range.len()); return box_empty()); let own_max = unwrap_or!(self.own_max_size(range.len()); return box_empty());
let own_min = self.own_min_size(); let own_min = self.own_min_size();
@@ -196,7 +199,7 @@ impl SliceMatcherDnC {
if !range.is_empty() {None} if !range.is_empty() {None}
else {Some(State::new())} else {Some(State::new())}
}, },
Some(m) => cache.try_find(&CacheEntry(range, m)).map(|s| s.as_ref().to_owned()) Some(m) => cache.find(&CacheEntry(range, m))
} }
} }

View File

@@ -1,15 +1,16 @@
use std::{ops::{Add, Index}, rc::Rc, fmt::Debug}; use std::{ops::{Add, Index}, rc::Rc, fmt::Debug};
use hashbrown::HashMap; use hashbrown::HashMap;
use lasso::Spur;
use crate::ast::Expr; use crate::ast::Expr;
#[derive(Debug, PartialEq, Eq)] #[derive(PartialEq, Eq)]
pub enum Entry { pub enum Entry {
Vec(Rc<Vec<Expr>>), Vec(Rc<Vec<Expr>>),
Scalar(Rc<Expr>), Scalar(Rc<Expr>),
Name(Rc<String>), Name(Rc<Vec<Spur>>),
NameOpt(Option<Rc<String>>) NameOpt(Option<Rc<Vec<Spur>>>)
} }
/// A bucket of indexed expression fragments. Addition may fail if there's a conflict. /// A bucket of indexed expression fragments. Addition may fail if there's a conflict.
@@ -55,33 +56,32 @@ impl State {
} }
Some(self) Some(self)
} }
pub fn insert_name<S1, S2>(mut self, k: &S1, v: &S2) -> Option<Self> pub fn insert_name<S1>(mut self, k: &S1, v: &[Spur]) -> Option<Self>
where where
S1: AsRef<str> + ToString + ?Sized, S1: AsRef<str> + ToString + ?Sized
S2: AsRef<str> + ToString + ?Sized
{ {
if let Some(old) = self.0.get(k.as_ref()) { if let Some(old) = self.0.get(k.as_ref()) {
if let Entry::Name(val) = old { if let Entry::Name(val) = old {
if val.as_str() != v.as_ref() {return None} if val.as_ref() != v.as_ref() {return None}
} else {return None} } else {return None}
} else { } else {
self.0.insert(k.to_string(), Entry::Name(Rc::new(v.to_string()))); self.0.insert(k.to_string(), Entry::Name(Rc::new(v.to_vec())));
} }
Some(self) Some(self)
} }
pub fn insert_name_opt<S1, S2>(mut self, k: &S1, v: Option<&S2>) -> Option<Self> pub fn insert_name_opt<S1, S2>(mut self, k: &S1, v: Option<&[Spur]>)
where -> Option<Self>
S1: AsRef<str> + ToString + ?Sized, where S1: AsRef<str> + ToString + ?Sized
S2: AsRef<str> + ToString + ?Sized
{ {
if let Some(old) = self.0.get(k.as_ref()) { if let Some(old) = self.0.get(k.as_ref()) {
if let Entry::NameOpt(val) = old { if let Entry::NameOpt(val) = old {
if val.as_ref().map(|s| s.as_ref().as_str()) != v.map(|s| s.as_ref()) { if val.as_ref().map(|s| s.as_ref().as_slice()) != v {
return None return None
} }
} else {return None} } else {return None}
} else { } else {
self.0.insert(k.to_string(), Entry::NameOpt(v.map(|s| Rc::new(s.to_string())))); let data = v.map(|s| Rc::new(s.to_vec()));
self.0.insert(k.to_string(), Entry::NameOpt(data));
} }
Some(self) Some(self)
} }
@@ -139,9 +139,3 @@ impl IntoIterator for State {
self.0.into_iter() self.0.into_iter()
} }
} }
impl Debug for State {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}

View File

@@ -1,53 +1,54 @@
use mappable_rc::Mrc; use std::rc::Rc;
use crate::{ast::{Expr, Clause}, utils::{replace_first, to_mrc_slice}}; use crate::utils::replace_first;
use crate::ast::{Expr, Clause};
/// Traverse the tree, calling pred on every sibling list until it returns some vec /// Traverse the tree, calling pred on every sibling list until it returns
/// then replace the sibling list with that vec and return true /// some vec then replace the sibling list with that vec and return true
/// return false if pred never returned some /// return false if pred never returned some
pub fn exprv<F>(input: Mrc<[Expr]>, pred: &mut F) -> Option<Mrc<[Expr]>> pub fn exprv<F>(input: Rc<Vec<Expr>>, pred: &mut F) -> Option<Rc<Vec<Expr>>>
where F: FnMut(Mrc<[Expr]>) -> Option<Mrc<[Expr]>> { where F: FnMut(Rc<Vec<Expr>>) -> Option<Rc<Vec<Expr>>> {
if let o@Some(_) = pred(Mrc::clone(&input)) {return o} if let o@Some(_) = pred(input.clone()) {return o}
replace_first(input.as_ref(), |ex| expr(ex, pred)) replace_first(input.as_ref(), |ex| expr(ex, pred))
.map(|i| to_mrc_slice(i.collect())) .map(|i| Rc::new(i.collect()))
} }
pub fn expr<F>(Expr(cls, typ): &Expr, pred: &mut F) -> Option<Expr> pub fn expr<F>(Expr(cls, typ): &Expr, pred: &mut F) -> Option<Expr>
where F: FnMut(Mrc<[Expr]>) -> Option<Mrc<[Expr]>> { where F: FnMut(Rc<Vec<Expr>>) -> Option<Rc<Vec<Expr>>> {
if let Some(t) = clausev(Mrc::clone(typ), pred) {return Some(Expr(cls.clone(), t))} if let Some(t) = clausev(typ.clone(), pred) {return Some(Expr(cls.clone(), t))}
if let Some(c) = clause(cls, pred) {return Some(Expr(c, Mrc::clone(typ)))} if let Some(c) = clause(cls, pred) {return Some(Expr(c, typ.clone()))}
None None
} }
pub fn clausev<F>(input: Mrc<[Clause]>, pred: &mut F) -> Option<Mrc<[Clause]>> pub fn clausev<F>(input: Rc<Vec<Clause>>, pred: &mut F) -> Option<Rc<Vec<Clause>>>
where F: FnMut(Mrc<[Expr]>) -> Option<Mrc<[Expr]>> { where F: FnMut(Rc<Vec<Expr>>) -> Option<Rc<Vec<Expr>>> {
replace_first(input.as_ref(), |c| clause(c, pred)) replace_first(input.as_ref(), |c| clause(c, pred))
.map(|i| to_mrc_slice(i.collect())) .map(|i| Rc::new(i.collect()))
} }
pub fn clause<F>(c: &Clause, pred: &mut F) -> Option<Clause> pub fn clause<F>(c: &Clause, pred: &mut F) -> Option<Clause>
where F: FnMut(Mrc<[Expr]>) -> Option<Mrc<[Expr]>> { where F: FnMut(Rc<Vec<Expr>>) -> Option<Rc<Vec<Expr>>> {
match c { match c {
Clause::P(_) | Clause::Placeh {..} | Clause::Name {..} => None, Clause::P(_) | Clause::Placeh {..} | Clause::Name {..} => None,
Clause::Lambda(n, typ, body) => { Clause::Lambda(n, typ, body) => {
if let Some(b) = exprv(Mrc::clone(body), pred) { if let Some(b) = exprv(body.clone(), pred) {
return Some(Clause::Lambda(n.clone(), Mrc::clone(typ), b)) return Some(Clause::Lambda(n.clone(), typ.clone(), b))
} }
if let Some(t) = exprv(Mrc::clone(typ), pred) { if let Some(t) = exprv(typ.clone(), pred) {
return Some(Clause::Lambda(n.clone(), t, Mrc::clone(body))) return Some(Clause::Lambda(n.clone(), t, body.clone()))
} }
None None
} }
Clause::Auto(n, typ, body) => { Clause::Auto(n, typ, body) => {
if let Some(b) = exprv(Mrc::clone(body), pred) { if let Some(b) = exprv(body.clone(), pred) {
return Some(Clause::Auto(n.clone(), Mrc::clone(typ), b)) return Some(Clause::Auto(n.clone(), typ.clone(), b))
} }
if let Some(t) = exprv(Mrc::clone(typ), pred) { if let Some(t) = exprv(typ.clone(), pred) {
return Some(Clause::Auto(n.clone(), t, Mrc::clone(body))) return Some(Clause::Auto(n.clone(), t, body.clone()))
} }
None None
} }
Clause::S(c, body) => Some(Clause::S(*c, exprv(Mrc::clone(body), pred)?)), Clause::S(c, body) => Some(Clause::S(*c, exprv(body.clone(), pred)?)),
Clause::Explicit(t) => Some(Clause::Explicit(Mrc::new(expr(t, pred)?))) Clause::Explicit(t) => Some(Clause::Explicit(Rc::new(expr(t, pred)?)))
} }
} }

View File

@@ -5,6 +5,8 @@ use std::hash::Hash;
use crate::unwrap_or; use crate::unwrap_or;
use crate::utils::BoxedIter; use crate::utils::BoxedIter;
// TODO: move to own crate
/// Two-stage breadth-first search; /// Two-stage breadth-first search;
/// Instead of enumerating neighbors before returning a node, it puts visited but not yet /// Instead of enumerating neighbors before returning a node, it puts visited but not yet
/// enumerated nodes in a separate queue and only enumerates them to refill the queue of children /// enumerated nodes in a separate queue and only enumerates them to refill the queue of children

View File

@@ -1,96 +1,47 @@
use std::{hash::Hash, cell::RefCell, rc::Rc}; use std::{hash::Hash, cell::RefCell, rc::Rc};
use hashbrown::HashMap; use hashbrown::HashMap;
use mappable_rc::Mrc;
/// Convenience trait for overriding Mrc's strange cloning logic // TODO: make this a crate
pub trait MyClone {
fn my_clone(&self) -> Self;
}
impl<T> MyClone for T where T: Clone {
default fn my_clone(&self) -> Self { self.clone() }
}
impl<T: ?Sized> MyClone for Rc<T> {
fn my_clone(&self) -> Self { Rc::clone(self) }
}
impl<T: ?Sized> MyClone for Mrc<T> {
fn my_clone(&self) -> Self { Mrc::clone(self) }
}
/// Cache the return values of an effectless closure in a hashmap /// Cache the return values of an effectless closure in a hashmap
/// Inspired by the closure_cacher crate. /// Inspired by the closure_cacher crate.
pub struct Cache<'a, I, O: 'static> { pub struct Cache<'a, I, O: 'static> {
store: RefCell<HashMap<I, Mrc<O>>>, store: RefCell<HashMap<I, O>>,
closure: Box<dyn Fn (I, &Self) -> Mrc<O> + 'a> closure: Box<dyn Fn (I, &Self) -> O + 'a>
} }
impl<'a, I, O> Cache<'a, I, O> where impl<'a, I, O> Cache<'a, I, O> where
I: Eq + Hash + MyClone I: Eq + Hash + Clone, O: Clone
{ {
pub fn new<F: 'a>(closure: F) -> Self where F: Fn(I, &Self) -> O { pub fn new<F: 'a>(closure: F) -> Self where F: Fn(I, &Self) -> O {
Self::new_raw(move |o, s| Mrc::new(closure(o, s)))
}
/// Take an Mrc<O> closure rather than an O closure
/// Used internally to derive caches from other systems working with Mrc-s
pub fn new_raw<F: 'a>(closure: F) -> Self where F: Fn(I, &Self) -> Mrc<O> {
Self { Self {
store: RefCell::new(HashMap::new()), store: RefCell::new(HashMap::new()),
closure: Box::new(closure) closure: Box::new(closure)
} }
} }
pub fn rc<F: 'a>(closure: F) -> Rc<Self> where F: Fn(I, &Self) -> O {
Rc::new(Self::new(closure))
}
/// Produce and cache a result by cloning I if necessary /// Produce and cache a result by cloning I if necessary
pub fn find(&self, i: &I) -> Mrc<O> { pub fn find(&self, i: &I) -> O {
let closure = &self.closure; let closure = &self.closure;
if let Some(v) = self.store.borrow().get(i) { if let Some(v) = self.store.borrow().get(i) {
return Mrc::clone(v) return v.clone()
} }
// In the moment of invocation the refcell is on immutable // In the moment of invocation the refcell is on immutable
// this is important for recursive calculations // this is important for recursive calculations
let result = closure(i.my_clone(), self); let result = closure(i.clone(), self);
let mut store = self.store.borrow_mut(); let mut store = self.store.borrow_mut();
Mrc::clone(store.raw_entry_mut().from_key(i) store.raw_entry_mut().from_key(i)
.or_insert_with(|| (i.my_clone(), result)).1) .or_insert_with(|| (i.clone(), result)).1.clone()
} }
#[allow(dead_code)] #[allow(dead_code)]
/// Return the result if it has already been computed /// Return the result if it has already been computed
pub fn known(&self, i: &I) -> Option<Mrc<O>> { pub fn known(&self, i: &I) -> Option<O> {
let store = self.store.borrow(); let store = self.store.borrow();
store.get(i).map(Mrc::clone) store.get(i).cloned()
}
#[allow(dead_code)]
/// Forget the output for the given input
pub fn drop(&self, i: &I) -> bool {
self.store.borrow_mut().remove(i).is_some()
}
}
impl<'a, I, O, E> Cache<'a, I, Result<O, E>> where
I: Eq + Hash + MyClone,
// O: Clone,
E: Clone
{
/// Sink the ref from a Result into the Ok value, such that cloning only occurs on the sad path
/// but the return value can be short-circuited
pub fn try_find(&self, i: &I) -> Result<Mrc<O>, E> {
let ent = self.find(i);
Mrc::try_map(ent, |t| t.as_ref().ok())
.map_err(|res| Result::as_ref(&res).err().unwrap().to_owned())
}
}
impl<'a, I, O> Cache<'a, I, Option<O>> where
I: Eq + Hash + MyClone,
// O: Clone
{
#[allow(dead_code)]
/// Sink the ref from an Option into the Some value such that the return value can be
/// short-circuited
pub fn try_find(&self, i: &I) -> Option<Mrc<O>> where I: Clone {
let ent = self.find(i);
Mrc::try_map(ent, |o| o.as_ref()).ok()
} }
} }

View File

@@ -0,0 +1,19 @@
use std::fmt::Display;
use lasso::RodeoResolver;
pub trait InternedDisplay {
fn fmt(&self,
f: &mut std::fmt::Formatter<'_>,
rr: RodeoResolver
) -> std::fmt::Result;
}
impl<T> InternedDisplay for T where T: Display {
fn fmt(&self,
f: &mut std::fmt::Formatter<'_>,
rr: RodeoResolver
) -> std::fmt::Result {
<Self as Display>::fmt(&self, f)
}
}

View File

@@ -1,27 +0,0 @@
// use std::{collections::HashSet, hash::Hash};
// use hashbrown::HashMap;
// #[derive(Copy, Clone)]
// pub struct Interned<'a, T> {
// interner: &'a Interner<T>,
// data: &'a T,
// }
// impl<'a, T: Eq> Eq for Interned<'a, T> {}
// impl<'a, T: PartialEq> PartialEq for Interned<'a, T> {
// fn eq(&self, other: &Self) -> bool {
// if (self.interner as *const _) == (other.interner as *const _) {
// (self.data as *const _) == (other.data as *const _)
// } else {self.data == other.data}
// }
// }
// pub struct Interner<T> {
// data: HashSet<T>,
// hash_cache: HashMap<>
// }
// impl Interner<T> {
// }

View File

@@ -1,6 +1,6 @@
/// Utility functions to get rid of explicit casts to BoxedIter which are tedious /// Utility functions to get rid of explicit casts to BoxedIter which are tedious
use std::iter; use std::{iter, mem};
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>; pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>; pub type BoxedIterIter<'a, T> = BoxedIter<'a, BoxedIter<'a, T>>;
@@ -30,6 +30,7 @@ where
{ {
Box::new(i.flatten()) Box::new(i.flatten())
} }
pub fn into_boxed_iter<'a, T: 'a>(t: T) -> BoxedIter<'a, <T as IntoIterator>::Item> pub fn into_boxed_iter<'a, T: 'a>(t: T) -> BoxedIter<'a, <T as IntoIterator>::Item>
where T: IntoIterator { where T: IntoIterator {
Box::new(t.into_iter()) Box::new(t.into_iter())

View File

@@ -1,27 +0,0 @@
use std::mem;
// use itertools::Itertools;
/// Merge two sorted iterators into a sorted iterator.
pub fn merge_sorted<T, I, J, F, O>(mut i: I, mut j: J, mut f: F) -> impl Iterator<Item = T>
where
I: Iterator<Item = T>, J: Iterator<Item = T>,
F: FnMut(&T) -> O, O: Ord,
{
let mut i_item: Option<T> = None;
let mut j_item: Option<T> = None;
std::iter::from_fn(move || {
match (&mut i_item, &mut j_item) {
(&mut None, &mut None) => None,
(&mut None, j_item @ &mut Some(_)) => Some((j_item, None)),
(i_item @ &mut Some(_), &mut None) => Some((i_item, i.next())),
(Some(i_val), Some(j_val)) => Some(
if f(i_val) < f(j_val) {
(&mut i_item, i.next())
} else {
(&mut j_item, j.next())
}
)
}.and_then(|(dest, value)| mem::replace(dest, value))
})
}

View File

@@ -1,7 +1,8 @@
mod cache; mod cache;
pub mod translate; pub mod translate;
mod replace_first; mod replace_first;
mod interner; mod interned_display;
pub use interned_display::InternedDisplay;
// mod visitor; // mod visitor;
pub use replace_first::replace_first; pub use replace_first::replace_first;
pub use cache::Cache; pub use cache::Cache;
@@ -9,16 +10,13 @@ mod substack;
pub use substack::Stackframe; pub use substack::Stackframe;
mod side; mod side;
pub use side::Side; pub use side::Side;
mod merge_sorted;
pub use merge_sorted::merge_sorted;
mod unwrap_or; mod unwrap_or;
pub mod iter; pub mod iter;
pub use iter::BoxedIter; pub use iter::BoxedIter;
mod bfs; mod bfs;
mod unless_let;
mod string_from_charset; mod string_from_charset;
pub use string_from_charset::string_from_charset; pub use string_from_charset::string_from_charset;
mod for_loop; mod xloop;
mod protomap; mod protomap;
pub use protomap::ProtoMap; pub use protomap::ProtoMap;
mod product2; mod product2;

View File

@@ -8,7 +8,9 @@ use super::Side;
pub enum Product2<T> { pub enum Product2<T> {
Left, Left,
Right, Right,
#[allow(unused)]
Either, Either,
#[allow(unused)]
New(T) New(T)
} }
impl<T> Product2<T> { impl<T> Product2<T> {

View File

@@ -2,16 +2,18 @@ use std::{iter, ops::{Index, Add}, borrow::Borrow};
use smallvec::SmallVec; use smallvec::SmallVec;
const INLINE_ENTRIES: usize = 2; // TODO: make this a crate alongside substack
/// Linked-array-list of key-value pairs. /// Linked-array-list of key-value pairs.
/// Lookup and modification is O(n + cachemiss * n / m) /// - Lookup and modification is O(n + cachemiss * n / m)
/// Can be extended by reference in O(m) < O(n) /// - Can be extended by reference in O(m) < O(n)
/// ///
/// The number of elements stored inline in a stackframe is 2 by default, which is enough for most /// The number of elements stored inline in a stackframe is 2 by default,
/// recursive algorithms. The cost of overruns is a heap allocation and subsequent heap indirections, /// which is enough for most recursive algorithms.
/// plus wasted stack space which is likely wasted L1 as well. The cost of underruns is wasted stack /// - The cost of overruns is a heap allocation and subsequent
/// space. /// heap indirections, plus wasted stack space which is likely wasted L1
/// as well.
/// - The cost of underruns is wasted stack space.
pub struct ProtoMap<'a, K, V, const STACK_COUNT: usize = 2> { pub struct ProtoMap<'a, K, V, const STACK_COUNT: usize = 2> {
entries: SmallVec<[(K, Option<V>); STACK_COUNT]>, entries: SmallVec<[(K, Option<V>); STACK_COUNT]>,
prototype: Option<&'a ProtoMap<'a, K, V, STACK_COUNT>> prototype: Option<&'a ProtoMap<'a, K, V, STACK_COUNT>>

View File

@@ -1,6 +1,9 @@
use std::iter; use std::iter;
pub fn replace_first<'a, T, F>(slice: &'a [T], mut f: F) -> Option<impl Iterator<Item = T> + 'a> /// Iterate over a sequence with the first element the function returns
/// Some() for updated, but only if there is such an element.
pub fn replace_first<'a, T, F>(slice: &'a [T], mut f: F)
-> Option<impl Iterator<Item = T> + 'a>
where T: Clone, F: FnMut(&T) -> Option<T> { where T: Clone, F: FnMut(&T) -> Option<T> {
for i in 0..slice.len() { for i in 0..slice.len() {
if let Some(new) = f(&slice[i]) { if let Some(new) = f(&slice[i]) {

View File

@@ -1,5 +1,7 @@
use std::fmt::Display; use std::fmt::Display;
/// A primitive for encoding the two sides Left and Right. While booleans
/// are technically usable for this purpose, they're less descriptive.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Side {Left, Right} pub enum Side {Left, Right}
@@ -32,8 +34,11 @@ impl Side {
pub fn crop<'a, T>(&self, margin: usize, slice: &'a [T]) -> &'a [T] { pub fn crop<'a, T>(&self, margin: usize, slice: &'a [T]) -> &'a [T] {
self.opposite().slice(slice.len() - margin, slice) self.opposite().slice(slice.len() - margin, slice)
} }
/// ignore N elements from this end and M elements from the other end of a slice /// ignore N elements from this end and M elements from the other end
pub fn crop_both<'a, T>(&self, margin: usize, opposite: usize, slice: &'a [T]) -> &'a [T] { /// of a slice
pub fn crop_both<'a, T>(&self,
margin: usize, opposite: usize, slice: &'a [T]
) -> &'a [T] {
self.crop(margin, self.opposite().crop(opposite, slice)) self.crop(margin, self.opposite().crop(opposite, slice))
} }
/// Pick this side from a pair of things /// Pick this side from a pair of things

View File

@@ -9,6 +9,8 @@ fn string_from_charset_rec(val: u64, digits: &str) -> String {
prefix prefix
} }
/// Generate alphabetized names from numbers using a set of permitted
/// characters
pub fn string_from_charset(val: u64, digits: &str) -> String { pub fn string_from_charset(val: u64, digits: &str) -> String {
string_from_charset_rec(val + 1, digits) string_from_charset_rec(val + 1, digits)
} }

View File

@@ -1,8 +1,10 @@
use std::fmt::Debug; use std::fmt::Debug;
/// Implement a FILO stack that lives on the regular call stack as a linked list. // TODO: extract to crate
/// Mainly useful to detect loops in recursive algorithms where the recursion isn't
/// deep enough to warrant a heap-allocated set /// A FILO stack that lives on the regular call stack as a linked list.
/// Mainly useful to detect loops in recursive algorithms where
/// the recursion isn't deep enough to warrant a heap-allocated set.
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub struct Stackframe<'a, T> { pub struct Stackframe<'a, T> {
pub item: T, pub item: T,
@@ -33,6 +35,7 @@ impl<'a, T: 'a> Stackframe<'a, T> {
len: self.len + 1 len: self.len + 1
} }
} }
#[allow(unused)]
pub fn opush(prev: Option<&'a Self>, item: T) -> Self { pub fn opush(prev: Option<&'a Self>, item: T) -> Self {
Self { Self {
item, item,
@@ -40,15 +43,19 @@ impl<'a, T: 'a> Stackframe<'a, T> {
len: prev.map_or(1, |s| s.len) len: prev.map_or(1, |s| s.len)
} }
} }
#[allow(unused)]
pub fn len(&self) -> usize { self.len } pub fn len(&self) -> usize { self.len }
#[allow(unused)]
pub fn pop(&self, count: usize) -> Option<&Self> { pub fn pop(&self, count: usize) -> Option<&Self> {
if count == 0 {Some(self)} if count == 0 {Some(self)}
else {self.prev.expect("Index out of range").pop(count - 1)} else {self.prev.expect("Index out of range").pop(count - 1)}
} }
#[allow(unused)]
pub fn opop(cur: Option<&Self>, count: usize) -> Option<&Self> { pub fn opop(cur: Option<&Self>, count: usize) -> Option<&Self> {
if count == 0 {cur} if count == 0 {cur}
else {Self::opop(cur.expect("Index out of range").prev, count - 1)} else {Self::opop(cur.expect("Index out of range").prev, count - 1)}
} }
#[allow(unused)]
pub fn o_into_iter(curr: Option<&Self>) -> StackframeIterator<T> { pub fn o_into_iter(curr: Option<&Self>) -> StackframeIterator<T> {
StackframeIterator { curr } StackframeIterator { curr }
} }
@@ -66,7 +73,9 @@ pub struct StackframeIterator<'a, T> {
} }
impl<'a, T> StackframeIterator<'a, T> { impl<'a, T> StackframeIterator<'a, T> {
pub fn first_some<U, F: Fn(&T) -> Option<U>>(&mut self, f: F) -> Option<U> { #[allow(unused)]
pub fn first_some<U, F>(&mut self, f: F) -> Option<U>
where F: Fn(&T) -> Option<U> {
while let Some(x) = self.next() { while let Some(x) = self.next() {
if let Some(result) = f(x) { if let Some(result) = f(x) {
return Some(result) return Some(result)

View File

@@ -1,5 +1,10 @@
use std::mem; use std::mem;
// TODO: extract to crate
#[allow(unused)]
/// Map over a `&mut` with a mapper function that takes ownership of
/// the value
pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) { pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) {
unsafe { unsafe {
let mut acc = mem::MaybeUninit::<T>::uninit().assume_init(); let mut acc = mem::MaybeUninit::<T>::uninit().assume_init();
@@ -10,6 +15,8 @@ pub fn translate<T, F: FnOnce(T) -> T>(data: &mut T, f: F) {
} }
} }
/// Map over a `&mut` with a mapper function that takes ownership of
/// the value and also produces some unrelated data.
pub fn process<T, U, F: FnOnce(T) -> (T, U)>(data: &mut T, f: F) -> U { pub fn process<T, U, F: FnOnce(T) -> (T, U)>(data: &mut T, f: F) -> U {
unsafe { unsafe {
let mut acc = mem::MaybeUninit::<T>::uninit().assume_init(); let mut acc = mem::MaybeUninit::<T>::uninit().assume_init();

View File

@@ -1,6 +0,0 @@
#[macro_export]
macro_rules! unless_let {
($m:pat_param = $expr:tt) => {
if let $m = $expr {} else
}
}

View File

@@ -1,3 +1,6 @@
/// A macro version of [Option::unwrap_or_else] which supports
/// flow control statements such as `return` and `break` in the "else"
/// branch.
#[macro_export] #[macro_export]
macro_rules! unwrap_or { macro_rules! unwrap_or {
($m:expr; $fail:expr) => { ($m:expr; $fail:expr) => {

View File

@@ -1,18 +0,0 @@
pub trait Visit<T> {
type Return;
fn visit(&self, target: T) -> Return;
}
pub trait ImpureVisit<T> {
type Shard;
type Return;
fn impure_visit(&self, target: T) -> (Shard, Return);
fn merge(&mut self, s: Shard);
}
pub struct OverlayVisitor<VBase, VOver>(VBase, VOver);
impl<VBase, VOver, T, R> Visitor<T> for OverlayVisitor<VBase, VOver>
where VBase: Visitor<T, Return = Option<R>>, VOver: Visitor<T, Return = Option<R>> {
}

View File

@@ -48,7 +48,8 @@
/// to these as well just like the others. In all cases the exit expression is optional, its /// to these as well just like the others. In all cases the exit expression is optional, its
/// default value is `()`. /// default value is `()`.
/// ///
/// **todo** find a valid use case for While let for a demo /// TODO: find a valid use case for While let for a demo
/// TODO: break out into crate
#[macro_export] #[macro_export]
macro_rules! xloop { macro_rules! xloop {
(for $p:pat in $it:expr; $body:stmt) => { (for $p:pat in $it:expr; $body:stmt) => {