modular_rs/core/
pattern.rs

1use anyhow::anyhow;
2use nom::branch::alt;
3use nom::bytes::complete::{escaped_transform, is_not, tag};
4use nom::character::complete::{alpha1, alphanumeric1, one_of};
5use nom::combinator::{eof, map, opt, recognize};
6use nom::multi::{many0, many0_count};
7use nom::sequence::{pair, preceded, tuple};
8use nom::IResult;
9use std::fmt::{Display, Formatter};
10
11#[derive(Debug, Clone)]
12pub struct Pattern {
13    nodes: Vec<Node>,
14    is_trailing_any: bool,
15}
16
17impl Display for Pattern {
18    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
19        let mut out = Vec::with_capacity(self.nodes.len() + 1);
20        for node in &self.nodes {
21            match node {
22                Node::Arg(Some(v)) => out.push(format!("{{{}}}", v)),
23                Node::Arg(None) => out.push("{}".to_owned()),
24                Node::Const(v) => out.push(v.to_owned()),
25            }
26        }
27
28        if self.is_trailing_any {
29            out.push(">".to_owned());
30        }
31
32        write!(f, "{}", out.join("."))
33    }
34}
35
36impl Pattern {
37    pub fn parse<S: AsRef<str>>(str: S) -> anyhow::Result<Self> {
38        let str = str.as_ref();
39
40        let (_, (nodes, is_trailing_any)) =
41            parse(str).map_err(|e| anyhow!("invalid pattern: {:?}", e))?;
42
43        Ok(Self {
44            nodes,
45            is_trailing_any,
46        })
47    }
48
49    pub fn matches<S: AsRef<str>>(&self, str: S) -> bool {
50        let mut nodes_iter = self.nodes.iter();
51        let other_iter = str.as_ref().split('.');
52
53        for other_node in other_iter {
54            match nodes_iter.next() {
55                Some(v) => match v {
56                    Node::Arg(_) => continue,
57                    Node::Const(v) => {
58                        if v != other_node {
59                            return false;
60                        }
61                    }
62                },
63                None => {
64                    return self.is_trailing_any;
65                }
66            }
67        }
68
69        true
70    }
71}
72
73#[derive(Debug, Clone)]
74enum Node {
75    Arg(Option<String>),
76    Const(String),
77}
78
79fn parse(str: &str) -> IResult<&str, (Vec<Node>, bool)> {
80    let (rest, (mut start_nodes, is_trailing)) = alt((
81        map(trailing_any, |is_trailing_any| (vec![], is_trailing_any)),
82        map(node, |e| (vec![e], false)),
83    ))(str)?;
84
85    if is_trailing {
86        return Ok((rest, (start_nodes, is_trailing)));
87    }
88
89    let (rest, nodes) = many0(preceded(tag("."), node))(rest)?;
90    let (rest, is_trailing_any) = trailing_any(rest)?;
91
92    start_nodes.extend(nodes);
93
94    Ok((rest, (start_nodes, is_trailing_any)))
95}
96
97fn node(str: &str) -> IResult<&str, Node> {
98    alt((const_node, arg_node))(str).map(|(s, node)| (s, node))
99}
100
101fn const_node(str: &str) -> IResult<&str, Node> {
102    escaped_node(str).map(|(s, v)| (s, Node::Const(v)))
103}
104
105#[rustfmt::skip]
106fn escaped_node(str: &str) -> IResult<&str, String> {
107    if str.is_empty() {
108        return Err(nom::Err::Error(nom::error::Error::new(
109            str,
110            nom::error::ErrorKind::Eof,
111        )));
112    }
113    
114    escaped_transform(
115        is_not("{}.\\>"), 
116        '\\', 
117        one_of("{}.\\>")
118    )(str)
119}
120
121#[rustfmt::skip]
122fn arg_node(str: &str) -> IResult<&str, Node> {
123    pub fn identifier(input: &str) -> IResult<&str, &str> {
124        recognize(
125            pair(
126                alt((alpha1, tag("_"))),
127                many0_count(alt((alphanumeric1, tag("_"))))
128            )
129        )(input)
130    }
131    
132    tuple((
133        tag("{"),
134        opt(identifier),
135        tag("}"),
136    ))(str)
137    .map(|(s, (_, arg, _))| (s, Node::Arg(arg.map(|i| i.to_string()))))
138}
139
140#[rustfmt::skip]
141fn trailing_any(str: &str) -> IResult<&str, bool> {
142    tuple((opt(tag(".>")), eof))(str)
143        .map(|(v, out)| (v, out.0.is_some()))
144}