1use crate::ParserOptions;
13use crate::ast;
14use crate::error;
15
16#[derive(Clone, Debug)]
18#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
19pub struct WordPieceWithSource {
20 pub piece: WordPiece,
22 pub start_index: usize,
24 pub end_index: usize,
26}
27
28#[derive(Clone, Debug)]
30#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
31pub enum WordPiece {
32 Text(String),
34 SingleQuotedText(String),
36 AnsiCQuotedText(String),
38 DoubleQuotedSequence(Vec<WordPieceWithSource>),
40 GettextDoubleQuotedSequence(Vec<WordPieceWithSource>),
42 TildePrefix(String),
44 ParameterExpansion(ParameterExpr),
46 CommandSubstitution(String),
48 BackquotedCommandSubstitution(String),
50 EscapeSequence(String),
52 ArithmeticExpression(ast::UnexpandedArithmeticExpr),
54}
55
56#[derive(Clone, Debug)]
58#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
59pub enum ParameterTestType {
60 UnsetOrNull,
62 Unset,
64}
65
66#[derive(Clone, Debug)]
68#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
69pub enum Parameter {
70 Positional(u32),
72 Special(SpecialParameter),
74 Named(String),
76 NamedWithIndex {
78 name: String,
80 index: String,
82 },
83 NamedWithAllIndices {
85 name: String,
87 concatenate: bool,
89 },
90}
91
92#[derive(Clone, Debug)]
94#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
95pub enum SpecialParameter {
96 AllPositionalParameters {
98 concatenate: bool,
100 },
101 PositionalParameterCount,
103 LastExitStatus,
105 CurrentOptionFlags,
107 ProcessId,
109 LastBackgroundProcessId,
111 ShellName,
113}
114
115#[derive(Clone, Debug)]
117#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
118pub enum ParameterExpr {
119 Parameter {
121 parameter: Parameter,
123 indirect: bool,
127 },
128 UseDefaultValues {
130 parameter: Parameter,
132 indirect: bool,
136 test_type: ParameterTestType,
138 default_value: Option<String>,
140 },
141 AssignDefaultValues {
143 parameter: Parameter,
145 indirect: bool,
149 test_type: ParameterTestType,
151 default_value: Option<String>,
153 },
154 IndicateErrorIfNullOrUnset {
156 parameter: Parameter,
158 indirect: bool,
162 test_type: ParameterTestType,
164 error_message: Option<String>,
166 },
167 UseAlternativeValue {
169 parameter: Parameter,
171 indirect: bool,
175 test_type: ParameterTestType,
177 alternative_value: Option<String>,
179 },
180 ParameterLength {
182 parameter: Parameter,
184 indirect: bool,
188 },
189 RemoveSmallestSuffixPattern {
191 parameter: Parameter,
193 indirect: bool,
197 pattern: Option<String>,
199 },
200 RemoveLargestSuffixPattern {
202 parameter: Parameter,
204 indirect: bool,
208 pattern: Option<String>,
210 },
211 RemoveSmallestPrefixPattern {
213 parameter: Parameter,
215 indirect: bool,
219 pattern: Option<String>,
221 },
222 RemoveLargestPrefixPattern {
224 parameter: Parameter,
226 indirect: bool,
230 pattern: Option<String>,
232 },
233 Substring {
235 parameter: Parameter,
237 indirect: bool,
241 offset: ast::UnexpandedArithmeticExpr,
244 length: Option<ast::UnexpandedArithmeticExpr>,
248 },
249 Transform {
251 parameter: Parameter,
253 indirect: bool,
257 op: ParameterTransformOp,
259 },
260 UppercaseFirstChar {
262 parameter: Parameter,
264 indirect: bool,
268 pattern: Option<String>,
270 },
271 UppercasePattern {
273 parameter: Parameter,
275 indirect: bool,
279 pattern: Option<String>,
281 },
282 LowercaseFirstChar {
284 parameter: Parameter,
286 indirect: bool,
290 pattern: Option<String>,
292 },
293 LowercasePattern {
295 parameter: Parameter,
297 indirect: bool,
301 pattern: Option<String>,
303 },
304 ReplaceSubstring {
306 parameter: Parameter,
308 indirect: bool,
312 pattern: String,
314 replacement: Option<String>,
316 match_kind: SubstringMatchKind,
318 },
319 VariableNames {
321 prefix: String,
323 concatenate: bool,
325 },
326 MemberKeys {
328 variable_name: String,
330 concatenate: bool,
332 },
333}
334
335#[derive(Clone, Debug)]
337#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
338pub enum SubstringMatchKind {
339 Prefix,
341 Suffix,
343 FirstOccurrence,
345 Anywhere,
347}
348
349#[derive(Clone, Debug)]
351#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
352pub enum ParameterTransformOp {
353 CapitalizeInitial,
355 ExpandEscapeSequences,
357 PossiblyQuoteWithArraysExpanded {
359 separate_words: bool,
361 },
362 PromptExpand,
364 Quoted,
366 ToAssignmentLogic,
368 ToAttributeFlags,
370 ToLowerCase,
372 ToUpperCase,
374}
375
376#[derive(Clone, Debug)]
378#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
379pub enum BraceExpressionOrText {
380 Expr(BraceExpression),
382 Text(String),
384}
385
386pub type BraceExpression = Vec<BraceExpressionMember>;
388
389#[derive(Clone, Debug)]
391#[cfg_attr(test, derive(PartialEq, Eq, serde::Serialize))]
392pub enum BraceExpressionMember {
393 NumberSequence {
395 start: i64,
397 end: i64,
399 increment: i64,
401 },
402 CharSequence {
404 start: char,
406 end: char,
408 increment: i64,
410 },
411 Child(Vec<BraceExpressionOrText>),
413}
414
415pub fn parse(
422 word: &str,
423 options: &ParserOptions,
424) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
425 cacheable_parse(word.to_owned(), options.to_owned())
426}
427
428#[cached::proc_macro::cached(size = 64, result = true)]
429fn cacheable_parse(
430 word: String,
431 options: ParserOptions,
432) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
433 tracing::debug!(target: "expansion", "Parsing word '{}'", word);
434
435 let pieces = expansion_parser::unexpanded_word(word.as_str(), &options)
436 .map_err(|err| error::WordParseError::Word(word.clone(), err.into()))?;
437
438 tracing::debug!(target: "expansion", "Parsed word '{}' => {{{:?}}}", word, pieces);
439
440 Ok(pieces)
441}
442
443pub fn parse_parameter(
450 word: &str,
451 options: &ParserOptions,
452) -> Result<Parameter, error::WordParseError> {
453 expansion_parser::parameter(word, options)
454 .map_err(|err| error::WordParseError::Parameter(word.to_owned(), err.into()))
455}
456
457pub fn parse_brace_expansions(
464 word: &str,
465 options: &ParserOptions,
466) -> Result<Option<Vec<BraceExpressionOrText>>, error::WordParseError> {
467 expansion_parser::brace_expansions(word, options)
468 .map_err(|err| error::WordParseError::BraceExpansion(word.to_owned(), err.into()))
469}
470
471peg::parser! {
472 grammar expansion_parser(parser_options: &ParserOptions) for str {
473 rule traced<T>(e: rule<T>) -> T =
475 &(input:$([_]*) {
476 #[cfg(feature = "debug-tracing")]
477 println!("[PEG_INPUT_START]\n{input}\n[PEG_TRACE_START]");
478 })
479 e:e()? {?
480 #[cfg(feature = "debug-tracing")]
481 println!("[PEG_TRACE_STOP]");
482 e.ok_or("")
483 }
484
485 pub(crate) rule unexpanded_word() -> Vec<WordPieceWithSource> = traced(<word(<![_]>)>)
486
487 rule word<T>(stop_condition: rule<T>) -> Vec<WordPieceWithSource> =
488 tilde:tilde_prefix_with_source()? pieces:word_piece_with_source(<stop_condition()>, false )* {
489 let mut all_pieces = Vec::new();
490 if let Some(tilde) = tilde {
491 all_pieces.push(tilde);
492 }
493 all_pieces.extend(pieces);
494 all_pieces
495 }
496
497 pub(crate) rule brace_expansions() -> Option<Vec<BraceExpressionOrText>> =
499 pieces:(brace_expansion_piece(<![_]>)+) { Some(pieces) } /
500 [_]* { None }
501
502 rule brace_expansion_piece<T>(stop_condition: rule<T>) -> BraceExpressionOrText =
505 expr:brace_expr() {
506 BraceExpressionOrText::Expr(expr)
507 } /
508 text:$(non_brace_expr_text(<stop_condition()>)+) { BraceExpressionOrText::Text(text.to_owned()) }
509
510 rule non_brace_expr_text<T>(stop_condition: rule<T>) -> () =
512 !"{" word_piece(<['{'] {} / stop_condition() {}>, false) {} /
513 !brace_expr() !stop_condition() "{" {}
514
515 pub(crate) rule brace_expr() -> BraceExpression =
517 "{" inner:brace_expr_inner() "}" { inner }
518
519 pub(crate) rule brace_expr_inner() -> BraceExpression =
522 brace_text_list_expr() /
523 seq:brace_sequence_expr() { vec![seq] }
524
525 pub(crate) rule brace_text_list_expr() -> BraceExpression =
528 brace_text_list_member() **<2,> ","
529
530 pub(crate) rule brace_text_list_member() -> BraceExpressionMember =
533 &[',' | '}'] { BraceExpressionMember::Child(vec![BraceExpressionOrText::Text(String::new())]) } /
535 child_pieces:(brace_expansion_piece(<[',' | '}']>)+) {
538 BraceExpressionMember::Child(child_pieces)
539 }
540
541 pub(crate) rule brace_sequence_expr() -> BraceExpressionMember =
542 start:number() ".." end:number() increment:(".." n:number() { n })? {
543 BraceExpressionMember::NumberSequence { start, end, increment: increment.unwrap_or(1) }
544 } /
545 start:character() ".." end:character() increment:(".." n:number() { n })? {
546 BraceExpressionMember::CharSequence { start, end, increment: increment.unwrap_or(1) }
547 }
548
549 rule number() -> i64 = sign:number_sign()? n:$(['0'..='9']+) {
550 let sign = sign.unwrap_or(1);
551 let num: i64 = n.parse().unwrap();
552 num * sign
553 }
554
555 rule number_sign() -> i64 =
556 ['-'] { -1 } /
557 ['+'] { 1 }
558
559 rule character() -> char = ['a'..='z' | 'A'..='Z']
560
561 pub(crate) rule is_arithmetic_word() =
562 arithmetic_word(<![_]>)
563
564 rule arithmetic_word<T>(stop_condition: rule<T>) =
567 arithmetic_word_piece(<stop_condition()>)* {}
568
569 pub(crate) rule is_arithmetic_word_piece() =
570 arithmetic_word_piece(<![_]>)
571
572 rule arithmetic_word_piece<T>(stop_condition: rule<T>) =
577 "(" arithmetic_word_plus_right_paren() {} /
582 !"(" word_piece(<param_rule_or_open_paren(<stop_condition()>)>, false ) {}
586
587 rule param_rule_or_open_paren<T>(stop_condition: rule<T>) -> () =
589 stop_condition() {} /
590 "(" {}
591
592 rule arithmetic_word_plus_right_paren() =
594 arithmetic_word(<[')']>) ")" /
595
596 rule word_piece_with_source<T>(stop_condition: rule<T>, in_command: bool) -> WordPieceWithSource =
597 start_index:position!() piece:word_piece(<stop_condition()>, in_command) end_index:position!() {
598 WordPieceWithSource { piece, start_index, end_index }
599 }
600
601 rule word_piece<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
602 s:double_quoted_sequence() { WordPiece::DoubleQuotedSequence(s) } /
604 s:single_quoted_literal_text() { WordPiece::SingleQuotedText(s.to_owned()) } /
605 s:ansi_c_quoted_text() { WordPiece::AnsiCQuotedText(s.to_owned()) } /
606 s:gettext_double_quoted_sequence() { WordPiece::GettextDoubleQuotedSequence(s) } /
607 dollar_sign_word_piece() /
609 normal_escape_sequence() /
611 unquoted_literal_text(<stop_condition()>, in_command)
612
613 rule dollar_sign_word_piece() -> WordPiece =
614 arithmetic_expansion() /
615 command_substitution() /
616 parameter_expansion()
617
618 rule double_quoted_word_piece() -> WordPiece =
619 arithmetic_expansion() /
620 command_substitution() /
621 parameter_expansion() /
622 double_quoted_escape_sequence() /
623 double_quoted_text()
624
625 rule double_quoted_sequence() -> Vec<WordPieceWithSource> =
626 "\"" i:double_quoted_sequence_inner()* "\"" { i }
627
628 rule gettext_double_quoted_sequence() -> Vec<WordPieceWithSource> =
629 "$\"" i:double_quoted_sequence_inner()* "\"" { i }
630
631 rule double_quoted_sequence_inner() -> WordPieceWithSource =
632 start_index:position!() piece:double_quoted_word_piece() end_index:position!() {
633 WordPieceWithSource {
634 piece,
635 start_index,
636 end_index
637 }
638 }
639
640 rule single_quoted_literal_text() -> &'input str =
641 "\'" inner:$([^'\'']*) "\'" { inner }
642
643 rule ansi_c_quoted_text() -> &'input str =
644 "$\'" inner:$(("\\'" / [^'\''])*) "\'" { inner }
645
646 rule unquoted_literal_text<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
647 s:$(unquoted_literal_text_piece(<stop_condition()>, in_command)+) { WordPiece::Text(s.to_owned()) }
648
649 rule unquoted_literal_text_piece<T>(stop_condition: rule<T>, in_command: bool) =
651 is_true(in_command) extglob_pattern() /
652 is_true(in_command) subshell_command() /
653 !stop_condition() !normal_escape_sequence() [^'\'' | '\"' | '$' | '`'] {}
654
655 rule is_true(value: bool) = &[_] {? if value { Ok(()) } else { Err("not true") } }
656
657 rule extglob_pattern() =
658 ("@" / "!" / "?" / "+" / "*") "(" extglob_body_piece()* ")" {}
659
660 rule extglob_body_piece() =
661 word_piece(<[')']>, true ) {}
662
663 rule subshell_command() =
664 "(" command() ")" {}
665
666 rule double_quoted_text() -> WordPiece =
667 s:double_quote_body_text() { WordPiece::Text(s.to_owned()) }
668
669 rule double_quote_body_text() -> &'input str =
670 $((!double_quoted_escape_sequence() !dollar_sign_word_piece() [^'\"'])+)
671
672 rule normal_escape_sequence() -> WordPiece =
673 s:$("\\" [c]) { WordPiece::EscapeSequence(s.to_owned()) }
674
675 rule double_quoted_escape_sequence() -> WordPiece =
676 s:$("\\" ['$' | '`' | '\"' | '\\']) { WordPiece::EscapeSequence(s.to_owned()) }
677
678 rule tilde_prefix_with_source() -> WordPieceWithSource =
679 start_index:position!() piece:tilde_prefix() end_index:position!() {
680 WordPieceWithSource {
681 piece,
682 start_index,
683 end_index
684 }
685 }
686
687 rule tilde_prefix() -> WordPiece =
689 tilde_parsing_enabled() "~" cs:$((!['/' | ':' | ';'] [c])*) { WordPiece::TildePrefix(cs.to_owned()) }
690
691 rule parameter_expansion() -> WordPiece =
694 "${" e:parameter_expression() "}" {
695 WordPiece::ParameterExpansion(e)
696 } /
697 "$" parameter:unbraced_parameter() {
698 WordPiece::ParameterExpansion(ParameterExpr::Parameter { parameter, indirect: false })
699 } /
700 "$" !['\''] {
701 WordPiece::Text("$".to_owned())
702 }
703
704 rule parameter_expression() -> ParameterExpr =
705 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "-" default_value:parameter_expression_word()? {
706 ParameterExpr::UseDefaultValues { parameter, indirect, test_type, default_value }
707 } /
708 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "=" default_value:parameter_expression_word()? {
709 ParameterExpr::AssignDefaultValues { parameter, indirect, test_type, default_value }
710 } /
711 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "?" error_message:parameter_expression_word()? {
712 ParameterExpr::IndicateErrorIfNullOrUnset { parameter, indirect, test_type, error_message }
713 } /
714 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "+" alternative_value:parameter_expression_word()? {
715 ParameterExpr::UseAlternativeValue { parameter, indirect, test_type, alternative_value }
716 } /
717 "#" parameter:parameter() {
718 ParameterExpr::ParameterLength { parameter, indirect: false }
719 } /
720 indirect:parameter_indirection() parameter:parameter() "%%" pattern:parameter_expression_word()? {
721 ParameterExpr::RemoveLargestSuffixPattern { parameter, indirect, pattern }
722 } /
723 indirect:parameter_indirection() parameter:parameter() "%" pattern:parameter_expression_word()? {
724 ParameterExpr::RemoveSmallestSuffixPattern { parameter, indirect, pattern }
725 } /
726 indirect:parameter_indirection() parameter:parameter() "##" pattern:parameter_expression_word()? {
727 ParameterExpr::RemoveLargestPrefixPattern { parameter, indirect, pattern }
728 } /
729 indirect:parameter_indirection() parameter:parameter() "#" pattern:parameter_expression_word()? {
730 ParameterExpr::RemoveSmallestPrefixPattern { parameter, indirect, pattern }
731 } /
732 non_posix_extensions_enabled() e:non_posix_parameter_expression() { e } /
734 indirect:parameter_indirection() parameter:parameter() {
735 ParameterExpr::Parameter { parameter, indirect }
736 }
737
738 rule parameter_test_type() -> ParameterTestType =
739 colon:":"? {
740 if colon.is_some() {
741 ParameterTestType::UnsetOrNull
742 } else {
743 ParameterTestType::Unset
744 }
745 }
746
747 rule non_posix_parameter_expression() -> ParameterExpr =
748 "!" variable_name:variable_name() "[*]" {
749 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: true }
750 } /
751 "!" variable_name:variable_name() "[@]" {
752 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: false }
753 } /
754 indirect:parameter_indirection() parameter:parameter() ":" offset:substring_offset() length:(":" l:substring_length() { l })? {
755 ParameterExpr::Substring { parameter, indirect, offset, length }
756 } /
757 indirect:parameter_indirection() parameter:parameter() "@" op:non_posix_parameter_transformation_op() {
758 ParameterExpr::Transform { parameter, indirect, op }
759 } /
760 "!" prefix:variable_name() "*" {
761 ParameterExpr::VariableNames { prefix: prefix.to_owned(), concatenate: true }
762 } /
763 "!" prefix:variable_name() "@" {
764 ParameterExpr::VariableNames { prefix: prefix.to_owned(), concatenate: false }
765 } /
766 indirect:parameter_indirection() parameter:parameter() "/#" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
767 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Prefix }
768 } /
769 indirect:parameter_indirection() parameter:parameter() "/%" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
770 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Suffix }
771 } /
772 indirect:parameter_indirection() parameter:parameter() "//" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
773 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Anywhere }
774 } /
775 indirect:parameter_indirection() parameter:parameter() "/" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
776 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::FirstOccurrence }
777 } /
778 indirect:parameter_indirection() parameter:parameter() "^^" pattern:parameter_expression_word()? {
779 ParameterExpr::UppercasePattern { parameter, indirect, pattern }
780 } /
781 indirect:parameter_indirection() parameter:parameter() "^" pattern:parameter_expression_word()? {
782 ParameterExpr::UppercaseFirstChar { parameter, indirect, pattern }
783 } /
784 indirect:parameter_indirection() parameter:parameter() ",," pattern:parameter_expression_word()? {
785 ParameterExpr::LowercasePattern { parameter, indirect, pattern }
786 } /
787 indirect:parameter_indirection() parameter:parameter() "," pattern:parameter_expression_word()? {
788 ParameterExpr::LowercaseFirstChar { parameter, indirect, pattern }
789 }
790
791 rule parameter_indirection() -> bool =
792 non_posix_extensions_enabled() "!" { true } /
793 { false }
794
795 rule non_posix_parameter_transformation_op() -> ParameterTransformOp =
796 "U" { ParameterTransformOp::ToUpperCase } /
797 "u" { ParameterTransformOp::CapitalizeInitial } /
798 "L" { ParameterTransformOp::ToLowerCase } /
799 "Q" { ParameterTransformOp::Quoted } /
800 "E" { ParameterTransformOp::ExpandEscapeSequences } /
801 "P" { ParameterTransformOp::PromptExpand } /
802 "A" { ParameterTransformOp::ToAssignmentLogic } /
803 "K" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: false } } /
804 "a" { ParameterTransformOp::ToAttributeFlags } /
805 "k" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: true } }
806
807
808 rule unbraced_parameter() -> Parameter =
809 p:unbraced_positional_parameter() { Parameter::Positional(p) } /
810 p:special_parameter() { Parameter::Special(p) } /
811 p:variable_name() { Parameter::Named(p.to_owned()) }
812
813 pub(crate) rule parameter() -> Parameter =
815 p:positional_parameter() { Parameter::Positional(p) } /
816 p:special_parameter() { Parameter::Special(p) } /
817 non_posix_extensions_enabled() p:variable_name() "[@]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: false } } /
818 non_posix_extensions_enabled() p:variable_name() "[*]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: true } } /
819 non_posix_extensions_enabled() p:variable_name() "[" index:$(arithmetic_word(<"]">)) "]" {?
820 Ok(Parameter::NamedWithIndex { name: p.to_owned(), index: index.to_owned() })
821 } /
822 p:variable_name() { Parameter::Named(p.to_owned()) }
823
824 rule positional_parameter() -> u32 =
825 n:$(['1'..='9'](['0'..='9']*)) {? n.parse().or(Err("u32")) }
826 rule unbraced_positional_parameter() -> u32 =
827 n:$(['1'..='9']) {? n.parse().or(Err("u32")) }
828
829 rule special_parameter() -> SpecialParameter =
830 "@" { SpecialParameter::AllPositionalParameters { concatenate: false } } /
831 "*" { SpecialParameter::AllPositionalParameters { concatenate: true } } /
832 "#" { SpecialParameter::PositionalParameterCount } /
833 "?" { SpecialParameter::LastExitStatus } /
834 "-" { SpecialParameter::CurrentOptionFlags } /
835 "$" { SpecialParameter::ProcessId } /
836 "!" { SpecialParameter::LastBackgroundProcessId } /
837 "0" { SpecialParameter::ShellName }
838
839 rule variable_name() -> &'input str =
840 $(!['0'..='9'] ['_' | '0'..='9' | 'a'..='z' | 'A'..='Z']+)
841
842 pub(crate) rule command_substitution() -> WordPiece =
843 "$(" c:command() ")" { WordPiece::CommandSubstitution(c.to_owned()) } /
844 "`" c:backquoted_command() "`" { WordPiece::BackquotedCommandSubstitution(c) }
845
846 pub(crate) rule command() -> &'input str =
847 $(command_piece()*)
848
849 pub(crate) rule command_piece() -> () =
850 word_piece(<[')']>, true ) {} /
851 ([' ' | '\t'])+ {}
852
853 rule backquoted_command() -> String =
854 chars:(backquoted_char()*) { chars.into_iter().collect() }
855
856 rule backquoted_char() -> &'input str =
857 "\\`" { "`" } /
858 "\\\\" { "\\\\" } /
859 s:$([^'`']) { s }
860
861 rule arithmetic_expansion() -> WordPiece =
862 "$((" e:$(arithmetic_word(<"))">)) "))" { WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value: e.to_owned() } ) }
863
864 rule substring_offset() -> ast::UnexpandedArithmeticExpr =
865 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
866
867 rule substring_length() -> ast::UnexpandedArithmeticExpr =
868 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
869
870 rule parameter_replacement_str() -> String =
871 "/" s:$(word(<['}']>)) { s.to_owned() }
872
873 rule parameter_search_pattern() -> String =
874 s:$(word(<['}' | '/']>)) { s.to_owned() }
875
876 rule parameter_expression_word() -> String =
877 s:$(word(<['}']>)) { s.to_owned() }
878
879 rule extglob_enabled() -> () =
880 &[_] {? if parser_options.enable_extended_globbing { Ok(()) } else { Err("no extglob") } }
881
882 rule non_posix_extensions_enabled() -> () =
883 &[_] {? if !parser_options.sh_mode { Ok(()) } else { Err("posix") } }
884
885 rule tilde_parsing_enabled() -> () =
886 &[_] {? if parser_options.tilde_expansion { Ok(()) } else { Err("no tilde expansion") } }
887 }
888}
889
890#[cfg(test)]
891mod tests {
892 use super::*;
893 use anyhow::Result;
894 use insta::assert_ron_snapshot;
895
896 #[derive(serde::Serialize)]
897 struct ParseTestResults<'a> {
898 input: &'a str,
899 result: Vec<WordPieceWithSource>,
900 }
901
902 fn test_parse(word: &str) -> Result<ParseTestResults<'_>> {
903 let parsed = super::parse(word, &ParserOptions::default())?;
904 Ok(ParseTestResults {
905 input: word,
906 result: parsed,
907 })
908 }
909
910 #[test]
911 fn parse_ansi_c_quoted_text() -> Result<()> {
912 assert_ron_snapshot!(test_parse(r"$'hi\nthere\t'")?);
913 Ok(())
914 }
915
916 #[test]
917 fn parse_double_quoted_text() -> Result<()> {
918 assert_ron_snapshot!(test_parse(r#""a ${b} c""#)?);
919 Ok(())
920 }
921
922 #[test]
923 fn parse_gettext_double_quoted_text() -> Result<()> {
924 assert_ron_snapshot!(test_parse(r#"$"a ${b} c""#)?);
925 Ok(())
926 }
927
928 #[test]
929 fn parse_command_substitution() -> Result<()> {
930 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
931 super::expansion_parser::command_piece("hi", &ParserOptions::default())?;
932 super::expansion_parser::command("echo hi", &ParserOptions::default())?;
933 super::expansion_parser::command_substitution("$(echo hi)", &ParserOptions::default())?;
934
935 assert_ron_snapshot!(test_parse("$(echo hi)")?);
936
937 Ok(())
938 }
939
940 #[test]
941 fn parse_command_substitution_with_embedded_quotes() -> Result<()> {
942 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
943 super::expansion_parser::command_piece(r#""hi""#, &ParserOptions::default())?;
944 super::expansion_parser::command(r#"echo "hi""#, &ParserOptions::default())?;
945 super::expansion_parser::command_substitution(
946 r#"$(echo "hi")"#,
947 &ParserOptions::default(),
948 )?;
949
950 assert_ron_snapshot!(test_parse(r#"$(echo "hi")"#)?);
951 Ok(())
952 }
953
954 #[test]
955 fn parse_command_substitution_with_embedded_extglob() -> Result<()> {
956 assert_ron_snapshot!(test_parse("$(echo !(x))")?);
957 Ok(())
958 }
959
960 #[test]
961 fn parse_backquoted_command() -> Result<()> {
962 assert_ron_snapshot!(test_parse("`echo hi`")?);
963 Ok(())
964 }
965
966 #[test]
967 fn parse_backquoted_command_in_double_quotes() -> Result<()> {
968 assert_ron_snapshot!(test_parse(r#""`echo hi`""#)?);
969 Ok(())
970 }
971
972 #[test]
973 fn parse_extglob_with_embedded_parameter() -> Result<()> {
974 assert_ron_snapshot!(test_parse("+([$var])")?);
975 Ok(())
976 }
977
978 #[test]
979 fn parse_arithmetic_expansion() -> Result<()> {
980 assert_ron_snapshot!(test_parse("$((0))")?);
981 Ok(())
982 }
983
984 #[test]
985 fn parse_arithmetic_expansion_with_parens() -> Result<()> {
986 assert_ron_snapshot!(test_parse("$((((1+2)*3)))")?);
987 Ok(())
988 }
989
990 #[test]
991 fn test_arithmetic_word_parsing() {
992 let options = ParserOptions::default();
993
994 assert!(super::expansion_parser::is_arithmetic_word("a", &options).is_ok());
995 assert!(super::expansion_parser::is_arithmetic_word("b", &options).is_ok());
996 assert!(super::expansion_parser::is_arithmetic_word(" a + b ", &options).is_ok());
997 assert!(super::expansion_parser::is_arithmetic_word("(a)", &options).is_ok());
998 assert!(super::expansion_parser::is_arithmetic_word("((a))", &options).is_ok());
999 assert!(super::expansion_parser::is_arithmetic_word("(((a)))", &options).is_ok());
1000 assert!(super::expansion_parser::is_arithmetic_word("(1+2)", &options).is_ok());
1001 assert!(super::expansion_parser::is_arithmetic_word("(1+2)*3", &options).is_ok());
1002 assert!(super::expansion_parser::is_arithmetic_word("((1+2)*3)", &options).is_ok());
1003 }
1004
1005 #[test]
1006 fn test_arithmetic_word_piece_parsing() {
1007 let options = ParserOptions::default();
1008
1009 assert!(super::expansion_parser::is_arithmetic_word_piece("a", &options).is_ok());
1010 assert!(super::expansion_parser::is_arithmetic_word_piece("b", &options).is_ok());
1011 assert!(super::expansion_parser::is_arithmetic_word_piece(" a + b ", &options).is_ok());
1012 assert!(super::expansion_parser::is_arithmetic_word_piece("(a)", &options).is_ok());
1013 assert!(super::expansion_parser::is_arithmetic_word_piece("((a))", &options).is_ok());
1014 assert!(super::expansion_parser::is_arithmetic_word_piece("(((a)))", &options).is_ok());
1015 assert!(super::expansion_parser::is_arithmetic_word_piece("(1+2)", &options).is_ok());
1016 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2))", &options).is_ok());
1017 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2)*3)", &options).is_ok());
1018 assert!(super::expansion_parser::is_arithmetic_word_piece("(a", &options).is_err());
1019 assert!(super::expansion_parser::is_arithmetic_word_piece("(a))", &options).is_err());
1020 assert!(super::expansion_parser::is_arithmetic_word_piece("((a)", &options).is_err());
1021 }
1022
1023 #[test]
1024 fn test_brace_expansion_parsing() -> Result<()> {
1025 let options = ParserOptions::default();
1026
1027 let inputs = ["x{a,b}y", "{a,b{1,2}}"];
1028
1029 for input in inputs {
1030 assert_ron_snapshot!(super::parse_brace_expansions(input, &options)?.ok_or_else(
1031 || anyhow::anyhow!("Expected brace expansion to be parsed successfully")
1032 )?);
1033 }
1034
1035 Ok(())
1036 }
1037}