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 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 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 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
335struct 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(), 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 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, {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}