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::Diagnostics;
31use crate::dsl_util::InvalidArguments;
32use crate::dsl_util::StringLiteralParser;
33
34#[derive(Parser)]
35#[grammar = "fileset.pest"]
36struct FilesetParser;
37
38const STRING_LITERAL_PARSER: StringLiteralParser<Rule> = StringLiteralParser {
39    content_rule: Rule::string_content,
40    escape_rule: Rule::string_escape,
41};
42
43impl Rule {
44    fn to_symbol(self) -> Option<&'static str> {
45        match self {
46            Self::EOI => None,
47            Self::whitespace => None,
48            Self::identifier => None,
49            Self::strict_identifier_part => None,
50            Self::strict_identifier => None,
51            Self::bare_string => None,
52            Self::string_escape => None,
53            Self::string_content_char => None,
54            Self::string_content => None,
55            Self::string_literal => None,
56            Self::raw_string_content => None,
57            Self::raw_string_literal => None,
58            Self::pattern_kind_op => Some(":"),
59            Self::negate_op => Some("~"),
60            Self::union_op => Some("|"),
61            Self::intersection_op => Some("&"),
62            Self::difference_op => Some("~"),
63            Self::prefix_ops => None,
64            Self::infix_ops => None,
65            Self::function => None,
66            Self::function_name => None,
67            Self::function_arguments => None,
68            Self::string_pattern => None,
69            Self::bare_string_pattern => None,
70            Self::primary => None,
71            Self::expression => None,
72            Self::program => None,
73            Self::program_or_bare_string => None,
74        }
75    }
76}
77
78/// Manages diagnostic messages emitted during fileset parsing and name
79/// resolution.
80pub type FilesetDiagnostics = Diagnostics<FilesetParseError>;
81
82/// Result of fileset parsing and name resolution.
83pub type FilesetParseResult<T> = Result<T, FilesetParseError>;
84
85/// Error occurred during fileset parsing and name resolution.
86#[derive(Debug, Error)]
87#[error("{pest_error}")]
88pub struct FilesetParseError {
89    kind: FilesetParseErrorKind,
90    pest_error: Box<pest::error::Error<Rule>>,
91    source: Option<Box<dyn error::Error + Send + Sync>>,
92}
93
94/// Categories of fileset parsing and name resolution error.
95#[expect(missing_docs)]
96#[derive(Clone, Debug, Eq, Error, PartialEq)]
97pub enum FilesetParseErrorKind {
98    #[error("Syntax error")]
99    SyntaxError,
100    #[error("Function `{name}` doesn't exist")]
101    NoSuchFunction {
102        name: String,
103        candidates: Vec<String>,
104    },
105    #[error("Function `{name}`: {message}")]
106    InvalidArguments { name: String, message: String },
107    #[error("{0}")]
108    Expression(String),
109}
110
111impl FilesetParseError {
112    pub(super) fn new(kind: FilesetParseErrorKind, span: pest::Span<'_>) -> Self {
113        let message = kind.to_string();
114        let pest_error = Box::new(pest::error::Error::new_from_span(
115            pest::error::ErrorVariant::CustomError { message },
116            span,
117        ));
118        Self {
119            kind,
120            pest_error,
121            source: None,
122        }
123    }
124
125    pub(super) fn with_source(
126        mut self,
127        source: impl Into<Box<dyn error::Error + Send + Sync>>,
128    ) -> Self {
129        self.source = Some(source.into());
130        self
131    }
132
133    /// Some other expression error.
134    pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
135        Self::new(FilesetParseErrorKind::Expression(message.into()), span)
136    }
137
138    /// Category of the underlying error.
139    pub fn kind(&self) -> &FilesetParseErrorKind {
140        &self.kind
141    }
142}
143
144impl From<pest::error::Error<Rule>> for FilesetParseError {
145    fn from(err: pest::error::Error<Rule>) -> Self {
146        Self {
147            kind: FilesetParseErrorKind::SyntaxError,
148            pest_error: Box::new(rename_rules_in_pest_error(err)),
149            source: None,
150        }
151    }
152}
153
154impl From<InvalidArguments<'_>> for FilesetParseError {
155    fn from(err: InvalidArguments<'_>) -> Self {
156        let kind = FilesetParseErrorKind::InvalidArguments {
157            name: err.name.to_owned(),
158            message: err.message,
159        };
160        Self::new(kind, err.span)
161    }
162}
163
164fn rename_rules_in_pest_error(err: pest::error::Error<Rule>) -> pest::error::Error<Rule> {
165    err.renamed_rules(|rule| {
166        rule.to_symbol()
167            .map(|sym| format!("`{sym}`"))
168            .unwrap_or_else(|| format!("<{rule:?}>"))
169    })
170}
171
172#[derive(Clone, Debug, Eq, PartialEq)]
173pub enum ExpressionKind<'i> {
174    Identifier(&'i str),
175    String(String),
176    /// `<kind>:<value>` where `<value>` should be `Identifier` or `String`.
177    Pattern {
178        kind: &'i str,
179        value: Box<ExpressionNode<'i>>,
180    },
181    Unary(UnaryOp, Box<ExpressionNode<'i>>),
182    Binary(BinaryOp, Box<ExpressionNode<'i>>, Box<ExpressionNode<'i>>),
183    /// `x | y | ..`
184    UnionAll(Vec<ExpressionNode<'i>>),
185    FunctionCall(Box<FunctionCallNode<'i>>),
186}
187
188#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
189pub enum UnaryOp {
190    /// `~`
191    Negate,
192}
193
194#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
195pub enum BinaryOp {
196    /// `&`
197    Intersection,
198    /// `~`
199    Difference,
200}
201
202pub type ExpressionNode<'i> = dsl_util::ExpressionNode<'i, ExpressionKind<'i>>;
203pub type FunctionCallNode<'i> = dsl_util::FunctionCallNode<'i, ExpressionKind<'i>>;
204
205fn union_nodes<'i>(lhs: ExpressionNode<'i>, rhs: ExpressionNode<'i>) -> ExpressionNode<'i> {
206    let span = lhs.span.start_pos().span(&rhs.span.end_pos());
207    let expr = match lhs.kind {
208        // Flatten "x | y | z" to save recursion stack. Machine-generated query
209        // might have long chain of unions.
210        ExpressionKind::UnionAll(mut nodes) => {
211            nodes.push(rhs);
212            ExpressionKind::UnionAll(nodes)
213        }
214        _ => ExpressionKind::UnionAll(vec![lhs, rhs]),
215    };
216    ExpressionNode::new(expr, span)
217}
218
219fn parse_function_call_node(pair: Pair<Rule>) -> FilesetParseResult<FunctionCallNode> {
220    assert_eq!(pair.as_rule(), Rule::function);
221    let [name_pair, args_pair] = pair.into_inner().collect_array().unwrap();
222    assert_eq!(name_pair.as_rule(), Rule::function_name);
223    assert_eq!(args_pair.as_rule(), Rule::function_arguments);
224    let name_span = name_pair.as_span();
225    let args_span = args_pair.as_span();
226    let name = name_pair.as_str();
227    let args = args_pair
228        .into_inner()
229        .map(parse_expression_node)
230        .try_collect()?;
231    Ok(FunctionCallNode {
232        name,
233        name_span,
234        args,
235        keyword_args: vec![], // unsupported
236        args_span,
237    })
238}
239
240fn parse_as_string_literal(pair: Pair<Rule>) -> String {
241    match pair.as_rule() {
242        Rule::identifier => pair.as_str().to_owned(),
243        Rule::string_literal => STRING_LITERAL_PARSER.parse(pair.into_inner()),
244        Rule::raw_string_literal => {
245            let [content] = pair.into_inner().collect_array().unwrap();
246            assert_eq!(content.as_rule(), Rule::raw_string_content);
247            content.as_str().to_owned()
248        }
249        r => panic!("unexpected string literal rule: {r:?}"),
250    }
251}
252
253fn parse_primary_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> {
254    assert_eq!(pair.as_rule(), Rule::primary);
255    let span = pair.as_span();
256    let first = pair.into_inner().next().unwrap();
257    let expr = match first.as_rule() {
258        // Ignore inner span to preserve parenthesized expression as such.
259        Rule::expression => parse_expression_node(first)?.kind,
260        Rule::function => {
261            let function = Box::new(parse_function_call_node(first)?);
262            ExpressionKind::FunctionCall(function)
263        }
264        Rule::string_pattern => {
265            let [lhs, op, rhs] = first.into_inner().collect_array().unwrap();
266            assert_eq!(lhs.as_rule(), Rule::strict_identifier);
267            assert_eq!(op.as_rule(), Rule::pattern_kind_op);
268            let kind = lhs.as_str();
269            let value_span = rhs.as_span();
270            let value_expr = match rhs.as_rule() {
271                Rule::identifier => ExpressionKind::Identifier(rhs.as_str()),
272                _ => ExpressionKind::String(parse_as_string_literal(rhs)),
273            };
274            let value = Box::new(ExpressionNode::new(value_expr, value_span));
275            ExpressionKind::Pattern { kind, value }
276        }
277        Rule::identifier => ExpressionKind::Identifier(first.as_str()),
278        Rule::string_literal | Rule::raw_string_literal => {
279            ExpressionKind::String(parse_as_string_literal(first))
280        }
281        r => panic!("unexpected primary rule: {r:?}"),
282    };
283    Ok(ExpressionNode::new(expr, span))
284}
285
286fn parse_expression_node(pair: Pair<Rule>) -> FilesetParseResult<ExpressionNode> {
287    assert_eq!(pair.as_rule(), Rule::expression);
288    static PRATT: LazyLock<PrattParser<Rule>> = LazyLock::new(|| {
289        PrattParser::new()
290            .op(Op::infix(Rule::union_op, Assoc::Left))
291            .op(Op::infix(Rule::intersection_op, Assoc::Left)
292                | Op::infix(Rule::difference_op, Assoc::Left))
293            .op(Op::prefix(Rule::negate_op))
294    });
295    PRATT
296        .map_primary(parse_primary_node)
297        .map_prefix(|op, rhs| {
298            let op_kind = match op.as_rule() {
299                Rule::negate_op => UnaryOp::Negate,
300                r => panic!("unexpected prefix operator rule {r:?}"),
301            };
302            let rhs = Box::new(rhs?);
303            let span = op.as_span().start_pos().span(&rhs.span.end_pos());
304            let expr = ExpressionKind::Unary(op_kind, rhs);
305            Ok(ExpressionNode::new(expr, span))
306        })
307        .map_infix(|lhs, op, rhs| {
308            let op_kind = match op.as_rule() {
309                Rule::union_op => return Ok(union_nodes(lhs?, rhs?)),
310                Rule::intersection_op => BinaryOp::Intersection,
311                Rule::difference_op => BinaryOp::Difference,
312                r => panic!("unexpected infix operator rule {r:?}"),
313            };
314            let lhs = Box::new(lhs?);
315            let rhs = Box::new(rhs?);
316            let span = lhs.span.start_pos().span(&rhs.span.end_pos());
317            let expr = ExpressionKind::Binary(op_kind, lhs, rhs);
318            Ok(ExpressionNode::new(expr, span))
319        })
320        .parse(pair.into_inner())
321}
322
323/// Parses text into expression tree. No name resolution is made at this stage.
324pub fn parse_program(text: &str) -> FilesetParseResult<ExpressionNode<'_>> {
325    let mut pairs = FilesetParser::parse(Rule::program, text)?;
326    let first = pairs.next().unwrap();
327    parse_expression_node(first)
328}
329
330/// Parses text into expression tree with bare string fallback. No name
331/// resolution is made at this stage.
332///
333/// If the text can't be parsed as a fileset expression, and if it doesn't
334/// contain any operator-like characters, it will be parsed as a file path.
335pub fn parse_program_or_bare_string(text: &str) -> FilesetParseResult<ExpressionNode<'_>> {
336    let mut pairs = FilesetParser::parse(Rule::program_or_bare_string, text)?;
337    let first = pairs.next().unwrap();
338    let span = first.as_span();
339    let expr = match first.as_rule() {
340        Rule::expression => return parse_expression_node(first),
341        Rule::bare_string_pattern => {
342            let [lhs, op, rhs] = first.into_inner().collect_array().unwrap();
343            assert_eq!(lhs.as_rule(), Rule::strict_identifier);
344            assert_eq!(op.as_rule(), Rule::pattern_kind_op);
345            assert_eq!(rhs.as_rule(), Rule::bare_string);
346            let kind = lhs.as_str();
347            let value_span = rhs.as_span();
348            let value_expr = ExpressionKind::String(rhs.as_str().to_owned());
349            let value = Box::new(ExpressionNode::new(value_expr, value_span));
350            ExpressionKind::Pattern { kind, value }
351        }
352        Rule::bare_string => ExpressionKind::String(first.as_str().to_owned()),
353        r => panic!("unexpected program or bare string rule: {r:?}"),
354    };
355    Ok(ExpressionNode::new(expr, span))
356}
357
358pub(super) fn expect_string_literal<'a>(
359    type_name: &str,
360    node: &'a ExpressionNode<'_>,
361) -> FilesetParseResult<&'a str> {
362    match &node.kind {
363        ExpressionKind::Identifier(name) => Ok(*name),
364        ExpressionKind::String(name) => Ok(name),
365        _ => Err(FilesetParseError::expression(
366            format!("Expected {type_name}"),
367            node.span,
368        )),
369    }
370}
371
372#[cfg(test)]
373mod tests {
374    use assert_matches::assert_matches;
375
376    use super::*;
377    use crate::dsl_util::KeywordArgument;
378
379    fn parse_into_kind(text: &str) -> Result<ExpressionKind<'_>, FilesetParseErrorKind> {
380        parse_program(text)
381            .map(|node| node.kind)
382            .map_err(|err| err.kind)
383    }
384
385    fn parse_maybe_bare_into_kind(text: &str) -> Result<ExpressionKind<'_>, FilesetParseErrorKind> {
386        parse_program_or_bare_string(text)
387            .map(|node| node.kind)
388            .map_err(|err| err.kind)
389    }
390
391    fn parse_normalized(text: &str) -> ExpressionNode<'_> {
392        normalize_tree(parse_program(text).unwrap())
393    }
394
395    fn parse_maybe_bare_normalized(text: &str) -> ExpressionNode<'_> {
396        normalize_tree(parse_program_or_bare_string(text).unwrap())
397    }
398
399    /// Drops auxiliary data from parsed tree so it can be compared with other.
400    fn normalize_tree(node: ExpressionNode) -> ExpressionNode {
401        fn empty_span() -> pest::Span<'static> {
402            pest::Span::new("", 0, 0).unwrap()
403        }
404
405        fn normalize_list(nodes: Vec<ExpressionNode>) -> Vec<ExpressionNode> {
406            nodes.into_iter().map(normalize_tree).collect()
407        }
408
409        fn normalize_function_call(function: FunctionCallNode) -> FunctionCallNode {
410            FunctionCallNode {
411                name: function.name,
412                name_span: empty_span(),
413                args: normalize_list(function.args),
414                keyword_args: function
415                    .keyword_args
416                    .into_iter()
417                    .map(|arg| KeywordArgument {
418                        name: arg.name,
419                        name_span: empty_span(),
420                        value: normalize_tree(arg.value),
421                    })
422                    .collect(),
423                args_span: empty_span(),
424            }
425        }
426
427        let normalized_kind = match node.kind {
428            ExpressionKind::Identifier(_) | ExpressionKind::String(_) => node.kind,
429            ExpressionKind::Pattern { kind, value } => {
430                let value = Box::new(normalize_tree(*value));
431                ExpressionKind::Pattern { kind, value }
432            }
433            ExpressionKind::Unary(op, arg) => {
434                let arg = Box::new(normalize_tree(*arg));
435                ExpressionKind::Unary(op, arg)
436            }
437            ExpressionKind::Binary(op, lhs, rhs) => {
438                let lhs = Box::new(normalize_tree(*lhs));
439                let rhs = Box::new(normalize_tree(*rhs));
440                ExpressionKind::Binary(op, lhs, rhs)
441            }
442            ExpressionKind::UnionAll(nodes) => {
443                let nodes = normalize_list(nodes);
444                ExpressionKind::UnionAll(nodes)
445            }
446            ExpressionKind::FunctionCall(function) => {
447                let function = Box::new(normalize_function_call(*function));
448                ExpressionKind::FunctionCall(function)
449            }
450        };
451        ExpressionNode {
452            kind: normalized_kind,
453            span: empty_span(),
454        }
455    }
456
457    #[test]
458    fn test_parse_tree_eq() {
459        assert_eq!(
460            parse_normalized(r#" foo( x ) | ~bar:"baz" "#),
461            parse_normalized(r#"(foo(x))|(~(bar:"baz"))"#)
462        );
463        assert_ne!(parse_normalized(r#" foo "#), parse_normalized(r#" "foo" "#));
464    }
465
466    #[test]
467    fn test_parse_invalid_function_name() {
468        assert_eq!(
469            parse_into_kind("5foo(x)"),
470            Err(FilesetParseErrorKind::SyntaxError)
471        );
472    }
473
474    #[test]
475    fn test_parse_whitespace() {
476        let ascii_whitespaces: String = ('\x00'..='\x7f')
477            .filter(char::is_ascii_whitespace)
478            .collect();
479        assert_eq!(
480            parse_normalized(&format!("{ascii_whitespaces}f()")),
481            parse_normalized("f()")
482        );
483    }
484
485    #[test]
486    fn test_parse_identifier() {
487        assert_eq!(
488            parse_into_kind("dir/foo-bar_0.baz"),
489            Ok(ExpressionKind::Identifier("dir/foo-bar_0.baz"))
490        );
491        assert_eq!(
492            parse_into_kind("cli-reference@.md.snap"),
493            Ok(ExpressionKind::Identifier("cli-reference@.md.snap"))
494        );
495        assert_eq!(
496            parse_into_kind("柔術.jj"),
497            Ok(ExpressionKind::Identifier("柔術.jj"))
498        );
499        assert_eq!(
500            parse_into_kind(r#"Windows\Path"#),
501            Ok(ExpressionKind::Identifier(r#"Windows\Path"#))
502        );
503        assert_eq!(
504            parse_into_kind("glob*[chars]?"),
505            Ok(ExpressionKind::Identifier("glob*[chars]?"))
506        );
507    }
508
509    #[test]
510    fn test_parse_string_literal() {
511        // "\<char>" escapes
512        assert_eq!(
513            parse_into_kind(r#" "\t\r\n\"\\\0\e" "#),
514            Ok(ExpressionKind::String("\t\r\n\"\\\0\u{1b}".to_owned())),
515        );
516
517        // Invalid "\<char>" escape
518        assert_eq!(
519            parse_into_kind(r#" "\y" "#),
520            Err(FilesetParseErrorKind::SyntaxError),
521        );
522
523        // Single-quoted raw string
524        assert_eq!(
525            parse_into_kind(r#" '' "#),
526            Ok(ExpressionKind::String("".to_owned())),
527        );
528        assert_eq!(
529            parse_into_kind(r#" 'a\n' "#),
530            Ok(ExpressionKind::String(r"a\n".to_owned())),
531        );
532        assert_eq!(
533            parse_into_kind(r#" '\' "#),
534            Ok(ExpressionKind::String(r"\".to_owned())),
535        );
536        assert_eq!(
537            parse_into_kind(r#" '"' "#),
538            Ok(ExpressionKind::String(r#"""#.to_owned())),
539        );
540
541        // Hex bytes
542        assert_eq!(
543            parse_into_kind(r#""\x61\x65\x69\x6f\x75""#),
544            Ok(ExpressionKind::String("aeiou".to_owned())),
545        );
546        assert_eq!(
547            parse_into_kind(r#""\xe0\xe8\xec\xf0\xf9""#),
548            Ok(ExpressionKind::String("àèìðù".to_owned())),
549        );
550        assert_eq!(
551            parse_into_kind(r#""\x""#),
552            Err(FilesetParseErrorKind::SyntaxError),
553        );
554        assert_eq!(
555            parse_into_kind(r#""\xf""#),
556            Err(FilesetParseErrorKind::SyntaxError),
557        );
558        assert_eq!(
559            parse_into_kind(r#""\xgg""#),
560            Err(FilesetParseErrorKind::SyntaxError),
561        );
562    }
563
564    #[test]
565    fn test_parse_string_pattern() {
566        assert_matches!(
567            parse_into_kind(r#" foo:bar "#),
568            Ok(ExpressionKind::Pattern { kind: "foo", value })
569                if value.kind == ExpressionKind::Identifier("bar")
570        );
571        assert_matches!(
572            parse_into_kind(" foo:glob*[chars]? "),
573            Ok(ExpressionKind::Pattern { kind: "foo", value })
574                if value.kind == ExpressionKind::Identifier("glob*[chars]?")
575        );
576        assert_matches!(
577            parse_into_kind(r#" foo:"bar" "#),
578            Ok(ExpressionKind::Pattern { kind: "foo", value })
579                if value.kind == ExpressionKind::String("bar".to_owned())
580        );
581        assert_matches!(
582            parse_into_kind(r#" foo:"" "#),
583            Ok(ExpressionKind::Pattern { kind: "foo", value })
584                if value.kind == ExpressionKind::String("".to_owned())
585        );
586        assert_matches!(
587            parse_into_kind(r#" foo:'\' "#),
588            Ok(ExpressionKind::Pattern { kind: "foo", value })
589                if value.kind == ExpressionKind::String(r"\".to_owned())
590        );
591        assert_eq!(
592            parse_into_kind(r#" foo: "#),
593            Err(FilesetParseErrorKind::SyntaxError)
594        );
595        assert_eq!(
596            parse_into_kind(r#" foo: "" "#),
597            Err(FilesetParseErrorKind::SyntaxError)
598        );
599        assert_eq!(
600            parse_into_kind(r#" foo :"" "#),
601            Err(FilesetParseErrorKind::SyntaxError)
602        );
603    }
604
605    #[test]
606    fn test_parse_operator() {
607        assert_matches!(
608            parse_into_kind("~x"),
609            Ok(ExpressionKind::Unary(UnaryOp::Negate, _))
610        );
611        assert_matches!(
612            parse_into_kind("x|y"),
613            Ok(ExpressionKind::UnionAll(nodes)) if nodes.len() == 2
614        );
615        assert_matches!(
616            parse_into_kind("x|y|z"),
617            Ok(ExpressionKind::UnionAll(nodes)) if nodes.len() == 3
618        );
619        assert_matches!(
620            parse_into_kind("x&y"),
621            Ok(ExpressionKind::Binary(BinaryOp::Intersection, _, _))
622        );
623        assert_matches!(
624            parse_into_kind("x~y"),
625            Ok(ExpressionKind::Binary(BinaryOp::Difference, _, _))
626        );
627
628        // Set operator associativity/precedence
629        assert_eq!(parse_normalized("~x|y"), parse_normalized("(~x)|y"));
630        assert_eq!(parse_normalized("x&~y"), parse_normalized("x&(~y)"));
631        assert_eq!(parse_normalized("x~~y"), parse_normalized("x~(~y)"));
632        assert_eq!(parse_normalized("x~~~y"), parse_normalized("x~(~(~y))"));
633        assert_eq!(parse_normalized("x|y|z"), parse_normalized("(x|y)|z"));
634        assert_eq!(parse_normalized("x&y|z"), parse_normalized("(x&y)|z"));
635        assert_eq!(parse_normalized("x|y&z"), parse_normalized("x|(y&z)"));
636        assert_eq!(parse_normalized("x|y~z"), parse_normalized("x|(y~z)"));
637        assert_eq!(parse_normalized("~x:y"), parse_normalized("~(x:y)"));
638        assert_eq!(parse_normalized("x|y:z"), parse_normalized("x|(y:z)"));
639
640        // Expression span
641        assert_eq!(parse_program(" ~ x ").unwrap().span.as_str(), "~ x");
642        assert_eq!(parse_program(" x |y ").unwrap().span.as_str(), "x |y");
643        assert_eq!(parse_program(" (x) ").unwrap().span.as_str(), "(x)");
644        assert_eq!(parse_program("~( x|y) ").unwrap().span.as_str(), "~( x|y)");
645    }
646
647    #[test]
648    fn test_parse_function_call() {
649        fn unwrap_function_call(node: ExpressionNode<'_>) -> Box<FunctionCallNode<'_>> {
650            match node.kind {
651                ExpressionKind::FunctionCall(function) => function,
652                _ => panic!("unexpected expression: {node:?}"),
653            }
654        }
655
656        assert_matches!(
657            parse_into_kind("foo()"),
658            Ok(ExpressionKind::FunctionCall(_))
659        );
660
661        // Trailing comma isn't allowed for empty argument
662        assert!(parse_into_kind("foo(,)").is_err());
663
664        // Trailing comma is allowed for the last argument
665        assert_eq!(parse_normalized("foo(a,)"), parse_normalized("foo(a)"));
666        assert_eq!(parse_normalized("foo(a ,  )"), parse_normalized("foo(a)"));
667        assert!(parse_into_kind("foo(,a)").is_err());
668        assert!(parse_into_kind("foo(a,,)").is_err());
669        assert!(parse_into_kind("foo(a  , , )").is_err());
670        assert_eq!(parse_normalized("foo(a,b,)"), parse_normalized("foo(a,b)"));
671        assert!(parse_into_kind("foo(a,,b)").is_err());
672
673        // Expression span
674        let function = unwrap_function_call(parse_program("foo( a, (b) , ~(c) )").unwrap());
675        assert_eq!(function.name_span.as_str(), "foo");
676        assert_eq!(function.args_span.as_str(), "a, (b) , ~(c)");
677        assert_eq!(function.args[0].span.as_str(), "a");
678        assert_eq!(function.args[1].span.as_str(), "(b)");
679        assert_eq!(function.args[2].span.as_str(), "~(c)");
680    }
681
682    #[test]
683    fn test_parse_bare_string() {
684        // Valid expression should be parsed as such
685        assert_eq!(
686            parse_maybe_bare_into_kind(" valid "),
687            Ok(ExpressionKind::Identifier("valid"))
688        );
689        assert_eq!(
690            parse_maybe_bare_normalized("f(x)&y"),
691            parse_normalized("f(x)&y")
692        );
693        assert_matches!(
694            parse_maybe_bare_into_kind("foo:bar"),
695            Ok(ExpressionKind::Pattern { kind: "foo", value })
696                if value.kind == ExpressionKind::Identifier("bar")
697        );
698
699        // Bare string
700        assert_eq!(
701            parse_maybe_bare_into_kind("Foo Bar.txt"),
702            Ok(ExpressionKind::String("Foo Bar.txt".to_owned()))
703        );
704        assert_eq!(
705            parse_maybe_bare_into_kind(r#"Windows\Path with space"#),
706            Ok(ExpressionKind::String(
707                r#"Windows\Path with space"#.to_owned()
708            ))
709        );
710        assert_eq!(
711            parse_maybe_bare_into_kind("柔 術 . j j"),
712            Ok(ExpressionKind::String("柔 術 . j j".to_owned()))
713        );
714        assert_eq!(
715            parse_maybe_bare_into_kind("Unicode emoji 💩"),
716            Ok(ExpressionKind::String("Unicode emoji 💩".to_owned()))
717        );
718        assert_eq!(
719            parse_maybe_bare_into_kind("looks like & expression"),
720            Err(FilesetParseErrorKind::SyntaxError)
721        );
722        assert_eq!(
723            parse_maybe_bare_into_kind("unbalanced_parens("),
724            Err(FilesetParseErrorKind::SyntaxError)
725        );
726
727        // Bare string pattern
728        assert_matches!(
729            parse_maybe_bare_into_kind("foo: bar baz"),
730            Ok(ExpressionKind::Pattern { kind: "foo", value })
731                if value.kind == ExpressionKind::String(" bar baz".to_owned())
732        );
733        assert_matches!(
734            parse_maybe_bare_into_kind("foo:glob * [chars]?"),
735            Ok(ExpressionKind::Pattern { kind: "foo", value })
736                if value.kind == ExpressionKind::String("glob * [chars]?".to_owned())
737        );
738        assert_eq!(
739            parse_maybe_bare_into_kind("foo:bar:baz"),
740            Err(FilesetParseErrorKind::SyntaxError)
741        );
742        assert_eq!(
743            parse_maybe_bare_into_kind("foo:"),
744            Err(FilesetParseErrorKind::SyntaxError)
745        );
746        assert_eq!(
747            parse_maybe_bare_into_kind(r#"foo:"unclosed quote"#),
748            Err(FilesetParseErrorKind::SyntaxError)
749        );
750
751        // Surrounding spaces are simply preserved. They could be trimmed, but
752        // space is valid bare_string character.
753        assert_eq!(
754            parse_maybe_bare_into_kind(" No trim "),
755            Ok(ExpressionKind::String(" No trim ".to_owned()))
756        );
757    }
758
759    #[test]
760    fn test_parse_error() {
761        insta::assert_snapshot!(parse_program("foo|").unwrap_err().to_string(), @r"
762         --> 1:5
763          |
764        1 | foo|
765          |     ^---
766          |
767          = expected `~` or <primary>
768        ");
769    }
770}