ccs2/ast/
parser.rs

1use crate::{
2    AstResult,
3    ast::{AstNode, ImportResolver, Key, Nested, Origin, PropDef, Selector},
4};
5use pest::{Parser, Span, iterators::Pair};
6use pest_derive::Parser;
7use std::{backtrace::Backtrace, path::PathBuf};
8
9#[derive(Parser)]
10#[grammar = "ast/ccs2.pest"]
11struct Ccs2Parser;
12
13#[derive(thiserror::Error, Debug)]
14pub enum ParseError {
15    #[error("Syntax error: {0}")]
16    SyntaxError(#[from] Box<pest::error::Error<Rule>>),
17    #[error("Unexpected rule: expected {0:?}, got {1:?}")]
18    UnexpectedRule(Rule, Rule),
19    #[error("Unexpected rule: expected any of {0:?}, got {1:?}")]
20    RuleNotInGroup(Vec<Rule>, Rule),
21    #[error("Unsupported rule: {rule:?}\n{backtrace}")]
22    UnsupportedRule { rule: Rule, backtrace: Backtrace },
23    #[error("Unexpected size: expected {0}, got {1}")]
24    UnexpectedSize(usize, usize),
25    #[error("Invalid key format: {0}")]
26    InvalidKey(String),
27    #[error("{0}")]
28    Other(String),
29}
30pub type ParseResult<T> = Result<T, ParseError>;
31
32pub fn parse(
33    file_contents: impl AsRef<str>,
34    resolver: impl ImportResolver,
35    in_progress: &mut Vec<PathBuf>,
36) -> AstResult<Nested> {
37    let mut file = Ccs2Parser::parse(Rule::file, file_contents.as_ref())
38        .map_err(|e| ParseError::SyntaxError(Box::new(e)))?;
39
40    let contents: Pair<Rule> = file.next().unwrap();
41    let mut nested: Nested = contents.try_into()?;
42
43    assert_eq!(file.next().map(|p| p.as_rule()), Some(Rule::EOI));
44
45    nested.resolve_imports_internal(&resolver, in_progress)?;
46
47    Ok(nested)
48}
49
50macro_rules! expect_rule {
51    ($actual:expr, [$exp1:expr $(, $exps:expr)* $(,)?]) => {{
52        let expected = vec![$exp1 $(, $exps)*];
53        (expected.iter().any(|e| $actual == *e))
54            .true_or(ParseError::RuleNotInGroup(expected, $actual))
55            .map(|_| {})
56    }};
57    ($actual:expr, $expected:expr) => {
58        ($actual == $expected)
59            .true_or(ParseError::UnexpectedRule($expected, $actual))
60            .map(|_| {})
61    };
62}
63
64mod keywords {
65    use crate::ast::Import;
66
67    use super::*;
68
69    pub struct Context(pub Selector);
70    impl TryFrom<Pair<'_, Rule>> for Context {
71        type Error = ParseError;
72
73        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
74            expect_rule!(value.as_rule(), Rule::context_stmt)?;
75            Ok(Self(value.into_inner().get_exactly_one()?.try_into()?))
76        }
77    }
78
79    impl TryFrom<Pair<'_, Rule>> for Import {
80        type Error = ParseError;
81
82        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
83            expect_rule!(value.as_rule(), Rule::import_stmt)?;
84            let location: Unquoted = value.into_inner().get_exactly_one()?.try_into()?;
85            Ok(Self::new(location.0))
86        }
87    }
88
89    pub struct Constrain(pub nested::SelectorDef);
90    impl TryFrom<Pair<'_, Rule>> for Constrain {
91        type Error = ParseError;
92
93        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
94            expect_rule!(value.as_rule(), Rule::constrain_stmt)?;
95            Ok(Self(value.into_inner().get_exactly_one()?.try_into()?))
96        }
97    }
98}
99
100mod nested {
101    use pest::pratt_parser::{Assoc, Op as PrattOp, PrattParser};
102
103    use super::*;
104    use crate::ast::{Expr, Op, flatten};
105
106    impl TryFrom<Pair<'_, Rule>> for Nested {
107        type Error = ParseError;
108
109        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
110            let original_rule = value.as_rule();
111            expect_rule!(
112                original_rule,
113                [Rule::ctx_block, Rule::ctx_def, Rule::file_contents]
114            )?;
115            let mut nested = Nested::default();
116            let mut context: Option<keywords::Context> = None;
117
118            let inner = value.into_inner();
119            for pair in inner {
120                match pair.as_rule() {
121                    Rule::context_stmt => {
122                        assert!(original_rule == Rule::file_contents);
123                        assert!(context.is_none());
124                        context = Some(pair.try_into()?);
125                    }
126                    Rule::constrain_stmt => {
127                        let statement: keywords::Constrain = pair.try_into()?;
128                        nested.append(AstNode::Constraint(statement.0.0))
129                    }
130                    Rule::import_stmt => nested.append(AstNode::Import(pair.try_into()?)),
131                    Rule::ctx_def => nested.append(AstNode::Nested(pair.try_into()?)),
132                    Rule::ctx_block => nested.append(AstNode::Nested(pair.try_into()?)),
133                    Rule::selector => nested.set_selector(pair.try_into()?),
134                    Rule::prop_def => nested.append(AstNode::PropDef(pair.try_into()?)),
135                    _ => Err(unsupported(pair.as_rule()))?,
136                }
137            }
138
139            if let Some(context) = context {
140                let inner_nested = nested;
141                nested = Nested::default();
142                nested.set_selector(context.0);
143                nested.append(AstNode::Nested(inner_nested));
144            }
145
146            Ok(nested)
147        }
148    }
149
150    /// This builds a chain of selector_components left-associatively, so the resulting expression
151    /// is initially nested from the left. It then flattens them to make things a bit more
152    /// consistent.
153    ///
154    /// Examples:
155    /// - `a.b` -> `a.b`
156    /// - `(a.b a.c), c.d` -> `OR!(AND!(a.b, a.c), c.d)`
157    /// - `c.d, (a.b a.c)` -> `OR!(c.d, AND!(a.b, a.c))`
158    /// - `a.b a.c a.d a.e`
159    ///   -> `AND!(AND!(AND!(a.b, a.c), a.d), a.e)`
160    ///   -> `AND!(a.b, a.c, a.d, a.e)`
161    impl TryFrom<Pair<'_, Rule>> for Selector {
162        type Error = ParseError;
163
164        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
165            expect_rule!(value.as_rule(), Rule::selector)?;
166
167            let inner = value.into_inner();
168
169            let pratt = PrattParser::new()
170                .op(PrattOp::infix(Rule::disjunction, Assoc::Left))
171                .op(PrattOp::infix(Rule::conjunction, Assoc::Left));
172
173            let parsed = pratt
174                .map_primary(|primary| {
175                    let component: SelectorComponent = primary.try_into().unwrap();
176                    Selector::from(component)
177                })
178                .map_infix(|lhs, op, rhs| match op.as_rule() {
179                    Rule::conjunction => Selector::Expr(Expr::new(Op::And, [lhs, rhs])),
180                    Rule::disjunction => Selector::Expr(Expr::new(Op::Or, [lhs, rhs])),
181                    _ => unreachable!(),
182                })
183                .parse(inner);
184
185            let parsed = flatten(parsed);
186            Ok(parsed)
187        }
188    }
189
190    #[derive(Debug)]
191    enum SelectorComponent {
192        SelectorDef(SelectorDef),
193        Selector(Selector),
194    }
195    impl TryFrom<Pair<'_, Rule>> for SelectorComponent {
196        type Error = ParseError;
197
198        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
199            expect_rule!(value.as_rule(), Rule::selector_component)?;
200
201            let inner = value.into_inner().get_exactly_one()?;
202            let res = match inner.as_rule() {
203                Rule::selector_def => Self::SelectorDef(inner.try_into()?),
204                Rule::selector => Self::Selector(inner.try_into()?),
205                _ => Err(unsupported(inner.as_rule()))?,
206            };
207            Ok(res)
208        }
209    }
210    impl From<SelectorComponent> for Selector {
211        fn from(value: SelectorComponent) -> Self {
212            match value {
213                SelectorComponent::SelectorDef(selector_def) => Self::Step(selector_def.0),
214                SelectorComponent::Selector(selector) => selector,
215            }
216        }
217    }
218
219    #[derive(Debug)]
220    pub struct SelectorDef(pub Key);
221    impl TryFrom<Pair<'_, Rule>> for SelectorDef {
222        type Error = ParseError;
223
224        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
225            expect_rule!(value.as_rule(), Rule::selector_def)?;
226
227            Ok(Self(value.into_inner().get_exactly_one()?.try_into()?))
228        }
229    }
230
231    /// The `a.b` part of `a.b { prop = val }`
232    impl TryFrom<Pair<'_, Rule>> for Key {
233        type Error = ParseError;
234
235        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
236            expect_rule!(value.as_rule(), Rule::key)?;
237
238            let mut inner = value.into_inner();
239            let name: Unquoted = inner.next().unwrap().try_into()?;
240
241            let values = if let Some(value) = inner.next() {
242                let value: KeyValue = value.try_into()?;
243                vec![value.0.0.into()]
244            } else {
245                vec![]
246            };
247
248            (inner.len() == 0).true_or(ParseError::UnexpectedSize(1, inner.len()))?;
249            Ok(Key::new(name.0, values))
250        }
251    }
252
253    /// The `b` part of `a.b { prop = val }`
254    struct KeyValue(Unquoted);
255    impl TryFrom<Pair<'_, Rule>> for KeyValue {
256        type Error = ParseError;
257
258        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
259            expect_rule!(value.as_rule(), Rule::key_value)?;
260
261            let inner = value.into_inner();
262            Ok(Self(inner.get_exactly_one()?.try_into()?))
263        }
264    }
265}
266
267mod prop_def {
268    use super::*;
269
270    impl TryFrom<Pair<'_, Rule>> for PropDef {
271        type Error = ParseError;
272
273        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
274            expect_rule!(value.as_rule(), Rule::prop_def)?;
275
276            let inner = value.into_inner().get_exactly_one()?;
277            match inner.as_rule() {
278                Rule::simple_prop_def => Ok(SimplePropDef::try_from(inner)?.0),
279                Rule::overridden_prop_def => Ok(OverriddenPropDef::try_from(inner)?.0),
280                _ => Err(unsupported(inner.as_rule())),
281            }
282        }
283    }
284
285    struct SimplePropDef(PropDef);
286    impl TryFrom<Pair<'_, Rule>> for SimplePropDef {
287        type Error = ParseError;
288
289        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
290            expect_rule!(value.as_rule(), Rule::simple_prop_def)?;
291
292            let origin: Origin = value.as_span().into();
293
294            let mut inner = value.into_inner();
295            let name: Unquoted = inner.next().unwrap().try_into()?;
296            let value: Value = inner.next().unwrap().try_into()?;
297            Ok(Self(PropDef {
298                name: name.0,
299                value: value.0.0,
300                origin,
301                should_override: false,
302            }))
303        }
304    }
305
306    struct OverriddenPropDef(PropDef);
307    impl TryFrom<Pair<'_, Rule>> for OverriddenPropDef {
308        type Error = ParseError;
309
310        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
311            expect_rule!(value.as_rule(), Rule::overridden_prop_def)?;
312
313            let simple_prop: SimplePropDef = value.into_inner().get_exactly_one()?.try_into()?;
314            Ok(Self(PropDef {
315                name: simple_prop.0.name,
316                value: simple_prop.0.value,
317                origin: simple_prop.0.origin,
318                should_override: true,
319            }))
320        }
321    }
322
323    struct Value(Unquoted);
324    impl TryFrom<Pair<'_, Rule>> for Value {
325        type Error = ParseError;
326
327        fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
328            expect_rule!(value.as_rule(), Rule::prop_value)?;
329            let value = value.into_inner().get_exactly_one()?;
330            Ok(Self(value.try_into()?))
331        }
332    }
333}
334
335/// Utility for flattening a `str` rule, and returning just its unquoted contents
336struct Unquoted(String);
337impl TryFrom<Pair<'_, Rule>> for Unquoted {
338    type Error = ParseError;
339
340    fn try_from(value: Pair<'_, Rule>) -> Result<Self, Self::Error> {
341        expect_rule!(value.as_rule(), [Rule::value_str, Rule::selector_str])?;
342
343        let value = value.into_inner().get_exactly_one()?;
344        match value.as_rule() {
345            Rule::unquoted_value_str
346            | Rule::unquoted_selector_str
347            | Rule::single_quote_str
348            | Rule::double_quote_str => Ok(Self(value.as_str().to_string())),
349            _ => Err(unsupported(value.as_rule())),
350        }
351    }
352}
353
354impl<'a> From<Span<'a>> for Origin {
355    fn from(value: Span) -> Self {
356        let (line_number, _) = value.start_pos().line_col();
357        Self {
358            filename: "".into(), // TODO: File names can't make it here, unfortunately
359            line_number: line_number as u32,
360        }
361    }
362}
363
364trait GetExactlyOne {
365    type Item;
366    fn get_exactly_one(self) -> ParseResult<Self::Item>;
367}
368impl<T, I: ExactSizeIterator<Item = T>> GetExactlyOne for I {
369    type Item = T;
370
371    fn get_exactly_one(mut self) -> ParseResult<Self::Item> {
372        (self.len() == 1).true_or(ParseError::UnexpectedSize(1, self.len()))?;
373        Ok(self.next().unwrap())
374    }
375}
376
377trait TrueOr {
378    fn true_or(self, error: ParseError) -> ParseResult<Self>
379    where
380        Self: Sized;
381}
382impl TrueOr for bool {
383    fn true_or(self, error: ParseError) -> ParseResult<Self> {
384        if self { Ok(self) } else { Err(error) }
385    }
386}
387
388fn unsupported(rule: Rule) -> ParseError {
389    ParseError::UnsupportedRule {
390        rule,
391        backtrace: Backtrace::force_capture(),
392    }
393}
394
395#[cfg(test)]
396mod tests {
397    use crate::{
398        AstError, AstResult,
399        ast::{NullResolver, flatten, macros::*, parser::ParseError},
400    };
401
402    use super::parse;
403    use anyhow::Result;
404
405    macro_rules! __test_suite {
406        () => {};
407        ({$(#[$attrs:meta])? succ: ($name:ident, $e:literal)}, $($rest:tt,)*) => {
408            #[test]
409            $(#[$attrs])?
410            fn $name() -> Result<()> {
411                test_impl($e, true)
412            }
413
414            __test_suite!{$($rest,)*}
415        };
416        ({$(#[$attrs:meta])? fail: ($name:ident, $e:literal)}, $($rest:tt,)*) => {
417            #[test]
418            $(#[$attrs])?
419            fn $name() -> Result<()> {
420                test_impl($e, false)
421            }
422            __test_suite!{$($rest,)*}
423        };
424    }
425    macro_rules! test_suite {
426        ($suite_name:ident, $($test_defs:tt,)+) => {
427            mod $suite_name {
428                use super::*;
429                __test_suite!($($test_defs,)+);
430            }
431        };
432    }
433
434    // Most of these tests (and the syntax) were taken from ccs2-py
435    test_suite! {
436        basic_phrases,
437        {succ: (empty, "")},
438        {succ: (import_outside_block, "@import 'file'")},
439        {fail: (bad_context, "@context (foo x.bar # baz)")},
440        {succ: (simple_str_prop, "prop = 'val'")},
441        {succ: (simple_str_prop_unquoted, "prop = val")},
442        {succ: (simple_block, "elem.id {}")},
443        {succ: (unquoted_str, "elem.id {x = abc1}")},
444        {succ: (unquoted_str_num_prefix, "elem.id {x = 1abc}")},
445        {succ: (simple_prop_in_block, "elem.id {prop = 'val'}")},
446        {fail: (bad_override, "elem.id {prop = @override 'hi'}")},
447        {succ: (conjunction_int_1, "a blah elem {prop=3}")},
448        {succ: (conjunction_int_2, "a.class blah elem.id {prop=3}")},
449        {succ: (conjunction_float, "a.class blah elem.id {prop=2.3}")},
450        {succ: (conjunction_str, "a.class blah elem.id {prop=\"val\"}")},
451        {fail: (conjunction_missing_block, "a.class blah elem.id prop=\"val\" }")},
452        {succ: (conjunction_hex_int, "a.class blah elem.id {prop=0xAB12}")},
453        {succ: (key_value_with_space, "a.class blah elem. id {prop=2.3}")},
454        {succ: (key_value_with_multiple_spaces, "a . class elem.id {prop=\"val\"}")},
455        {fail: (lonely_selector, "blah")},
456        {fail: (context_after_import, "@import 'file'; @context (foo)")},
457        {fail: (invlid_keyword, "@yuno?")},
458        {succ: (import_and_constrain, "@import 'file' ; @constrain foo")},
459        {succ: (import_in_block, "a.class { @import 'file' }")},
460        {fail: (context_in_block, "a.class { @context (foo) }")},
461        {succ: (context_outside_block, "@context (foo) a.class { }")},
462        {succ: (multiple_props_in_block, "elem.id { prop = 'val'; prop2 = 31337 }")},
463        {succ: (prop_val_with_quotes, "prop.'val' { p = 1; }")},
464        {succ: (conjunction_disjunction, "a b, c d {p=1}")},
465        {succ: (disjunction_conjunction_parens, "(a, b) (c, d) {p=1}")},
466        {succ: (multi_disjunction, "a, b, c {p=1}")},
467        {succ: (mixed_parens, "a, (b c) d {p=1}")},
468        {succ: (value_quotes_simple, "a.\"foo\" {}")},
469        {succ: (value_prop_quotes_simple, "a.\"foo\" {'test' = 1}")},
470        {succ: (value_prop_quotes_conj, "a.\"foo\" 'bar' {'test' = 1}")},
471    }
472
473    test_suite! {
474        comments,
475        {succ: (single_line, "// single line comment\n")},
476        {succ: (single_line_no_newline, "// single line comment nonl")},
477        {succ: (multi_line, "/* multi-line\ncomment */")},
478        {succ: (inline_with_prop, "prop = /* comment */ 'val'")},
479        {succ: (inline_extra_slash, "prop = /*/ comment */ 'val'")},
480        {succ: (empty_inline, "prop = /**/ 'val'")},
481        {#[ignore] succ: (inline_nested, "prop = /* comment /*nest*/ more */ 'val'")},
482        {succ: (inline_after_key, "elem.id /* comment */ {prop = 'val'}")},
483        {fail: (inline_not_closed, "elem.id /* comment {prop = 'val'}")},
484        {succ: (single_line_before_block, "// comment\nelem { prop = 'val' prop = 'val' }")},
485    }
486
487    test_suite! {
488        ugly_abutments,
489        {fail: (two_properties_touching, "foo {p = 1x = 2}")},
490        {succ: (two_properties_with_space, "foo {p = 1x p2 = 2}")},
491        {succ: (property_with_quotes_touching, "foo {p = 'x'x = 2}")},
492        {succ: (two_properties_with_space_2, "foo {p = 1 x = 2}")},
493        {fail: (property_and_key, "value=12asdf.foo {}")},
494        {succ: (property_and_key_with_space, "value=12asdf.foo nextsel {}")},
495        {succ: (no_spaces_needed, "foo{p=1;x=2}")},
496        {fail: (keyword_with_property, "foo{@overridep=1}")},
497        {succ: (comment_between_keyword_and_property, "foo{@override /*hi*/ p=1}")},
498        {#[ignore] succ: (import_with_quotes, "@import'asdf'")},
499        {fail: (import_without_quotes, "@importasdf")},
500        {#[ignore] succ: (constrain_with_quotes, "@constrain'asdf'")},
501        {fail: (constrain_without_quotes, "@constrainasdf")},
502        {succ: (weird_spacing, "@import 'asdf' \n ; \n @constrain asdf \n ; @import 'foo'  ")},
503        {succ: (comment_between_import, "@import /*hi*/ 'asdf'")},
504        {succ: (comment_between_key_and_block, "env.foo/* some comment */{ }")},
505    }
506
507    test_suite! {
508        in_file_constraints,
509        {succ: (constraint_in_constraint, "a.b: @constrain a.c")},
510    }
511
512    test_suite! {
513        interpolation, // TODO: Name is wrong
514        {succ: (single_quotes, "a = 'hi'")},
515        {fail: (missing_close_quote, "a = 'hi")},
516        {fail: (missing_close_quote_with_escape, "a = 'hi\\")},
517        {#[ignore] fail: (invalid_escape, "a = 'hi\\4 there'")},
518        {succ: (mid_string_interpolation, "a = 'h${there}i'")},
519        {#[ignore] fail: (missing_bracket, "a = 'h$there}i'")},
520        {#[ignore] fail: (dash_in_interpolation, "a = 'h${t-here}i'")},
521    }
522
523    test_suite! {
524        demo,
525        {succ: (demo, "prop = 'val';\nprop2 = 'val2'\nelem.id b.c, c.d : 'prop3'='val3'")},
526    }
527
528    fn test_impl(to_parse: &str, should_succeed: bool) -> Result<()> {
529        let res = parse(to_parse, NullResolver(), &mut vec![]);
530        if should_succeed {
531            assert!(res.is_ok(), "Expected success, got error: {res:?}");
532        } else {
533            assert!(
534                matches!(res, Err(AstError::ParseError(ParseError::SyntaxError(..)))),
535                "Expected syntax error, got {res:?}"
536            );
537        }
538        Ok(())
539    }
540
541    #[test]
542    fn test_selector_operator_precedence() -> AstResult<()> {
543        let example = r#"
544        a.b a.c, b.c b.d b.e {
545            prop1 = val1
546        }
547        "#;
548        let parsed = parse(example, NullResolver(), &mut vec![])?;
549
550        assert_eq!(parsed.rules.len(), 1);
551        let rule = parsed.rules.into_iter().next().unwrap();
552        let selector = match rule {
553            crate::ast::AstNode::Nested(nested) => flatten(nested.selector.unwrap()),
554            _ => unreachable!(),
555        };
556
557        let expected = OR!(AND!("a.b", "a.c"), AND!("b.c", "b.d", "b.e"));
558        assert_eq!(expected, flatten(selector));
559
560        Ok(())
561    }
562}