mod eval;
mod glob;
mod parser;
mod pattern;
mod predicate;
use std::fmt;
use std::str::FromStr;
use crate::sdf::Path;
pub use eval::{IncrementalSearcher, PathExpressionEval};
pub use glob::GlobPattern;
pub use pattern::{Component, PathPattern};
pub use predicate::{
FnArg, FnCall, FnCallKind, PredResult, PredicateArg, PredicateBinder, PredicateExpression, PredicateFn,
PredicateLibrary, PredicateProgram, link_predicate_expression,
};
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("{message}")]
pub struct EvalError {
message: String,
}
impl EvalError {
pub(crate) fn new(message: impl Into<String>) -> Self {
Self {
message: message.into(),
}
}
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct PathExpression(Repr);
#[derive(Debug, Clone, PartialEq, Default)]
enum Repr {
#[default]
Nothing,
Expr(ExprNode),
Invalid { text: String, message: String },
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ExprNode {
Pattern(PathPattern),
Reference(ExpressionReference),
Complement(Box<ExprNode>),
Op(SetOp, Box<ExprNode>, Box<ExprNode>),
}
enum Atom {
Pattern(PathPattern),
Reference(ExpressionReference),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetOp {
ImpliedUnion,
Union,
Intersection,
Difference,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ExpressionReference {
pub path: Path,
pub name: String,
}
impl ExpressionReference {
pub fn weaker() -> Self {
ExpressionReference {
path: Path::default(),
name: "_".to_string(),
}
}
pub fn is_weaker(&self) -> bool {
self.path.is_empty() && self.name == "_"
}
}
impl PathExpression {
pub fn parse(text: &str) -> Self {
if text.is_empty() {
return Self::nothing();
}
parser::parse_path_expression(text)
}
pub fn everything() -> Self {
Self::make_atom_pattern(PathPattern::everything())
}
pub fn every_descendant() -> Self {
Self::make_atom_pattern(PathPattern::every_descendant())
}
pub fn nothing() -> Self {
Self::default()
}
pub fn make_atom_pattern(pattern: PathPattern) -> Self {
PathExpression(Repr::Expr(ExprNode::Pattern(pattern)))
}
pub fn make_atom_reference(reference: ExpressionReference) -> Self {
PathExpression(Repr::Expr(ExprNode::Reference(reference)))
}
fn invalid(text: String, message: String) -> Self {
PathExpression(Repr::Invalid { text, message })
}
pub fn make_complement(expr: PathExpression) -> Self {
if expr == Self::everything() {
return Self::nothing();
}
match expr.0 {
Repr::Nothing | Repr::Invalid { .. } => Self::everything(),
Repr::Expr(ExprNode::Complement(inner)) => PathExpression(Repr::Expr(*inner)),
Repr::Expr(node) => PathExpression(Repr::Expr(ExprNode::Complement(Box::new(node)))),
}
}
pub fn make_op(op: SetOp, left: PathExpression, right: PathExpression) -> Self {
let mut op = op;
let mut right = right;
let is_constant = |e: &PathExpression| e.is_empty() || *e == Self::everything();
if op == SetOp::Difference && (is_constant(&left) || is_constant(&right)) {
op = SetOp::Intersection;
right = Self::make_complement(right);
}
if left.is_empty() {
return if op == SetOp::Intersection {
Self::nothing()
} else {
right
};
}
if right.is_empty() {
return if op == SetOp::Intersection {
Self::nothing()
} else {
left
};
}
if left == Self::everything() {
return if op == SetOp::Intersection {
right
} else {
Self::everything()
};
}
if right == Self::everything() {
return if op == SetOp::Intersection {
left
} else {
Self::everything()
};
}
match (left.0, right.0) {
(Repr::Expr(left), Repr::Expr(right)) => {
PathExpression(Repr::Expr(ExprNode::Op(op, Box::new(left), Box::new(right))))
}
_ => unreachable!("empty operands were absorbed above"),
}
}
pub fn is_empty(&self) -> bool {
!matches!(self.0, Repr::Expr(_))
}
pub fn parse_error(&self) -> Option<&str> {
match &self.0 {
Repr::Invalid { message, .. } => Some(message),
_ => None,
}
}
pub fn contains_expression_references(&self) -> bool {
self.any_reference(|_| true)
}
pub fn contains_weaker_reference(&self) -> bool {
self.any_reference(ExpressionReference::is_weaker)
}
pub fn is_absolute(&self) -> bool {
!self.any_atom(
|pattern| !pattern.prefix().is_abs(),
|reference| !reference.path.is_empty() && !reference.path.is_abs(),
)
}
pub fn is_complete(&self) -> bool {
!self.contains_expression_references() && self.is_absolute()
}
pub fn make_absolute(self, anchor: &Path) -> Self {
self.map_atoms(&mut |atom| match atom {
Atom::Pattern(mut pattern) => {
pattern.set_prefix(anchor_path(anchor, pattern.prefix()));
Self::make_atom_pattern(pattern)
}
Atom::Reference(mut reference) => {
if !reference.path.is_empty() && !reference.path.is_abs() {
reference.path = anchor.make_absolute(&reference.path);
}
Self::make_atom_reference(reference)
}
})
}
pub fn map_paths(self, mut f: impl FnMut(&Path) -> Option<Path>) -> Self {
self.map_atoms(&mut |atom| match atom {
Atom::Pattern(mut pattern) => match f(pattern.prefix()) {
Some(mapped) => {
pattern.set_prefix(mapped);
Self::make_atom_pattern(pattern)
}
None => Self::nothing(),
},
Atom::Reference(mut reference) => {
if reference.path.is_empty() {
return Self::make_atom_reference(reference);
}
match f(&reference.path) {
Some(mapped) => {
reference.path = mapped;
Self::make_atom_reference(reference)
}
None => Self::nothing(),
}
}
})
}
pub fn replace_prefix(self, old: &Path, new: &Path) -> Self {
self.map_atoms(&mut |atom| match atom {
Atom::Pattern(mut pattern) => {
if let Some(replaced) = pattern
.prefix()
.has_prefix(old)
.then(|| pattern.prefix().replace_prefix(old, new))
.flatten()
{
pattern.set_prefix(replaced);
}
Self::make_atom_pattern(pattern)
}
Atom::Reference(mut reference) => {
if let Some(replaced) = reference
.path
.has_prefix(old)
.then(|| reference.path.replace_prefix(old, new))
.flatten()
{
reference.path = replaced;
}
Self::make_atom_reference(reference)
}
})
}
pub fn resolve_references(self, resolve: &mut impl FnMut(&ExpressionReference) -> PathExpression) -> Self {
self.map_atoms(&mut |atom| match atom {
Atom::Pattern(pattern) => Self::make_atom_pattern(pattern),
Atom::Reference(reference) => resolve(&reference),
})
}
pub fn compose_over(self, weaker: &PathExpression) -> Self {
if self.is_empty() {
return self;
}
self.resolve_references(&mut |reference| {
if reference.is_weaker() {
weaker.clone()
} else {
Self::make_atom_reference(reference.clone())
}
})
}
fn any_reference(&self, wanted: impl Fn(&ExpressionReference) -> bool) -> bool {
self.any_atom(|_| false, wanted)
}
fn any_atom(
&self,
pattern: impl Fn(&PathPattern) -> bool,
reference: impl Fn(&ExpressionReference) -> bool,
) -> bool {
fn walk(
node: &ExprNode,
pattern: &impl Fn(&PathPattern) -> bool,
reference: &impl Fn(&ExpressionReference) -> bool,
) -> bool {
match node {
ExprNode::Pattern(p) => pattern(p),
ExprNode::Reference(r) => reference(r),
ExprNode::Complement(inner) => walk(inner, pattern, reference),
ExprNode::Op(_, left, right) => walk(left, pattern, reference) || walk(right, pattern, reference),
}
}
match &self.0 {
Repr::Expr(root) => walk(root, &pattern, &reference),
_ => false,
}
}
fn map_atoms(self, f: &mut impl FnMut(Atom) -> PathExpression) -> Self {
fn rebuild(node: ExprNode, f: &mut impl FnMut(Atom) -> PathExpression) -> PathExpression {
match node {
ExprNode::Pattern(pattern) => f(Atom::Pattern(pattern)),
ExprNode::Reference(reference) => f(Atom::Reference(reference)),
ExprNode::Complement(inner) => PathExpression::make_complement(rebuild(*inner, f)),
ExprNode::Op(op, left, right) => PathExpression::make_op(op, rebuild(*left, f), rebuild(*right, f)),
}
}
let Repr::Expr(root) = self.0 else {
return self;
};
rebuild(root, f)
}
pub(super) fn root(&self) -> Option<&ExprNode> {
match &self.0 {
Repr::Expr(root) => Some(root),
_ => None,
}
}
fn fmt_node(node: &ExprNode, parent_rank: u8, right_operand: bool, out: &mut String) {
let rank = match node {
ExprNode::Pattern(_) | ExprNode::Reference(_) => 0,
ExprNode::Complement(_) => 1,
ExprNode::Op(SetOp::ImpliedUnion, ..) => 2,
ExprNode::Op(SetOp::Union, ..) => 3,
ExprNode::Op(SetOp::Intersection, ..) => 4,
ExprNode::Op(SetOp::Difference, ..) => 5,
};
let parenthesize = rank > parent_rank || (rank == parent_rank && right_operand);
if parenthesize {
out.push('(');
}
match node {
ExprNode::Pattern(pattern) => out.push_str(&pattern.to_string()),
ExprNode::Reference(reference) => {
out.push('%');
out.push_str(reference.path.as_str());
if reference.is_weaker() {
out.push('_');
} else {
out.push(':');
out.push_str(&reference.name);
}
}
ExprNode::Complement(inner) => {
out.push('~');
Self::fmt_node(inner, rank, false, out);
}
ExprNode::Op(op, left, right) => {
Self::fmt_node(left, rank, false, out);
out.push_str(match op {
SetOp::ImpliedUnion => " ",
SetOp::Union => " + ",
SetOp::Intersection => " & ",
SetOp::Difference => " - ",
});
Self::fmt_node(right, rank, true, out);
}
}
if parenthesize {
out.push(')');
}
}
}
impl fmt::Display for PathExpression {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.0 {
Repr::Nothing => Ok(()),
Repr::Expr(root) => {
let mut out = String::new();
Self::fmt_node(root, u8::MAX, false, &mut out);
f.write_str(&out)
}
Repr::Invalid { text, .. } => f.write_str(text),
}
}
}
impl FromStr for PathExpression {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(PathExpression::parse(s))
}
}
fn anchor_path(anchor: &Path, prefix: &Path) -> Path {
if prefix.is_abs() {
return prefix.clone();
}
if prefix.as_str() == "." {
return anchor.clone();
}
anchor.make_absolute(prefix)
}