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