backup commit
This commit is contained in:
7
Cargo.lock
generated
7
Cargo.lock
generated
@@ -160,6 +160,7 @@ dependencies = [
|
|||||||
"itertools",
|
"itertools",
|
||||||
"mappable-rc",
|
"mappable-rc",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
|
"smallvec",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -196,6 +197,12 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
|
|||||||
@@ -13,3 +13,4 @@ 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 = "1.10.0"
|
||||||
|
|||||||
@@ -1,23 +1,46 @@
|
|||||||
-- import std::io::(println, out) -- imports
|
opaque := \T. T
|
||||||
|
|
||||||
-- single word rule (alias)
|
--[ Typeclass definition (also just a type) ]--
|
||||||
greet =1=> (\name. printf out "Hello {}!\n" [name])
|
define Add $L:type $R:type $O:type as $L -> $R -> $O
|
||||||
|
-- HKTC
|
||||||
-- multi-word exported rule
|
define Mappable $C:(type -> type) as @T. @U. (T -> U) -> $C T -> $C U
|
||||||
export ;> $a =200=> (greet $a)
|
-- Dependency on existing typeclass
|
||||||
|
define Zippable $C:(type -> type) as @:Mappable $C. (
|
||||||
reeee := \$a.b
|
@T. @U. @V. (T -> U -> V) -> $C T -> $C U -> $C V
|
||||||
|
|
||||||
-- single-word exported rule
|
|
||||||
export main := (
|
|
||||||
print "What is your name?" >>
|
|
||||||
readln >>= \name.
|
|
||||||
greet name
|
|
||||||
)
|
)
|
||||||
|
define Default $T:type as $T
|
||||||
|
-- Is the intersection of typeclasses an operation we need?
|
||||||
|
|
||||||
export < $a ...$rest /> := (createElement (tok_to_str $a) [(props_carriage ...$rest)])
|
--[ Type definition ]--
|
||||||
export (props_carriage $key = $value) := (tok_to_str $key) => $value
|
define Cons $elem:type as loop \r. Option (Pair T $elem)
|
||||||
|
nil := @T. from @(Cons T) none
|
||||||
|
cons := @T. \el:T. (
|
||||||
|
generalise @(Cons T)
|
||||||
|
|> (\list. some t[el, into list])
|
||||||
|
|> categorise @(Cons T)
|
||||||
|
)
|
||||||
|
export map := @T. @U. \f:T -> U. (
|
||||||
|
generalise @(Cons T)
|
||||||
|
|> loop ( \recurse. \option.
|
||||||
|
map option \pair. t[f (fst pair), recurse (snd pair)]
|
||||||
|
)
|
||||||
|
|> categorise @(Cons U)
|
||||||
|
)
|
||||||
|
-- Universal typeclass implementation; no parameters, no overrides, no name for overriding
|
||||||
|
impl Mappable Cons via map
|
||||||
|
-- Blanket typeclass implementation; parametric, may override, must have name for overriding
|
||||||
|
impl (@T. Add (Cons T) (Cons T) (Cons T)) by concatenation over elementwiseAddition via concat
|
||||||
|
|
||||||
-- The broadest trait definition in existence
|
-- Scratchpad
|
||||||
Foo := (Bar Baz)
|
|
||||||
-- default anyFoo = @T. @impl:(T (Bar Baz)). impl:(T Foo)
|
filterBadWords := @C:type -> type. @:Mappable C. \strings:C String. (
|
||||||
|
map strings \s. if intersects badWords (slice " " s) then none else some s
|
||||||
|
):(C (Option String))
|
||||||
|
|
||||||
|
-- /Scratchpad
|
||||||
|
|
||||||
|
main := \x. foo @bar x
|
||||||
|
|
||||||
|
foo := @util. \x. util x
|
||||||
|
|
||||||
|
export opaque := \T. atom
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
export ::(main, foo)
|
|
||||||
|
|
||||||
main := [foo, bar, baz, quz]
|
|
||||||
|
|
||||||
foo := steamed hams
|
|
||||||
|
|
||||||
[...$data] := (cons_start ...$data cons_carriage(none))
|
|
||||||
|
|
||||||
[] := none
|
|
||||||
|
|
||||||
...$prefix:1 , ...$item cons_carriage(
|
|
||||||
$tail
|
|
||||||
) := ...$prefix cons_carriage(
|
|
||||||
(some (cons (...$item) $tail))
|
|
||||||
)
|
|
||||||
|
|
||||||
cons_start ...$item cons_carriage($tail) := some (cons (...$item) $tail)
|
|
||||||
|
|
||||||
18
notes.md
18
notes.md
@@ -1,18 +0,0 @@
|
|||||||
# Anatomy of a code file
|
|
||||||
|
|
||||||
```orchid
|
|
||||||
import std::io::(println, out) -- imports
|
|
||||||
|
|
||||||
-- single word substitution (alias)
|
|
||||||
greet == \name. printf out "Hello {}!\n" [name]
|
|
||||||
|
|
||||||
-- multi-word exported substitution with nonzero priority
|
|
||||||
export (...$pre ;) $a ...$post) =200=> (...$pre (greet $a) ...$post)
|
|
||||||
|
|
||||||
-- single-word exported substitution
|
|
||||||
export main == (
|
|
||||||
print "What is your name? >>
|
|
||||||
readln >>= \name.
|
|
||||||
greet name
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|||||||
35
notes/type_system/definitions.md
Normal file
35
notes/type_system/definitions.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
## Type definitions
|
||||||
|
|
||||||
|
```orc
|
||||||
|
define Cons as \T:type. loop \r. Option (Pair T r)
|
||||||
|
```
|
||||||
|
|
||||||
|
Results in
|
||||||
|
- (Cons Int) is not assignable to @T. Option T
|
||||||
|
- An instance of (Cons Int) can be constructed with `categorise @(Cons Int) (some (pair 1 none))`
|
||||||
|
but the type parameter can also be inferred from the expected return type
|
||||||
|
- An instance of (Cons Int) can be deconstructed with `generalise @(Cons Int) numbers`
|
||||||
|
but the type parameter can also be inferred from the argument
|
||||||
|
|
||||||
|
These inference rules are never reversible
|
||||||
|
|
||||||
|
```orc
|
||||||
|
categorise :: @T:type. (definition T) -> T
|
||||||
|
generalise :: @T:type. T -> (definition T)
|
||||||
|
definition :: type -> type -- opaque function
|
||||||
|
```
|
||||||
|
|
||||||
|
## Unification
|
||||||
|
|
||||||
|
The following must unify:
|
||||||
|
|
||||||
|
```orc
|
||||||
|
@T. @:Add T T T. Mult Int T T
|
||||||
|
Mult Int (Cons Int) (Cons Int)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Impls for types
|
||||||
|
|
||||||
|
Impls for types are generally not a good idea as autos with types like Int can
|
||||||
|
often be used in dependent typing to represent eg. an index into a type-level conslist to be
|
||||||
|
deduced by the compiler, and impls take precedence over resolution by unification.
|
||||||
0
notes/type_system/impls.md
Normal file
0
notes/type_system/impls.md
Normal file
27
notes/type_system/unification.md
Normal file
27
notes/type_system/unification.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Steps of validating typed lambda
|
||||||
|
|
||||||
|
- Identify all expressions that describe the type of the same expression
|
||||||
|
- enqueue evaluation steps for each of them and put them in a unification group
|
||||||
|
- evaluation step refers to previous step, complete expression tree
|
||||||
|
- unification **succeeds** if either
|
||||||
|
- the trees are syntactically identical in any two steps between the targets
|
||||||
|
- unification succeeds for all substeps:
|
||||||
|
- try to find an ancestor step that provably produces the same value as any lambda in this
|
||||||
|
step (for example, by syntactic equality)
|
||||||
|
- if found, substitute it with the recursive normal form of the lambda
|
||||||
|
- recursive normal form is `Apply(Y, \r.[body referencing r on point of recursion])`
|
||||||
|
- find all `Apply(\x.##, ##)` nodes in the tree and execute them
|
||||||
|
- unification **fails** if a member of the concrete tree differs (only outermost steps add to
|
||||||
|
the concrete tree so it belongs to the group and not the resolution) or no substeps are found
|
||||||
|
for a resolution step _(failure: unresolved higher kinded type)_
|
||||||
|
- if neither of these conclusions is reached within a set number of steps, unification is
|
||||||
|
**indeterminate** which is also a failure but suggests that the same value-level operations
|
||||||
|
may be unifiable with better types.
|
||||||
|
|
||||||
|
The time complexity of this operation is O(h no) >= O(2^n). For this reason, a two-stage limit
|
||||||
|
is recommended: one for the recursion depth which is replicable and static, and another,
|
||||||
|
configurable, time-based limit enforced by a separate thread.
|
||||||
|
|
||||||
|
How does this interact with impls?
|
||||||
|
Idea: excluding value-universe code from type-universe execution.
|
||||||
|
Digression: Is it possible to recurse across universes?
|
||||||
102
src/executor/foreign.rs
Normal file
102
src/executor/foreign.rs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
use std::any::Any;
|
||||||
|
use std::fmt::{Display, Debug};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
|
use crate::representations::typed::{Expr, Clause};
|
||||||
|
|
||||||
|
pub trait ExternError: Display {}
|
||||||
|
|
||||||
|
/// Represents an externally defined function from the perspective of the executor
|
||||||
|
/// Since Orchid lacks basic numerical operations, these are also external functions.
|
||||||
|
#[derive(Eq)]
|
||||||
|
pub struct ExternFn {
|
||||||
|
name: String, param: Mrc<Expr>, rttype: Mrc<Expr>,
|
||||||
|
function: Mrc<dyn Fn(Clause) -> Result<Clause, Mrc<dyn ExternError>>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternFn {
|
||||||
|
pub fn new<F: 'static + Fn(Clause) -> Result<Clause, Mrc<dyn ExternError>>>(
|
||||||
|
name: String, param: Mrc<Expr>, rttype: Mrc<Expr>, f: F
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
name, param, rttype,
|
||||||
|
function: Mrc::map(Mrc::new(f), |f| {
|
||||||
|
f as &dyn Fn(Clause) -> Result<Clause, Mrc<dyn ExternError>>
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn name(&self) -> &str {&self.name}
|
||||||
|
fn apply(&self, arg: Clause) -> Result<Clause, Mrc<dyn ExternError>> {(self.function)(arg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for ExternFn { fn clone(&self) -> Self { Self {
|
||||||
|
name: self.name.clone(),
|
||||||
|
param: Mrc::clone(&self.param),
|
||||||
|
rttype: Mrc::clone(&self.rttype),
|
||||||
|
function: Mrc::clone(&self.function)
|
||||||
|
}}}
|
||||||
|
impl PartialEq for ExternFn { fn eq(&self, other: &Self) -> bool { self.name() == other.name() }}
|
||||||
|
impl Hash for ExternFn {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.name.hash(state) }
|
||||||
|
}
|
||||||
|
impl Debug for ExternFn {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "##EXTERN[{}]:{:?} -> {:?}##", self.name(), self.param, self.rttype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Atomic: Any + Debug where Self: 'static {
|
||||||
|
fn as_any(&self) -> &dyn Any;
|
||||||
|
fn definitely_eq(&self, _other: &dyn Any) -> bool;
|
||||||
|
fn hash(&self, hasher: &mut dyn std::hash::Hasher);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents a unit of information from the perspective of the executor. This may be
|
||||||
|
/// something like a file descriptor which functions can operate on, but it can also be
|
||||||
|
/// information in the universe of types or kinds such as the type of signed integers or
|
||||||
|
/// the kind of types. Ad absurdum it can also be just a number, although Literal is
|
||||||
|
/// preferable for types it's defined on.
|
||||||
|
#[derive(Eq)]
|
||||||
|
pub struct Atom {
|
||||||
|
typ: Mrc<Expr>,
|
||||||
|
data: Mrc<dyn Atomic>
|
||||||
|
}
|
||||||
|
impl Atom {
|
||||||
|
pub fn new<T: 'static + Atomic>(data: T, typ: Mrc<Expr>) -> Self { Self{
|
||||||
|
typ,
|
||||||
|
data: Mrc::map(Mrc::new(data), |d| d as &dyn Atomic)
|
||||||
|
} }
|
||||||
|
pub fn data(&self) -> &dyn Atomic { self.data.as_ref() as &dyn Atomic }
|
||||||
|
pub fn try_cast<T: Atomic>(&self) -> Result<&T, ()> {
|
||||||
|
self.data().as_any().downcast_ref().ok_or(())
|
||||||
|
}
|
||||||
|
pub fn is<T: 'static>(&self) -> bool { self.data().as_any().is::<T>() }
|
||||||
|
pub fn cast<T: 'static>(&self) -> &T {
|
||||||
|
self.data().as_any().downcast_ref().expect("Type mismatch on Atom::cast")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Atom {
|
||||||
|
fn clone(&self) -> Self { Self {
|
||||||
|
typ: Mrc::clone(&self.typ),
|
||||||
|
data: Mrc::clone(&self.data)
|
||||||
|
} }
|
||||||
|
}
|
||||||
|
impl Hash for Atom {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.data.hash(state);
|
||||||
|
self.typ.hash(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Debug for Atom {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "##ATOM[{:?}]:{:?}##", self.data(), self.typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl PartialEq for Atom {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.data().definitely_eq(other.data().as_any())
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/executor/mod.rs
Normal file
3
src/executor/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
mod foreign;
|
||||||
|
pub use foreign::ExternFn;
|
||||||
|
pub use foreign::Atom;
|
||||||
86
src/main.rs
86
src/main.rs
@@ -1,13 +1,18 @@
|
|||||||
#![feature(specialization)]
|
#![feature(specialization)]
|
||||||
|
|
||||||
use std::{env::current_dir, process::exit};
|
use std::env::current_dir;
|
||||||
|
|
||||||
|
mod executor;
|
||||||
mod parse;
|
mod parse;
|
||||||
mod project;
|
mod project;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod expression;
|
mod representations;
|
||||||
mod rule;
|
mod rule;
|
||||||
use expression::{Expr, Clause};
|
mod types;
|
||||||
|
use file_loader::LoadingError;
|
||||||
|
pub use representations::ast;
|
||||||
|
use ast::{Expr, Clause};
|
||||||
|
use representations::typed as t;
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
use project::{rule_collector, Loaded, file_loader};
|
use project::{rule_collector, Loaded, file_loader};
|
||||||
use rule::Repository;
|
use rule::Repository;
|
||||||
@@ -34,37 +39,60 @@ export (match_sequence $lhs) >>= (match_sequence $rhs) =100=> (bind ($lhs) ($rhs
|
|||||||
fn initial_tree() -> Mrc<[Expr]> {
|
fn initial_tree() -> Mrc<[Expr]> {
|
||||||
to_mrc_slice(vec![Expr(Clause::Name {
|
to_mrc_slice(vec![Expr(Clause::Name {
|
||||||
local: None,
|
local: None,
|
||||||
qualified: to_mrc_slice(vec!["main".to_string(), "main".to_string()])
|
qualified: literal(&["main", "main"])
|
||||||
}, None)])
|
}, to_mrc_slice(vec![]))])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
#[allow(unused)]
|
||||||
|
fn typed_notation_debug() {
|
||||||
|
let t = t::Clause::Auto(None,
|
||||||
|
t::Clause::Lambda(Some(Mrc::new(t::Clause::Argument(0))),
|
||||||
|
t::Clause::Lambda(Some(Mrc::new(t::Clause::Argument(1))),
|
||||||
|
t::Clause::Argument(1).wrap_t(t::Clause::Argument(2))
|
||||||
|
).wrap()
|
||||||
|
).wrap()
|
||||||
|
).wrap();
|
||||||
|
let f = t::Clause::Auto(None,
|
||||||
|
t::Clause::Lambda(Some(Mrc::new(t::Clause::Argument(0))),
|
||||||
|
t::Clause::Lambda(Some(Mrc::new(t::Clause::Argument(1))),
|
||||||
|
t::Clause::Argument(0).wrap_t(t::Clause::Argument(2))
|
||||||
|
).wrap()
|
||||||
|
).wrap()
|
||||||
|
).wrap();
|
||||||
|
println!("{:?}", t::Clause::Apply(t::Clause::Apply(Mrc::clone(&t), t).wrap(), f))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn load_project() {
|
||||||
let cwd = current_dir().unwrap();
|
let cwd = current_dir().unwrap();
|
||||||
let collect_rules = rule_collector(move |n| {
|
let collect_rules = rule_collector(move |n| -> Result<Loaded, LoadingError> {
|
||||||
if n == literal(&["prelude"]) { Ok(Loaded::Module(PRELUDE.to_string())) }
|
if n == literal(&["prelude"]) { Ok(Loaded::Module(PRELUDE.to_string())) }
|
||||||
else { file_loader(cwd.clone())(n) }
|
else { file_loader(cwd.clone())(n) }
|
||||||
}, vliteral(&["...", ">>", ">>=", "[", "]", ",", "=", "=>"]));
|
}, vliteral(&["...", ">>", ">>=", "[", "]", ",", "=", "=>"]));
|
||||||
match collect_rules.try_find(&literal(&["main"])) {
|
let rules = match collect_rules.try_find(&literal(&["main"])) {
|
||||||
Ok(rules) => {
|
Ok(rules) => rules,
|
||||||
let mut tree = initial_tree();
|
Err(err) => panic!("{:#?}", err)
|
||||||
println!("Start processing {tree:?}");
|
};
|
||||||
let repo = Repository::new(rules.as_ref().to_owned());
|
let mut tree = initial_tree();
|
||||||
println!("Ruleset: {repo:?}");
|
println!("Start processing {tree:?}");
|
||||||
let mut i = 0; loop {
|
let repo = Repository::new(rules.as_ref().to_owned());
|
||||||
if 10 <= i {break} else {i += 1}
|
println!("Ruleset: {repo:?}");
|
||||||
match repo.step(Mrc::clone(&tree)) {
|
xloop!(let mut i = 0; i < 10; i += 1; {
|
||||||
Ok(Some(phase)) => {
|
match repo.step(Mrc::clone(&tree)) {
|
||||||
tree = phase;
|
Ok(Some(phase)) => {
|
||||||
println!("Step {i}: {tree:?}")
|
println!("Step {i}: {phase:?}");
|
||||||
},
|
tree = phase;
|
||||||
Ok(None) => exit(0),
|
},
|
||||||
Err(e) => {
|
Ok(None) => {
|
||||||
eprintln!("Rule error: {e:?}");
|
println!("Execution complete");
|
||||||
exit(0)
|
break
|
||||||
}
|
},
|
||||||
}
|
Err(e) => panic!("Rule error: {e:?}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(err) => println!("{:#?}", err)
|
}; println!("Macro execution didn't halt"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// lambda_notation_debug();
|
||||||
|
load_project();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/// Produces parsers for tokenized sequences of enum types:
|
||||||
|
/// ```rs
|
||||||
|
/// enum_parser!(Foo::Bar | "Some error!") // Parses Foo::Bar(T) into T
|
||||||
|
/// enum_parser!(Foo::Bar) // same as above but with the default error "Expected Foo::Bar"
|
||||||
|
/// enum_parser!(Foo >> Quz; Bar, Baz) // Parses Foo::Bar(T) into Quz::Bar(T) and Foo::Baz(U) into Quz::Baz(U)
|
||||||
|
/// ```
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! enum_parser {
|
macro_rules! enum_parser {
|
||||||
($p:path | $m:tt) => {
|
($p:path | $m:tt) => {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
use chumsky::{self, prelude::*, Parser};
|
use chumsky::{self, prelude::*, Parser};
|
||||||
use mappable_rc::Mrc;
|
|
||||||
use crate::enum_parser;
|
use crate::enum_parser;
|
||||||
use crate::expression::{Clause, Expr, Literal};
|
use crate::representations::{Literal, ast::{Clause, Expr}};
|
||||||
use crate::utils::to_mrc_slice;
|
use crate::utils::{to_mrc_slice, one_mrc_slice};
|
||||||
|
|
||||||
use super::lexer::Lexeme;
|
use super::lexer::Lexeme;
|
||||||
|
|
||||||
|
/// Parses any number of expr wrapped in (), [] or {}
|
||||||
fn sexpr_parser<P>(
|
fn sexpr_parser<P>(
|
||||||
expr: P
|
expr: P
|
||||||
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
||||||
@@ -13,6 +13,8 @@ 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, to_mrc_slice(b)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses `\name.body` or `\name:type.body` where name is any valid name and type and body are
|
||||||
|
/// both expressions. Comments are allowed and ignored everywhere in between the tokens
|
||||||
fn lambda_parser<P>(
|
fn lambda_parser<P>(
|
||||||
expr: P
|
expr: P
|
||||||
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
||||||
@@ -37,6 +39,7 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// see [lambda_parser] but `@` instead of `\` and the name is optional
|
||||||
fn auto_parser<P>(
|
fn auto_parser<P>(
|
||||||
expr: P
|
expr: P
|
||||||
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
) -> impl Parser<Lexeme, Clause, Error = Simple<Lexeme>> + Clone
|
||||||
@@ -50,17 +53,22 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
|
|||||||
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
|
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
|
||||||
.ignore_then(expr.clone().repeated())
|
.ignore_then(expr.clone().repeated())
|
||||||
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
|
.then_ignore(enum_parser!(Lexeme::Comment).repeated())
|
||||||
|
.or_not().map(Option::unwrap_or_default)
|
||||||
)
|
)
|
||||||
.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), s| if name.is_none() && typ.is_empty() {
|
.try_map(|((name, typ), body): ((Option<String>, Vec<Expr>), Vec<Expr>), s| {
|
||||||
Err(Simple::custom(s, "Auto without name or type has no effect"))
|
if name.is_none() && typ.is_empty() {
|
||||||
} else {
|
Err(Simple::custom(s, "Auto without name or type has no effect"))
|
||||||
Ok(Clause::Auto(name, to_mrc_slice(typ), to_mrc_slice(body)))
|
} else {
|
||||||
|
Ok(Clause::Auto(name, to_mrc_slice(typ), to_mrc_slice(body)))
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a sequence of names separated by :: <br/>
|
||||||
|
/// Comments are allowed and ignored in between
|
||||||
fn name_parser() -> impl Parser<Lexeme, Vec<String>, Error = Simple<Lexeme>> + Clone {
|
fn name_parser() -> impl Parser<Lexeme, Vec<String>, Error = Simple<Lexeme>> + Clone {
|
||||||
enum_parser!(Lexeme::Name).separated_by(
|
enum_parser!(Lexeme::Name).separated_by(
|
||||||
enum_parser!(Lexeme::Comment).repeated()
|
enum_parser!(Lexeme::Comment).repeated()
|
||||||
@@ -69,6 +77,7 @@ fn name_parser() -> impl Parser<Lexeme, Vec<String>, Error = Simple<Lexeme>> + C
|
|||||||
).at_least(1)
|
).at_least(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse any legal argument name starting with a `$`
|
||||||
fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> + Clone {
|
fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> + Clone {
|
||||||
enum_parser!(Lexeme::Name).try_map(|name, span| {
|
enum_parser!(Lexeme::Name).try_map(|name, span| {
|
||||||
name.strip_prefix('$').map(&str::to_string)
|
name.strip_prefix('$').map(&str::to_string)
|
||||||
@@ -76,7 +85,7 @@ fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> +
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse an expression without a type annotation
|
/// Parse an expression
|
||||||
pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> {
|
pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> {
|
||||||
recursive(|expr| {
|
recursive(|expr| {
|
||||||
let clause =
|
let clause =
|
||||||
@@ -102,12 +111,17 @@ pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> {
|
|||||||
}),
|
}),
|
||||||
sexpr_parser(expr.clone()),
|
sexpr_parser(expr.clone()),
|
||||||
lambda_parser(expr.clone()),
|
lambda_parser(expr.clone()),
|
||||||
auto_parser(expr.clone())
|
auto_parser(expr.clone()),
|
||||||
|
just(Lexeme::At).to(Clause::Name {
|
||||||
|
local: Some("@".to_string()),
|
||||||
|
qualified: one_mrc_slice("@".to_string())
|
||||||
|
})
|
||||||
))).then_ignore(enum_parser!(Lexeme::Comment).repeated());
|
))).then_ignore(enum_parser!(Lexeme::Comment).repeated());
|
||||||
clause.clone().then(
|
clause.clone().then(
|
||||||
just(Lexeme::Type)
|
just(Lexeme::Type)
|
||||||
.ignore_then(expr.clone()).or_not()
|
.ignore_then(clause.clone())
|
||||||
|
.repeated()
|
||||||
)
|
)
|
||||||
.map(|(val, typ)| Expr(val, typ.map(Mrc::new)))
|
.map(|(val, typ)| Expr(val, to_mrc_slice(typ)))
|
||||||
}).labelled("Expression")
|
}).labelled("Expression")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ use super::lexer::Lexeme;
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Import {
|
pub struct Import {
|
||||||
pub path: Mrc<[String]>,
|
pub path: Mrc<[String]>,
|
||||||
|
/// If name is None, this is a wildcard import
|
||||||
pub name: Option<String>
|
pub name: Option<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ fn op_parser<'a, T: AsRef<str> + Clone>(ops: &[T]) -> BoxedParser<'a, char, Stri
|
|||||||
/// - `"` and `'` are read as primitives and would never match.
|
/// - `"` and `'` are read as primitives and would never match.
|
||||||
/// - `(` and `)` are strictly balanced and this must remain the case for automation and streaming.
|
/// - `(` and `)` are strictly balanced and this must remain the case for automation and streaming.
|
||||||
/// - `.` is the discriminator for parametrics.
|
/// - `.` is the discriminator for parametrics.
|
||||||
|
/// - ',' is always a standalone single operator, so it can never be part of a name
|
||||||
///
|
///
|
||||||
/// FIXME: `@name` without a dot should be parsed correctly for overrides. Could be an operator but
|
/// FIXME: `@name` without a dot should be parsed correctly for overrides. Could be an operator but
|
||||||
/// then parametrics should take precedence, which might break stuff. investigate.
|
/// then parametrics should take precedence, which might break stuff. investigate.
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use chumsky::{prelude::{Simple, end}, Stream, Parser};
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{expression::Rule, parse::lexer::LexedText};
|
use crate::{ast::Rule, parse::lexer::LexedText};
|
||||||
|
|
||||||
use super::{Lexeme, FileEntry, lexer, line_parser, LexerEntry};
|
use super::{Lexeme, FileEntry, lexer, line_parser, LexerEntry};
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::{enum_parser, box_chain};
|
use crate::{enum_parser, box_chain};
|
||||||
use crate::expression::{Expr, Clause, Rule};
|
use crate::ast::{Expr, Clause, Rule};
|
||||||
use crate::utils::to_mrc_slice;
|
use crate::utils::to_mrc_slice;
|
||||||
use crate::utils::Stackframe;
|
use crate::utils::Stackframe;
|
||||||
use crate::utils::iter::box_empty;
|
use crate::utils::iter::box_empty;
|
||||||
@@ -74,8 +74,8 @@ fn visit_all_names_expr_recur<'a, F>(
|
|||||||
) where F: FnMut(&'a [String]) {
|
) where F: FnMut(&'a [String]) {
|
||||||
let Expr(val, typ) = expr;
|
let Expr(val, typ) = expr;
|
||||||
visit_all_names_clause_recur(val, binds.clone(), cb);
|
visit_all_names_clause_recur(val, binds.clone(), cb);
|
||||||
if let Some(t) = typ {
|
for typ in typ.as_ref() {
|
||||||
visit_all_names_expr_recur(t, binds, cb)
|
visit_all_names_clause_recur(typ, binds.clone(), cb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ mod name_resolver;
|
|||||||
mod loaded;
|
mod loaded;
|
||||||
pub use loaded::Loaded;
|
pub use loaded::Loaded;
|
||||||
mod module_error;
|
mod module_error;
|
||||||
mod file_loader;
|
pub mod file_loader;
|
||||||
pub use file_loader::file_loader;
|
pub use file_loader::file_loader;
|
||||||
use crate::expression::Rule;
|
use crate::ast::Rule;
|
||||||
@@ -4,7 +4,7 @@ use thiserror::Error;
|
|||||||
|
|
||||||
use crate::utils::{Stackframe, to_mrc_slice};
|
use crate::utils::{Stackframe, to_mrc_slice};
|
||||||
|
|
||||||
use crate::expression::{Expr, Clause};
|
use crate::ast::{Expr, Clause};
|
||||||
|
|
||||||
type ImportMap = HashMap<String, Mrc<[String]>>;
|
type ImportMap = HashMap<String, Mrc<[String]>>;
|
||||||
|
|
||||||
@@ -50,14 +50,16 @@ where
|
|||||||
symbol: Mrc<[String]>,
|
symbol: Mrc<[String]>,
|
||||||
import_path: Stackframe<Mrc<[String]>>
|
import_path: Stackframe<Mrc<[String]>>
|
||||||
) -> Result<Mrc<[String]>, ResolutionError<E>> {
|
) -> Result<Mrc<[String]>, ResolutionError<E>> {
|
||||||
if let Some(cached) = self.cache.get(&symbol) { return cached.as_ref().map_err(|e| e.clone()).map(Mrc::clone) }
|
if let Some(cached) = self.cache.get(&symbol) {
|
||||||
|
return cached.as_ref().map_err(|e| e.clone()).map(Mrc::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 = (self.get_modname)(Mrc::clone(&symbol)).ok_or_else(|| {
|
||||||
ResolutionError::NoModule(Mrc::clone(&symbol))
|
ResolutionError::NoModule(Mrc::clone(&symbol))
|
||||||
})?;
|
})?;
|
||||||
let name = &symbol[path.len()..];
|
let name = &symbol[path.len()..];
|
||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
panic!("Something's really broken\n{:?}", import_path)
|
panic!("get_modname matched all to module and nothing to name in {:?}", import_path)
|
||||||
}
|
}
|
||||||
let imports = (self.get_imports)(Mrc::clone(&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]) {
|
||||||
@@ -110,7 +112,7 @@ 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)?,
|
||||||
self.process_exprmrcopt_rec(typ)?
|
typ.iter().map(|t| self.process_clause_rec(t)).collect::<Result<_, _>>()?
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
use crate::{expression::{Expr, Clause}, utils::collect_to_mrc};
|
use crate::{ast::{Expr, Clause}, utils::{collect_to_mrc, to_mrc_slice}};
|
||||||
|
|
||||||
/// 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
|
||||||
|
|
||||||
@@ -36,6 +36,6 @@ fn prefix_clause(
|
|||||||
pub fn prefix_expr(Expr(clause, typ): &Expr, namespace: Mrc<[String]>) -> Expr {
|
pub fn prefix_expr(Expr(clause, typ): &Expr, namespace: Mrc<[String]>) -> Expr {
|
||||||
Expr(
|
Expr(
|
||||||
prefix_clause(clause, Mrc::clone(&namespace)),
|
prefix_clause(clause, Mrc::clone(&namespace)),
|
||||||
typ.as_ref().map(|e| Mrc::new(prefix_expr(e, namespace)))
|
to_mrc_slice(typ.iter().map(|e| prefix_clause(e, Mrc::clone(&namespace))).collect())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
use crate::expression::Rule;
|
use crate::ast::Rule;
|
||||||
use crate::parse::{self, FileEntry};
|
use crate::parse::{self, FileEntry};
|
||||||
use crate::utils::{Cache, mrc_derive, to_mrc_slice};
|
use crate::utils::{Cache, mrc_derive, to_mrc_slice};
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ where
|
|||||||
(load_mod_rc.borrow_mut())(path).map_err(ModuleError::Load)
|
(load_mod_rc.borrow_mut())(path).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
|
||||||
let modname = Rc::new(Cache::new({
|
let modname = Rc::new(Cache::new({
|
||||||
let loaded = Rc::clone(&loaded);
|
let loaded = Rc::clone(&loaded);
|
||||||
move |symbol: Mrc<[String]>, _| -> Result<Mrc<[String]>, Vec<ModuleError<ELoad>>> {
|
move |symbol: Mrc<[String]>, _| -> Result<Mrc<[String]>, Vec<ModuleError<ELoad>>> {
|
||||||
@@ -50,7 +51,7 @@ where
|
|||||||
else { Ok(()) }
|
else { Ok(()) }
|
||||||
};
|
};
|
||||||
loop {
|
loop {
|
||||||
let path = mrc_derive(&symbol, |s| &s[..s.len() - errv.len()]);
|
let path = mrc_derive(&symbol, |s| &s[..s.len() - errv.len() - 1]);
|
||||||
match loaded.try_find(&path) {
|
match loaded.try_find(&path) {
|
||||||
Ok(imports) => match imports.as_ref() {
|
Ok(imports) => match imports.as_ref() {
|
||||||
Loaded::Module(_) => break Ok(path),
|
Loaded::Module(_) => break Ok(path),
|
||||||
|
|||||||
@@ -1,35 +1,19 @@
|
|||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
|
use std::hash::Hash;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
use crate::executor::{ExternFn, Atom};
|
||||||
|
|
||||||
/// An exact value
|
use super::Literal;
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
|
||||||
pub enum Literal {
|
|
||||||
Num(NotNan<f64>),
|
|
||||||
Int(u64),
|
|
||||||
Char(char),
|
|
||||||
Str(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Literal {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
Self::Num(arg0) => write!(f, "{:?}", arg0),
|
|
||||||
Self::Int(arg0) => write!(f, "{:?}", arg0),
|
|
||||||
Self::Char(arg0) => write!(f, "{:?}", arg0),
|
|
||||||
Self::Str(arg0) => write!(f, "{:?}", arg0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 Option<Mrc<Expr>>);
|
pub struct Expr(pub Clause, pub Mrc<[Clause]>);
|
||||||
|
|
||||||
impl Clone for Expr {
|
impl Clone for Expr {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self(self.0.clone(), self.1.as_ref().map(Mrc::clone))
|
Self(self.0.clone(), Mrc::clone(&self.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,8 +21,10 @@ impl Debug for Expr {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
let Expr(val, typ) = self;
|
let Expr(val, typ) = self;
|
||||||
write!(f, "{:?}", val)?;
|
write!(f, "{:?}", val)?;
|
||||||
if let Some(typ) = typ { write!(f, "{:?}", typ) }
|
for typ in typ.as_ref() {
|
||||||
else { Ok(()) }
|
write!(f, ":{:?}", typ)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +39,14 @@ pub enum Clause {
|
|||||||
S(char, Mrc<[Expr]>),
|
S(char, Mrc<[Expr]>),
|
||||||
Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>),
|
Lambda(String, Mrc<[Expr]>, Mrc<[Expr]>),
|
||||||
Auto(Option<String>, Mrc<[Expr]>, Mrc<[Expr]>),
|
Auto(Option<String>, Mrc<[Expr]>, Mrc<[Expr]>),
|
||||||
/// Second parameter:
|
ExternFn(ExternFn),
|
||||||
/// None => matches one token
|
Atom(Atom),
|
||||||
/// Some((prio, nonzero)) =>
|
|
||||||
/// prio is the sizing priority for the vectorial (higher prio grows first)
|
|
||||||
/// nonzero is whether the vectorial matches 1..n or 0..n tokens
|
|
||||||
Placeh{
|
Placeh{
|
||||||
key: String,
|
key: String,
|
||||||
|
/// None => matches one token
|
||||||
|
/// Some((prio, nonzero)) =>
|
||||||
|
/// prio is the sizing priority for the vectorial (higher prio grows first)
|
||||||
|
/// nonzero is whether the vectorial matches 1..n or 0..n tokens
|
||||||
vec: Option<(usize, bool)>
|
vec: Option<(usize, bool)>
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -94,7 +81,9 @@ impl Clone for Clause {
|
|||||||
n.clone(), Mrc::clone(t), Mrc::clone(b)
|
n.clone(), Mrc::clone(t), Mrc::clone(b)
|
||||||
),
|
),
|
||||||
Clause::Placeh{key, vec} => Clause::Placeh{key: key.clone(), vec: *vec},
|
Clause::Placeh{key, vec} => Clause::Placeh{key: key.clone(), vec: *vec},
|
||||||
Clause::Literal(l) => Clause::Literal(l.clone())
|
Clause::Literal(l) => Clause::Literal(l.clone()),
|
||||||
|
Clause::ExternFn(nc) => Clause::ExternFn(nc.clone()),
|
||||||
|
Clause::Atom(a) => Clause::Atom(a.clone())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,7 +125,9 @@ impl Debug for Clause {
|
|||||||
},
|
},
|
||||||
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))} => write!(f, "...${key}:{prio}"),
|
||||||
Self::Placeh{key, vec: Some((prio, false))} => write!(f, "..${key}:{prio}")
|
Self::Placeh{key, vec: Some((prio, false))} => write!(f, "..${key}:{prio}"),
|
||||||
|
Self::ExternFn(nc) => write!(f, "{nc:?}"),
|
||||||
|
Self::Atom(a) => write!(f, "{a:?}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
120
src/representations/ast_to_typed.rs
Normal file
120
src/representations/ast_to_typed.rs
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
|
use crate::utils::{Stackframe, to_mrc_slice};
|
||||||
|
|
||||||
|
use super::{ast, typed};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Error {
|
||||||
|
/// `()` as a clause is meaningless in lambda calculus
|
||||||
|
EmptyS,
|
||||||
|
/// Only `(...)` may be converted to typed lambdas. `[...]` and `{...}` left in the code are
|
||||||
|
/// signs of incomplete macro execution
|
||||||
|
BadGroup(char),
|
||||||
|
/// `foo:bar:baz` will be parsed as `(foo:bar):baz`, explicitly specifying `foo:(bar:baz)`
|
||||||
|
/// is forbidden and it's also meaningless since `baz` can only ever be the kind of types
|
||||||
|
ExplicitBottomKind,
|
||||||
|
/// Name never bound in an enclosing scope - indicates incomplete macro substitution
|
||||||
|
Unbound(String),
|
||||||
|
/// Namespaced names can never occur in the code, these are signs of incomplete macro execution
|
||||||
|
Symbol,
|
||||||
|
/// Placeholders shouldn't even occur in the code during macro execution. Something is clearly
|
||||||
|
/// terribly wrong
|
||||||
|
Placeholder,
|
||||||
|
/// It's possible to try and transform the clause `(foo:bar)` into a 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
|
||||||
|
/// error if you're calling [clause] directly
|
||||||
|
ExprToClause(typed::Expr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to convert an expression from AST format to typed lambda
|
||||||
|
pub fn expr(expr: &ast::Expr) -> Result<typed::Expr, Error> {
|
||||||
|
expr_rec(expr, Stackframe::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try and convert a single clause from AST format to typed lambda
|
||||||
|
pub fn clause(clause: &ast::Clause) -> Result<typed::Clause, Error> {
|
||||||
|
clause_rec(clause, Stackframe::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try and convert a sequence of expressions from AST format to typed lambda
|
||||||
|
pub fn exprv(exprv: &[ast::Expr]) -> Result<typed::Expr, Error> {
|
||||||
|
exprv_rec(exprv, Stackframe::new(None))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive state of [exprv]
|
||||||
|
fn exprv_rec(v: &[ast::Expr], names: Stackframe<Option<&str>>) -> Result<typed::Expr, Error> {
|
||||||
|
if v.len() == 0 {return Err(Error::EmptyS)}
|
||||||
|
if v.len() == 1 {return expr_rec(&v[0], names)}
|
||||||
|
let (head, tail) = v.split_at(2);
|
||||||
|
let f = expr_rec(&head[0], names)?;
|
||||||
|
let x = expr_rec(&head[1], names)?;
|
||||||
|
// TODO this could probably be normalized, it's a third copy.
|
||||||
|
tail.iter().map(|e| expr_rec(e, names)).fold(
|
||||||
|
Ok(typed::Clause::Apply(Mrc::new(f), Mrc::new(x))),
|
||||||
|
|acc, e| Ok(typed::Clause::Apply(
|
||||||
|
Mrc::new(typed::Expr(acc?, to_mrc_slice(vec![]))),
|
||||||
|
Mrc::new(e?)
|
||||||
|
))
|
||||||
|
).map(|cls| typed::Expr(cls, to_mrc_slice(vec![])))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive state of [expr]
|
||||||
|
fn expr_rec(ast::Expr(val, typ): &ast::Expr, names: Stackframe<Option<&str>>)
|
||||||
|
-> Result<typed::Expr, Error> {
|
||||||
|
let typ: Vec<typed::Clause> = typ.iter()
|
||||||
|
.map(|c| clause_rec(c, names))
|
||||||
|
.collect::<Result<_, _>>()?;
|
||||||
|
if let ast::Clause::S(paren, body) = val {
|
||||||
|
if *paren != '(' {return Err(Error::BadGroup(*paren))}
|
||||||
|
let typed::Expr(inner, inner_t) = exprv_rec(body.as_ref(), names)?;
|
||||||
|
let new_t = if typ.len() == 0 { inner_t } else {
|
||||||
|
to_mrc_slice(if inner_t.len() == 0 { typ } else {
|
||||||
|
inner_t.iter().chain(typ.iter()).cloned().collect()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
Ok(typed::Expr(inner, new_t))
|
||||||
|
} else {
|
||||||
|
Ok(typed::Expr(clause_rec(&val, names)?, to_mrc_slice(typ)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Recursive state of [clause]
|
||||||
|
fn clause_rec(cls: &ast::Clause, names: Stackframe<Option<&str>>)
|
||||||
|
-> Result<typed::Clause, Error> {
|
||||||
|
match cls {
|
||||||
|
ast::Clause::ExternFn(e) => Ok(typed::Clause::ExternFn(e.clone())),
|
||||||
|
ast::Clause::Atom(a) => Ok(typed::Clause::Atom(a.clone())),
|
||||||
|
ast::Clause::Auto(no, t, b) => Ok(typed::Clause::Auto(
|
||||||
|
if t.len() == 0 {None} else {
|
||||||
|
let typed::Expr(c, t) = exprv_rec(t.as_ref(), names)?;
|
||||||
|
if t.len() > 0 {return Err(Error::ExplicitBottomKind)}
|
||||||
|
else {Some(Mrc::new(c))}
|
||||||
|
},
|
||||||
|
Mrc::new(exprv_rec(b.as_ref(), names.push(no.as_ref().map(|n| &**n)))?)
|
||||||
|
)),
|
||||||
|
ast::Clause::Lambda(n, t, b) => Ok(typed::Clause::Lambda(
|
||||||
|
if t.len() == 0 {None} else {
|
||||||
|
let typed::Expr(c, t) = exprv_rec(t.as_ref(), names)?;
|
||||||
|
if t.len() > 0 {return Err(Error::ExplicitBottomKind)}
|
||||||
|
else {Some(Mrc::new(c))}
|
||||||
|
},
|
||||||
|
Mrc::new(exprv_rec(b.as_ref(), names.push(Some(&**n)))?)
|
||||||
|
)),
|
||||||
|
ast::Clause::Literal(l) => Ok(typed::Clause::Literal(l.clone())),
|
||||||
|
ast::Clause::Name { local: Some(arg), .. } => Ok(typed::Clause::Argument(
|
||||||
|
names.iter().position(|no| no == &Some(&**arg))
|
||||||
|
.ok_or_else(|| Error::Unbound(arg.clone()))?
|
||||||
|
)),
|
||||||
|
ast::Clause::S(paren, entries) => {
|
||||||
|
if *paren != '(' {return Err(Error::BadGroup(*paren))}
|
||||||
|
let typed::Expr(val, typ) = exprv_rec(entries.as_ref(), names)?;
|
||||||
|
if typ.len() == 0 {Ok(val)}
|
||||||
|
else {Err(Error::ExprToClause(typed::Expr(val, typ)))}
|
||||||
|
},
|
||||||
|
ast::Clause::Name { local: None, .. } => Err(Error::Symbol),
|
||||||
|
ast::Clause::Placeh { .. } => Err(Error::Placeholder)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
src/representations/literal.rs
Normal file
22
src/representations/literal.rs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
use ordered_float::NotNan;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// An exact value, read from the AST and unmodified in shape until compilation
|
||||||
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Literal {
|
||||||
|
Num(NotNan<f64>),
|
||||||
|
Int(u64),
|
||||||
|
Char(char),
|
||||||
|
Str(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Literal {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Num(arg0) => write!(f, "{:?}", arg0),
|
||||||
|
Self::Int(arg0) => write!(f, "{:?}", arg0),
|
||||||
|
Self::Char(arg0) => write!(f, "{:?}", arg0),
|
||||||
|
Self::Str(arg0) => write!(f, "{:?}", arg0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/representations/mod.rs
Normal file
5
src/representations/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod ast;
|
||||||
|
pub mod typed;
|
||||||
|
pub mod literal;
|
||||||
|
pub mod ast_to_typed;
|
||||||
|
pub use literal::Literal;
|
||||||
147
src/representations/typed.rs
Normal file
147
src/representations/typed.rs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
use mappable_rc::Mrc;
|
||||||
|
use crate::executor::Atom;
|
||||||
|
use crate::utils::{to_mrc_slice, one_mrc_slice};
|
||||||
|
use crate::{executor::ExternFn, utils::string_from_charset};
|
||||||
|
|
||||||
|
use super::{Literal, ast_to_typed};
|
||||||
|
use super::ast;
|
||||||
|
|
||||||
|
use std::fmt::{Debug, Write};
|
||||||
|
|
||||||
|
/// Indicates whether either side needs to be wrapped. Syntax whose end is ambiguous on that side
|
||||||
|
/// must use parentheses, or forward the flag
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
struct Wrap(bool, bool);
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
pub struct Expr(pub Clause, pub Mrc<[Clause]>);
|
||||||
|
impl Expr {
|
||||||
|
fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, tr: Wrap) -> std::fmt::Result {
|
||||||
|
let Expr(val, typ) = self;
|
||||||
|
if typ.len() > 0 {
|
||||||
|
val.deep_fmt(f, depth, Wrap(true, true))?;
|
||||||
|
for typ in typ.as_ref() {
|
||||||
|
f.write_char(':')?;
|
||||||
|
typ.deep_fmt(f, depth, Wrap(true, true))?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val.deep_fmt(f, depth, tr)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Expr {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(self.0.clone(), Mrc::clone(&self.1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Expr {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.deep_fmt(f, 0, Wrap(false, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash)]
|
||||||
|
pub enum Clause {
|
||||||
|
Literal(Literal),
|
||||||
|
Apply(Mrc<Expr>, Mrc<Expr>),
|
||||||
|
/// Explicit specification of an Auto value
|
||||||
|
Explicit(Mrc<Expr>, Mrc<Expr>),
|
||||||
|
Lambda(Option<Mrc<Clause>>, Mrc<Expr>),
|
||||||
|
Auto(Option<Mrc<Clause>>, Mrc<Expr>),
|
||||||
|
Argument(usize),
|
||||||
|
ExternFn(ExternFn),
|
||||||
|
Atom(Atom)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ARGNAME_CHARSET: &str = "abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
fn parametric_fmt(
|
||||||
|
f: &mut std::fmt::Formatter<'_>,
|
||||||
|
prefix: &str, argtyp: Option<Mrc<Clause>>, body: Mrc<Expr>, depth: usize, wrap_right: bool
|
||||||
|
) -> std::fmt::Result {
|
||||||
|
if wrap_right { f.write_char('(')?; }
|
||||||
|
f.write_str(prefix)?;
|
||||||
|
f.write_str(&string_from_charset(depth, ARGNAME_CHARSET))?;
|
||||||
|
if let Some(typ) = argtyp {
|
||||||
|
f.write_str(":")?;
|
||||||
|
typ.deep_fmt(f, depth, Wrap(false, false))?;
|
||||||
|
}
|
||||||
|
f.write_str(".")?;
|
||||||
|
body.deep_fmt(f, depth + 1, Wrap(false, false))?;
|
||||||
|
if wrap_right { f.write_char(')')?; }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clause {
|
||||||
|
fn deep_fmt(&self, f: &mut std::fmt::Formatter<'_>, depth: usize, Wrap(wl, wr): Wrap)
|
||||||
|
-> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Literal(arg0) => write!(f, "{arg0:?}"),
|
||||||
|
Self::ExternFn(nc) => write!(f, "{nc:?}"),
|
||||||
|
Self::Atom(a) => write!(f, "{a:?}"),
|
||||||
|
Self::Lambda(argtyp, body) => parametric_fmt(f,
|
||||||
|
"\\", argtyp.as_ref().map(Mrc::clone), Mrc::clone(body), depth, wr
|
||||||
|
),
|
||||||
|
Self::Auto(argtyp, body) => parametric_fmt(f,
|
||||||
|
"@", argtyp.as_ref().map(Mrc::clone), Mrc::clone(body), depth, wr
|
||||||
|
),
|
||||||
|
Self::Argument(up) => f.write_str(&string_from_charset(depth - up - 1, ARGNAME_CHARSET)),
|
||||||
|
Self::Explicit(expr, param) => {
|
||||||
|
if wl { f.write_char('(')?; }
|
||||||
|
expr.deep_fmt(f, depth, Wrap(false, true))?;
|
||||||
|
f.write_str(" @")?;
|
||||||
|
param.deep_fmt(f, depth, Wrap(true, wr && !wl))?;
|
||||||
|
if wl { f.write_char(')')?; }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Self::Apply(func, x) => {
|
||||||
|
if wl { f.write_char('(')?; }
|
||||||
|
func.deep_fmt(f, depth, Wrap(false, true) )?;
|
||||||
|
f.write_char(' ')?;
|
||||||
|
x.deep_fmt(f, depth, Wrap(true, wr && !wl) )?;
|
||||||
|
if wl { f.write_char(')')?; }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn wrap(self) -> Mrc<Expr> { Mrc::new(Expr(self, to_mrc_slice(vec![]))) }
|
||||||
|
pub fn wrap_t(self, t: Clause) -> Mrc<Expr> { Mrc::new(Expr(self, one_mrc_slice(t))) }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for Clause {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Clause::Auto(t, b) => Clause::Auto(t.as_ref().map(Mrc::clone), Mrc::clone(b)),
|
||||||
|
Clause::Lambda(t, b) => Clause::Lambda(t.as_ref().map(Mrc::clone), Mrc::clone(b)),
|
||||||
|
Clause::Literal(l) => Clause::Literal(l.clone()),
|
||||||
|
Clause::ExternFn(nc) => Clause::ExternFn(nc.clone()),
|
||||||
|
Clause::Atom(a) => Clause::Atom(a.clone()),
|
||||||
|
Clause::Apply(f, x) => Clause::Apply(Mrc::clone(f), Mrc::clone(x)),
|
||||||
|
Clause::Explicit(f, x) => Clause::Explicit(Mrc::clone(f), Mrc::clone(x)),
|
||||||
|
Clause::Argument(lvl) => Clause::Argument(*lvl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Clause {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.deep_fmt(f, 0, Wrap(false, false))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&ast::Expr> for Expr {
|
||||||
|
type Error = ast_to_typed::Error;
|
||||||
|
fn try_from(value: &ast::Expr) -> Result<Self, Self::Error> {
|
||||||
|
ast_to_typed::expr(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&ast::Clause> for Clause {
|
||||||
|
type Error = ast_to_typed::Error;
|
||||||
|
fn try_from(value: &ast::Clause) -> Result<Self, Self::Error> {
|
||||||
|
ast_to_typed::clause(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
use crate::{expression::{Expr, Clause}, utils::{iter::{box_once, into_boxed_iter}, to_mrc_slice, one_mrc_slice}};
|
use crate::{ast::{Expr, Clause}, utils::{iter::{box_once, into_boxed_iter}, to_mrc_slice, one_mrc_slice}, unwrap_or};
|
||||||
use super::{super::RuleError, state::{State, Entry}, slice_matcher::SliceMatcherDnC};
|
use super::{super::RuleError, state::{State, Entry}, slice_matcher::SliceMatcherDnC};
|
||||||
|
|
||||||
fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
|
fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
|
||||||
@@ -36,18 +36,18 @@ fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
|
|||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
};
|
};
|
||||||
let Expr(val, typ_opt) = pattern;
|
let Expr(val, typ) = pattern;
|
||||||
verify_clause(val, is_vec)?;
|
verify_clause(val, is_vec)?;
|
||||||
if let Some(typ) = typ_opt {
|
for typ in typ.as_ref() {
|
||||||
verify_scalar_vec(typ, is_vec)?;
|
verify_clause(typ, is_vec)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn slice_to_vec(src: &mut Mrc<[Expr]>, tgt: &mut Mrc<[Expr]>) {
|
fn slice_to_vec(src: &mut Mrc<[Expr]>, tgt: &mut Mrc<[Expr]>) {
|
||||||
let prefix_expr = Expr(Clause::Placeh{key: "::prefix".to_string(), vec: Some((0, false))}, None);
|
let prefix_expr = Expr(Clause::Placeh{key: "::prefix".to_string(), vec: Some((0, false))}, to_mrc_slice(vec![]));
|
||||||
let postfix_expr = Expr(Clause::Placeh{key: "::postfix".to_string(), vec: Some((0, false))}, None);
|
let postfix_expr = Expr(Clause::Placeh{key: "::postfix".to_string(), vec: Some((0, false))}, to_mrc_slice(vec![]));
|
||||||
// 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!(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 tail_multi = matches!(src.last().expect("Impossible branch!").0, Clause::Placeh{vec: Some(_), ..});
|
||||||
@@ -121,13 +121,13 @@ fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> {
|
|||||||
write_slice(state, body)
|
write_slice(state, body)
|
||||||
), xpr_typ.to_owned())),
|
), xpr_typ.to_owned())),
|
||||||
Clause::Placeh{key, vec: None} => {
|
Clause::Placeh{key, vec: None} => {
|
||||||
let real_key = if let Some(real_key) = key.strip_prefix('_') {real_key} else {key};
|
let real_key = unwrap_or!(key.strip_prefix('_'); key);
|
||||||
match &state[real_key] {
|
match &state[real_key] {
|
||||||
Entry::Scalar(x) => box_once(x.as_ref().to_owned()),
|
Entry::Scalar(x) => box_once(x.as_ref().to_owned()),
|
||||||
Entry::Name(n) => box_once(Expr(Clause::Name {
|
Entry::Name(n) => box_once(Expr(Clause::Name {
|
||||||
local: Some(n.as_ref().to_owned()),
|
local: Some(n.as_ref().to_owned()),
|
||||||
qualified: one_mrc_slice(n.as_ref().to_owned())
|
qualified: one_mrc_slice(n.as_ref().to_owned())
|
||||||
}, None)),
|
}, to_mrc_slice(vec![]))),
|
||||||
_ => panic!("Scalar template may only be derived from scalar placeholder"),
|
_ => panic!("Scalar template may only be derived from scalar placeholder"),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -135,7 +135,7 @@ fn write_slice(state: &State, tpl: &Mrc<[Expr]>) -> Mrc<[Expr]> {
|
|||||||
into_boxed_iter(v.as_ref().to_owned())
|
into_boxed_iter(v.as_ref().to_owned())
|
||||||
} else {panic!("Vectorial template may only be derived from vectorial placeholder")},
|
} else {panic!("Vectorial template may only be derived from vectorial placeholder")},
|
||||||
// Explicit base case so that we get an error if Clause gets new values
|
// Explicit base case so that we get an error if Clause gets new values
|
||||||
c@Clause::Literal(_) | c@Clause::Name { .. } =>
|
c@Clause::Literal(_) | c@Clause::Name { .. } | c@Clause::ExternFn(_) | c@Clause::Atom(_) =>
|
||||||
box_once(Expr(c.to_owned(), xpr_typ.to_owned()))
|
box_once(Expr(c.to_owned(), xpr_typ.to_owned()))
|
||||||
}).collect()
|
}).collect()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
use crate::expression::{Expr, Clause};
|
use crate::ast::{Expr, Clause};
|
||||||
use crate::unwrap_or_continue;
|
use crate::unwrap_or;
|
||||||
use crate::utils::iter::box_empty;
|
use crate::utils::iter::box_empty;
|
||||||
use crate::utils::{Side, Cache, mrc_derive, mrc_try_derive, to_mrc_slice};
|
use crate::utils::{Side, Cache, mrc_derive, mrc_try_derive, to_mrc_slice};
|
||||||
|
|
||||||
@@ -92,10 +92,7 @@ impl SliceMatcherDnC {
|
|||||||
pub fn valid_subdivisions(&self,
|
pub fn valid_subdivisions(&self,
|
||||||
range: Mrc<[Expr]>
|
range: Mrc<[Expr]>
|
||||||
) -> impl Iterator<Item = (Mrc<[Expr]>, Mrc<[Expr]>, Mrc<[Expr]>)> {
|
) -> impl Iterator<Item = (Mrc<[Expr]>, Mrc<[Expr]>, Mrc<[Expr]>)> {
|
||||||
let own_max = {
|
let own_max = unwrap_or!(self.own_max_size(range.len()); return box_empty());
|
||||||
if let Some(x) = self.own_max_size(range.len()) {x}
|
|
||||||
else {return box_empty()}
|
|
||||||
};
|
|
||||||
let own_min = self.own_min_size();
|
let own_min = self.own_min_size();
|
||||||
let lmin = self.min(Side::Left);
|
let lmin = self.min(Side::Left);
|
||||||
let _lmax = self.max(Side::Left, range.len());
|
let _lmax = self.max(Side::Left, range.len());
|
||||||
@@ -261,10 +258,11 @@ impl SliceMatcherDnC {
|
|||||||
// Step through valid slicings based on reported size constraints in order
|
// Step through valid slicings based on reported size constraints in order
|
||||||
// from longest own section to shortest and from left to right
|
// from longest own section to shortest and from left to right
|
||||||
for (left, own, right) in self.valid_subdivisions(target) {
|
for (left, own, right) in self.valid_subdivisions(target) {
|
||||||
return Some(unwrap_or_continue!(
|
return Some(unwrap_or!(
|
||||||
self.apply_side_with_cache(Side::Left, left, cache)
|
self.apply_side_with_cache(Side::Left, left, cache)
|
||||||
.and_then(|lres| lres + self.apply_side_with_cache(Side::Right, right, cache))
|
.and_then(|lres| lres + self.apply_side_with_cache(Side::Right, right, cache))
|
||||||
.and_then(|side_res| side_res.insert_vec(name, own.as_ref()))
|
.and_then(|side_res| side_res.insert_vec(name, own.as_ref()));
|
||||||
|
continue
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::expression::{Expr, Clause};
|
use crate::ast::{Expr, Clause};
|
||||||
use crate::utils::{mrc_derive, mrc_try_derive};
|
use crate::utils::{mrc_derive, mrc_try_derive};
|
||||||
|
|
||||||
pub type MaxVecSplit = (Mrc<[Expr]>, (Mrc<str>, usize, bool), Mrc<[Expr]>);
|
pub type MaxVecSplit = (Mrc<[Expr]>, (Mrc<str>, usize, bool), Mrc<[Expr]>);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::{ops::{Add, Index}, rc::Rc, fmt::Debug};
|
|||||||
|
|
||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
|
|
||||||
use crate::expression::Expr;
|
use crate::ast::Expr;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Entry {
|
pub enum Entry {
|
||||||
@@ -76,7 +76,9 @@ impl State {
|
|||||||
{
|
{
|
||||||
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()) {return None}
|
if val.as_ref().map(|s| s.as_ref().as_str()) != v.map(|s| s.as_ref()) {
|
||||||
|
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()))));
|
self.0.insert(k.to_string(), Entry::NameOpt(v.map(|s| Rc::new(s.to_string()))));
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ use std::fmt::Debug;
|
|||||||
|
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
use crate::expression::Expr;
|
use crate::representations::ast::Expr;
|
||||||
|
|
||||||
use super::{super::expression::Rule, executor::execute, RuleError};
|
use super::{super::ast::Rule, executor::execute, RuleError};
|
||||||
|
|
||||||
|
/// Manages a priority queue of substitution rules and allows to apply them
|
||||||
pub struct Repository(Vec<Rule>);
|
pub struct Repository(Vec<Rule>);
|
||||||
impl Repository {
|
impl Repository {
|
||||||
pub fn new(mut rules: Vec<Rule>) -> Self {
|
pub fn new(mut rules: Vec<Rule>) -> Self {
|
||||||
@@ -13,6 +14,7 @@ impl Repository {
|
|||||||
Self(rules)
|
Self(rules)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Attempt to run each rule in priority order once
|
||||||
pub fn step(&self, mut code: Mrc<[Expr]>) -> Result<Option<Mrc<[Expr]>>, RuleError> {
|
pub fn step(&self, mut code: Mrc<[Expr]>) -> Result<Option<Mrc<[Expr]>>, RuleError> {
|
||||||
let mut ran_once = false;
|
let mut ran_once = false;
|
||||||
for rule in self.0.iter() {
|
for rule in self.0.iter() {
|
||||||
@@ -27,11 +29,16 @@ impl Repository {
|
|||||||
Ok(if ran_once {Some(code)} else {None})
|
Ok(if ran_once {Some(code)} else {None})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn long_step(&self, mut code: Mrc<[Expr]>) -> Result<Mrc<[Expr]>, RuleError> {
|
/// Attempt to run each rule in priority order `limit` times. Returns the final
|
||||||
|
/// tree and the number of iterations left to the limit.
|
||||||
|
pub fn long_step(&self, mut code: Mrc<[Expr]>, mut limit: usize)
|
||||||
|
-> Result<(Mrc<[Expr]>, usize), RuleError> {
|
||||||
while let Some(tmp) = self.step(Mrc::clone(&code))? {
|
while let Some(tmp) = self.step(Mrc::clone(&code))? {
|
||||||
|
if 0 >= limit {break}
|
||||||
|
limit -= 1;
|
||||||
code = tmp
|
code = tmp
|
||||||
}
|
}
|
||||||
Ok(code)
|
Ok((code, limit))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
src/types/hindley_milner.rs.proto
Normal file
52
src/types/hindley_milner.rs.proto
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::{borrow::Borrow};
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use hashbrown::HashMap;
|
||||||
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
|
use crate::{ast::{Expr, Clause}, utils::mrc_to_iter};
|
||||||
|
|
||||||
|
pub struct Substitution(HashMap<String, Mrc<Expr>>);
|
||||||
|
impl Substitution {
|
||||||
|
fn new() -> Self { Self(HashMap::new()) }
|
||||||
|
fn apply<Q: ?Sized + Hash + Eq>(&self, q: &Q) -> Option<Mrc<Expr>>
|
||||||
|
where String: Borrow<Q> {
|
||||||
|
self.0.get(q).map(Mrc::clone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hindley_milner(a: Mrc<[Expr]>, b: Mrc<[Expr]>) -> Result<Substitution, ()> {
|
||||||
|
hindley_milner_rec(Substitution::new(), a, b)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hindley_milner_rec(mut s: Substitution, a: Mrc<[Expr]>, b: Mrc<[Expr]>)
|
||||||
|
-> Result<Substitution, ()> {
|
||||||
|
if a.len() != b.len() {return Err(())}
|
||||||
|
for (mut a, mut b) in mrc_to_iter(a).zip(mrc_to_iter(b)) {
|
||||||
|
if let Clause::Placeh{key, ..} = &a.0 {
|
||||||
|
if let Some(ex) = s.apply(key) { a = ex }
|
||||||
|
}
|
||||||
|
if let Clause::Placeh{key, ..} = &b.0 {
|
||||||
|
if let Some(ex) = s.apply(key) { b = ex }
|
||||||
|
}
|
||||||
|
if !matches!(&a.0, Clause::Placeh{..}) { (a, b) = (b, a) }
|
||||||
|
match (&a.0, &b.0) {
|
||||||
|
(Clause::Placeh{key:a_key,..}, Clause::Placeh{key:b_key,..}) =>
|
||||||
|
if a_key == b_key {return Ok(s)},
|
||||||
|
|
||||||
|
_ => return Err(())
|
||||||
|
}
|
||||||
|
if let (Clause::Placeh{key: a_key,..}, Clause::Placeh{key: b_key,..}) = (&a.0, &b.0) {
|
||||||
|
if a_key == b_key {return Ok(s)}
|
||||||
|
} else if let (Clause::S(_, a_body), Clause::S(_, b_body)) = (&a.0, &b.0) {
|
||||||
|
s = hindley_milner_rec(s, Mrc::clone(a_body), Mrc::clone(b_body))?
|
||||||
|
} else if let ()
|
||||||
|
}
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn occurs(key: &str, val: &Expr) -> bool {
|
||||||
|
match val.0 {
|
||||||
|
Clause::Auto(_, _, body) => body.
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/types/mod.rs
Normal file
13
src/types/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// mod hindley_milner;
|
||||||
|
|
||||||
|
#[derive(Clone, Hash, PartialEq, Eq)]
|
||||||
|
pub enum Expression<L, V, O, F> {
|
||||||
|
Literal(L),
|
||||||
|
Variable(V),
|
||||||
|
Operation(O, Vec<Expression<L, V, O, F>>),
|
||||||
|
Lazy(F)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Rule {
|
||||||
|
|
||||||
|
}
|
||||||
0
src/types/unifier.rs
Normal file
0
src/types/unifier.rs
Normal file
113
src/utils/bfs.rs
Normal file
113
src/utils/bfs.rs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
use std::collections::{VecDeque, HashSet};
|
||||||
|
use std::iter;
|
||||||
|
use std::hash::Hash;
|
||||||
|
|
||||||
|
use crate::unwrap_or;
|
||||||
|
use crate::utils::BoxedIter;
|
||||||
|
|
||||||
|
/// Two-stage breadth-first search;
|
||||||
|
/// 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
|
||||||
|
/// one by one once it's empty. This method is preferable for generated graphs because it doesn't
|
||||||
|
/// allocate memory for the children until necessary, but it's also probably a bit slower since
|
||||||
|
/// it involves additional processing.
|
||||||
|
///
|
||||||
|
/// # Performance
|
||||||
|
/// `T` is cloned twice for each returned value.
|
||||||
|
pub fn bfs<T, F, I>(init: T, neighbors: F)
|
||||||
|
-> impl Iterator<Item = T>
|
||||||
|
where T: Eq + Hash + Clone + std::fmt::Debug,
|
||||||
|
F: Fn(T) -> I, I: Iterator<Item = T>
|
||||||
|
{
|
||||||
|
let mut visited: HashSet<T> = HashSet::new();
|
||||||
|
let mut visit_queue: VecDeque<T> = VecDeque::from([init]);
|
||||||
|
let mut unpack_queue: VecDeque<T> = VecDeque::new();
|
||||||
|
iter::from_fn(move || {
|
||||||
|
let next = {loop {
|
||||||
|
let next = unwrap_or!(visit_queue.pop_front(); break None);
|
||||||
|
if !visited.contains(&next) { break Some(next) }
|
||||||
|
}}.or_else(|| loop {
|
||||||
|
let unpacked = unwrap_or!(unpack_queue.pop_front(); break None);
|
||||||
|
let mut nbv = neighbors(unpacked).filter(|t| !visited.contains(t));
|
||||||
|
if let Some(next) = nbv.next() {
|
||||||
|
visit_queue.extend(nbv);
|
||||||
|
break Some(next)
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
visited.insert(next.clone());
|
||||||
|
unpack_queue.push_back(next.clone());
|
||||||
|
Some(next)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as [bfs] but with a recursion depth limit
|
||||||
|
///
|
||||||
|
/// The main intent is to effectively walk infinite graphs of unknown breadth without making the
|
||||||
|
/// recursion depth dependent on the number of nodes. If predictable runtime is more important
|
||||||
|
/// than predictable depth, [bfs] with [std::iter::Iterator::take] should be used instead
|
||||||
|
pub fn bfs_upto<'a, T: 'a, F: 'a, I: 'a>(init: T, neighbors: F, limit: usize)
|
||||||
|
-> impl Iterator<Item = T> + 'a
|
||||||
|
where T: Eq + Hash + Clone + std::fmt::Debug,
|
||||||
|
F: Fn(T) -> I, I: Iterator<Item = T>
|
||||||
|
{
|
||||||
|
/// Newtype to store the recursion depth but exclude it from equality comparisons
|
||||||
|
/// Because BFS visits nodes in increasing distance order, when a node is visited for the
|
||||||
|
/// second time it will never override the earlier version of itself. This is not the case
|
||||||
|
/// with Djikstra's algorithm, which can be conceptualised as a "weighted BFS".
|
||||||
|
#[derive(Eq, Clone, Debug)]
|
||||||
|
struct Wrap<U>(usize, U);
|
||||||
|
impl<U: PartialEq> PartialEq for Wrap<U> {
|
||||||
|
fn eq(&self, other: &Self) -> bool { self.1.eq(&other.1) }
|
||||||
|
}
|
||||||
|
impl<U: Hash> Hash for Wrap<U> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) { self.1.hash(state) }
|
||||||
|
}
|
||||||
|
bfs(Wrap(0, init), move |Wrap(dist, t)| -> BoxedIter<Wrap<T>> { // boxed because we branch
|
||||||
|
if dist == limit {Box::new(iter::empty())}
|
||||||
|
else {Box::new(neighbors(t).map(move |t| Wrap(dist + 1, t)))}
|
||||||
|
}).map(|Wrap(_, t)| t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
type Graph = Vec<Vec<usize>>;
|
||||||
|
fn neighbors(graph: &Graph, pt: usize) -> impl Iterator<Item = usize> + '_ {
|
||||||
|
graph[pt].iter().copied()
|
||||||
|
}
|
||||||
|
fn from_neighborhood_matrix(matrix: Vec<Vec<usize>>) -> Graph {
|
||||||
|
matrix.into_iter().map(|v| {
|
||||||
|
v.into_iter().enumerate().filter_map(|(i, ent)| {
|
||||||
|
if ent > 1 {panic!("Neighborhood matrices must contain binary values")}
|
||||||
|
else if ent == 1 {Some(i)}
|
||||||
|
else {None}
|
||||||
|
}).collect()
|
||||||
|
}).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_square() {
|
||||||
|
let simple_graph = from_neighborhood_matrix(vec![
|
||||||
|
vec![0,1,0,1,1,0,0,0],
|
||||||
|
vec![1,0,1,0,0,1,0,0],
|
||||||
|
vec![0,1,0,1,0,0,1,0],
|
||||||
|
vec![1,0,1,0,0,0,0,1],
|
||||||
|
vec![1,0,0,0,0,1,0,1],
|
||||||
|
vec![0,1,0,0,1,0,1,0],
|
||||||
|
vec![0,0,1,0,0,1,0,1],
|
||||||
|
vec![0,0,0,1,1,0,1,0],
|
||||||
|
]);
|
||||||
|
let scan = bfs(0, |n| neighbors(&simple_graph, n)).collect_vec();
|
||||||
|
assert_eq!(scan, vec![0, 1, 3, 4, 2, 5, 7, 6])
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_stringbuilder() {
|
||||||
|
let scan = bfs("".to_string(), |s| {
|
||||||
|
vec![s.clone()+";", s.clone()+"a", s+"aaa"].into_iter()
|
||||||
|
}).take(30).collect_vec();
|
||||||
|
println!("{scan:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
91
src/utils/for_loop.rs
Normal file
91
src/utils/for_loop.rs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/// Imitates a regular for loop with an exit clause using Rust's `loop` keyword.
|
||||||
|
/// This macro brings the break value to all existing Rust loops, by allowing you to specify
|
||||||
|
/// an exit expression in case the loop was broken by the condition and not an explicit `break`.
|
||||||
|
///
|
||||||
|
/// Since the exit expression can also be a block, this also allows you to execute other code when
|
||||||
|
/// the condition fails. This can also be used to re-enter the loop with an explicit `continue`
|
||||||
|
/// statement.
|
||||||
|
///
|
||||||
|
/// The macro also adds support for classic for loops familiar to everyone since C, except with
|
||||||
|
/// the addition of an exit statement these too can be turned into expressions.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// xloop!(for i in 0..10; {
|
||||||
|
/// connection.try_connect()
|
||||||
|
/// if connection.ready() {
|
||||||
|
/// break Some(connection)
|
||||||
|
/// }
|
||||||
|
/// }; None)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// While loop with reentry. This is a very convoluted example but displays the idea quite clearly.
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// xloop!(while socket.is_open(); {
|
||||||
|
/// let (data, is_end) = socket.read();
|
||||||
|
/// all_data.append(data)
|
||||||
|
/// if is_end { break Ok(all_data) }
|
||||||
|
/// }; {
|
||||||
|
/// if let Ok(new_sock) = open_socket(socket.position()) {
|
||||||
|
/// new_sock.set_position(socket.position());
|
||||||
|
/// socket = new_sock;
|
||||||
|
/// continue
|
||||||
|
/// } else {
|
||||||
|
/// Err(DownloadError::ConnectionLost)
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// CUDA algorythm for O(log n) summation using a C loop
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// xloop!(let mut leap = 1; own_id*2 + leap < batch_size; leap *= 2; {
|
||||||
|
/// batch[own_id*2] += batch[own_id*2 + leap]
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The above loop isn't used as an expression, but an exit expression - or block - can be added
|
||||||
|
/// to these as well just like the others. In all cases the exit expression is optional, its
|
||||||
|
/// default value is `()`.
|
||||||
|
///
|
||||||
|
/// **todo** find a valid use case for While let for a demo
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! xloop {
|
||||||
|
(for $p:pat in $it:expr; $body:stmt) => {
|
||||||
|
xloop!(for $p in $it; $body; ())
|
||||||
|
};
|
||||||
|
(for $p:pat in $it:expr; $body:stmt; $exit:stmt) => {
|
||||||
|
{
|
||||||
|
let mut __xloop__ = $it.into_iter();
|
||||||
|
xloop!(let Some($p) = __xloop__.next(); $body; $exit)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(let $p:pat = $e:expr; $body:stmt) => {
|
||||||
|
xloop!(let $p = $e; $body; ())
|
||||||
|
};
|
||||||
|
(let $p:pat = $e:expr; $body:stmt; $exit:stmt) => {
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
if let $p = $e { $body }
|
||||||
|
else { break { $exit } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
(while $cond:expr; $body:stmt) => {
|
||||||
|
xloop!($cond; $body; ())
|
||||||
|
};
|
||||||
|
(while $cond:expr; $body:stmt; $exit:stmt) => {
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
if $cond { break { $exit } }
|
||||||
|
else { $body }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($init:stmt; $cond:expr; $step:stmt; $body:stmt) => {
|
||||||
|
xloop!(for ( $init; $cond; $step ) $body; ())
|
||||||
|
};
|
||||||
|
($init:stmt; $cond:expr; $step:stmt; $body:stmt; $exit:stmt) => {
|
||||||
|
{ $init; xloop!(while !($cond); { $body; $step }; $exit) }
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -2,8 +2,13 @@ mod cache;
|
|||||||
mod substack;
|
mod substack;
|
||||||
mod side;
|
mod side;
|
||||||
mod merge_sorted;
|
mod merge_sorted;
|
||||||
mod unwrap_or_continue;
|
mod unwrap_or;
|
||||||
pub mod iter;
|
pub mod iter;
|
||||||
|
mod bfs;
|
||||||
|
mod unless_let;
|
||||||
|
mod string_from_charset;
|
||||||
|
mod for_loop;
|
||||||
|
mod protomap;
|
||||||
|
|
||||||
pub use cache::Cache;
|
pub use cache::Cache;
|
||||||
use mappable_rc::Mrc;
|
use mappable_rc::Mrc;
|
||||||
@@ -11,6 +16,7 @@ pub use substack::Stackframe;
|
|||||||
pub use side::Side;
|
pub use side::Side;
|
||||||
pub use merge_sorted::merge_sorted;
|
pub use merge_sorted::merge_sorted;
|
||||||
pub use iter::BoxedIter;
|
pub use iter::BoxedIter;
|
||||||
|
pub use string_from_charset::string_from_charset;
|
||||||
|
|
||||||
pub fn mrc_derive<T: ?Sized, P, U: ?Sized>(m: &Mrc<T>, p: P) -> Mrc<U>
|
pub fn mrc_derive<T: ?Sized, P, U: ?Sized>(m: &Mrc<T>, p: P) -> Mrc<U>
|
||||||
where P: for<'a> FnOnce(&'a T) -> &'a U {
|
where P: for<'a> FnOnce(&'a T) -> &'a U {
|
||||||
@@ -37,3 +43,31 @@ pub fn mrc_derive_slice<T>(mv: &Mrc<Vec<T>>) -> Mrc<[T]> {
|
|||||||
pub fn one_mrc_slice<T>(t: T) -> Mrc<[T]> {
|
pub fn one_mrc_slice<T>(t: T) -> Mrc<[T]> {
|
||||||
Mrc::map(Mrc::new([t; 1]), |v| v.as_slice())
|
Mrc::map(Mrc::new([t; 1]), |v| v.as_slice())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn mrc_to_iter<T>(ms: Mrc<[T]>) -> impl Iterator<Item = Mrc<T>> {
|
||||||
|
let mut i = 0;
|
||||||
|
std::iter::from_fn(move || if i < ms.len() {
|
||||||
|
let out = Some(mrc_derive(&ms, |s| &s[i]));
|
||||||
|
i += 1;
|
||||||
|
out
|
||||||
|
} else {None})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mrc_unnest<T>(m: &Mrc<Mrc<T>>) -> Mrc<T> {
|
||||||
|
Mrc::clone(m.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mrc_slice_to_only<T>(m: Mrc<[T]>) -> Result<Mrc<T>, ()> {
|
||||||
|
Mrc::try_map(m, |slice| {
|
||||||
|
if slice.len() != 1 {None}
|
||||||
|
else {Some(&slice[0])}
|
||||||
|
}).map_err(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mrc_slice_to_only_option<T>(m: Mrc<[T]>) -> Result<Option<Mrc<T>>, ()> {
|
||||||
|
if m.len() > 1 {return Err(())}
|
||||||
|
Ok(Mrc::try_map(m, |slice| {
|
||||||
|
if slice.len() == 0 {None}
|
||||||
|
else {Some(&slice[0])}
|
||||||
|
}).ok())
|
||||||
|
}
|
||||||
152
src/utils/protomap.rs
Normal file
152
src/utils/protomap.rs
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
use std::{iter, ops::{Index, Add}, borrow::Borrow};
|
||||||
|
|
||||||
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
const INLINE_ENTRIES: usize = 2;
|
||||||
|
|
||||||
|
/// Linked-array-list of key-value pairs.
|
||||||
|
/// Lookup and modification is O(n + cachemiss * n / m)
|
||||||
|
/// Can be extended by reference in O(m) < O(n)
|
||||||
|
pub struct ProtoMap<'a, K, V> {
|
||||||
|
entries: SmallVec<[(K, Option<V>); INLINE_ENTRIES]>,
|
||||||
|
prototype: Option<&'a ProtoMap<'a, K, V>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K, V> ProtoMap<'a, K, V> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
entries: SmallVec::new(),
|
||||||
|
prototype: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mutable reference to entry without checking proto in O(m)
|
||||||
|
fn local_entry_mut<'b, Q: ?Sized>(&'b mut self, query: &Q)
|
||||||
|
-> Option<(usize, &'b mut K, &'b mut Option<V>)>
|
||||||
|
where K: Borrow<Q>, Q: Eq
|
||||||
|
{
|
||||||
|
self.entries.iter_mut().enumerate().find_map(|(i, (k, v))| {
|
||||||
|
if query.eq((*k).borrow()) { Some((i, k, v)) } else { None }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Entry without checking proto in O(m)
|
||||||
|
fn local_entry<'b, Q: ?Sized>(&'b self, query: &Q)
|
||||||
|
-> Option<(usize, &'b K, &'b Option<V>)>
|
||||||
|
where K: Borrow<Q>, Q: Eq
|
||||||
|
{
|
||||||
|
self.entries.iter().enumerate().find_map(|(i, (k, v))| {
|
||||||
|
if query.eq((*k).borrow()) { Some((i, k, v)) } else { None }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find entry in prototype chain in O(n)
|
||||||
|
pub fn get<'b, Q: ?Sized>(&'b self, query: &Q) -> Option<&'b V>
|
||||||
|
where K: Borrow<Q>, Q: Eq
|
||||||
|
{
|
||||||
|
if let Some((_, _, v)) = self.local_entry(query) {
|
||||||
|
v.as_ref()
|
||||||
|
} else {
|
||||||
|
self.prototype?.get(query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Record a value for the given key in O(m)
|
||||||
|
pub fn set(&mut self, key: &K, value: V) where K: Eq + Clone {
|
||||||
|
if let Some((_, _, v)) = self.local_entry_mut(key) {
|
||||||
|
*v = Some(value);
|
||||||
|
} else {
|
||||||
|
self.entries.push((key.clone(), Some(value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete in a memory-efficient way in O(n)
|
||||||
|
pub fn delete_small(&mut self, key: &K) where K: Eq + Clone {
|
||||||
|
let exists_up = self.prototype.and_then(|p| p.get(key)).is_some();
|
||||||
|
let local_entry = self.local_entry_mut(key);
|
||||||
|
match (exists_up, local_entry) {
|
||||||
|
(false, None) => (), // nothing to do
|
||||||
|
(false, Some((i, _, _))) => { self.entries.remove(i); }, // forget locally
|
||||||
|
(true, Some((_, _, v))) => *v = None, // update local override to cover
|
||||||
|
(true, None) => self.entries.push((key.clone(), None)), // create new
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Delete in O(m) without checking the prototype chain
|
||||||
|
/// May produce unnecessary cover over previously unknown key
|
||||||
|
pub fn delete_fast(&mut self, key: &K) where K: Eq + Clone {
|
||||||
|
if let Some((_, _, v)) = self.local_entry_mut(key) {
|
||||||
|
*v = None
|
||||||
|
} else {
|
||||||
|
self.entries.push((key.clone(), None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Iterate over the values defined herein and on the prototype chain
|
||||||
|
/// Note that this will visit keys multiple times
|
||||||
|
pub fn iter(&self) -> impl Iterator<Item = &(K, Option<V>)> {
|
||||||
|
let mut map = self;
|
||||||
|
iter::from_fn(move || {
|
||||||
|
let pairs = map.entries.iter();
|
||||||
|
map = map.prototype?;
|
||||||
|
Some(pairs)
|
||||||
|
}).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit the keys in an unsafe random order, repeated arbitrarily many times
|
||||||
|
pub fn keys(&self) -> impl Iterator<Item = &K> {
|
||||||
|
self.iter().map(|(k, _)| k)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit the values in random order
|
||||||
|
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||||
|
self.iter().filter_map(|(_, v)| v.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the prototype, and correspondingly the lifetime of the map
|
||||||
|
pub fn set_proto<'b>(self, proto: &'b ProtoMap<'b, K, V>) -> ProtoMap<'b, K, V> {
|
||||||
|
ProtoMap {
|
||||||
|
entries: self.entries,
|
||||||
|
prototype: Some(proto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, K, V> From<T> for ProtoMap<'_, K, V> where T: IntoIterator<Item = (K, V)> {
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
entries: value.into_iter().map(|(k, v)| (k, Some(v))).collect(),
|
||||||
|
prototype: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Q: ?Sized, K, V> Index<&Q> for ProtoMap<'_, K, V> where K: Borrow<Q>, Q: Eq {
|
||||||
|
type Output = V;
|
||||||
|
fn index(&self, index: &Q) -> &Self::Output {
|
||||||
|
self.get(index).expect("Index not found in map")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<K: Clone, V: Clone> Clone for ProtoMap<'_, K, V> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
entries: self.entries.clone(),
|
||||||
|
prototype: self.prototype
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, K: 'a, V: 'a> Add<(K, V)> for &'a ProtoMap<'a, K, V> {
|
||||||
|
type Output = ProtoMap<'a, K, V>;
|
||||||
|
fn add(self, rhs: (K, V)) -> Self::Output {
|
||||||
|
ProtoMap::from([rhs]).set_proto(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! protomap {
|
||||||
|
($($ent:expr),*) => {
|
||||||
|
ProtoMap::from([$($ent:expr),*])
|
||||||
|
};
|
||||||
|
}
|
||||||
14
src/utils/string_from_charset.rs
Normal file
14
src/utils/string_from_charset.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
fn string_from_charset_rec(val: usize, digits: &str) -> String {
|
||||||
|
let radix = digits.len();
|
||||||
|
let mut prefix = if val > radix {
|
||||||
|
string_from_charset_rec(val / radix, digits)
|
||||||
|
} else {String::new()};
|
||||||
|
prefix.push(digits.chars().nth(val - 1).unwrap_or_else(|| {
|
||||||
|
panic!("Overindexed digit set \"{}\" with {}", digits, val - 1)
|
||||||
|
}));
|
||||||
|
prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string_from_charset(val: usize, digits: &str) -> String {
|
||||||
|
string_from_charset_rec(val + 1, digits)
|
||||||
|
}
|
||||||
6
src/utils/unless_let.rs
Normal file
6
src/utils/unless_let.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! unless_let {
|
||||||
|
($m:pat_param = $expr:tt) => {
|
||||||
|
if let $m = $expr {} else
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/utils/unwrap_or.rs
Normal file
6
src/utils/unwrap_or.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! unwrap_or {
|
||||||
|
($m:expr; $fail:expr) => {
|
||||||
|
{ if let Some(res) = ($m) {res} else {$fail} }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
#[macro_export]
|
|
||||||
macro_rules! unwrap_or_continue {
|
|
||||||
($m:expr) => {
|
|
||||||
{ if let Some(res) = ($m) {res} else {continue} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user