1use std::error;
18use std::sync::LazyLock;
19
20use itertools::Itertools as _;
21use pest::Parser as _;
22use pest::iterators::Pair;
23use pest::pratt_parser::Assoc;
24use pest::pratt_parser::Op;
25use pest::pratt_parser::PrattParser;
26use thiserror::Error;
27
28use self::private::FilesetParser;
29use self::private::Rule;
30use crate::dsl_util;
31use crate::dsl_util::AliasDeclaration;
32use crate::dsl_util::AliasDeclarationParser;
33use crate::dsl_util::AliasDefinitionParser;
34use crate::dsl_util::AliasExpandError;
35use crate::dsl_util::AliasExpandableExpression;
36use crate::dsl_util::AliasId;
37use crate::dsl_util::AliasesMap;
38use crate::dsl_util::Diagnostics;
39use crate::dsl_util::ExpressionFolder;
40use crate::dsl_util::FoldableExpression;
41use crate::dsl_util::InvalidArguments;
42use crate::dsl_util::StringLiteralParser;
43
44mod private {
45 use pest_derive::Parser;
46
47 #[derive(Parser)]
49 #[grammar = "fileset.pest"]
50 pub struct FilesetParser;
51}
52
53const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser {
54 content_rule: Rule::string_content,
55 escape_rule: Rule::string_escape,
56};
57
58impl Rule {
59 fn to_symbol(self) -> Option<&'static str> {
60 match self {
61 Self::EOI => None,
62 Self::whitespace => None,
63 Self::identifier => None,
64 Self::strict_identifier_part => None,
65 Self::strict_identifier => None,
66 Self::bare_string => None,
67 Self::string_escape => None,
68 Self::string_content_char => None,
69 Self::string_content => None,
70 Self::string_literal => None,
71 Self::raw_string_content => None,
72 Self::raw_string_literal => None,
73 Self::pattern_kind_op => Some(":"),
74 Self::negate_op => Some("~"),
75 Self::union_op => Some("|"),
76 Self::intersection_op => Some("&"),
77 Self::difference_op => Some("~"),
78 Self::prefix_ops => None,
79 Self::infix_ops => None,
80 Self::function => None,
81 Self::function_name => None,
82 Self::function_arguments => None,
83 Self::formal_parameters => None,
84 Self::pattern => None,
85 Self::bare_string_pattern => None,
86 Self::primary => None,
87 Self::expression => None,
88 Self::program => None,
89 Self::program_or_bare_string => None,
90 Self::function_alias_declaration => None,
91 Self::pattern_alias_declaration => None,
92 Self::alias_declaration => None,
93 }
94 }
95}
96
97pub type FilesetDiagnostics = Diagnostics<FilesetParseError>;
100
101pub type FilesetParseResult<T> = Result<T, FilesetParseError>;
103
104#[derive(Debug, Error)]
106#[error("{pest_error}")]
107pub struct FilesetParseError {
108 kind: FilesetParseErrorKind,
109 pest_error: Box<pest::error::Error<Rule>>,
110 source: Option<Box<dyn error::Error + Send + Sync>>,
111}
112
113#[expect(missing_docs)]
115#[derive(Clone, Debug, Eq, Error, PartialEq)]
116pub enum FilesetParseErrorKind {
117 #[error("Syntax error")]
118 SyntaxError,
119 #[error("Function `{name}` doesn't exist")]
120 NoSuchFunction {
121 name: String,
122 candidates: Vec<String>,
123 },
124 #[error("Function `{name}`: {message}")]
125 InvalidArguments { name: String, message: String },
126 #[error("Redefinition of function parameter")]
127 RedefinedFunctionParameter,
128 #[error("{0}")]
129 Expression(String),
130 #[error("In alias `{0}`")]
131 InAliasExpansion(String),
132 #[error("In function parameter `{0}`")]
133 InParameterExpansion(String),
134 #[error("Alias `{0}` expanded recursively")]
135 RecursiveAlias(String),
136}
137
138impl FilesetParseError {
139 pub(super) fn new(kind: FilesetParseErrorKind, span: pest::Span<'_>) -> Self {
141 let message = kind.to_string();
142 let pest_error = Box::new(pest::error::Error::new_from_span(
143 pest::error::ErrorVariant::CustomError { message },
144 span,
145 ));
146 Self {
147 kind,
148 pest_error,
149 source: None,
150 }
151 }
152
153 pub(super) fn with_source(
155 mut self,
156 source: impl Into<Box<dyn error::Error + Send + Sync>>,
157 ) -> Self {
158 self.source = Some(source.into());
159 self
160 }
161
162 pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
164 Self::new(FilesetParseErrorKind::Expression(message.into()), span)
165 }
166
167 pub fn kind(&self) -> &FilesetParseErrorKind {
169 &self.kind
170 }
171}
172
173impl AliasExpandError for FilesetParseError {
174 fn invalid_arguments(err: InvalidArguments<'_>) -> Self {
175 err.into()
176 }
177
178 fn recursive_expansion(id: AliasId<'_>, span: pest::Span<'_>) -> Self {
179 Self::new(FilesetParseErrorKind::RecursiveAlias(id.to_string()), span)
180 }
181
182 fn within_alias_expansion(self, id: AliasId<'_>, span: pest::Span<'_>) -> Self {
183 let kind = match id {
184 AliasId::Symbol(_) | AliasId::Pattern(..) | AliasId::Function(..) => {
185 FilesetParseErrorKind::InAliasExpansion(id.to_string())
186 }
187 AliasId::Parameter(_) => FilesetParseErrorKind::InParameterExpansion(id.to_string()),
188 };
189 Self::new(kind, span).with_source(self)
190 }
191}
192
193impl From<pest::error::Error<Rule>> for FilesetParseError {
194 fn from(err: pest::error::Error<Rule>) -> Self {
195 Self {
196 kind: FilesetParseErrorKind::SyntaxError,
197 pest_error: Box::new(rename_rules_in_pest_error(err)),
198 source: None,
199 }
200 }
201}
202
203impl From<InvalidArguments<'_>> for FilesetParseError {
204 fn from(err: InvalidArguments<'_>) -> Self {
205 let kind = FilesetParseErrorKind::InvalidArguments {
206 name: err.name.to_owned(),
207 message: err.message,
208 };
209 Self::new(kind, err.span)
210 }
211}
212
213fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Error<Rule> {
214 err.renamed_rules(|rule| {
215 rule.to_symbol()
216 .map(|sym| format!("`{sym}`"))
217 .unwrap_or_else(|| format!("<{rule:?}>"))
218 })
219}
220
221#[derive(Clone, Debug, Eq, PartialEq)]
223pub enum ExpressionKind<'i> {
224 Identifier(&'i str),
226 String(String),
228 Pattern(Box<PatternNode<'i>>),
230 Unary(UnaryOp, Box<ExpressionNode<'i>>),
232 Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
234 UnionAll(Vec<ExpressionNode<'i>>),
236 FunctionCall(Box<FunctionCallNode<'i>>),
238 AliasExpanded(AliasId<'i>, Box<ExpressionNode<'i>>),
240}
241
242impl<'i> FoldableExpression<'i> for ExpressionKind<'i> {
243 fn fold<F>(self, folder: &mut F, span: pest::Span<'i>) -> Result<Self, F::Error>
244 where
245 F: ExpressionFolder<'i, Self> + ?Sized,
246 {
247 match self {
248 Self::Identifier(name) => folder.fold_identifier(name, span),
249 Self::String(_) => Ok(self),
250 Self::Pattern(pattern) => folder.fold_pattern(pattern, span),
251 Self::Unary(op, arg) => {
252 let arg = Box::new(folder.fold_expression(*arg)?);
253 Ok(Self::Unary(op, arg))
254 }
255 Self::Binary(op, lhs, rhs) => {
256 let lhs = Box::new(folder.fold_expression(*lhs)?);
257 let rhs = Box::new(folder.fold_expression(*rhs)?);
258 Ok(Self::Binary(op, lhs, rhs))
259 }
260 Self::UnionAll(nodes) => {
261 let nodes = dsl_util::fold_expression_nodes(folder, nodes)?;
262 Ok(Self::UnionAll(nodes))
263 }
264 Self::FunctionCall(function) => folder.fold_function_call(function, span),
265 Self::AliasExpanded(id, subst) => {
266 let subst = Box::new(folder.fold_expression(*subst)?);
267 Ok(Self::AliasExpanded(id, subst))
268 }
269 }
270 }
271}
272
273impl<'i> AliasExpandableExpression<'i> for ExpressionKind<'i> {
274 fn identifier(name: &'i str) -> Self {
275 Self::Identifier(name)
276 }
277
278 fn pattern(pattern: Box<PatternNode<'i>>) -> Self {
279 Self::Pattern(pattern)
280 }
281
282 fn function_call(function: Box<FunctionCallNode<'i>>) -> Self {
283 Self::FunctionCall(function)
284 }
285
286 fn alias_expanded(id: AliasId<'i>, subst: Box<ExpressionNode<'i>>) -> Self {
287 Self::AliasExpanded(id, subst)
288 }
289}
290
291#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
293pub enum UnaryOp {
294 Negate,
296}
297
298#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
300pub enum BinaryOp {
301 Intersection,
303 Difference,
305}
306
307pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
309pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;
311pub type PatternNode<'i> = dsl_util::PatternNode<'i, ExpressionKind<'i>>;
313
314fn union_nodes<'i>(lhs: ExpressionNode<'i>, rhs: ExpressionNode<'i>) -> ExpressionNode<'i> {
315 let span = lhs.span.start_pos().span(&rhs.span.end_pos());
316 let expr = match lhs.kind {
317 ExpressionKind::UnionAll(mut nodes) => {
320 nodes.push(rhs);
321 ExpressionKind::UnionAll(nodes)
322 }
323 _ => ExpressionKind::UnionAll(vec![lhs, rhs]),
324 };
325 ExpressionNode::new(expr, span)
326}
327
328fn parse_function_call_node(pair: Pair<Rule>) -> FilesetParseResult<FunctionCallNode> {
329 assert_eq!(pair.as_rule(), Rule::function);
330 let [name_pair, args_pair] = pair.into_inner().collect_array().unwrap();
331 assert_eq!(name_pair.as_rule(), Rule::function_name);
332 assert_eq!(args_pair.as_rule(), Rule::function_arguments);
333 let name_span = name_pair.as_span();
334 let args_span = args_pair.as_span();
335 let name = name_pair.as_str();
336 let args = args_pair
337 .into_inner()
338 .map(parse_expression_node)
339 .try_collect()?;
340 Ok(FunctionCallNode {
341 name,
342 name_span,
343 args,
344 keyword_args: vec![], args_span,
346 })
347}
348
349fn parse_as_string_literal(pair: Pair<Rule>) -> String {
350 match pair.as_rule() {
351 Rule::identifier => pair.as_str().to_owned(),
352 Rule::string_literal => STRING_LITERAL_PARSER.parse(pair.into_inner()),
353 Rule::raw_string_literal => {
354 let [content] = pair.into_inner().collect_array().unwrap();
355 assert_eq!(content.as_rule(), Rule::raw_string_content);
356 content.as_str().to_owned()
357 }
358 r => panic!("unexpected string literal rule: {r:?}"),
359 }
360}
361
362fn parse_primary_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> {
363 assert_eq!(pair.as_rule(), Rule::primary);
364 let span = pair.as_span();
365 let first = pair.into_inner().next().unwrap();
366 let expr = match first.as_rule() {
367 Rule::expression => parse_expression_node(first)?.kind,
369 Rule::function => {
370 let function = Box::new(parse_function_call_node(first)?);
371 ExpressionKind::FunctionCall(function)
372 }
373 Rule::pattern => {
374 let [lhs, op, rhs] = first.into_inner().collect_array().unwrap();
375 assert_eq!(lhs.as_rule(), Rule::strict_identifier);
376 assert_eq!(op.as_rule(), Rule::pattern_kind_op);
377 let pattern = Box::new(PatternNode {
378 name: lhs.as_str(),
379 name_span: lhs.as_span(),
380 value: parse_primary_node(rhs)?,
381 });
382 ExpressionKind::Pattern(pattern)
383 }
384 Rule::identifier => ExpressionKind::Identifier(first.as_str()),
385 Rule::string_literal | Rule::raw_string_literal => {
386 ExpressionKind::String(parse_as_string_literal(first))
387 }
388 r => panic!("unexpected primary rule: {r:?}"),
389 };
390 Ok(ExpressionNode::new(expr, span))
391}
392
393fn parse_expression_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> {
394 assert_eq!(pair.as_rule(), Rule::expression);
395 static PRATT: LazyLock<PrattParser<Rule>> = LazyLock::new(|| {
396 PrattParser::new()
397 .op(Op::infix(Rule::union_op, Assoc::Left))
398 .op(Op::infix(Rule::intersection_op, Assoc::Left)
399 | Op::infix(Rule::difference_op, Assoc::Left))
400 .op(Op::prefix(Rule::negate_op))
401 });
402 PRATT
403 .map_primary(parse_primary_node)
404 .map_prefix(|op, rhs| {
405 let op_kind = match op.as_rule() {
406 Rule::negate_op => UnaryOp::Negate,
407 r => panic!("unexpected prefix operator rule {r:?}"),
408 };
409 let rhs = Box::new(rhs?);
410 let span = op.as_span().start_pos().span(&rhs.span.end_pos());
411 let expr = ExpressionKind::Unary(op_kind, rhs);
412 Ok(ExpressionNode::new(expr, span))
413 })
414 .map_infix(|lhs, op, rhs| {
415 let op_kind = match op.as_rule() {
416 Rule::union_op => return Ok(union_nodes(lhs?, rhs?)),
417 Rule::intersection_op => BinaryOp::Intersection,
418 Rule::difference_op => BinaryOp::Difference,
419 r => panic!("unexpected infix operator rule {r:?}"),
420 };
421 let lhs = Box::new(lhs?);
422 let rhs = Box::new(rhs?);
423 let span = lhs.span.start_pos().span(&rhs.span.end_pos());
424 let expr = ExpressionKind::Binary(op_kind, lhs, rhs);
425 Ok(ExpressionNode::new(expr, span))
426 })
427 .parse(pair.into_inner())
428}
429
430pub fn parse_program(text: &str) -> FilesetParseResult<ExpressionNode<'_>> {
432 let mut pairs = FilesetParser::parse(Rule::program, text)?;
433 let first = pairs.next().unwrap();
434 parse_expression_node(first)
435}
436
437pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<ExpressionNode<'_>> {
443 let mut pairs = FilesetParser::parse(Rule::program_or_bare_string, text)?;
444 let first = pairs.next().unwrap();
445 let span = first.as_span();
446 let expr = match first.as_rule() {
447 Rule::expression => return parse_expression_node(first),
448 Rule::bare_string_pattern => {
449 let [lhs, op, rhs] = first.into_inner().collect_array().unwrap();
450 assert_eq!(lhs.as_rule(), Rule::strict_identifier);
451 assert_eq!(op.as_rule(), Rule::pattern_kind_op);
452 assert_eq!(rhs.as_rule(), Rule::bare_string);
453 let name_span = lhs.as_span();
454 let value_span = rhs.as_span();
455 let name = lhs.as_str();
456 let value_expr = ExpressionKind::String(rhs.as_str().to_owned());
457 let pattern = Box::new(PatternNode {
458 name,
459 name_span,
460 value: ExpressionNode::new(value_expr, value_span),
461 });
462 ExpressionKind::Pattern(pattern)
463 }
464 Rule::bare_string => ExpressionKind::String(first.as_str().to_owned()),
465 r => panic!("unexpected program or bare string rule: {r:?}"),
466 };
467 Ok(ExpressionNode::new(expr, span))
468}
469
470pub type FilesetAliasesMap = AliasesMap<FilesetAliasParser, String>;
472
473#[derive(Clone, Debug, Default)]
475pub struct FilesetAliasParser;
476
477impl AliasDeclarationParser for FilesetAliasParser {
478 type Error = FilesetParseError;
479
480 fn parse_declaration(&self, source: &str) -> Result<AliasDeclaration, Self::Error> {
481 let mut pairs = FilesetParser::parse(Rule::alias_declaration, source)?;
482 let first = pairs.next().unwrap();
483 match first.as_rule() {
484 Rule::strict_identifier => Ok(AliasDeclaration::Symbol(first.as_str().to_owned())),
485 Rule::pattern_alias_declaration => {
486 let [name_pair, op, param_pair] = first.into_inner().collect_array().unwrap();
487 assert_eq!(name_pair.as_rule(), Rule::strict_identifier);
488 assert_eq!(op.as_rule(), Rule::pattern_kind_op);
489 assert_eq!(param_pair.as_rule(), Rule::strict_identifier);
490 let name = name_pair.as_str().to_owned();
491 let param = param_pair.as_str().to_owned();
492 Ok(AliasDeclaration::Pattern(name, param))
493 }
494 Rule::function_alias_declaration => {
495 let [name_pair, params_pair] = first.into_inner().collect_array().unwrap();
496 assert_eq!(name_pair.as_rule(), Rule::function_name);
497 assert_eq!(params_pair.as_rule(), Rule::formal_parameters);
498 let name = name_pair.as_str().to_owned();
499 let params_span = params_pair.as_span();
500 let params = params_pair
501 .into_inner()
502 .map(|pair| match pair.as_rule() {
503 Rule::strict_identifier => pair.as_str().to_owned(),
504 r => panic!("unexpected formal parameter rule {r:?}"),
505 })
506 .collect_vec();
507 if params.iter().all_unique() {
508 Ok(AliasDeclaration::Function(name, params))
509 } else {
510 Err(FilesetParseError::new(
511 FilesetParseErrorKind::RedefinedFunctionParameter,
512 params_span,
513 ))
514 }
515 }
516 r => panic!("unexpected alias declaration rule {r:?}"),
517 }
518 }
519}
520
521impl AliasDefinitionParser for FilesetAliasParser {
522 type Output<'i> = ExpressionKind<'i>;
523 type Error = FilesetParseError;
524
525 fn parse_definition<'i>(&self, source: &'i str) -> Result<ExpressionNode<'i>, Self::Error> {
526 parse_program(source)
527 }
528}
529
530pub fn expand_aliases<'i>(
532 node: ExpressionNode<'i>,
533 aliases_map: &'i FilesetAliasesMap,
534) -> FilesetParseResult<ExpressionNode<'i>> {
535 dsl_util::expand_aliases(node, aliases_map)
536}
537
538pub(super) fn expect_string_literal<'a>(
540 type_name: &str,
541 node: &'a ExpressionNode<'_>,
542) -> FilesetParseResult<&'a str> {
543 catch_aliases_no_diagnostics(node, |node| match &node.kind {
544 ExpressionKind::Identifier(name) => Ok(*name),
545 ExpressionKind::String(name) => Ok(name),
546 _ => Err(FilesetParseError::expression(
547 format!("Expected {type_name}"),
548 node.span,
549 )),
550 })
551}
552
553pub(super) fn catch_aliases<'a, 'i, T>(
556 diagnostics: &mut FilesetDiagnostics,
557 node: &'a ExpressionNode<'i>,
558 f: impl FnOnce(&mut FilesetDiagnostics, &'a ExpressionNode<'i>) -> Result<T, FilesetParseError>,
559) -> Result<T, FilesetParseError> {
560 let (node, stack) = skip_aliases(node);
561 if stack.is_empty() {
562 f(diagnostics, node)
563 } else {
564 let mut inner_diagnostics = FilesetDiagnostics::new();
565 let result = f(&mut inner_diagnostics, node);
566 diagnostics.extend_with(inner_diagnostics, |diag| attach_aliases_err(diag, &stack));
567 result.map_err(|err| attach_aliases_err(err, &stack))
568 }
569}
570
571fn catch_aliases_no_diagnostics<'a, 'i, T>(
572 node: &'a ExpressionNode<'i>,
573 f: impl FnOnce(&'a ExpressionNode<'i>) -> Result<T, FilesetParseError>,
574) -> Result<T, FilesetParseError> {
575 let (node, stack) = skip_aliases(node);
576 f(node).map_err(|err| attach_aliases_err(err, &stack))
577}
578
579fn skip_aliases<'a, 'i>(
580 mut node: &'a ExpressionNode<'i>,
581) -> (&'a ExpressionNode<'i>, Vec<(AliasId<'i>, pest::Span<'i>)>) {
582 let mut stack = Vec::new();
583 while let ExpressionKind::AliasExpanded(id, subst) = &node.kind {
584 stack.push((*id, node.span));
585 node = subst;
586 }
587 (node, stack)
588}
589
590fn attach_aliases_err(
591 err: FilesetParseError,
592 stack: &[(AliasId<'_>, pest::Span<'_>)],
593) -> FilesetParseError {
594 stack
595 .iter()
596 .rfold(err, |err, &(id, span)| err.within_alias_expansion(id, span))
597}
598
599#[cfg(test)]
600mod tests {
601 use std::assert_matches;
602
603 use super::*;
604 use crate::dsl_util::KeywordArgument;
605 use crate::tests::TestResult;
606
607 #[derive(Debug)]
608 struct WithFilesetAliasesMap {
609 aliases_map: FilesetAliasesMap,
610 }
611
612 impl WithFilesetAliasesMap {
613 fn parse<'i>(&'i self, text: &'i str) -> FilesetParseResult<ExpressionNode<'i>> {
614 let node = parse_program(text)?;
615 expand_aliases(node, &self.aliases_map)
616 }
617
618 fn parse_normalized<'i>(&'i self, text: &'i str) -> ExpressionNode<'i> {
619 normalize_tree(self.parse(text).unwrap())
620 }
621 }
622
623 fn with_aliases(
624 aliases: impl IntoIterator<Item = (impl AsRef<str>, impl Into<String>)>,
625 ) -> WithFilesetAliasesMap {
626 let mut aliases_map = FilesetAliasesMap::new();
627 for (decl, defn) in aliases {
628 aliases_map.insert(decl, defn, None).unwrap();
629 }
630 WithFilesetAliasesMap { aliases_map }
631 }
632
633 fn parse_into_kind(text: &str) -> Result<ExpressionKind<'_>, FilesetParseErrorKind> {
634 parse_program(text)
635 .map(|node| node.kind)
636 .map_err(|err| err.kind)
637 }
638
639 fn parse_maybe_bare_into_kind(text: &str) -> Result<ExpressionKind<'_>, FilesetParseErrorKind> {
640 parse_program_or_bare_string(text)
641 .map(|node| node.kind)
642 .map_err(|err| err.kind)
643 }
644
645 fn parse_normalized(text: &str) -> ExpressionNode<'_> {
646 normalize_tree(parse_program(text).unwrap())
647 }
648
649 fn parse_maybe_bare_normalized(text: &str) -> ExpressionNode<'_> {
650 normalize_tree(parse_program_or_bare_string(text).unwrap())
651 }
652
653 fn normalize_tree(node: ExpressionNode) -> ExpressionNode {
655 fn empty_span() -> pest::Span<'static> {
656 pest::Span::new("", 0, 0).unwrap()
657 }
658
659 fn normalize_list(nodes: Vec<ExpressionNode>) -> Vec<ExpressionNode> {
660 nodes.into_iter().map(normalize_tree).collect()
661 }
662
663 fn normalize_function_call(function: FunctionCallNode) -> FunctionCallNode {
664 FunctionCallNode {
665 name: function.name,
666 name_span: empty_span(),
667 args: normalize_list(function.args),
668 keyword_args: function
669 .keyword_args
670 .into_iter()
671 .map(|arg| KeywordArgument {
672 name: arg.name,
673 name_span: empty_span(),
674 value: normalize_tree(arg.value),
675 })
676 .collect(),
677 args_span: empty_span(),
678 }
679 }
680
681 let normalized_kind = match node.kind {
682 ExpressionKind::Identifier(_) | ExpressionKind::String(_) => node.kind,
683 ExpressionKind::Pattern(pattern) => {
684 let pattern = Box::new(PatternNode {
685 name: pattern.name,
686 name_span: empty_span(),
687 value: normalize_tree(pattern.value),
688 });
689 ExpressionKind::Pattern(pattern)
690 }
691 ExpressionKind::Unary(op, arg) => {
692 let arg = Box::new(normalize_tree(*arg));
693 ExpressionKind::Unary(op, arg)
694 }
695 ExpressionKind::Binary(op, lhs, rhs) => {
696 let lhs = Box::new(normalize_tree(*lhs));
697 let rhs = Box::new(normalize_tree(*rhs));
698 ExpressionKind::Binary(op, lhs, rhs)
699 }
700 ExpressionKind::UnionAll(nodes) => {
701 let nodes = normalize_list(nodes);
702 ExpressionKind::UnionAll(nodes)
703 }
704 ExpressionKind::FunctionCall(function) => {
705 let function = Box::new(normalize_function_call(*function));
706 ExpressionKind::FunctionCall(function)
707 }
708 ExpressionKind::AliasExpanded(_, subst) => normalize_tree(*subst).kind,
709 };
710 ExpressionNode {
711 kind: normalized_kind,
712 span: empty_span(),
713 }
714 }
715
716 #[test]
717 fn test_parse_tree_eq() {
718 assert_eq!(
719 parse_normalized(r#" foo( x ) | ~bar:"baz" "#),
720 parse_normalized(r#"(foo(x))|(~(bar:"baz"))"#)
721 );
722 assert_ne!(parse_normalized(r#" foo "#), parse_normalized(r#" "foo" "#));
723 }
724
725 #[test]
726 fn test_parse_invalid_function_name() {
727 assert_eq!(
728 parse_into_kind("5foo(x)"),
729 Err(FilesetParseErrorKind::SyntaxError)
730 );
731 }
732
733 #[test]
734 fn test_parse_whitespace() {
735 let ascii_whitespaces: String = ('\x00'..='\x7f')
736 .filter(char::is_ascii_whitespace)
737 .collect();
738 assert_eq!(
739 parse_normalized(&format!("{ascii_whitespaces}f()")),
740 parse_normalized("f()")
741 );
742 }
743
744 #[test]
745 fn test_parse_identifier() {
746 assert_eq!(
747 parse_into_kind("dir/foo-bar_0.baz"),
748 Ok(ExpressionKind::Identifier("dir/foo-bar_0.baz"))
749 );
750 assert_eq!(
751 parse_into_kind("cli-reference@.md.snap"),
752 Ok(ExpressionKind::Identifier("cli-reference@.md.snap"))
753 );
754 assert_eq!(
755 parse_into_kind("柔術.jj"),
756 Ok(ExpressionKind::Identifier("柔術.jj"))
757 );
758 assert_eq!(
759 parse_into_kind(r#"Windows\Path"#),
760 Ok(ExpressionKind::Identifier(r#"Windows\Path"#))
761 );
762 assert_eq!(
763 parse_into_kind("glob*[chars]?"),
764 Ok(ExpressionKind::Identifier("glob*[chars]?"))
765 );
766 }
767
768 #[test]
769 fn test_parse_string_literal() {
770 assert_eq!(
772 parse_into_kind(r#" "\t\r\n\"\\\0\e" "#),
773 Ok(ExpressionKind::String("\t\r\n\"\\\0\u{1b}".to_owned())),
774 );
775
776 assert_eq!(
778 parse_into_kind(r#" "\y" "#),
779 Err(FilesetParseErrorKind::SyntaxError),
780 );
781
782 assert_eq!(
784 parse_into_kind(r#" '' "#),
785 Ok(ExpressionKind::String("".to_owned())),
786 );
787 assert_eq!(
788 parse_into_kind(r#" 'a\n' "#),
789 Ok(ExpressionKind::String(r"a\n".to_owned())),
790 );
791 assert_eq!(
792 parse_into_kind(r#" '\' "#),
793 Ok(ExpressionKind::String(r"\".to_owned())),
794 );
795 assert_eq!(
796 parse_into_kind(r#" '"' "#),
797 Ok(ExpressionKind::String(r#"""#.to_owned())),
798 );
799
800 assert_eq!(
802 parse_into_kind(r#""\x61\x65\x69\x6f\x75""#),
803 Ok(ExpressionKind::String("aeiou".to_owned())),
804 );
805 assert_eq!(
806 parse_into_kind(r#""\xe0\xe8\xec\xf0\xf9""#),
807 Ok(ExpressionKind::String("àèìðù".to_owned())),
808 );
809 assert_eq!(
810 parse_into_kind(r#""\x""#),
811 Err(FilesetParseErrorKind::SyntaxError),
812 );
813 assert_eq!(
814 parse_into_kind(r#""\xf""#),
815 Err(FilesetParseErrorKind::SyntaxError),
816 );
817 assert_eq!(
818 parse_into_kind(r#""\xgg""#),
819 Err(FilesetParseErrorKind::SyntaxError),
820 );
821 }
822
823 #[test]
824 fn test_parse_pattern() -> TestResult {
825 fn unwrap_pattern(kind: ExpressionKind<'_>) -> (&str, ExpressionKind<'_>) {
826 match kind {
827 ExpressionKind::Pattern(pattern) => (pattern.name, pattern.value.kind),
828 _ => panic!("unexpected expression: {kind:?}"),
829 }
830 }
831
832 assert_eq!(
833 unwrap_pattern(parse_into_kind(r#" foo:bar "#)?),
834 ("foo", ExpressionKind::Identifier("bar"))
835 );
836 assert_eq!(
837 unwrap_pattern(parse_into_kind(" foo:glob*[chars]? ")?),
838 ("foo", ExpressionKind::Identifier("glob*[chars]?"))
839 );
840 assert_eq!(
841 unwrap_pattern(parse_into_kind(r#" foo:"bar" "#)?),
842 ("foo", ExpressionKind::String("bar".to_owned()))
843 );
844 assert_eq!(
845 unwrap_pattern(parse_into_kind(r#" foo:"" "#)?),
846 ("foo", ExpressionKind::String("".to_owned()))
847 );
848 assert_eq!(
849 unwrap_pattern(parse_into_kind(r#" foo:'\' "#)?),
850 ("foo", ExpressionKind::String(r"\".to_owned()))
851 );
852 assert_eq!(
853 parse_into_kind(r#" foo: "#),
854 Err(FilesetParseErrorKind::SyntaxError)
855 );
856
857 assert_eq!(
859 parse_into_kind(r#" foo: "" "#),
860 Err(FilesetParseErrorKind::SyntaxError)
861 );
862 assert_eq!(
863 parse_into_kind(r#" foo :"" "#),
864 Err(FilesetParseErrorKind::SyntaxError)
865 );
866 assert_eq!(
868 parse_normalized("foo:( 'bar' )"),
869 parse_normalized("foo:'bar'")
870 );
871
872 assert_eq!(parse_normalized("x:f(y)"), parse_normalized("x:(f(y))"));
874 assert_eq!(parse_normalized("x:y&z"), parse_normalized("(x:y)&(z)"));
876 assert_matches!(
877 parse_into_kind("x:~y"), Err(FilesetParseErrorKind::SyntaxError)
879 );
880
881 assert_eq!(parse_normalized("x:y:z"), parse_normalized("x:(y:z)"));
883 Ok(())
884 }
885
886 #[test]
887 fn test_parse_operator() -> TestResult {
888 assert_matches!(
889 parse_into_kind("~x"),
890 Ok(ExpressionKind::Unary(UnaryOp::Negate, _))
891 );
892 assert_matches!(
893 parse_into_kind("x|y"),
894 Ok(ExpressionKind::UnionAll(nodes)) if nodes.len() == 2
895 );
896 assert_matches!(
897 parse_into_kind("x|y|z"),
898 Ok(ExpressionKind::UnionAll(nodes)) if nodes.len() == 3
899 );
900 assert_matches!(
901 parse_into_kind("x&y"),
902 Ok(ExpressionKind::Binary(BinaryOp::Intersection, _, _))
903 );
904 assert_matches!(
905 parse_into_kind("x~y"),
906 Ok(ExpressionKind::Binary(BinaryOp::Difference, _, _))
907 );
908
909 assert_eq!(parse_normalized("~x|y"), parse_normalized("(~x)|y"));
911 assert_eq!(parse_normalized("x&~y"), parse_normalized("x&(~y)"));
912 assert_eq!(parse_normalized("x~~y"), parse_normalized("x~(~y)"));
913 assert_eq!(parse_normalized("x~~~y"), parse_normalized("x~(~(~y))"));
914 assert_eq!(parse_normalized("x|y|z"), parse_normalized("(x|y)|z"));
915 assert_eq!(parse_normalized("x&y|z"), parse_normalized("(x&y)|z"));
916 assert_eq!(parse_normalized("x|y&z"), parse_normalized("x|(y&z)"));
917 assert_eq!(parse_normalized("x|y~z"), parse_normalized("x|(y~z)"));
918 assert_eq!(parse_normalized("~x:y"), parse_normalized("~(x:y)"));
919 assert_eq!(parse_normalized("x|y:z"), parse_normalized("x|(y:z)"));
920
921 assert_eq!(parse_program(" ~ x ")?.span.as_str(), "~ x");
923 assert_eq!(parse_program(" x |y ")?.span.as_str(), "x |y");
924 assert_eq!(parse_program(" (x) ")?.span.as_str(), "(x)");
925 assert_eq!(parse_program("~( x|y) ")?.span.as_str(), "~( x|y)");
926 Ok(())
927 }
928
929 #[test]
930 fn test_parse_function_call() -> TestResult {
931 fn unwrap_function_call(node: ExpressionNode<'_>) -> Box<FunctionCallNode<'_>> {
932 match node.kind {
933 ExpressionKind::FunctionCall(function) => function,
934 _ => panic!("unexpected expression: {node:?}"),
935 }
936 }
937
938 assert_matches!(
939 parse_into_kind("foo()"),
940 Ok(ExpressionKind::FunctionCall(_))
941 );
942
943 assert!(parse_into_kind("foo(,)").is_err());
945
946 assert_eq!(parse_normalized("foo(a,)"), parse_normalized("foo(a)"));
948 assert_eq!(parse_normalized("foo(a , )"), parse_normalized("foo(a)"));
949 assert!(parse_into_kind("foo(,a)").is_err());
950 assert!(parse_into_kind("foo(a,,)").is_err());
951 assert!(parse_into_kind("foo(a , , )").is_err());
952 assert_eq!(parse_normalized("foo(a,b,)"), parse_normalized("foo(a,b)"));
953 assert!(parse_into_kind("foo(a,,b)").is_err());
954
955 let function = unwrap_function_call(parse_program("foo( a, (b) , ~(c) )")?);
957 assert_eq!(function.name_span.as_str(), "foo");
958 assert_eq!(function.args_span.as_str(), "a, (b) , ~(c)");
959 assert_eq!(function.args[0].span.as_str(), "a");
960 assert_eq!(function.args[1].span.as_str(), "(b)");
961 assert_eq!(function.args[2].span.as_str(), "~(c)");
962 Ok(())
963 }
964
965 #[test]
966 fn test_parse_bare_string() -> TestResult {
967 fn unwrap_pattern(kind: ExpressionKind<'_>) -> (&str, ExpressionKind<'_>) {
968 match kind {
969 ExpressionKind::Pattern(pattern) => (pattern.name, pattern.value.kind),
970 _ => panic!("unexpected expression: {kind:?}"),
971 }
972 }
973
974 assert_eq!(
976 parse_maybe_bare_into_kind(" valid "),
977 Ok(ExpressionKind::Identifier("valid"))
978 );
979 assert_eq!(
980 parse_maybe_bare_normalized("f(x)&y"),
981 parse_normalized("f(x)&y")
982 );
983 assert_eq!(
984 unwrap_pattern(parse_maybe_bare_into_kind("foo:bar")?),
985 ("foo", ExpressionKind::Identifier("bar"))
986 );
987
988 assert_eq!(
990 parse_maybe_bare_into_kind("Foo Bar.txt"),
991 Ok(ExpressionKind::String("Foo Bar.txt".to_owned()))
992 );
993 assert_eq!(
994 parse_maybe_bare_into_kind(r#"Windows\Path with space"#),
995 Ok(ExpressionKind::String(
996 r#"Windows\Path with space"#.to_owned()
997 ))
998 );
999 assert_eq!(
1000 parse_maybe_bare_into_kind("柔 術 . j j"),
1001 Ok(ExpressionKind::String("柔 術 . j j".to_owned()))
1002 );
1003 assert_eq!(
1004 parse_maybe_bare_into_kind("Unicode emoji 💩"),
1005 Ok(ExpressionKind::String("Unicode emoji 💩".to_owned()))
1006 );
1007 assert_eq!(
1008 parse_maybe_bare_into_kind("looks like & expression"),
1009 Err(FilesetParseErrorKind::SyntaxError)
1010 );
1011 assert_eq!(
1012 parse_maybe_bare_into_kind("unbalanced_parens("),
1013 Err(FilesetParseErrorKind::SyntaxError)
1014 );
1015
1016 assert_eq!(
1018 unwrap_pattern(parse_maybe_bare_into_kind("foo: bar baz")?),
1019 ("foo", ExpressionKind::String(" bar baz".to_owned()))
1020 );
1021 assert_eq!(
1022 unwrap_pattern(parse_maybe_bare_into_kind("foo:glob * [chars]?")?),
1023 ("foo", ExpressionKind::String("glob * [chars]?".to_owned()))
1024 );
1025 assert_eq!(
1026 parse_maybe_bare_into_kind("foo: bar:baz"),
1027 Err(FilesetParseErrorKind::SyntaxError)
1028 );
1029 assert_eq!(
1030 parse_maybe_bare_into_kind("foo:"),
1031 Err(FilesetParseErrorKind::SyntaxError)
1032 );
1033 assert_eq!(
1034 parse_maybe_bare_into_kind(r#"foo:"unclosed quote"#),
1035 Err(FilesetParseErrorKind::SyntaxError)
1036 );
1037
1038 assert_eq!(
1041 parse_maybe_bare_into_kind(" No trim "),
1042 Ok(ExpressionKind::String(" No trim ".to_owned()))
1043 );
1044 Ok(())
1045 }
1046
1047 #[test]
1048 fn test_parse_error() {
1049 insta::assert_snapshot!(parse_program("foo|").unwrap_err().to_string(), @"
1050 --> 1:5
1051 |
1052 1 | foo|
1053 | ^---
1054 |
1055 = expected `~` or <primary>
1056 ");
1057 }
1058
1059 #[test]
1060 fn test_parse_alias_symbol_decl() -> TestResult {
1061 let mut aliases_map = FilesetAliasesMap::new();
1062 aliases_map.insert("sym", "symbol", None)?;
1063 assert_eq!(aliases_map.symbol_names().count(), 1);
1064 let (id, defn, _doc) = aliases_map.get_symbol("sym").unwrap();
1065 assert_eq!(id, AliasId::Symbol("sym"));
1066 assert_eq!(defn, "symbol");
1067
1068 assert!(aliases_map.insert("柔術", "none()", None).is_err());
1071 Ok(())
1072 }
1073
1074 #[test]
1075 fn test_parse_alias_pattern_decl() -> TestResult {
1076 let mut aliases_map = FilesetAliasesMap::new();
1077 assert!(aliases_map.insert("pat:", "bad_pattern", None).is_err());
1078 aliases_map.insert("pat:a", "pattern_a", None)?;
1079 aliases_map.insert("pat:b", "pattern_b", None)?;
1080 assert_eq!(aliases_map.pattern_names().count(), 1);
1081 let (id, param, defn, _doc) = aliases_map.get_pattern("pat").unwrap();
1082 assert_eq!(id, AliasId::Pattern("pat", "b"));
1083 assert_eq!(param, "b");
1084 assert_eq!(defn, "pattern_b");
1085
1086 assert!(aliases_map.insert("柔術:x", "none()", None).is_err());
1089 assert!(aliases_map.insert("x:柔術", "none()", None).is_err());
1090 Ok(())
1091 }
1092
1093 #[test]
1094 fn test_parse_alias_func_decl() -> TestResult {
1095 let mut aliases_map = FilesetAliasesMap::new();
1096 assert!(aliases_map.insert("5func()", "bad_function", None).is_err());
1097 aliases_map.insert("func()", "function_0", None)?;
1098 aliases_map.insert("func(a)", "function_1a", None)?;
1099 aliases_map.insert("func(b)", "function_1b", None)?;
1100 aliases_map.insert("func(a, b)", "function_2", None)?;
1101 assert_eq!(aliases_map.function_names().count(), 1);
1102
1103 let (id, params, defn, _doc) = aliases_map.get_function("func", 0).unwrap();
1104 assert_eq!(id, AliasId::Function("func", &[]));
1105 assert!(params.is_empty());
1106 assert_eq!(defn, "function_0");
1107
1108 let (id, params, defn, _doc) = aliases_map.get_function("func", 1).unwrap();
1109 assert_eq!(id, AliasId::Function("func", &["b".to_owned()]));
1110 assert_eq!(params, ["b"]);
1111 assert_eq!(defn, "function_1b");
1112
1113 let (id, params, defn, _doc) = aliases_map.get_function("func", 2).unwrap();
1114 assert_eq!(
1115 id,
1116 AliasId::Function("func", &["a".to_owned(), "b".to_owned()])
1117 );
1118 assert_eq!(params, ["a", "b"]);
1119 assert_eq!(defn, "function_2");
1120
1121 assert!(aliases_map.get_function("func", 3).is_none());
1122 Ok(())
1123 }
1124
1125 #[test]
1126 fn test_parse_alias_formal_parameter() {
1127 let mut aliases_map = FilesetAliasesMap::new();
1128 assert_eq!(
1130 aliases_map.insert("f(a, a)", "bad", None).unwrap_err().kind,
1131 FilesetParseErrorKind::RedefinedFunctionParameter
1132 );
1133 assert!(aliases_map.insert("f(,)", "bad", None).is_err());
1135 assert!(aliases_map.insert("g(a,)", "bad", None).is_ok());
1137 assert!(aliases_map.insert("h(a , )", "bad", None).is_ok());
1138 assert!(aliases_map.insert("i(,a)", "bad", None).is_err());
1139 assert!(aliases_map.insert("j(a,,)", "bad", None).is_err());
1140 assert!(aliases_map.insert("k(a , , )", "bad", None).is_err());
1141 assert!(aliases_map.insert("l(a,b,)", "bad", None).is_ok());
1142 assert!(aliases_map.insert("m(a,,b)", "bad", None).is_err());
1143 }
1144
1145 #[test]
1146 fn test_expand_symbol_alias() {
1147 assert_eq!(
1148 with_aliases([("AB", "a&b")]).parse_normalized("AB|c"),
1149 parse_normalized("(a&b)|c")
1150 );
1151 assert_eq!(
1152 with_aliases([("AB", "a|b")]).parse_normalized("AB~f(AB)"),
1153 parse_normalized("(a|b)~f(a|b)")
1154 );
1155
1156 assert_eq!(
1158 with_aliases([("BC", "b|c")]).parse_normalized("a&BC"),
1159 parse_normalized("a&(b|c)")
1160 );
1161
1162 assert_eq!(
1164 with_aliases([("A", "a")]).parse_normalized(r#"A|"A"|'A'"#),
1165 parse_normalized("a|'A'|'A'")
1166 );
1167
1168 assert_eq!(
1171 with_aliases([("A", "a")]).parse_normalized("A:b"),
1172 parse_normalized("A:b")
1173 );
1174
1175 assert_eq!(
1177 with_aliases([("A", "a")]).parse_normalized("p:A"),
1178 parse_normalized("p:a")
1179 );
1180 assert_eq!(
1181 with_aliases([("A", "a")]).parse_normalized("p:'A'"),
1182 parse_normalized("p:'A'")
1183 );
1184
1185 assert_eq!(
1187 with_aliases([("A", "BC"), ("BC", "b|C"), ("C", "c")]).parse_normalized("A"),
1188 parse_normalized("b|c")
1189 );
1190
1191 assert_eq!(
1193 with_aliases([("A", "A")]).parse("A").unwrap_err().kind,
1194 FilesetParseErrorKind::InAliasExpansion("A".to_owned())
1195 );
1196 assert_eq!(
1197 with_aliases([("A", "B"), ("B", "b|C"), ("C", "c|B")])
1198 .parse("A")
1199 .unwrap_err()
1200 .kind,
1201 FilesetParseErrorKind::InAliasExpansion("A".to_owned())
1202 );
1203
1204 assert_eq!(
1206 with_aliases([("A", "a(")]).parse("A").unwrap_err().kind,
1207 FilesetParseErrorKind::InAliasExpansion("A".to_owned())
1208 );
1209 }
1210
1211 #[test]
1212 fn test_expand_pattern_alias() {
1213 assert_eq!(
1214 with_aliases([("P:x", "x")]).parse_normalized("P:a"),
1215 parse_normalized("a")
1216 );
1217
1218 assert_eq!(
1220 with_aliases([("P:x", "x|a")]).parse_normalized("P:x"),
1221 parse_normalized("x|a")
1222 );
1223 assert_eq!(
1225 with_aliases([("P:x", "(Q:x)&y"), ("Q:y", "x|y")]).parse_normalized("P:a"),
1226 parse_normalized("(x|a)&y")
1227 );
1228
1229 assert_eq!(
1231 with_aliases([("P:X", "X"), ("X", "x")]).parse_normalized("(P:a)|X"),
1232 parse_normalized("a|x")
1233 );
1234
1235 assert_eq!(
1237 with_aliases([("P:x", "x|A"), ("A", "x")]).parse_normalized("P:a"),
1238 parse_normalized("a|x")
1239 );
1240
1241 assert_eq!(
1243 with_aliases([("P:x", "x|'x'")]).parse_normalized("P:a"),
1244 parse_normalized("a|'x'")
1245 );
1246
1247 assert_eq!(
1249 with_aliases([("A:x", "A"), ("A", "a")]).parse_normalized("A:x"),
1250 parse_normalized("a")
1251 );
1252
1253 assert_eq!(
1255 with_aliases([("P:x", "Q:x"), ("Q:x", "R:x"), ("R:x", "P:x")])
1256 .parse("P:a")
1257 .unwrap_err()
1258 .kind,
1259 FilesetParseErrorKind::InAliasExpansion("P:x".to_owned())
1260 );
1261 }
1262
1263 #[test]
1264 fn test_expand_function_alias() {
1265 assert_eq!(
1266 with_aliases([("F( )", "a")]).parse_normalized("F()"),
1267 parse_normalized("a")
1268 );
1269 assert_eq!(
1270 with_aliases([("F( x )", "x")]).parse_normalized("F(a)"),
1271 parse_normalized("a")
1272 );
1273 assert_eq!(
1274 with_aliases([("F( x, y )", "x|y")]).parse_normalized("F(a, b)"),
1275 parse_normalized("a|b")
1276 );
1277
1278 assert_eq!(
1280 with_aliases([("F(x)", "F(x,b)"), ("F(x,y)", "x|y")]).parse_normalized("F(a)"),
1281 parse_normalized("a|b")
1282 );
1283
1284 assert_eq!(
1286 with_aliases([("F(x,y)", "x|y")]).parse_normalized("F(a~y,b~x)"),
1287 parse_normalized("(a~y)|(b~x)")
1288 );
1289 assert_eq!(
1291 with_aliases([("F(x)", "G(x)&y"), ("G(y)", "x|y")]).parse_normalized("F(a)"),
1292 parse_normalized("(x|a)&y")
1293 );
1294 assert_eq!(
1296 with_aliases([("F(x)", "G(x)&y"), ("G(y)", "x|y")]).parse_normalized("F(G(a))"),
1297 parse_normalized("(x|(x|a))&y")
1298 );
1299
1300 assert_eq!(
1302 with_aliases([("F(X)", "X"), ("X", "x")]).parse_normalized("F(a)|X"),
1303 parse_normalized("a|x")
1304 );
1305
1306 assert_eq!(
1308 with_aliases([("F(x)", "x|A"), ("A", "x")]).parse_normalized("F(a)"),
1309 parse_normalized("a|x")
1310 );
1311
1312 assert_eq!(
1314 with_aliases([("F(x)", "x|'x'")]).parse_normalized("F(a)"),
1315 parse_normalized("a|'x'")
1316 );
1317
1318 assert_eq!(
1320 with_aliases([("A()", "A"), ("A", "a")]).parse_normalized("A()"),
1321 parse_normalized("a")
1322 );
1323
1324 assert_eq!(
1326 with_aliases([("F()", "x")]).parse("F(a)").unwrap_err().kind,
1327 FilesetParseErrorKind::InvalidArguments {
1328 name: "F".to_owned(),
1329 message: "Expected 0 arguments".to_owned()
1330 }
1331 );
1332 assert_eq!(
1333 with_aliases([("F(x)", "x")]).parse("F()").unwrap_err().kind,
1334 FilesetParseErrorKind::InvalidArguments {
1335 name: "F".to_owned(),
1336 message: "Expected 1 arguments".to_owned()
1337 }
1338 );
1339 assert_eq!(
1340 with_aliases([("F(x,y)", "x|y")])
1341 .parse("F(a,b,c)")
1342 .unwrap_err()
1343 .kind,
1344 FilesetParseErrorKind::InvalidArguments {
1345 name: "F".to_owned(),
1346 message: "Expected 2 arguments".to_owned()
1347 }
1348 );
1349 assert_eq!(
1350 with_aliases([("F(x)", "x"), ("F(x,y)", "x|y")])
1351 .parse("F()")
1352 .unwrap_err()
1353 .kind,
1354 FilesetParseErrorKind::InvalidArguments {
1355 name: "F".to_owned(),
1356 message: "Expected 1 to 2 arguments".to_owned()
1357 }
1358 );
1359 assert_eq!(
1360 with_aliases([("F()", "x"), ("F(x,y)", "x|y")])
1361 .parse("F(a)")
1362 .unwrap_err()
1363 .kind,
1364 FilesetParseErrorKind::InvalidArguments {
1365 name: "F".to_owned(),
1366 message: "Expected 0, 2 arguments".to_owned()
1367 }
1368 );
1369
1370 assert_eq!(
1372 with_aliases([("F(x)", "G(x)"), ("G(x)", "H(x)"), ("H(x)", "F(x)")])
1373 .parse("F(a)")
1374 .unwrap_err()
1375 .kind,
1376 FilesetParseErrorKind::InAliasExpansion("F(x)".to_owned())
1377 );
1378 assert_eq!(
1379 with_aliases([("F(x)", "F(x,b)"), ("F(x,y)", "F(x|y)")])
1380 .parse("F(a)")
1381 .unwrap_err()
1382 .kind,
1383 FilesetParseErrorKind::InAliasExpansion("F(x)".to_owned())
1384 );
1385 }
1386}