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 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
91/// Manages diagnostic messages emitted during fileset parsing and name
92/// resolution.
93pub type FilesetDiagnostics = Diagnostics<FilesetParseError>;
94
95/// Result of fileset parsing and name resolution.
96pub type FilesetParseResult<T> = Result<T, FilesetParseError>;
97
98/// Error occurred during fileset parsing and name resolution.
99#[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/// Categories of fileset parsing and name resolution error.
108#[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    /// Some other expression error.
155    pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
156        Self::new(FilesetParseErrorKind::Expression(message.into()), span)
157    }
158
159    /// Category of the underlying error.
160    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    /// `<name>:<value>` where `<value>` is usually `Identifier` or `String`.
218    Pattern(Box<PatternNode<'i>>),
219    Unary(UnaryOp, Box<ExpressionNode<'i>>),
220    Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
221    /// `x | y | ..`
222    UnionAll(Vec<ExpressionNode<'i>>),
223    FunctionCall(Box<FunctionCallNode<'i>>),
224    /// Identity node to preserve the span in the source text.
225    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    /// `~`
280    Negate,
281}
282
283#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
284pub enum BinaryOp {
285    /// `&`
286    Intersection,
287    /// `~`
288    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        // Flatten "x | y | z" to save recursion stack. Machine-generated query
299        // might have long chain of unions.
300        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![], // unsupported
326        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        // Ignore inner span to preserve parenthesized expression as such.
349        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
411/// Parses text into expression tree. No name resolution is made at this stage.
412pub 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
418/// Parses text into expression tree with bare string fallback. No name
419/// resolution is made at this stage.
420///
421/// If the text can't be parsed as a fileset expression, and if it doesn't
422/// contain any operator-like characters, it will be parsed as a file path.
423pub 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
451/// Map of fileset aliases.
452pub 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
531/// Applies the given function to the innermost `node` by unwrapping alias
532/// expansion nodes. Appends alias expansion stack to error and diagnostics.
533pub(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    /// Drops auxiliary data from parsed tree so it can be compared with other.
632    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        // "\<char>" escapes
749        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        // Invalid "\<char>" escape
755        assert_eq!(
756            parse_into_kind(r#" "\y" "#),
757            Err(FilesetParseErrorKind::SyntaxError),
758        );
759
760        // Single-quoted raw string
761        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        // Hex bytes
779        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        // Whitespace isn't allowed in between
836        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        // Whitespace is allowed in parenthesized value expression
845        assert_eq!(
846            parse_normalized("foo:( 'bar' )"),
847            parse_normalized("foo:'bar'")
848        );
849
850        // Functions are allowed
851        assert_eq!(parse_normalized("x:f(y)"), parse_normalized("x:(f(y))"));
852        // Logical operators have lower binding strength
853        assert_eq!(parse_normalized("x:y&z"), parse_normalized("(x:y)&(z)"));
854        assert_matches!(
855            parse_into_kind("x:~y"), // (x:) ~ (y)
856            Err(FilesetParseErrorKind::SyntaxError)
857        );
858
859        // Pattern prefix is like (type)x cast, so is evaluated from right
860        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        // Set operator associativity/precedence
888        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        // Expression span
900        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        // Trailing comma isn't allowed for empty argument
922        assert!(parse_into_kind("foo(,)").is_err());
923
924        // Trailing comma is allowed for the last argument
925        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        // Expression span
934        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        // Valid expression should be parsed as such
953        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        // Bare string
967        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        // Bare string pattern
995        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        // Surrounding spaces are simply preserved. They could be trimmed, but
1017        // space is valid bare_string character.
1018        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        // Non-ASCII character isn't allowed in alias symbol. This rule can be
1047        // relaxed if needed.
1048        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        // Non-ASCII character isn't allowed. This rule can be relaxed if
1065        // needed.
1066        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        // Formal parameter 'a' can't be redefined
1107        assert_eq!(
1108            aliases_map.insert("f(a, a)", "bad").unwrap_err().kind,
1109            FilesetParseErrorKind::RedefinedFunctionParameter
1110        );
1111        // Trailing comma isn't allowed for empty parameter
1112        assert!(aliases_map.insert("f(,)", "bad").is_err());
1113        // Trailing comma is allowed for the last parameter
1114        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        // Not string substitution 'a&b|c', but tree substitution.
1135        assert_eq!(
1136            with_aliases([("BC", "b|c")]).parse_normalized("a&BC"),
1137            parse_normalized("a&(b|c)")
1138        );
1139
1140        // String literal should not be substituted with alias.
1141        assert_eq!(
1142            with_aliases([("A", "a")]).parse_normalized(r#"A|"A"|'A'"#),
1143            parse_normalized("a|'A'|'A'")
1144        );
1145
1146        // Kind of string pattern should not be substituted, which is similar to
1147        // function name.
1148        assert_eq!(
1149            with_aliases([("A", "a")]).parse_normalized("A:b"),
1150            parse_normalized("A:b")
1151        );
1152
1153        // Value of string pattern can be substituted if it's an identifier.
1154        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        // Multi-level substitution.
1164        assert_eq!(
1165            with_aliases([("A", "BC"), ("BC", "b|C"), ("C", "c")]).parse_normalized("A"),
1166            parse_normalized("b|c")
1167        );
1168
1169        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1170        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        // Error in alias definition.
1183        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        // Argument should be resolved in the current scope.
1197        assert_eq!(
1198            with_aliases([("P:x", "x|a")]).parse_normalized("P:x"),
1199            parse_normalized("x|a")
1200        );
1201        // P:a -> (Q:a)&y -> (x|a)&y
1202        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        // Pattern parameter should precede the symbol alias.
1208        assert_eq!(
1209            with_aliases([("P:X", "X"), ("X", "x")]).parse_normalized("(P:a)|X"),
1210            parse_normalized("a|x")
1211        );
1212
1213        // Pattern parameter shouldn't be expanded in symbol alias.
1214        assert_eq!(
1215            with_aliases([("P:x", "x|A"), ("A", "x")]).parse_normalized("P:a"),
1216            parse_normalized("a|x")
1217        );
1218
1219        // String literal should not be substituted with pattern parameter.
1220        assert_eq!(
1221            with_aliases([("P:x", "x|'x'")]).parse_normalized("P:a"),
1222            parse_normalized("a|'x'")
1223        );
1224
1225        // Pattern and symbol aliases reside in separate namespaces.
1226        assert_eq!(
1227            with_aliases([("A:x", "A"), ("A", "a")]).parse_normalized("A:x"),
1228            parse_normalized("a")
1229        );
1230
1231        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1232        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        // Not recursion because functions are overloaded by arity.
1257        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        // Arguments should be resolved in the current scope.
1263        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        // F(a) -> G(a)&y -> (x|a)&y
1268        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        // F(G(a)) -> F(x|a) -> G(x|a)&y -> (x|(x|a))&y
1273        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        // Function parameter should precede the symbol alias.
1279        assert_eq!(
1280            with_aliases([("F(X)", "X"), ("X", "x")]).parse_normalized("F(a)|X"),
1281            parse_normalized("a|x")
1282        );
1283
1284        // Function parameter shouldn't be expanded in symbol alias.
1285        assert_eq!(
1286            with_aliases([("F(x)", "x|A"), ("A", "x")]).parse_normalized("F(a)"),
1287            parse_normalized("a|x")
1288        );
1289
1290        // String literal should not be substituted with function parameter.
1291        assert_eq!(
1292            with_aliases([("F(x)", "x|'x'")]).parse_normalized("F(a)"),
1293            parse_normalized("a|'x'")
1294        );
1295
1296        // Function and symbol aliases reside in separate namespaces.
1297        assert_eq!(
1298            with_aliases([("A()", "A"), ("A", "a")]).parse_normalized("A()"),
1299            parse_normalized("a")
1300        );
1301
1302        // Invalid number of arguments.
1303        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        // Infinite recursion, where the top-level error isn't of RecursiveAlias kind.
1349        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}