1use crate::ast::*;
2
3use std::backtrace::Backtrace;
4use std::ops::Not as _;
5use std::panic::Location;
6
7use rust_utils::default::default;
8
9#[derive(Debug)]
10pub struct Error<'p> {
11 pub input: &'p str,
12 pub location: &'static Location<'static>,
13 pub backtrace: Backtrace,
14}
15
16pub type Result<'p, T, E = Error<'p>> = std::result::Result<T, E>;
17
18macro_rules! ensure {
19 ($s:expr, $cond:expr) => {
20 if !$cond {
21 return Err(Error {
22 input: $s,
23 location: Location::caller(),
24 backtrace: Backtrace::capture(),
25 });
26 }
27 };
28}
29
30pub fn parse(mut s: &str) -> Result<Expr> {
31 let s = &mut s;
32 skip_space0(s);
33 let expr = parse_expr(s)?;
34 skip_space0(s);
35 ensure!(s, s.is_empty());
36 Ok(expr)
37}
38
39fn take_while1<'p>(s: &mut &'p str, f: impl Fn(char) -> bool) -> Result<'p, &'p str> {
40 let end = s
41 .char_indices()
42 .find_map(|(i, c)| f(c).not().then_some(i))
43 .unwrap_or(s.len());
44
45 ensure!(s, end > 0);
46
47 let (ans, rest) = s.split_at(end);
48 *s = rest;
49 Ok(ans)
50}
51
52fn consume_tag<'p>(s: &mut &'p str, tag: &str) -> Result<'p, &'p str> {
53 ensure!(s, s.starts_with(tag));
54 let (ans, rest) = s.split_at(tag.len());
55 *s = rest;
56 Ok(ans)
57}
58
59fn skip_space0(s: &mut &str) {
60 *s = s.trim_start();
61}
62
63fn skip_tag<'p>(s: &mut &'p str, tag: &str) -> Option<&'p str> {
64 if s.starts_with(tag) {
65 let (ans, rest) = s.split_at(tag.len());
66 *s = rest;
67 Some(ans)
68 } else {
69 None
70 }
71}
72
73fn parse_expr<'p>(s: &mut &'p str) -> Result<'p, Expr> {
76 if s.starts_with("any") {
77 Ok(expr(parse_any(s)?))
78 } else if s.starts_with("all") {
79 Ok(expr(parse_all(s)?))
80 } else if s.starts_with("not") {
81 Ok(expr(parse_not(s)?))
82 } else {
83 Ok(expr(parse_pred(s)?))
84 }
85}
86
87fn parse_pred<'p>(s: &mut &'p str) -> Result<'p, Pred> {
88 let key = parse_identifier(s)?;
89
90 let has_value = s.trim_start().starts_with('=');
91
92 let value = if has_value {
93 skip_space0(s);
94 skip_tag(s, "=");
95 skip_space0(s);
96 Some(parse_string_literal(s)?)
97 } else {
98 None
99 };
100
101 Ok(Pred {
102 key: key.into(),
103 value: value.map(Into::into),
104 })
105}
106
107fn parse_identifier<'p>(s: &mut &'p str) -> Result<'p, &'p str> {
109 ensure!(s, s.starts_with(|c| matches!(c, 'a'..='z'|'A'..='Z'|'_')));
110 take_while1(s, |c| matches!(c, 'a'..='z'|'A'..='Z'|'0'..='9'|'_'))
111}
112
113fn parse_string_literal<'p>(s: &mut &'p str) -> Result<'p, &'p str> {
115 consume_tag(s, "\"")?;
116
117 let ans = take_while1(s, |c| c != '"')?;
118 assert!(ans.contains('\\').not());
119
120 consume_tag(s, "\"")?;
121
122 Ok(ans)
123}
124
125fn parse_any<'p>(s: &mut &'p str) -> Result<'p, Any<Pred>> {
126 consume_tag(s, "any")?;
127 skip_space0(s);
128 consume_tag(s, "(")?;
129 let list = parse_expr_list(s)?;
130 skip_space0(s);
131 consume_tag(s, ")")?;
132 Ok(any(list))
133}
134
135fn parse_all<'p>(s: &mut &'p str) -> Result<'p, All<Pred>> {
136 consume_tag(s, "all")?;
137 skip_space0(s);
138 consume_tag(s, "(")?;
139 let list = parse_expr_list(s)?;
140 skip_space0(s);
141 consume_tag(s, ")")?;
142 Ok(all(list))
143}
144
145fn parse_not<'p>(s: &mut &'p str) -> Result<'p, Not<Pred>> {
146 consume_tag(s, "not")?;
147 skip_space0(s);
148 consume_tag(s, "(")?;
149 let expr = parse_expr(s)?;
150 skip_space0(s);
151 consume_tag(s, ")")?;
152 Ok(not(expr))
153}
154
155fn parse_expr_list<'p>(s: &mut &'p str) -> Result<'p, Vec<Expr>> {
156 let mut ans: Vec<Expr> = default();
157
158 while s.starts_with(')').not() {
159 skip_space0(s);
160 ans.push(parse_expr(s)?);
161 skip_space0(s);
162 skip_tag(s, ",");
163 }
164
165 Ok(ans)
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 #[test]
173 fn cfg_string() {
174 let input = r#" all(not(any(target_os = "linux", target_os = "macos")), all(), any(unix)) "#;
175 let expr = parse(input).unwrap();
176 assert_eq!(expr.to_string(), input.trim());
177 }
178}