forked from Orchid/orchid
Mainly worked on the rule matcher
Also fixed the name collector, and lambda parameters are no longer resolved at parsing to support planned macro-based pattern matching. The rule matcher clones a lot, the number of clones could be zero.
This commit is contained in:
@@ -6,6 +6,8 @@ greet =1=> (\name. printf out "Hello {}!\n" [name])
|
|||||||
-- multi-word exported rule
|
-- multi-word exported rule
|
||||||
export ;> $a =200=> (greet $a)
|
export ;> $a =200=> (greet $a)
|
||||||
|
|
||||||
|
reeee := \$a.b
|
||||||
|
|
||||||
-- single-word exported rule
|
-- single-word exported rule
|
||||||
export main == (
|
export main == (
|
||||||
print "What is your name?" >>
|
print "What is your name?" >>
|
||||||
|
|||||||
@@ -36,25 +36,37 @@ impl Debug for Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Expr {
|
|
||||||
/// Replace all occurences of a name in the tree with a parameter, to bypass name resolution
|
|
||||||
pub fn bind_parameter(&mut self, name: &str) {
|
|
||||||
self.0.bind_parameter(name);
|
|
||||||
if let Some(typ) = &mut self.1 {
|
|
||||||
typ.bind_parameter(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An S-expression as read from a source file
|
/// An S-expression as read from a source file
|
||||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum Clause {
|
pub enum Clause {
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
Name(Vec<String>),
|
Name{
|
||||||
|
local: Option<String>,
|
||||||
|
qualified: Vec<String>
|
||||||
|
},
|
||||||
S(char, Vec<Expr>),
|
S(char, Vec<Expr>),
|
||||||
Lambda(String, Vec<Expr>, Vec<Expr>),
|
Lambda(String, Vec<Expr>, Vec<Expr>),
|
||||||
Auto(Option<String>, Vec<Expr>, Vec<Expr>),
|
Auto(Option<String>, Vec<Expr>, Vec<Expr>),
|
||||||
Parameter(String)
|
/// Second parameter:
|
||||||
|
/// None => matches one token
|
||||||
|
/// Some(prio) => prio is the sizing priority for the vectorial (higher prio grows first)
|
||||||
|
Placeh(String, Option<usize>),
|
||||||
|
}
|
||||||
|
impl Clause {
|
||||||
|
pub fn body(&self) -> Option<&Vec<Expr>> {
|
||||||
|
match self {
|
||||||
|
Clause::Auto(_, _, body) |
|
||||||
|
Clause::Lambda(_, _, body) |
|
||||||
|
Clause::S(_, body) => Some(body),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn typ(&self) -> Option<&Vec<Expr>> {
|
||||||
|
match self {
|
||||||
|
Clause::Auto(_, typ, _) | Clause::Lambda(_, typ, _) => Some(typ),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<'_>) -> std::fmt::Result {
|
||||||
@@ -69,7 +81,9 @@ impl Debug for Clause {
|
|||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Literal(arg0) => write!(f, "{:?}", arg0),
|
Self::Literal(arg0) => write!(f, "{:?}", arg0),
|
||||||
Self::Name(arg0) => write!(f, "{}", arg0.join("::")),
|
Self::Name{local, qualified} =>
|
||||||
|
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)?;
|
||||||
@@ -90,24 +104,9 @@ impl Debug for Clause {
|
|||||||
f.write_str(":")?; fmt_expr_seq(&mut argtyp.iter(), f)?; f.write_str(".")?;
|
f.write_str(":")?; fmt_expr_seq(&mut argtyp.iter(), f)?; f.write_str(".")?;
|
||||||
fmt_expr_seq(&mut body.iter(), f)
|
fmt_expr_seq(&mut body.iter(), f)
|
||||||
},
|
},
|
||||||
Self::Parameter(name) => write!(f, "`{}", name)
|
// Self::Parameter(name) => write!(f, "`{}", name),
|
||||||
}
|
Self::Placeh(name, None) => write!(f, "${}", name),
|
||||||
}
|
Self::Placeh(name, Some(prio)) => write!(f, "...${}:{}", name, prio)
|
||||||
}
|
|
||||||
|
|
||||||
impl Clause {
|
|
||||||
/// Replace all occurences of a name in the tree with a parameter, to bypass name resolution
|
|
||||||
pub fn bind_parameter(&mut self, name: &str) {
|
|
||||||
match self {
|
|
||||||
Clause::Name(n) => if n.len() == 1 && n[0] == name {
|
|
||||||
*self = Clause::Parameter(name.to_string())
|
|
||||||
}
|
|
||||||
Clause::S(_, exprv) => for expr in exprv { expr.bind_parameter(name) }
|
|
||||||
Clause::Lambda(_, typ, body) | Clause::Auto(_, typ, body) => {
|
|
||||||
for expr in typ { expr.bind_parameter(name) }
|
|
||||||
for expr in body { expr.bind_parameter(name) }
|
|
||||||
}
|
|
||||||
_ => ()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ fn main() {
|
|||||||
let collect_rules = rule_collector(move |n| {
|
let collect_rules = rule_collector(move |n| {
|
||||||
if n == vec!["prelude"] { Ok(Loaded::Module(PRELUDE.to_string())) }
|
if n == vec!["prelude"] { Ok(Loaded::Module(PRELUDE.to_string())) }
|
||||||
else { file_loader(cwd.clone())(n) }
|
else { file_loader(cwd.clone())(n) }
|
||||||
}, literal(&["...", ">>", ">>=", "[", "]", ",", "$", "=", "=>"]));
|
}, literal(&["...", ">>", ">>=", "[", "]", ",", "=", "=>"]));
|
||||||
match collect_rules.try_find(&literal(&["main"])) {
|
match collect_rules.try_find(&literal(&["main"])) {
|
||||||
Ok(rules) => for rule in rules.iter() {
|
Ok(rules) => for rule in rules.iter() {
|
||||||
println!("{rule:?}")
|
println!("{rule:?}")
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
|
|||||||
.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), mut body): ((String, Vec<Expr>), Vec<Expr>)| {
|
.map(|((name, typ), mut body): ((String, Vec<Expr>), Vec<Expr>)| {
|
||||||
for ent in &mut body { ent.bind_parameter(&name) };
|
// for ent in &mut body { ent.bind_parameter(&name) };
|
||||||
Clause::Lambda(name, typ, body)
|
Clause::Lambda(name, typ, body)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -54,9 +54,9 @@ where P: Parser<Lexeme, Expr, Error = Simple<Lexeme>> + Clone {
|
|||||||
.try_map(|((name, typ), mut body), s| if name == None && typ.is_empty() {
|
.try_map(|((name, typ), mut body), s| if name == 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 {
|
||||||
if let Some(n) = &name {
|
// if let Some(n) = &name {
|
||||||
for ent in &mut body { ent.bind_parameter(n) }
|
// for ent in &mut body { ent.bind_parameter(n) }
|
||||||
}
|
// }
|
||||||
Ok(Clause::Auto(name, typ, body))
|
Ok(Clause::Auto(name, typ, body))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -69,6 +69,13 @@ fn name_parser() -> impl Parser<Lexeme, Vec<String>, Error = Simple<Lexeme>> + C
|
|||||||
).at_least(1)
|
).at_least(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn placeholder_parser() -> impl Parser<Lexeme, String, Error = Simple<Lexeme>> + Clone {
|
||||||
|
enum_parser!(Lexeme::Name).try_map(|name, span| {
|
||||||
|
name.strip_prefix("$").map(&str::to_string)
|
||||||
|
.ok_or(Simple::custom(span, "Not a placeholder"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse an expression without a type annotation
|
/// Parse an expression without a type annotation
|
||||||
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| {
|
||||||
@@ -76,7 +83,19 @@ pub fn xpr_parser() -> impl Parser<Lexeme, Expr, Error = Simple<Lexeme>> {
|
|||||||
enum_parser!(Lexeme::Comment).repeated()
|
enum_parser!(Lexeme::Comment).repeated()
|
||||||
.ignore_then(choice((
|
.ignore_then(choice((
|
||||||
enum_parser!(Lexeme >> Literal; Int, Num, Char, Str).map(Clause::Literal),
|
enum_parser!(Lexeme >> Literal; Int, Num, Char, Str).map(Clause::Literal),
|
||||||
name_parser().map(Clause::Name),
|
placeholder_parser().map(|n| Clause::Placeh(n, None)),
|
||||||
|
just(Lexeme::name("..."))
|
||||||
|
.ignore_then(placeholder_parser())
|
||||||
|
.then(
|
||||||
|
just(Lexeme::Type)
|
||||||
|
.ignore_then(enum_parser!(Lexeme::Int))
|
||||||
|
.or_not().map(Option::unwrap_or_default)
|
||||||
|
)
|
||||||
|
.map(|(name, prio)| Clause::Placeh(name, Some(prio.try_into().unwrap()))),
|
||||||
|
name_parser().map(|qualified| Clause::Name {
|
||||||
|
local: if qualified.len() == 1 {Some(qualified[0].clone())} else {None},
|
||||||
|
qualified
|
||||||
|
}),
|
||||||
sexpr_parser(expr.clone()),
|
sexpr_parser(expr.clone()),
|
||||||
lambda_parser(expr.clone()),
|
lambda_parser(expr.clone()),
|
||||||
auto_parser(expr.clone())
|
auto_parser(expr.clone())
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ fn op_parser<'a, T: AsRef<str> + Clone>(ops: &[T]) -> BoxedParser<'a, char, Stri
|
|||||||
/// TODO: `.` could possibly be parsed as an operator depending on context. This operator is very
|
/// TODO: `.` could possibly be parsed as an operator depending on context. This operator is very
|
||||||
/// common in maths so it's worth a try. Investigate.
|
/// common in maths so it's worth a try. Investigate.
|
||||||
pub fn modname_parser<'a>() -> impl Parser<char, String, Error = Simple<char>> + 'a {
|
pub fn modname_parser<'a>() -> impl Parser<char, String, Error = Simple<char>> + 'a {
|
||||||
let not_name_char: Vec<char> = vec![':', '\\', '@', '"', '\'', '(', ')', ','];
|
let not_name_char: Vec<char> = vec![':', '\\', '@', '"', '\'', '(', ')', ',', '.'];
|
||||||
filter(move |c| !not_name_char.contains(c) && !c.is_whitespace())
|
filter(move |c| !not_name_char.contains(c) && !c.is_whitespace())
|
||||||
.repeated().at_least(1)
|
.repeated().at_least(1)
|
||||||
.collect()
|
.collect()
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
use crate::{enum_parser, expression::{Expr, Clause, Rule}};
|
use crate::enum_parser;
|
||||||
|
use crate::expression::{Expr, Clause, Rule};
|
||||||
use crate::utils::BoxedIter;
|
use crate::utils::BoxedIter;
|
||||||
|
use crate::utils::Stackframe;
|
||||||
|
|
||||||
use super::expression::xpr_parser;
|
use super::expression::xpr_parser;
|
||||||
use super::import;
|
use super::import;
|
||||||
@@ -19,32 +21,74 @@ pub enum FileEntry {
|
|||||||
Rule(Rule, bool)
|
Rule(Rule, bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn visit_all_names_clause_recur<'a, F>(
|
||||||
|
clause: &'a Clause,
|
||||||
|
binds: Stackframe<String>,
|
||||||
|
mut cb: &mut F
|
||||||
|
) where F: FnMut(&'a Vec<String>) {
|
||||||
|
match clause {
|
||||||
|
Clause::Auto(name, typ, body) => {
|
||||||
|
for x in typ.iter() {
|
||||||
|
visit_all_names_expr_recur(x, binds.clone(), &mut 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(), &mut cb)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Clause::Lambda(name, typ, body) => {
|
||||||
|
for x in typ.iter() {
|
||||||
|
visit_all_names_expr_recur(x, binds.clone(), &mut cb)
|
||||||
|
}
|
||||||
|
for x in body.iter() {
|
||||||
|
visit_all_names_expr_recur(x, binds.push(name.to_owned()), &mut cb)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Clause::S(_, body) => for x in body.iter() {
|
||||||
|
visit_all_names_expr_recur(x, binds.clone(), &mut cb)
|
||||||
|
},
|
||||||
|
Clause::Name{ local, qualified } => {
|
||||||
|
if let Some(name) = local {
|
||||||
|
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
|
/// 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
|
/// aren't names, such as all bound parameters. Generally speaking, this is not a very
|
||||||
/// sophisticated search.
|
/// sophisticated search.
|
||||||
///
|
///
|
||||||
/// TODO: find a way to exclude parameters
|
/// TODO: find a way to exclude parameters
|
||||||
fn find_all_names_recur<'a>(expr: &'a Expr) -> BoxedIter<&'a Vec<String>> {
|
fn visit_all_names_expr_recur<'a, F>(
|
||||||
let proc_clause = |clause: &'a Clause| match clause {
|
expr: &'a Expr,
|
||||||
Clause::Auto(_, typ, body) | Clause::Lambda(_, typ, body) => Box::new(
|
binds: Stackframe<String>,
|
||||||
typ.iter().flat_map(find_all_names_recur)
|
cb: &mut F
|
||||||
.chain(body.iter().flat_map(find_all_names_recur))
|
) where F: FnMut(&'a Vec<String>) {
|
||||||
) as BoxedIter<&'a Vec<String>>,
|
|
||||||
Clause::S(_, body) => Box::new(
|
|
||||||
body.iter().flat_map(find_all_names_recur)
|
|
||||||
),
|
|
||||||
Clause::Name(x) => Box::new(iter::once(x)),
|
|
||||||
_ => Box::new(iter::empty())
|
|
||||||
};
|
|
||||||
let Expr(val, typ) = expr;
|
let Expr(val, typ) = expr;
|
||||||
|
visit_all_names_clause_recur(val, binds.clone(), cb);
|
||||||
if let Some(t) = typ {
|
if let Some(t) = typ {
|
||||||
Box::new(proc_clause(val).chain(find_all_names_recur(t)))
|
visit_all_names_expr_recur(t, binds, cb)
|
||||||
} else { proc_clause(val) }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all names that occur in an expression
|
/// Collect all names that occur in an expression
|
||||||
fn find_all_names(expr: &Expr) -> HashSet<&Vec<String>> {
|
fn find_all_names(expr: &Expr) -> HashSet<&Vec<String>> {
|
||||||
find_all_names_recur(expr).collect()
|
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>> {
|
fn rule_parser() -> impl Parser<Lexeme, (Vec<Expr>, NotNan<f64>, Vec<Expr>), Error = Simple<Lexeme>> {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::{collections::HashMap};
|
use std::{collections::HashMap};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::utils::Substack;
|
use crate::utils::Stackframe;
|
||||||
|
|
||||||
use crate::expression::{Expr, Clause};
|
use crate::expression::{Expr, Clause};
|
||||||
|
|
||||||
@@ -43,10 +43,10 @@ where
|
|||||||
|
|
||||||
/// 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<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
symbol: &Vec<String>,
|
symbol: &'a Vec<String>,
|
||||||
import_path: &Substack<'_, &Vec<String>>
|
import_path: Stackframe<'a, &'a Vec<String>>
|
||||||
) -> Result<Vec<String>, ResolutionError<E>> {
|
) -> Result<Vec<String>, ResolutionError<E>> {
|
||||||
if let Some(cached) = self.cache.get(symbol) { return cached.clone() }
|
if let Some(cached) = self.cache.get(symbol) { 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
|
||||||
@@ -58,7 +58,7 @@ where
|
|||||||
if import_path.iter().any(|el| el == &&new_sym) {
|
if import_path.iter().any(|el| el == &&new_sym) {
|
||||||
Err(ResolutionError::Cycle(import_path.iter().cloned().cloned().collect()))
|
Err(ResolutionError::Cycle(import_path.iter().cloned().cloned().collect()))
|
||||||
} else {
|
} else {
|
||||||
self.find_origin_rec(&new_sym, &import_path.push(symbol))
|
self.find_origin_rec(&new_sym, import_path.push(symbol))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(symbol.clone()) // If not imported, it must be locally defined
|
Ok(symbol.clone()) // If not imported, it must be locally defined
|
||||||
@@ -92,7 +92,10 @@ where
|
|||||||
self.process_exprv_rec(typ)?,
|
self.process_exprv_rec(typ)?,
|
||||||
self.process_exprv_rec(body)?
|
self.process_exprv_rec(body)?
|
||||||
),
|
),
|
||||||
Clause::Name(qualified) => Clause::Name(self.find_origin(qualified)?),
|
Clause::Name{local, qualified} => Clause::Name{
|
||||||
|
local: local.clone(),
|
||||||
|
qualified: self.find_origin(qualified)?
|
||||||
|
},
|
||||||
x => x.clone()
|
x => x.clone()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn find_origin(&mut self, symbol: &Vec<String>) -> Result<Vec<String>, ResolutionError<E>> {
|
pub fn find_origin(&mut self, symbol: &Vec<String>) -> Result<Vec<String>, ResolutionError<E>> {
|
||||||
self.find_origin_rec(symbol, &Substack::new(symbol))
|
self.find_origin_rec(symbol, Stackframe::new(symbol))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ fn prefix_clause(
|
|||||||
typ.iter().map(|e| prefix_expr(e, namespace)).collect(),
|
typ.iter().map(|e| prefix_expr(e, namespace)).collect(),
|
||||||
body.iter().map(|e| prefix_expr(e, namespace)).collect(),
|
body.iter().map(|e| prefix_expr(e, namespace)).collect(),
|
||||||
),
|
),
|
||||||
Clause::Name(name) => Clause::Name (
|
Clause::Name{local, qualified} => Clause::Name{
|
||||||
namespace.iter().chain(name.iter()).cloned().collect()
|
local: local.clone(),
|
||||||
),
|
qualified: namespace.iter().chain(qualified.iter()).cloned().collect()
|
||||||
|
},
|
||||||
x => x.clone()
|
x => x.clone()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,20 +24,20 @@ pub fn rule_collector<F: 'static, ELoad>(
|
|||||||
mut load_mod: F,
|
mut load_mod: F,
|
||||||
prelude: Vec<String>
|
prelude: Vec<String>
|
||||||
// ) -> impl FnMut(Vec<String>) -> Result<&'a Vec<super::Rule>, ParseError<ELoad>> + 'a
|
// ) -> impl FnMut(Vec<String>) -> Result<&'a Vec<super::Rule>, ParseError<ELoad>> + 'a
|
||||||
) -> Cache<Vec<String>, Result<Vec<super::Rule>, ModuleError<ELoad>>>
|
) -> Cache<'static, Vec<String>, Result<Vec<super::Rule>, ModuleError<ELoad>>>
|
||||||
where
|
where
|
||||||
F: FnMut(Vec<String>) -> Result<Loaded, ELoad>,
|
F: FnMut(Vec<String>) -> Result<Loaded, ELoad>,
|
||||||
ELoad: Clone + Debug
|
ELoad: Clone + Debug
|
||||||
{
|
{
|
||||||
// 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: Vec<String>|
|
let loaded = Rc::new(Cache::new(move |path: Vec<String>, _|
|
||||||
-> ParseResult<Loaded, ELoad> {
|
-> ParseResult<Loaded, ELoad> {
|
||||||
load_mod(path).map_err(ModuleError::Load)
|
load_mod(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
|
||||||
let modname = Rc::new(Cache::new({
|
let modname = Rc::new(Cache::new({
|
||||||
let loaded = Rc::clone(&loaded);
|
let loaded = Rc::clone(&loaded);
|
||||||
move |symbol: Vec<String>| -> Result<Vec<String>, Vec<ModuleError<ELoad>>> {
|
move |symbol: Vec<String>, _| -> Result<Vec<String>, Vec<ModuleError<ELoad>>> {
|
||||||
let mut errv: Vec<ModuleError<ELoad>> = Vec::new();
|
let mut errv: Vec<ModuleError<ELoad>> = Vec::new();
|
||||||
let reg_err = |e, errv: &mut Vec<ModuleError<ELoad>>| {
|
let reg_err = |e, errv: &mut Vec<ModuleError<ELoad>>| {
|
||||||
errv.push(e);
|
errv.push(e);
|
||||||
@@ -61,7 +61,7 @@ where
|
|||||||
let preparsed = Rc::new(Cache::new({
|
let preparsed = Rc::new(Cache::new({
|
||||||
let loaded = Rc::clone(&loaded);
|
let loaded = Rc::clone(&loaded);
|
||||||
let prelude2 = prelude.clone();
|
let prelude2 = prelude.clone();
|
||||||
move |path: Vec<String>| -> ParseResult<Vec<FileEntry>, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<Vec<FileEntry>, ELoad> {
|
||||||
let loaded = loaded.try_find(&path)?;
|
let loaded = loaded.try_find(&path)?;
|
||||||
if let Loaded::Module(source) = loaded.as_ref() {
|
if let Loaded::Module(source) = loaded.as_ref() {
|
||||||
Ok(parse::parse(&prelude2, source.as_str())?)
|
Ok(parse::parse(&prelude2, source.as_str())?)
|
||||||
@@ -72,7 +72,7 @@ where
|
|||||||
let exports = Rc::new(Cache::new({
|
let exports = Rc::new(Cache::new({
|
||||||
let loaded = Rc::clone(&loaded);
|
let loaded = Rc::clone(&loaded);
|
||||||
let preparsed = Rc::clone(&preparsed);
|
let preparsed = Rc::clone(&preparsed);
|
||||||
move |path: Vec<String>| -> ParseResult<Vec<String>, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<Vec<String>, ELoad> {
|
||||||
let loaded = loaded.try_find(&path)?;
|
let loaded = loaded.try_find(&path)?;
|
||||||
if let Loaded::Namespace(names) = loaded.as_ref() {
|
if let Loaded::Namespace(names) = loaded.as_ref() {
|
||||||
return Ok(names.clone());
|
return Ok(names.clone());
|
||||||
@@ -88,7 +88,7 @@ where
|
|||||||
let imports = Rc::new(Cache::new({
|
let imports = Rc::new(Cache::new({
|
||||||
let preparsed = Rc::clone(&preparsed);
|
let preparsed = Rc::clone(&preparsed);
|
||||||
let exports = Rc::clone(&exports);
|
let exports = Rc::clone(&exports);
|
||||||
move |path: Vec<String>| -> ParseResult<HashMap<String, Vec<String>>, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<HashMap<String, Vec<String>>, ELoad> {
|
||||||
let entv = preparsed.try_find(&path)?.clone();
|
let entv = preparsed.try_find(&path)?.clone();
|
||||||
let import_entries = parse::imports(entv.iter());
|
let import_entries = parse::imports(entv.iter());
|
||||||
let mut imported_symbols: HashMap<String, Vec<String>> = HashMap::new();
|
let mut imported_symbols: HashMap<String, Vec<String>> = HashMap::new();
|
||||||
@@ -112,7 +112,7 @@ where
|
|||||||
let preparsed = Rc::clone(&preparsed);
|
let preparsed = Rc::clone(&preparsed);
|
||||||
let imports = Rc::clone(&imports);
|
let imports = Rc::clone(&imports);
|
||||||
let loaded = Rc::clone(&loaded);
|
let loaded = Rc::clone(&loaded);
|
||||||
move |path: Vec<String>| -> ParseResult<Vec<FileEntry>, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<Vec<FileEntry>, ELoad> {
|
||||||
let imported_ops: Vec<String> =
|
let imported_ops: Vec<String> =
|
||||||
imports.try_find(&path)?
|
imports.try_find(&path)?
|
||||||
.keys()
|
.keys()
|
||||||
@@ -144,7 +144,7 @@ where
|
|||||||
let exports = Rc::clone(&exports);
|
let exports = Rc::clone(&exports);
|
||||||
let imports = Rc::clone(&imports);
|
let imports = Rc::clone(&imports);
|
||||||
let modname = Rc::clone(&modname);
|
let modname = Rc::clone(&modname);
|
||||||
move |path: Vec<String>| -> ParseResult<Module, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<Module, ELoad> {
|
||||||
let module = Module {
|
let module = Module {
|
||||||
rules: parsed.try_find(&path)?
|
rules: parsed.try_find(&path)?
|
||||||
.iter()
|
.iter()
|
||||||
@@ -182,7 +182,7 @@ where
|
|||||||
}));
|
}));
|
||||||
let all_rules = Cache::new({
|
let all_rules = Cache::new({
|
||||||
let resolved = Rc::clone(&resolved);
|
let resolved = Rc::clone(&resolved);
|
||||||
move |path: Vec<String>| -> ParseResult<Vec<super::Rule>, ELoad> {
|
move |path: Vec<String>, _| -> ParseResult<Vec<super::Rule>, ELoad> {
|
||||||
let mut processed: HashSet<Vec<String>> = HashSet::new();
|
let mut processed: HashSet<Vec<String>> = HashSet::new();
|
||||||
let mut rules: Vec<super::Rule> = Vec::new();
|
let mut rules: Vec<super::Rule> = Vec::new();
|
||||||
let mut pending: VecDeque<Vec<String>> = VecDeque::new();
|
let mut pending: VecDeque<Vec<String>> = VecDeque::new();
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
use std::{fmt, error::Error};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
||||||
pub struct BadState(Vec<String>);
|
|
||||||
|
|
||||||
impl fmt::Display for BadState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(f, "The following key(s) weren't produced by the matching pattern: {:?}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Error for BadState {}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
use crate::expression::Expr;
|
|
||||||
|
|
||||||
use super::{Rule, BadState};
|
|
||||||
|
|
||||||
pub fn execute<Src, Tgt>(src: &Src, tgt: &Tgt, mut input: Vec<Expr>)
|
|
||||||
-> Result<(Vec<Expr>, bool), BadState> where Src: Rule, Tgt: Rule {
|
|
||||||
let (range, state) = match src.scan_slice(&input) {
|
|
||||||
Some(res) => res,
|
|
||||||
None => return Ok((input, false))
|
|
||||||
};
|
|
||||||
let output = tgt.write(&state)?;
|
|
||||||
input.splice(range, output);
|
|
||||||
Ok((input, true))
|
|
||||||
}
|
|
||||||
4
src/rule/executor/mod.rs
Normal file
4
src/rule/executor/mod.rs
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
mod slice_matcher;
|
||||||
|
mod state;
|
||||||
|
|
||||||
|
use state::State;
|
||||||
346
src/rule/executor/slice_matcher.rs
Normal file
346
src/rule/executor/slice_matcher.rs
Normal file
@@ -0,0 +1,346 @@
|
|||||||
|
use hashbrown::HashMap;
|
||||||
|
use itertools::Itertools;
|
||||||
|
|
||||||
|
use crate::expression::{Expr, Clause};
|
||||||
|
use crate::unwrap_or_continue;
|
||||||
|
use crate::utils::{Side, Cache};
|
||||||
|
use super::super::RuleError;
|
||||||
|
use super::State;
|
||||||
|
|
||||||
|
fn split_at_max_vec(pattern: &[Expr]) -> Option<(&[Expr], (&str, usize), &[Expr])> {
|
||||||
|
let rngidx = pattern.iter().position_max_by_key(|ex| {
|
||||||
|
if let Expr(Clause::Placeh(_, Some(prio)), _) = ex { *prio as i64 } else { -1 }
|
||||||
|
})?;
|
||||||
|
let (left, not_left) = pattern.split_at(rngidx);
|
||||||
|
let (placeh, right) = if rngidx == pattern.len() {
|
||||||
|
(¬_left[0].0, [].as_slice())
|
||||||
|
} else {
|
||||||
|
let (placeh_unary_slice, right) = pattern.split_at(rngidx + 1);
|
||||||
|
(&placeh_unary_slice[0].0, right)
|
||||||
|
};
|
||||||
|
if let Clause::Placeh(name, Some(prio)) = placeh {
|
||||||
|
Some((left, (name, *prio), right))
|
||||||
|
} else {None}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matcher that applies a pattern to a slice via divide-and-conquer
|
||||||
|
///
|
||||||
|
/// Upon construction, it selects the clause of highest priority, then
|
||||||
|
/// initializes its internal state for matching that clause and delegates
|
||||||
|
/// the left and right halves of the pattern to two submatchers.
|
||||||
|
///
|
||||||
|
/// Upon matching, it uses a cache to accelerate the process of executing
|
||||||
|
/// a pattern on the entire tree.
|
||||||
|
#[derive(Debug, Clone, Eq)]
|
||||||
|
pub struct SliceMatcherDnC<'a> {
|
||||||
|
/// The entire pattern this will match
|
||||||
|
pattern: &'a [Expr],
|
||||||
|
/// The exact clause this can match
|
||||||
|
clause: &'a Clause,
|
||||||
|
/// Matcher for the parts of the pattern right from us
|
||||||
|
right_subm: Option<Box<SliceMatcherDnC<'a>>>,
|
||||||
|
/// Matcher for the parts of the pattern left from us
|
||||||
|
left_subm: Option<Box<SliceMatcherDnC<'a>>>,
|
||||||
|
/// Matcher for the body of this clause if it has one.
|
||||||
|
/// Must be Some if pattern is (Auto, Lambda or S)
|
||||||
|
body_subm: Option<Box<SliceMatcherDnC<'a>>>,
|
||||||
|
/// Matcher for the type of this expression if it has one (Auto usually does)
|
||||||
|
/// Optional
|
||||||
|
typ_subm: Option<Box<SliceMatcherDnC<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PartialEq for SliceMatcherDnC<'a> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.pattern == other.pattern
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> std::hash::Hash for SliceMatcherDnC<'a> {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.pattern.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SliceMatcherDnC<'a> {
|
||||||
|
/// If this is true, `clause`, `typ_subm`, `body_subm` and `clause_qual_name` are meaningless.
|
||||||
|
/// If it's false, it's also false for both side matchers.
|
||||||
|
pub fn clause_is_vectorial(&self) -> bool {
|
||||||
|
if let Clause::Placeh(_, Some(_)) = self.clause {true} else {false}
|
||||||
|
}
|
||||||
|
/// If clause is a name, the qualified name this can match
|
||||||
|
pub fn clause_qual_name(&self) -> Option<&'a Vec<String>> {
|
||||||
|
if let Clause::Name { qualified, .. } = self.clause {Some(qualified)} else {None}
|
||||||
|
}
|
||||||
|
/// If clause is a Placeh, the key in the state the match will be stored at
|
||||||
|
pub fn state_key(&self) -> Option<&'a String> {
|
||||||
|
if let Clause::Placeh(key, _) = self.clause {Some(key)} else {None}
|
||||||
|
}
|
||||||
|
pub fn own_max_size(&self, total: usize) -> usize {
|
||||||
|
if !self.clause_is_vectorial() {return self.len()}
|
||||||
|
return total - self.min(Side::Left) - self.min(Side::Right)
|
||||||
|
}
|
||||||
|
/// Enumerate all valid subdivisions based on the reported size constraints of self and
|
||||||
|
/// the two subranges
|
||||||
|
pub fn valid_subdivisions<'b>(&self,
|
||||||
|
range: &'b [Expr]
|
||||||
|
) -> impl Iterator<Item = (&'b [Expr], &'b [Expr], &'b [Expr])> {
|
||||||
|
let own_size = self.own_max_size(range.len());
|
||||||
|
let lmin = self.min(Side::Left);
|
||||||
|
let lmax = self.max(Side::Left, range.len());
|
||||||
|
let rmin = self.min(Side::Right);
|
||||||
|
let rmax = self.max(Side::Right, range.len());
|
||||||
|
let full_len = range.len();
|
||||||
|
(1..=own_size).rev().flat_map(move |own_len| {
|
||||||
|
let wiggle = full_len - lmin - rmin - own_len;
|
||||||
|
(0..wiggle).map(move |offset| {
|
||||||
|
let first_break = lmin + offset;
|
||||||
|
let (left, rest) = range.split_at(first_break);
|
||||||
|
let (mid, right) = rest.split_at(own_len);
|
||||||
|
(left, mid, right)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(pattern: &'a [Expr]) -> Self {
|
||||||
|
let (Expr(clause, _), left_subm, right_subm) = if pattern.len() == 1 {
|
||||||
|
(&pattern[0], None, None)
|
||||||
|
} else if let Some((left, _, right)) = split_at_max_vec(pattern) {(
|
||||||
|
&pattern[left.len()],
|
||||||
|
Some(Box::new(Self::new(left))),
|
||||||
|
Some(Box::new(Self::new(right)))
|
||||||
|
)} else {(
|
||||||
|
&pattern[0],
|
||||||
|
None,
|
||||||
|
Some(Box::new(Self::new(&pattern[1..])))
|
||||||
|
)};
|
||||||
|
Self {
|
||||||
|
pattern, right_subm, left_subm, clause,
|
||||||
|
body_subm: clause.body().map(|b| Box::new(Self::new(b))),
|
||||||
|
typ_subm: clause.typ().map(|t| Box::new(Self::new(t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The shortest slice this pattern can match
|
||||||
|
fn len(&self) -> usize {self.pattern.len()}
|
||||||
|
/// Pick a subpattern based on the parameter
|
||||||
|
fn side(&self, side: Side) -> Option<&Box<SliceMatcherDnC<'a>>> {
|
||||||
|
match side {
|
||||||
|
Side::Left => &self.left_subm,
|
||||||
|
Side::Right => &self.right_subm
|
||||||
|
}.as_ref()
|
||||||
|
}
|
||||||
|
/// The shortest slice the given side can match
|
||||||
|
fn min(&self, side: Side) -> usize {self.side(side).map_or(0, |right| right.len())}
|
||||||
|
/// The longest slice the given side can match
|
||||||
|
fn max(&self, side: Side, total: usize) -> usize {
|
||||||
|
self.side(side).map_or(0, |m| if m.clause_is_vectorial() {
|
||||||
|
total - self.min(side.opposite()) - 1
|
||||||
|
} else {m.len()})
|
||||||
|
}
|
||||||
|
/// Take the smallest possible slice from the given side
|
||||||
|
fn slice_min<'b>(&self, side: Side, range: &'b [Expr]) -> &'b [Expr] {
|
||||||
|
side.slice(self.min(side), range)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Matches the body on a range
|
||||||
|
/// # Panics
|
||||||
|
/// when called on an instance that does not have a body (not Auto, Lambda or S)
|
||||||
|
fn match_body<'b>(&'a self,
|
||||||
|
range: &'b [Expr], cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
self.body_subm.as_ref().unwrap().match_range_cached(range, cache)
|
||||||
|
}
|
||||||
|
/// Matches the type and body on respective ranges
|
||||||
|
/// # Panics
|
||||||
|
/// when called on an instance that does not have a body (not Auto, Lambda or S)
|
||||||
|
fn match_parts<'b>(&'a self,
|
||||||
|
typ_range: &'b [Expr], body_range: &'b [Expr],
|
||||||
|
cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
let typ_state = if let Some(typ) = &self.typ_subm {
|
||||||
|
typ.match_range_cached(&typ_range, cache)?
|
||||||
|
} else {State::new()};
|
||||||
|
let body_state = self.match_body(body_range, cache)?;
|
||||||
|
typ_state + body_state
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match the specified side-submatcher on the specified range with the cache
|
||||||
|
/// In absence of a side-submatcher empty ranges are matched to empty state
|
||||||
|
fn apply_side_with_cache<'b>(&'a self,
|
||||||
|
side: Side, range: &'b [Expr],
|
||||||
|
cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
match &self.side(side) {
|
||||||
|
None => {
|
||||||
|
if range.len() != 0 {None}
|
||||||
|
else {Some(State::new())}
|
||||||
|
},
|
||||||
|
Some(m) => cache.try_find(&(range, &m)).map(|s| s.as_ref().to_owned())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn match_range_scalar_cached<'b>(&'a self,
|
||||||
|
target: &'b [Expr],
|
||||||
|
cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
let pos = self.min(Side::Left);
|
||||||
|
if target.len() != self.pattern.len() {return None}
|
||||||
|
let mut own_state = (
|
||||||
|
self.apply_side_with_cache(Side::Left, &target[0..pos], cache)?
|
||||||
|
+ self.apply_side_with_cache(Side::Right, &target[pos+1..], cache)
|
||||||
|
)?;
|
||||||
|
match (self.clause, &target[pos].0) {
|
||||||
|
(Clause::Literal(val), Clause::Literal(tgt)) => {
|
||||||
|
if val == tgt {Some(own_state)} else {None}
|
||||||
|
}
|
||||||
|
(Clause::Placeh(name, None), _) => {
|
||||||
|
own_state.insert(name, &[target[pos].clone()])
|
||||||
|
}
|
||||||
|
(Clause::S(c, _), Clause::S(c_tgt, body_range)) => {
|
||||||
|
if c != c_tgt {return None}
|
||||||
|
own_state + self.match_parts(&[], body_range, cache)
|
||||||
|
}
|
||||||
|
(Clause::Name{qualified, ..}, Clause::Name{qualified: q_tgt, ..}) => {
|
||||||
|
if qualified == q_tgt {Some(own_state)} else {None}
|
||||||
|
}
|
||||||
|
(Clause::Lambda(name, _, _), Clause::Lambda(name_tgt, typ_tgt, body_tgt)) => {
|
||||||
|
// Primarily, the name works as a placeholder
|
||||||
|
if let Some(state_key) = name.strip_prefix("$") {
|
||||||
|
own_state = own_state.insert(
|
||||||
|
state_key,
|
||||||
|
&[Expr(Clause::Name{
|
||||||
|
local: Some(name_tgt.clone()),
|
||||||
|
qualified: vec![name_tgt.clone()]
|
||||||
|
}, None)]
|
||||||
|
)?
|
||||||
|
// But if you're weird like that, it can also work as a constraint
|
||||||
|
} else if name != name_tgt {return None}
|
||||||
|
own_state + self.match_parts(typ_tgt, body_tgt, cache)
|
||||||
|
}
|
||||||
|
(Clause::Auto(name_opt, _, _), Clause::Auto(name_range, typ_range, body_range)) => {
|
||||||
|
if let Some(name) = name_opt {
|
||||||
|
if let Some(state_name) = name.strip_prefix("$") {
|
||||||
|
own_state = own_state.insert(
|
||||||
|
state_name,
|
||||||
|
&[Expr(Clause::Name{
|
||||||
|
local: name_range.clone(),
|
||||||
|
qualified: name_range.as_ref()
|
||||||
|
.map(|s| vec![s.clone()])
|
||||||
|
.unwrap_or_default()
|
||||||
|
}, None)]
|
||||||
|
)?
|
||||||
|
// TODO: Enforce this at construction, on a type system level
|
||||||
|
} else {panic!("Auto patterns may only reference, never enforce the name")}
|
||||||
|
}
|
||||||
|
own_state + self.match_parts(typ_range, body_range, cache)
|
||||||
|
},
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match the range with a vectorial _assuming we are a vectorial_
|
||||||
|
fn match_range_vectorial_cached<'b>(&'a self,
|
||||||
|
name: &str,
|
||||||
|
target: &'b [Expr],
|
||||||
|
cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
// Step through valid slicings based on reported size constraints in order
|
||||||
|
// from longest own section to shortest and from left to right
|
||||||
|
for (left, own, right) in self.valid_subdivisions(target) {
|
||||||
|
let left_result = unwrap_or_continue!(self.apply_side_with_cache(Side::Left, left, cache));
|
||||||
|
let right_result = unwrap_or_continue!(self.apply_side_with_cache(Side::Right, right, cache));
|
||||||
|
return Some(unwrap_or_continue!(
|
||||||
|
right_result.clone()
|
||||||
|
+ left_result.insert(name, own)
|
||||||
|
))
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try and match the specified range
|
||||||
|
pub fn match_range_cached<'b>(&'a self,
|
||||||
|
target: &'b [Expr],
|
||||||
|
cache: &Cache<(&'b [Expr], &'a SliceMatcherDnC<'a>), Option<State>>
|
||||||
|
) -> Option<State> {
|
||||||
|
if self.pattern.len() == 0 {
|
||||||
|
return if target.len() == 0 {Some(State::new())} else {None}
|
||||||
|
}
|
||||||
|
match self.clause {
|
||||||
|
Clause::Placeh(name, Some(_)) => self.match_range_vectorial_cached(name, target, cache),
|
||||||
|
_ => self.match_range_scalar_cached(target, cache)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn match_range(&self, target: &[Expr]) -> Option<State> {
|
||||||
|
self.match_range_cached(target,&Cache::<(&[Expr], &SliceMatcherDnC), _>::new(
|
||||||
|
|(tgt, matcher), cache| {
|
||||||
|
matcher.match_range_cached(tgt, cache)
|
||||||
|
}
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify_scalar_vec(pattern: &Expr, is_vec: &mut HashMap<String, bool>)
|
||||||
|
-> Result<(), String> {
|
||||||
|
let verify_clause = |clause: &Clause, is_vec: &mut HashMap<String, bool>| -> Result<(), String> {
|
||||||
|
match clause {
|
||||||
|
Clause::Placeh(name, prio) => {
|
||||||
|
if let Some(known) = is_vec.get(name) {
|
||||||
|
if known != &prio.is_some() { return Err(name.to_string()) }
|
||||||
|
} else {
|
||||||
|
is_vec.insert(name.clone(), prio.is_some());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Clause::Auto(name, typ, body) => {
|
||||||
|
if let Some(key) = name.as_ref().map(|key| key.strip_prefix("$")).flatten() {
|
||||||
|
if is_vec.get(key) == Some(&true) { return Err(key.to_string()) }
|
||||||
|
}
|
||||||
|
typ.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) => {
|
||||||
|
if let Some(key) = name.strip_prefix("$") {
|
||||||
|
if is_vec.get(key) == Some(&true) { return Err(key.to_string()) }
|
||||||
|
}
|
||||||
|
typ.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
|
||||||
|
body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
|
||||||
|
}
|
||||||
|
Clause::S(_, body) => {
|
||||||
|
body.iter().try_for_each(|e| verify_scalar_vec(e, is_vec))?;
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
let Expr(val, typ_opt) = pattern;
|
||||||
|
verify_clause(val, is_vec)?;
|
||||||
|
if let Some(typ) = typ_opt {
|
||||||
|
verify_scalar_vec(typ, is_vec)?;
|
||||||
|
}
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(mut src: Vec<Expr>, mut tgt: Vec<Expr>, mut input: Vec<Expr>)
|
||||||
|
-> Result<(Vec<Expr>, bool), RuleError> {
|
||||||
|
// Static values
|
||||||
|
let prefix_expr = Expr(Clause::Placeh("::prefix".to_string(), Some(0)), None);
|
||||||
|
let postfix_expr = Expr(Clause::Placeh("::postfix".to_string(), Some(0)), None);
|
||||||
|
// Dimension check
|
||||||
|
let mut is_vec_db = HashMap::new();
|
||||||
|
src.iter().try_for_each(|e| verify_scalar_vec(e, &mut is_vec_db))
|
||||||
|
.map_err(RuleError::ScalarVecMismatch)?;
|
||||||
|
tgt.iter().try_for_each(|e| verify_scalar_vec(e, &mut is_vec_db))
|
||||||
|
.map_err(RuleError::ScalarVecMismatch)?;
|
||||||
|
// Prefix or postfix to match the full vector
|
||||||
|
let head_multi = if let Clause::Placeh(_, Some(_)) = src.first().unwrap().0 {true} else {false};
|
||||||
|
let tail_multi = if let Clause::Placeh(_, Some(_)) = src.last().unwrap().0 {true} else {false};
|
||||||
|
if !head_multi {
|
||||||
|
src.insert(0, prefix_expr.clone());
|
||||||
|
tgt.insert(0, prefix_expr.clone());
|
||||||
|
}
|
||||||
|
if !tail_multi {
|
||||||
|
src.push(postfix_expr.clone());
|
||||||
|
tgt.push(postfix_expr.clone());
|
||||||
|
}
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
89
src/rule/executor/state.rs
Normal file
89
src/rule/executor/state.rs
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
use std::ops::{Add, Index};
|
||||||
|
|
||||||
|
use hashbrown::{HashMap, hash_map::IntoIter};
|
||||||
|
use mappable_rc::Mrc;
|
||||||
|
|
||||||
|
use crate::expression::Expr;
|
||||||
|
|
||||||
|
/// A bucket of indexed expression fragments. Addition may fail if there's a conflict.
|
||||||
|
#[derive(PartialEq, Eq)]
|
||||||
|
pub struct State(HashMap<String, Mrc<Vec<Expr>>>);
|
||||||
|
|
||||||
|
/// Clone without also cloning arbitrarily heavy Expr objects.
|
||||||
|
/// Key is expected to be a very short string with an allocator overhead close to zero.
|
||||||
|
impl Clone for State {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(HashMap::from_iter(
|
||||||
|
self.0.iter().map(|(k, v)| (k.clone(), Mrc::clone(v)))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(HashMap::new())
|
||||||
|
}
|
||||||
|
/// Insert a new element, return None on conflict, clone only on success
|
||||||
|
pub fn insert<S>(mut self, k: &S, v: &[Expr]) -> Option<State>
|
||||||
|
where S: AsRef<str> + ToString + ?Sized {
|
||||||
|
if let Some(old) = self.0.get(k.as_ref()) {
|
||||||
|
if old.as_ref() != v {return None}
|
||||||
|
} else {
|
||||||
|
self.0.insert(k.to_string(), Mrc::new(v.to_vec()));
|
||||||
|
}
|
||||||
|
return Some(self)
|
||||||
|
}
|
||||||
|
/// Insert a new entry, return None on conflict
|
||||||
|
pub fn insert_pair(mut self, (k, v): (String, Mrc<Vec<Expr>>)) -> Option<State> {
|
||||||
|
if let Some(old) = self.0.get(&k) {
|
||||||
|
if old != &v {return None}
|
||||||
|
} else {
|
||||||
|
self.0.insert(k, v);
|
||||||
|
}
|
||||||
|
return Some(self)
|
||||||
|
}
|
||||||
|
/// Returns `true` if the state contains no data
|
||||||
|
pub fn empty(&self) -> bool {
|
||||||
|
self.0.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add for State {
|
||||||
|
type Output = Option<State>;
|
||||||
|
|
||||||
|
fn add(mut self, rhs: Self) -> Self::Output {
|
||||||
|
if self.empty() {
|
||||||
|
return Some(rhs)
|
||||||
|
}
|
||||||
|
for pair in rhs.0 {
|
||||||
|
self = self.insert_pair(pair)?
|
||||||
|
}
|
||||||
|
return Some(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Add<Option<State>> for State {
|
||||||
|
type Output = Option<State>;
|
||||||
|
|
||||||
|
fn add(self, rhs: Option<State>) -> Self::Output {
|
||||||
|
rhs.and_then(|s| self + s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, S> Index<&S> for State where S: AsRef<str> {
|
||||||
|
type Output = Vec<Expr>;
|
||||||
|
|
||||||
|
fn index(&self, index: &S) -> &Self::Output {
|
||||||
|
return &self.0[index.as_ref()]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoIterator for State {
|
||||||
|
type Item = (String, Mrc<Vec<Expr>>);
|
||||||
|
|
||||||
|
type IntoIter = IntoIter<String, Mrc<Vec<Expr>>>;
|
||||||
|
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
mod rule;
|
// mod rule;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod bad_state_error;
|
mod rule_error;
|
||||||
|
|
||||||
pub use rule::Rule;
|
// pub use rule::Rule;
|
||||||
pub use bad_state_error::BadState;
|
pub use rule_error::RuleError;
|
||||||
18
src/rule/rule_error.rs
Normal file
18
src/rule/rule_error.rs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
use std::{fmt, error::Error};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub enum RuleError {
|
||||||
|
BadState(String),
|
||||||
|
ScalarVecMismatch(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for RuleError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::BadState(key) => write!(f, "Key {:?} not in match pattern", key),
|
||||||
|
Self::ScalarVecMismatch(key) =>
|
||||||
|
write!(f, "Key {:?} used inconsistently with and without ellipsis", key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Error for RuleError {}
|
||||||
@@ -4,16 +4,21 @@ use mappable_rc::Mrc;
|
|||||||
|
|
||||||
/// 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<I, O: 'static> where O: Clone {
|
pub struct Cache<'a, I, O: 'static> /*where O: Clone*/ {
|
||||||
store: RefCell<HashMap<I, Mrc<O>>>,
|
store: RefCell<HashMap<I, Mrc<O>>>,
|
||||||
closure: RefCell<Box<dyn FnMut (I) -> O + 'static>>
|
closure: RefCell<Box<dyn FnMut (I, &Self) -> Mrc<O> + 'a>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, O> Cache<I, O> where
|
impl<'a, I, O> Cache<'a, I, O> where
|
||||||
I: Eq + Hash + Clone,
|
I: Eq + Hash + Clone
|
||||||
O: Clone
|
|
||||||
{
|
{
|
||||||
pub fn new<F: 'static>(closure: F) -> Self where F: FnMut(I) -> O {
|
pub fn new<F: 'a>(mut closure: F) -> Self where F: FnMut(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: FnMut(I, &Self) -> Mrc<O> {
|
||||||
Self {
|
Self {
|
||||||
store: RefCell::new(HashMap::new()),
|
store: RefCell::new(HashMap::new()),
|
||||||
closure: RefCell::new(Box::new(closure))
|
closure: RefCell::new(Box::new(closure))
|
||||||
@@ -25,7 +30,7 @@ impl<I, O> Cache<I, O> where
|
|||||||
let mut closure = self.closure.borrow_mut();
|
let mut closure = self.closure.borrow_mut();
|
||||||
let mut store = self.store.borrow_mut();
|
let mut store = self.store.borrow_mut();
|
||||||
Mrc::clone(store.raw_entry_mut().from_key(i)
|
Mrc::clone(store.raw_entry_mut().from_key(i)
|
||||||
.or_insert_with(|| (i.clone(), Mrc::new(closure(i.clone())))).1)
|
.or_insert_with(|| (i.clone(), closure(i.clone(), self))).1)
|
||||||
}
|
}
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// Return the result if it has already been computed
|
/// Return the result if it has already been computed
|
||||||
@@ -40,9 +45,9 @@ impl<I, O> Cache<I, O> where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, O, E> Cache<I, Result<O, E>> where
|
impl<'a, I, O, E> Cache<'a, I, Result<O, E>> where
|
||||||
I: Eq + Hash + Clone,
|
I: Eq + Hash + Clone,
|
||||||
O: Clone,
|
// O: Clone,
|
||||||
E: Clone
|
E: Clone
|
||||||
{
|
{
|
||||||
/// Sink the ref from a Result into the Ok value, such that cloning only occurs on the sad path
|
/// Sink the ref from a Result into the Ok value, such that cloning only occurs on the sad path
|
||||||
@@ -54,9 +59,9 @@ impl<I, O, E> Cache<I, Result<O, E>> where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, O> Cache<I, Option<O>> where
|
impl<'a, I, O> Cache<'a, I, Option<O>> where
|
||||||
I: Eq + Hash + Clone,
|
I: Eq + Hash + Clone,
|
||||||
O: Clone
|
// O: Clone
|
||||||
{
|
{
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
/// Sink the ref from an Option into the Some value such that the return value can be
|
/// Sink the ref from an Option into the Some value such that the return value can be
|
||||||
@@ -65,4 +70,4 @@ impl<I, O> Cache<I, Option<O>> where
|
|||||||
let ent = self.find(i);
|
let ent = self.find(i);
|
||||||
Mrc::try_map(ent, |o| o.as_ref()).ok()
|
Mrc::try_map(ent, |o| o.as_ref()).ok()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
27
src/utils/merge_sorted.rs
Normal file
27
src/utils/merge_sorted.rs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
mod cache;
|
mod cache;
|
||||||
mod substack;
|
mod substack;
|
||||||
|
mod side;
|
||||||
|
mod merge_sorted;
|
||||||
|
mod sorted_pairs;
|
||||||
|
mod unwrap_or_continue;
|
||||||
pub use cache::Cache;
|
pub use cache::Cache;
|
||||||
pub use substack::Substack;
|
pub use substack::Stackframe;
|
||||||
|
pub use side::Side;
|
||||||
|
pub use merge_sorted::merge_sorted;
|
||||||
|
|
||||||
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
pub type BoxedIter<'a, T> = Box<dyn Iterator<Item = T> + 'a>;
|
||||||
53
src/utils/side.rs
Normal file
53
src/utils/side.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
pub enum Side {Left, Right}
|
||||||
|
|
||||||
|
impl Display for Side {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Left => write!(f, "Left"),
|
||||||
|
Self::Right => write!(f, "Right"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Side {
|
||||||
|
pub fn opposite(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Left => Self::Right,
|
||||||
|
Self::Right => Self::Left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Shorthand for opposite
|
||||||
|
pub fn inv(&self) -> Self { self.opposite() }
|
||||||
|
/// take N elements from this end of a slice
|
||||||
|
pub fn slice<'a, T>(&self, size: usize, slice: &'a [T]) -> &'a [T] {
|
||||||
|
match self {
|
||||||
|
Side::Left => &slice[..size],
|
||||||
|
Side::Right => &slice[slice.len() - size..]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// ignore N elements from this end of a slice
|
||||||
|
pub fn crop<'a, T>(&self, margin: usize, slice: &'a [T]) -> &'a [T] {
|
||||||
|
self.opposite().slice(slice.len() - margin, slice)
|
||||||
|
}
|
||||||
|
/// ignore N elements from this end and M elements from the other end of a slice
|
||||||
|
pub fn crop_both<'a, T>(&self, margin: usize, opposite: usize, slice: &'a [T]) -> &'a [T] {
|
||||||
|
self.crop(margin, self.opposite().crop(opposite, slice))
|
||||||
|
}
|
||||||
|
/// Pick this side from a pair of things
|
||||||
|
pub fn pick<T>(&self, pair: (T, T)) -> T {
|
||||||
|
match self {
|
||||||
|
Side::Left => pair.0,
|
||||||
|
Side::Right => pair.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Make a pair with the first element on this side
|
||||||
|
pub fn pair<T>(&self, this: T, opposite: T) -> (T, T) {
|
||||||
|
match self {
|
||||||
|
Side::Left => (this, opposite),
|
||||||
|
Side::Right => (opposite, this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
35
src/utils/sorted_pairs.rs
Normal file
35
src/utils/sorted_pairs.rs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
use std::ops::Add;
|
||||||
|
|
||||||
|
/// Combine two sorted iterators with their mapper function into a sorted iterator of pairs
|
||||||
|
pub struct SortedPairs<L, R, IL, IR, ML, MR, O> {
|
||||||
|
left: IL, right: IR,
|
||||||
|
left_map: ML, right_map: MR,
|
||||||
|
left_buf: Vec<(L, O)>, right_buf: Vec<(R, O)>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R, IL, IR, ML, MR, O> SortedPairs<L, R, IL, IR, ML, MR, O>
|
||||||
|
where IL: Iterator<Item = L>, IR: Iterator<Item = R>,
|
||||||
|
ML: Fn(L) -> O, MR: Fn(R) -> O,
|
||||||
|
O: Ord + Add + Clone
|
||||||
|
{
|
||||||
|
pub fn new(left: IL, right: IR, left_map: ML, right_map: MR) -> Self {
|
||||||
|
Self {
|
||||||
|
left, right, left_map, right_map,
|
||||||
|
left_buf: Vec::new(),
|
||||||
|
right_buf: Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, L: 'a, R: 'a, IL: 'a, IR: 'a, ML: 'a, MR: 'a, O: 'a> Iterator
|
||||||
|
for &'a mut SortedPairs<L, R, IL, IR, ML, MR, O>
|
||||||
|
where IL: Iterator<Item = L>, IR: Iterator<Item = R>,
|
||||||
|
ML: Fn(L) -> O, MR: Fn(R) -> O,
|
||||||
|
O: Ord + Add + Clone,
|
||||||
|
{
|
||||||
|
type Item = (&'a L, &'a R);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,45 +1,55 @@
|
|||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
/// Implement a FILO stack that lives on the regular call stack as a linked list.
|
/// Implement 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
|
/// Mainly useful to detect loops in recursive algorithms where the recursion isn't
|
||||||
/// deep enough to warrant a heap-allocated set
|
/// deep enough to warrant a heap-allocated set
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Substack<'a, T> {
|
pub struct Stackframe<'a, T> {
|
||||||
pub item: T,
|
pub item: T,
|
||||||
pub prev: Option<&'a Self>
|
pub prev: Option<&'a Stackframe<'a, T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Substack<'a, T> {
|
impl<'a, T: 'a> Stackframe<'a, T> {
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn item(&self) -> &T { &self.item }
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub fn prev(&self) -> Option<&'a Substack<'a, T>> { self.prev }
|
|
||||||
|
|
||||||
pub fn new(item: T) -> Self {
|
pub fn new(item: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
item,
|
item,
|
||||||
prev: None
|
prev: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn push(&'a self, item: T) -> Self {
|
/// Get the item owned by this listlike, very fast O(1)
|
||||||
Self {
|
pub fn item(&self) -> &T { &self.item }
|
||||||
|
/// Get the next link in the list, very fast O(1)
|
||||||
|
pub fn prev(&self) -> Option<&'a Stackframe<T>> { self.prev }
|
||||||
|
/// Construct an iterator over the listlike, very fast O(1)
|
||||||
|
pub fn iter(&self) -> StackframeIterator<T> {
|
||||||
|
StackframeIterator { curr: Some(self) }
|
||||||
|
}
|
||||||
|
pub fn push(&self, item: T) -> Stackframe<'_, T> {
|
||||||
|
Stackframe {
|
||||||
item,
|
item,
|
||||||
prev: Some(self)
|
prev: Some(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn iter(&'a self) -> SubstackIterator<'a, T> {
|
}
|
||||||
SubstackIterator { curr: Some(self) }
|
|
||||||
|
impl<'a, T> Debug for Stackframe<'a, T> where T: Debug {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Substack")?;
|
||||||
|
f.debug_list().entries(self.iter()).finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SubstackIterator<'a, T> {
|
pub struct StackframeIterator<'a, T> {
|
||||||
curr: Option<&'a Substack<'a, T>>
|
curr: Option<&'a Stackframe<'a, T>>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> Iterator for SubstackIterator<'a, T> {
|
impl<'a, T> Iterator for StackframeIterator<'a, T> {
|
||||||
type Item = &'a T;
|
type Item = &'a T;
|
||||||
fn next(&mut self) -> Option<&'a T> {
|
fn next(&mut self) -> Option<&'a T> {
|
||||||
let Substack{ item, prev } = self.curr?;
|
let curr = self.curr?;
|
||||||
self.curr = *prev;
|
let item = curr.item();
|
||||||
|
let prev = curr.prev();
|
||||||
|
self.curr = prev;
|
||||||
Some(item)
|
Some(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/utils/unwrap_or_continue.rs
Normal file
6
src/utils/unwrap_or_continue.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! unwrap_or_continue {
|
||||||
|
($m:expr) => {
|
||||||
|
{ if let Some(res) = ($m) {res} else {continue} }
|
||||||
|
}
|
||||||
|
}
|
||||||
1
swap.md
Normal file
1
swap.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Optimizations mostly left for later, len() was critical, should make most things O(N) instead of O(N!). A trivial keyword cache in the executor should prevent trying variable length patterns onto windows of unrelated sequences. Investigate different strategies as issue likely to re-emerge with marginal added pattern complexity
|
||||||
Reference in New Issue
Block a user