Skip to main content

jj_lib/
fileset_parser.rs

1// Copyright 2024 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Parser for the fileset language.
16
17use 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    // This generates a `pub enum Rule` type.
48    #[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
97/// Manages diagnostic messages emitted during fileset parsing and name
98/// resolution.
99pub type FilesetDiagnostics = Diagnostics<FilesetParseError>;
100
101/// Result of fileset parsing and name resolution.
102pub type FilesetParseResult<T> = Result<T, FilesetParseError>;
103
104/// Error occurred during fileset parsing and name resolution.
105#[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/// Categories of fileset parsing and name resolution error.
114#[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    /// Creates a new error with the given `kind` and `span`.
140    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    /// Attaches the `source` error.
154    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    /// Some other expression error.
163    pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
164        Self::new(FilesetParseErrorKind::Expression(message.into()), span)
165    }
166
167    /// Category of the underlying error.
168    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/// AST expression item.
222#[derive(Clone, Debug, Eq, PartialEq)]
223pub enum ExpressionKind<'i> {
224    /// Unquoted symbol.
225    Identifier(&'i str),
226    /// Quoted symbol or string.
227    String(String),
228    /// `<name>:<value>` where `<value>` is usually `Identifier` or `String`.
229    Pattern(Box<PatternNode<'i>>),
230    /// `<op> <arg>` or `<arg> <op>`.
231    Unary(UnaryOp, Box<ExpressionNode<'i>>),
232    /// `<lhs> <op> <rhs>`.
233    Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
234    /// `x | y | ..`
235    UnionAll(Vec<ExpressionNode<'i>>),
236    /// `<name>(<args>..)`
237    FunctionCall(Box<FunctionCallNode<'i>>),
238    /// Identity node to preserve the span in the source text.
239    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/// Unary operator.
292#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
293pub enum UnaryOp {
294    /// `~`
295    Negate,
296}
297
298/// Binary operator.
299#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
300pub enum BinaryOp {
301    /// `&`
302    Intersection,
303    /// `~`
304    Difference,
305}
306
307/// AST node without type or name checking.
308pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
309/// Function call in AST.
310pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;
311/// `<name>:<value>` expression in AST.
312pub 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        // Flatten "x | y | z" to save recursion stack. Machine-generated query
318        // might have long chain of unions.
319        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![], // unsupported
345        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        // Ignore inner span to preserve parenthesized expression as such.
368        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
430/// Parses text into expression tree. No name resolution is made at this stage.
431pub 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
437/// Parses text into expression tree with bare string fallback. No name
438/// resolution is made at this stage.
439///
440/// If the text can't be parsed as a fileset expression, and if it doesn't
441/// contain any operator-like characters, it will be parsed as a file path.
442pub 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
470/// Map of fileset aliases.
471pub type FilesetAliasesMap = AliasesMap<FilesetAliasParser, String>;
472
473/// Parser for the fileset symbol and function alias declarations.
474#[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
530/// Expands aliases recursively.
531pub 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
538/// Unwraps the inner value if the given `node` is an identifier or string.
539pub(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
553/// Applies the given function to the innermost `node` by unwrapping alias
554/// expansion nodes. Appends alias expansion stack to error and diagnostics.
555pub(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    /// Drops auxiliary data from parsed tree so it can be compared with other.
654    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        // "\<char>" escapes
771        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        // Invalid "\<char>" escape
777        assert_eq!(
778            parse_into_kind(r#" "\y" "#),
779            Err(FilesetParseErrorKind::SyntaxError),
780        );
781
782        // Single-quoted raw string
783        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        // Hex bytes
801        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        // Whitespace isn't allowed in between
858        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        // Whitespace is allowed in parenthesized value expression
867        assert_eq!(
868            parse_normalized("foo:( 'bar' )"),
869            parse_normalized("foo:'bar'")
870        );
871
872        // Functions are allowed
873        assert_eq!(parse_normalized("x:f(y)"), parse_normalized("x:(f(y))"));
874        // Logical operators have lower binding strength
875        assert_eq!(parse_normalized("x:y&z"), parse_normalized("(x:y)&(z)"));
876        assert_matches!(
877            parse_into_kind("x:~y"), // (x:) ~ (y)
878            Err(FilesetParseErrorKind::SyntaxError)
879        );
880
881        // Pattern prefix is like (type)x cast, so is evaluated from right
882        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        // Set operator associativity/precedence
910        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        // Expression span
922        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        // Trailing comma isn't allowed for empty argument
944        assert!(parse_into_kind("foo(,)").is_err());
945
946        // Trailing comma is allowed for the last argument
947        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        // Expression span
956        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        // Valid expression should be parsed as such
975        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        // Bare string
989        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        // Bare string pattern
1017        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        // Surrounding spaces are simply preserved. They could be trimmed, but
1039        // space is valid bare_string character.
1040        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        // Non-ASCII character isn't allowed in alias symbol. This rule can be
1069        // relaxed if needed.
1070        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        // Non-ASCII character isn't allowed. This rule can be relaxed if
1087        // needed.
1088        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        // Formal parameter 'a' can't be redefined
1129        assert_eq!(
1130            aliases_map.insert("f(a, a)", "bad", None).unwrap_err().kind,
1131            FilesetParseErrorKind::RedefinedFunctionParameter
1132        );
1133        // Trailing comma isn't allowed for empty parameter
1134        assert!(aliases_map.insert("f(,)", "bad", None).is_err());
1135        // Trailing comma is allowed for the last parameter
1136        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        // Not string substitution 'a&b|c', but tree substitution.
1157        assert_eq!(
1158            with_aliases([("BC", "b|c")]).parse_normalized("a&BC"),
1159            parse_normalized("a&(b|c)")
1160        );
1161
1162        // String literal should not be substituted with alias.
1163        assert_eq!(
1164            with_aliases([("A", "a")]).parse_normalized(r#"A|"A"|'A'"#),
1165            parse_normalized("a|'A'|'A'")
1166        );
1167
1168        // Kind of string pattern should not be substituted, which is similar to
1169        // function name.
1170        assert_eq!(
1171            with_aliases([("A", "a")]).parse_normalized("A:b"),
1172            parse_normalized("A:b")
1173        );
1174
1175        // Value of string pattern can be substituted if it's an identifier.
1176        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        // Multi-level substitution.
1186        assert_eq!(
1187            with_aliases([("A", "BC"), ("BC", "b|C"), ("C", "c")]).parse_normalized("A"),
1188            parse_normalized("b|c")
1189        );
1190
1191        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1192        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        // Error in alias definition.
1205        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        // Argument should be resolved in the current scope.
1219        assert_eq!(
1220            with_aliases([("P:x", "x|a")]).parse_normalized("P:x"),
1221            parse_normalized("x|a")
1222        );
1223        // P:a -> (Q:a)&y -> (x|a)&y
1224        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        // Pattern parameter should precede the symbol alias.
1230        assert_eq!(
1231            with_aliases([("P:X", "X"), ("X", "x")]).parse_normalized("(P:a)|X"),
1232            parse_normalized("a|x")
1233        );
1234
1235        // Pattern parameter shouldn't be expanded in symbol alias.
1236        assert_eq!(
1237            with_aliases([("P:x", "x|A"), ("A", "x")]).parse_normalized("P:a"),
1238            parse_normalized("a|x")
1239        );
1240
1241        // String literal should not be substituted with pattern parameter.
1242        assert_eq!(
1243            with_aliases([("P:x", "x|'x'")]).parse_normalized("P:a"),
1244            parse_normalized("a|'x'")
1245        );
1246
1247        // Pattern and symbol aliases reside in separate namespaces.
1248        assert_eq!(
1249            with_aliases([("A:x", "A"), ("A", "a")]).parse_normalized("A:x"),
1250            parse_normalized("a")
1251        );
1252
1253        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1254        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        // Not recursion because functions are overloaded by arity.
1279        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        // Arguments should be resolved in the current scope.
1285        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        // F(a) -> G(a)&y -> (x|a)&y
1290        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        // F(G(a)) -> F(x|a) -> G(x|a)&y -> (x|(x|a))&y
1295        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        // Function parameter should precede the symbol alias.
1301        assert_eq!(
1302            with_aliases([("F(X)", "X"), ("X", "x")]).parse_normalized("F(a)|X"),
1303            parse_normalized("a|x")
1304        );
1305
1306        // Function parameter shouldn't be expanded in symbol alias.
1307        assert_eq!(
1308            with_aliases([("F(x)", "x|A"), ("A", "x")]).parse_normalized("F(a)"),
1309            parse_normalized("a|x")
1310        );
1311
1312        // String literal should not be substituted with function parameter.
1313        assert_eq!(
1314            with_aliases([("F(x)", "x|'x'")]).parse_normalized("F(a)"),
1315            parse_normalized("a|'x'")
1316        );
1317
1318        // Function and symbol aliases reside in separate namespaces.
1319        assert_eq!(
1320            with_aliases([("A()", "A"), ("A", "a")]).parse_normalized("A()"),
1321            parse_normalized("a")
1322        );
1323
1324        // Invalid number of arguments.
1325        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        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1371        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}