modular_rs/core/
pattern.rs1use 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}