1use std::error;
18use std::sync::LazyLock;
19
20use itertools::Itertools as _;
21use pest::Parser as _;
22use pest::iterators::Pair;
23use pest::pratt_parser::Assoc;
24use pest::pratt_parser::Op;
25use pest::pratt_parser::PrattParser;
26use pest_derive::Parser;
27use thiserror::Error;
28
29use crate::dsl_util;
30use crate::dsl_util::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
78pub type FilesetDiagnostics = Diagnostics<FilesetParseError>;
81
82pub type FilesetParseResult<T> = Result<T, FilesetParseError>;
84
85#[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#[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 pub(super) fn expression(message: impl Into<String>, span: pest::Span<'_>) -> Self {
135 Self::new(FilesetParseErrorKind::Expression(message.into()), span)
136 }
137
138 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 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 UnionAll(Vec<ExpressionNode<'i>>),
185 FunctionCall(Box<FunctionCallNode<'i>>),
186}
187
188#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
189pub enum UnaryOp {
190 Negate,
192}
193
194#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
195pub enum BinaryOp {
196 Intersection,
198 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 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![], 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 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
323pub 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
330pub 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 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 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 assert_eq!(
519 parse_into_kind(r#" "\y" "#),
520 Err(FilesetParseErrorKind::SyntaxError),
521 );
522
523 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 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 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 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 assert!(parse_into_kind("foo(,)").is_err());
663
664 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 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 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 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 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 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}