Skip to main content

wp_primitives/
atom.rs

1use crate::symbol::symbol_colon;
2use winnow::ascii::{multispace0, multispace1};
3use winnow::combinator::fail;
4use winnow::error::{StrContext, StrContextValue};
5use winnow::token::{literal, take_till, take_while};
6use winnow::{ModalResult as WResult, Parser};
7
8pub fn take_var_name<'a>(input: &mut &'a str) -> WResult<&'a str> {
9    let _ = multispace0.parse_next(input)?;
10    take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.'])).parse_next(input)
11}
12
13pub fn take_json_path<'a>(input: &mut &'a str) -> WResult<&'a str> {
14    let _ = multispace0.parse_next(input)?;
15    take_while(
16        1..,
17        ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.', '/', '[', ']']),
18    )
19    .parse_next(input)
20}
21
22pub fn take_wild_key<'a>(input: &mut &'a str) -> WResult<&'a str> {
23    let _ = multispace0.parse_next(input)?;
24    take_while(
25        1..,
26        (
27            '0'..='9',
28            'A'..='Z',
29            'a'..='z',
30            ['_', '.', '*', '/', '[', ']'],
31        ),
32    )
33    .parse_next(input)
34}
35
36pub fn take_path<'a>(input: &mut &'a str) -> WResult<&'a str> {
37    let _ = multispace0.parse_next(input)?;
38    take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.', '/'], '-')).parse_next(input)
39}
40
41pub fn take_string<'a>(input: &mut &'a str) -> WResult<&'a str> {
42    let _ = multispace0.parse_next(input)?;
43    take_while(1.., |c: char| {
44        c.is_alphanumeric() || matches!(c, '_' | '.' | '/' | '-')
45    })
46    .parse_next(input)
47}
48
49pub fn take_obj_path<'a>(input: &mut &'a str) -> WResult<&'a str> {
50    let _ = multispace0.parse_next(input)?;
51    let key = take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '/'])).parse_next(input)?;
52    let _ = multispace1.parse_next(input)?;
53    Ok(key)
54}
55
56pub fn take_obj_wild_path<'a>(input: &mut &'a str) -> WResult<&'a str> {
57    let _ = multispace0.parse_next(input)?;
58    let key =
59        take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '/', '*'])).parse_next(input)?;
60    let _ = multispace1.parse_next(input)?;
61    Ok(key)
62}
63
64pub fn take_key_pair<'a>(input: &mut &'a str) -> WResult<(&'a str, &'a str)> {
65    let _ = multispace0.parse_next(input)?;
66    let key = take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.'])).parse_next(input)?;
67    symbol_colon.parse_next(input)?;
68    let _ = multispace0.parse_next(input)?;
69    let val = take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.'])).parse_next(input)?;
70    Ok((key, val))
71}
72
73pub fn take_key_val<'a>(input: &mut &'a str) -> WResult<(&'a str, &'a str)> {
74    let _ = multispace0.parse_next(input)?;
75    let key = take_while(1.., ('0'..='9', 'A'..='Z', 'a'..='z', ['_', '.'])).parse_next(input)?;
76    symbol_colon.parse_next(input)?;
77    let _ = multispace0.parse_next(input)?;
78    let val = take_till(1.., |c| c == ',' || c == ';').parse_next(input)?;
79    Ok((key, val))
80}
81pub fn take_empty(input: &mut &str) -> WResult<()> {
82    let _ = multispace0.parse_next(input)?;
83    Ok(())
84}
85
86pub fn take_parentheses_val(data: &mut &str) -> WResult<String> {
87    use crate::scope::ScopeEval;
88    use winnow::token::take;
89
90    let _ = multispace0.parse_next(data)?;
91
92    // Calculate the length of the complete balanced parentheses scope
93    let scope_len = ScopeEval::len(data, '(', ')');
94
95    if scope_len < 2 {
96        return fail
97            .context(StrContext::Label("syntax"))
98            .context(StrContext::Expected(StrContextValue::Description(
99                "need match '(x)', lack '(' or unbalanced parentheses",
100            )))
101            .parse_next(data);
102    }
103
104    // Parse opening parenthesis
105    literal("(")
106        .context(StrContext::Label("syntax"))
107        .context(StrContext::Expected(StrContextValue::Description(
108            "need match '(x)', lack '('",
109        )))
110        .parse_next(data)?;
111
112    // Extract the content (excluding the outer parentheses)
113    let content = take(scope_len - 2).parse_next(data)?;
114
115    // Parse closing parenthesis
116    literal(")")
117        .context(StrContext::Label("syntax"))
118        .context(StrContext::Expected(StrContextValue::Description(
119            "need match '(x)', lack ')'",
120        )))
121        .parse_next(data)?;
122
123    let trimmed = content.trim();
124
125    // Check if the content is a quoted string
126    let result = if (trimmed.starts_with('\'') && trimmed.ends_with('\''))
127        || (trimmed.starts_with('"') && trimmed.ends_with('"'))
128    {
129        // Remove quotes and process escape sequences
130        if trimmed.len() >= 2 {
131            let unquoted = &trimmed[1..trimmed.len() - 1];
132            // Process basic escape sequences
133            unquoted
134                .replace("\\n", "\n")
135                .replace("\\r", "\r")
136                .replace("\\t", "\t")
137                .replace("\\\\", "\\")
138                .replace("\\'", "'")
139                .replace("\\\"", "\"")
140        } else {
141            String::new()
142        }
143    } else {
144        // No quotes, return as-is
145        trimmed.to_string()
146    };
147
148    Ok(result)
149}
150
151pub fn take_parentheses_scope<'a>(data: &mut &'a str) -> WResult<(&'a str, &'a str)> {
152    let _ = multispace0.parse_next(data)?;
153    literal("(").parse_next(data)?;
154    let beg = take_till(0.., |x| x == ',').parse_next(data)?;
155    literal(",").parse_next(data)?;
156    let _ = multispace0.parse_next(data)?;
157    let end = take_till(0.., |x| x == ')').parse_next(data)?;
158    literal(")").parse_next(data)?;
159    Ok((beg, end))
160}
161
162#[cfg(test)]
163mod tests {
164    use crate::atom::{take_parentheses_val, take_var_name};
165    use winnow::{ModalResult as WResult, Parser};
166
167    mod var_name {
168        use super::*;
169
170        #[test]
171        fn valid_simple_name() -> WResult<()> {
172            let mut data = "x";
173            let key = take_var_name.parse_next(&mut data)?;
174            assert_eq!(key, "x");
175            Ok(())
176        }
177
178        #[test]
179        fn valid_name_followed_by_parens() -> WResult<()> {
180            let mut data = "x(10)";
181            let key = take_var_name.parse_next(&mut data)?;
182            assert_eq!(key, "x");
183            assert_eq!(data, "(10)");
184            Ok(())
185        }
186
187        #[test]
188        fn valid_alphanumeric() -> WResult<()> {
189            let mut data = "x10(10)";
190            let key = take_var_name.parse_next(&mut data)?;
191            assert_eq!(key, "x10");
192            Ok(())
193        }
194
195        #[test]
196        fn valid_with_leading_space() -> WResult<()> {
197            let mut data = " x10 (10)";
198            let key = take_var_name.parse_next(&mut data)?;
199            assert_eq!(key, "x10");
200            Ok(())
201        }
202
203        #[test]
204        fn valid_with_underscore() -> WResult<()> {
205            let mut data = " x_1 (10)";
206            let key = take_var_name.parse_next(&mut data)?;
207            assert_eq!(key, "x_1");
208            Ok(())
209        }
210
211        #[test]
212        fn valid_with_dot() -> WResult<()> {
213            let mut data = "foo.bar.baz";
214            let key = take_var_name.parse_next(&mut data)?;
215            assert_eq!(key, "foo.bar.baz");
216            Ok(())
217        }
218
219        #[test]
220        fn valid_complex_path() -> WResult<()> {
221            let mut data = "user_data.profile.name_field";
222            let key = take_var_name.parse_next(&mut data)?;
223            assert_eq!(key, "user_data.profile.name_field");
224            Ok(())
225        }
226
227        #[test]
228        fn invalid_empty_input() {
229            let mut data = "";
230            let result = take_var_name.parse_next(&mut data);
231            assert!(result.is_err(), "Should fail on empty input");
232        }
233
234        #[test]
235        fn invalid_starts_with_special() {
236            let mut data = "@invalid";
237            let result = take_var_name.parse_next(&mut data);
238            assert!(result.is_err(), "Should fail on special characters");
239        }
240
241        #[test]
242        fn invalid_only_whitespace() {
243            let mut data = "   ";
244            let result = take_var_name.parse_next(&mut data);
245            assert!(result.is_err(), "Should fail on only whitespace");
246        }
247    }
248
249    mod parentheses_val {
250        use super::*;
251
252        #[test]
253        fn valid_simple_value() -> WResult<()> {
254            let mut data = "(hello)";
255            let val = take_parentheses_val.parse_next(&mut data)?;
256            assert_eq!(val, "hello");
257            Ok(())
258        }
259
260        #[test]
261        fn valid_nested_parens() -> WResult<()> {
262            let mut data = "(outer(inner)value)";
263            let val = take_parentheses_val.parse_next(&mut data)?;
264            assert_eq!(val, "outer(inner)value");
265            Ok(())
266        }
267
268        #[test]
269        fn valid_deeply_nested() -> WResult<()> {
270            let mut data = "(a(b(c)d)e)";
271            let val = take_parentheses_val.parse_next(&mut data)?;
272            assert_eq!(val, "a(b(c)d)e");
273            Ok(())
274        }
275
276        #[test]
277        fn valid_with_leading_space() -> WResult<()> {
278            let mut data = "  (value)";
279            let val = take_parentheses_val.parse_next(&mut data)?;
280            assert_eq!(val, "value");
281            Ok(())
282        }
283
284        #[test]
285        fn valid_trims_internal_space() -> WResult<()> {
286            let mut data = "(  value  )";
287            let val = take_parentheses_val.parse_next(&mut data)?;
288            assert_eq!(val, "value");
289            Ok(())
290        }
291
292        #[test]
293        fn invalid_missing_open_paren() {
294            let mut data = "value)";
295            let result = take_parentheses_val.parse_next(&mut data);
296            assert!(result.is_err(), "Should fail on missing '('");
297        }
298
299        #[test]
300        fn invalid_missing_close_paren() {
301            let mut data = "(value";
302            let result = take_parentheses_val.parse_next(&mut data);
303            assert!(result.is_err(), "Should fail on missing ')'");
304        }
305
306        #[test]
307        fn invalid_unbalanced_nested() {
308            let mut data = "(value"; // Simplified test case
309            let result = take_parentheses_val.parse_next(&mut data);
310            assert!(result.is_err(), "Should fail on unbalanced parentheses");
311        }
312
313        #[test]
314        fn invalid_empty_input() {
315            let mut data = "";
316            let result = take_parentheses_val.parse_next(&mut data);
317            assert!(result.is_err(), "Should fail on empty input");
318        }
319
320        #[test]
321        fn quoted_single_quotes() -> WResult<()> {
322            let mut data = "('hello world')";
323            let val = take_parentheses_val.parse_next(&mut data)?;
324            assert_eq!(val, "hello world");
325            Ok(())
326        }
327
328        #[test]
329        fn quoted_double_quotes() -> WResult<()> {
330            let mut data = r#"("hello world")"#;
331            let val = take_parentheses_val.parse_next(&mut data)?;
332            assert_eq!(val, "hello world");
333            Ok(())
334        }
335
336        #[test]
337        fn quoted_with_escape_sequences() -> WResult<()> {
338            let mut data = r#"('hello\nworld\ttab')"#;
339            let val = take_parentheses_val.parse_next(&mut data)?;
340            assert_eq!(val, "hello\nworld\ttab");
341            Ok(())
342        }
343
344        #[test]
345        fn quoted_with_escaped_quotes() -> WResult<()> {
346            let mut data = r#"('it\'s working')"#;
347            let val = take_parentheses_val.parse_next(&mut data)?;
348            assert_eq!(val, "it's working");
349            Ok(())
350        }
351
352        #[test]
353        fn unquoted_backward_compatible() -> WResult<()> {
354            let mut data = "(hello)";
355            let val = take_parentheses_val.parse_next(&mut data)?;
356            assert_eq!(val, "hello");
357            Ok(())
358        }
359
360        #[test]
361        fn unquoted_with_special_chars() -> WResult<()> {
362            let mut data = "(1.0.0)";
363            let val = take_parentheses_val.parse_next(&mut data)?;
364            assert_eq!(val, "1.0.0");
365            Ok(())
366        }
367    }
368}