1use crate::ast;
13use crate::error;
14use crate::ParserOptions;
15
16#[derive(Clone, Debug)]
18pub struct WordPieceWithSource {
19 pub piece: WordPiece,
21 pub start_index: usize,
23 pub end_index: usize,
25}
26
27#[derive(Clone, Debug)]
29pub enum WordPiece {
30 Text(String),
32 SingleQuotedText(String),
34 AnsiCQuotedText(String),
36 DoubleQuotedSequence(Vec<WordPieceWithSource>),
38 TildePrefix(String),
40 ParameterExpansion(ParameterExpr),
42 CommandSubstitution(String),
44 BackquotedCommandSubstitution(String),
46 EscapeSequence(String),
48 ArithmeticExpression(ast::UnexpandedArithmeticExpr),
50}
51
52#[derive(Clone, Debug)]
54pub enum ParameterTestType {
55 UnsetOrNull,
57 Unset,
59}
60
61#[derive(Clone, Debug)]
63pub enum Parameter {
64 Positional(u32),
66 Special(SpecialParameter),
68 Named(String),
70 NamedWithIndex {
72 name: String,
74 index: String,
76 },
77 NamedWithAllIndices {
79 name: String,
81 concatenate: bool,
83 },
84}
85
86#[derive(Clone, Debug)]
88pub enum SpecialParameter {
89 AllPositionalParameters {
91 concatenate: bool,
93 },
94 PositionalParameterCount,
96 LastExitStatus,
98 CurrentOptionFlags,
100 ProcessId,
102 LastBackgroundProcessId,
104 ShellName,
106}
107
108#[derive(Clone, Debug)]
110pub enum ParameterExpr {
111 Parameter {
113 parameter: Parameter,
115 indirect: bool,
119 },
120 UseDefaultValues {
122 parameter: Parameter,
124 indirect: bool,
128 test_type: ParameterTestType,
130 default_value: Option<String>,
132 },
133 AssignDefaultValues {
135 parameter: Parameter,
137 indirect: bool,
141 test_type: ParameterTestType,
143 default_value: Option<String>,
145 },
146 IndicateErrorIfNullOrUnset {
148 parameter: Parameter,
150 indirect: bool,
154 test_type: ParameterTestType,
156 error_message: Option<String>,
158 },
159 UseAlternativeValue {
161 parameter: Parameter,
163 indirect: bool,
167 test_type: ParameterTestType,
169 alternative_value: Option<String>,
171 },
172 ParameterLength {
174 parameter: Parameter,
176 indirect: bool,
180 },
181 RemoveSmallestSuffixPattern {
183 parameter: Parameter,
185 indirect: bool,
189 pattern: Option<String>,
191 },
192 RemoveLargestSuffixPattern {
194 parameter: Parameter,
196 indirect: bool,
200 pattern: Option<String>,
202 },
203 RemoveSmallestPrefixPattern {
205 parameter: Parameter,
207 indirect: bool,
211 pattern: Option<String>,
213 },
214 RemoveLargestPrefixPattern {
216 parameter: Parameter,
218 indirect: bool,
222 pattern: Option<String>,
224 },
225 Substring {
227 parameter: Parameter,
229 indirect: bool,
233 offset: ast::UnexpandedArithmeticExpr,
236 length: Option<ast::UnexpandedArithmeticExpr>,
240 },
241 Transform {
243 parameter: Parameter,
245 indirect: bool,
249 op: ParameterTransformOp,
251 },
252 UppercaseFirstChar {
254 parameter: Parameter,
256 indirect: bool,
260 pattern: Option<String>,
262 },
263 UppercasePattern {
265 parameter: Parameter,
267 indirect: bool,
271 pattern: Option<String>,
273 },
274 LowercaseFirstChar {
276 parameter: Parameter,
278 indirect: bool,
282 pattern: Option<String>,
284 },
285 LowercasePattern {
287 parameter: Parameter,
289 indirect: bool,
293 pattern: Option<String>,
295 },
296 ReplaceSubstring {
298 parameter: Parameter,
300 indirect: bool,
304 pattern: String,
306 replacement: Option<String>,
308 match_kind: SubstringMatchKind,
310 },
311 VariableNames {
313 prefix: String,
315 concatenate: bool,
317 },
318 MemberKeys {
320 variable_name: String,
322 concatenate: bool,
324 },
325}
326
327#[derive(Clone, Debug)]
329pub enum SubstringMatchKind {
330 Prefix,
332 Suffix,
334 FirstOccurrence,
336 Anywhere,
338}
339
340#[derive(Clone, Debug)]
342pub enum ParameterTransformOp {
343 CapitalizeInitial,
345 ExpandEscapeSequences,
347 PossiblyQuoteWithArraysExpanded {
349 separate_words: bool,
351 },
352 PromptExpand,
354 Quoted,
356 ToAssignmentLogic,
358 ToAttributeFlags,
360 ToLowerCase,
362 ToUpperCase,
364}
365
366#[derive(Clone, Debug)]
368pub enum BraceExpressionOrText {
369 Expr(BraceExpression),
371 Text(String),
373}
374
375pub type BraceExpression = Vec<BraceExpressionMember>;
377
378impl BraceExpressionOrText {
379 pub fn generate(self) -> Box<dyn Iterator<Item = String>> {
381 match self {
382 BraceExpressionOrText::Expr(members) => {
383 let mut iters = vec![];
384 for m in members {
385 iters.push(m.generate());
386 }
387 Box::new(iters.into_iter().flatten())
388 }
389 BraceExpressionOrText::Text(text) => Box::new(std::iter::once(text)),
390 }
391 }
392}
393
394#[derive(Clone, Debug)]
396pub enum BraceExpressionMember {
397 NumberSequence {
399 low: i64,
401 high: i64,
403 increment: i64,
405 },
406 CharSequence {
408 low: char,
410 high: char,
412 increment: i64,
414 },
415 Text(String),
417}
418
419impl BraceExpressionMember {
420 #[allow(clippy::cast_possible_truncation)]
422 #[allow(clippy::cast_sign_loss)]
423 pub fn generate(self) -> Box<dyn Iterator<Item = String>> {
424 match self {
425 BraceExpressionMember::NumberSequence {
426 low,
427 high,
428 increment,
429 } => Box::new(
430 (low..=high)
431 .step_by(increment as usize)
432 .map(|n| n.to_string()),
433 ),
434 BraceExpressionMember::CharSequence {
435 low,
436 high,
437 increment,
438 } => Box::new(
439 (low..=high)
440 .step_by(increment as usize)
441 .map(|c| c.to_string()),
442 ),
443 BraceExpressionMember::Text(text) => Box::new(std::iter::once(text)),
444 }
445 }
446}
447
448pub fn parse(
455 word: &str,
456 options: &ParserOptions,
457) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
458 cacheable_parse(word.to_owned(), options.to_owned())
459}
460
461#[cached::proc_macro::cached(size = 64, result = true)]
462fn cacheable_parse(
463 word: String,
464 options: ParserOptions,
465) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
466 tracing::debug!(target: "expansion", "Parsing word '{}'", word);
467
468 let pieces = expansion_parser::unexpanded_word(word.as_str(), &options)
469 .map_err(|err| error::WordParseError::Word(word.clone(), err))?;
470
471 tracing::debug!(target: "expansion", "Parsed word '{}' => {{{:?}}}", word, pieces);
472
473 Ok(pieces)
474}
475
476pub fn parse_parameter(
483 word: &str,
484 options: &ParserOptions,
485) -> Result<Parameter, error::WordParseError> {
486 expansion_parser::parameter(word, options)
487 .map_err(|err| error::WordParseError::Parameter(word.to_owned(), err))
488}
489
490pub fn parse_brace_expansions(
497 word: &str,
498 options: &ParserOptions,
499) -> Result<Option<Vec<BraceExpressionOrText>>, error::WordParseError> {
500 expansion_parser::brace_expansions(word, options)
501 .map_err(|err| error::WordParseError::BraceExpansion(word.to_owned(), err))
502}
503
504peg::parser! {
505 grammar expansion_parser(parser_options: &ParserOptions) for str {
506 rule traced<T>(e: rule<T>) -> T =
508 &(input:$([_]*) {
509 #[cfg(feature = "debug-tracing")]
510 println!("[PEG_INPUT_START]\n{input}\n[PEG_TRACE_START]");
511 })
512 e:e()? {?
513 #[cfg(feature = "debug-tracing")]
514 println!("[PEG_TRACE_STOP]");
515 e.ok_or("")
516 }
517
518 pub(crate) rule unexpanded_word() -> Vec<WordPieceWithSource> = traced(<word(<![_]>)>)
519
520 rule word<T>(stop_condition: rule<T>) -> Vec<WordPieceWithSource> =
521 tilde:tilde_prefix_with_source()? pieces:word_piece_with_source(<stop_condition()>, false )* {
522 let mut all_pieces = Vec::new();
523 if let Some(tilde) = tilde {
524 all_pieces.push(tilde);
525 }
526 all_pieces.extend(pieces);
527 all_pieces
528 }
529
530 pub(crate) rule brace_expansions() -> Option<Vec<BraceExpressionOrText>> =
531 pieces:(brace_expansion_piece()+) { Some(pieces) } /
532 [_]* { None }
533
534 rule brace_expansion_piece() -> BraceExpressionOrText =
535 expr:brace_expr() {
536 BraceExpressionOrText::Expr(expr)
537 } /
538 text:$(non_brace_expr_text()+) { BraceExpressionOrText::Text(text.to_owned()) }
539
540 rule non_brace_expr_text() -> () =
541 !"{" word_piece(<['{']>, false) {} /
542 !brace_expr() "{" {}
543
544 rule brace_expr() -> BraceExpression =
545 "{" inner:brace_expr_inner() "}" { inner }
546
547 rule brace_expr_inner() -> BraceExpression =
548 brace_text_list_expr() /
549 seq:brace_sequence_expr() { vec![seq] }
550
551 rule brace_text_list_expr() -> BraceExpression =
552 members:(brace_text_list_member() **<2,> ",") {
553 members.into_iter().flatten().collect()
554 }
555
556 rule brace_text_list_member() -> BraceExpression =
557 &[',' | '}'] { vec![BraceExpressionMember::Text(String::new())] } /
558 brace_expr() /
559 text:$(word_piece(<[',' | '}']>, false)) {
560 vec![BraceExpressionMember::Text(text.to_owned())]
561 }
562
563 rule brace_sequence_expr() -> BraceExpressionMember =
564 low:number() ".." high:number() increment:(".." n:number() { n })? {
565 BraceExpressionMember::NumberSequence { low, high, increment: increment.unwrap_or(1) }
566 } /
567 low:character() ".." high:character() increment:(".." n:number() { n })? {
568 BraceExpressionMember::CharSequence { low, high, increment: increment.unwrap_or(1) }
569 }
570
571 rule number() -> i64 = n:$(['0'..='9']+) { n.parse().unwrap() }
572 rule character() -> char = ['a'..='z' | 'A'..='Z']
573
574 pub(crate) rule is_arithmetic_word() =
575 arithmetic_word(<![_]>)
576
577 rule arithmetic_word<T>(stop_condition: rule<T>) =
580 arithmetic_word_piece(<stop_condition()>)* {}
581
582 pub(crate) rule is_arithmetic_word_piece() =
583 arithmetic_word_piece(<![_]>)
584
585 rule arithmetic_word_piece<T>(stop_condition: rule<T>) =
590 "(" arithmetic_word_plus_right_paren() {} /
595 !"(" word_piece(<param_rule_or_open_paren(<stop_condition()>)>, false ) {}
599
600 rule param_rule_or_open_paren<T>(stop_condition: rule<T>) -> () =
602 stop_condition() {} /
603 "(" {}
604
605 rule arithmetic_word_plus_right_paren() =
607 arithmetic_word(<[')']>) ")" /
608
609 rule word_piece_with_source<T>(stop_condition: rule<T>, in_command: bool) -> WordPieceWithSource =
610 start_index:position!() piece:word_piece(<stop_condition()>, in_command) end_index:position!() {
611 WordPieceWithSource { piece, start_index, end_index }
612 }
613
614 rule word_piece<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
615 arithmetic_expansion() /
616 command_substitution() /
617 parameter_expansion() /
618 unquoted_text(<stop_condition()>, in_command)
619
620 rule double_quoted_word_piece() -> WordPiece =
621 arithmetic_expansion() /
622 command_substitution() /
623 parameter_expansion() /
624 double_quoted_escape_sequence() /
625 double_quoted_text()
626
627 rule unquoted_text<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
628 s:double_quoted_sequence() { WordPiece::DoubleQuotedSequence(s) } /
629 s:single_quoted_literal_text() { WordPiece::SingleQuotedText(s.to_owned()) } /
630 s:ansi_c_quoted_text() { WordPiece::AnsiCQuotedText(s.to_owned()) } /
631 normal_escape_sequence() /
632 unquoted_literal_text(<stop_condition()>, in_command)
633
634 rule double_quoted_sequence() -> Vec<WordPieceWithSource> =
635 "\"" i:double_quoted_sequence_inner()* "\"" { i }
636
637 rule double_quoted_sequence_inner() -> WordPieceWithSource =
638 start_index:position!() piece:double_quoted_word_piece() end_index:position!() {
639 WordPieceWithSource {
640 piece,
641 start_index,
642 end_index
643 }
644 }
645
646 rule single_quoted_literal_text() -> &'input str =
647 "\'" inner:$([^'\'']*) "\'" { inner }
648
649 rule ansi_c_quoted_text() -> &'input str =
650 "$\'" inner:$([^'\'']*) "\'" { inner }
651
652 rule unquoted_literal_text<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
653 s:$(unquoted_literal_text_piece(<stop_condition()>, in_command)+) { WordPiece::Text(s.to_owned()) }
654
655 rule unquoted_literal_text_piece<T>(stop_condition: rule<T>, in_command: bool) =
657 is_true(in_command) extglob_pattern() /
658 is_true(in_command) subshell_command() /
659 !stop_condition() !normal_escape_sequence() [^'$' | '\'' | '\"' | '`'] {}
660
661 rule is_true(value: bool) = &[_] {? if value { Ok(()) } else { Err("not true") } }
662
663 rule extglob_pattern() =
664 ("@" / "!" / "?" / "+" / "*") "(" extglob_body_piece()* ")" {}
665
666 rule extglob_body_piece() =
667 word_piece(<[')']>, true ) {}
668
669 rule subshell_command() =
670 "(" command() ")" {}
671
672 rule double_quoted_text() -> WordPiece =
673 s:double_quote_body_text() { WordPiece::Text(s.to_owned()) }
674
675 rule double_quote_body_text() -> &'input str =
676 $((!double_quoted_escape_sequence() [^'$' | '\"' | '`'])+)
677
678 rule normal_escape_sequence() -> WordPiece =
679 s:$("\\" [c]) { WordPiece::EscapeSequence(s.to_owned()) }
680
681 rule double_quoted_escape_sequence() -> WordPiece =
682 s:$("\\" ['$' | '`' | '\"' | '\'' | '\\']) { WordPiece::EscapeSequence(s.to_owned()) }
683
684 rule tilde_prefix_with_source() -> WordPieceWithSource =
685 start_index:position!() piece:tilde_prefix() end_index:position!() {
686 WordPieceWithSource {
687 piece,
688 start_index,
689 end_index
690 }
691 }
692
693 rule tilde_prefix() -> WordPiece =
695 tilde_parsing_enabled() "~" cs:$((!['/' | ':' | ';'] [c])*) { WordPiece::TildePrefix(cs.to_owned()) }
696
697 rule parameter_expansion() -> WordPiece =
700 "${" e:parameter_expression() "}" {
701 WordPiece::ParameterExpansion(e)
702 } /
703 "$" parameter:unbraced_parameter() {
704 WordPiece::ParameterExpansion(ParameterExpr::Parameter { parameter, indirect: false })
705 } /
706 "$" !['\''] {
707 WordPiece::Text("$".to_owned())
708 }
709
710 rule parameter_expression() -> ParameterExpr =
711 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "-" default_value:parameter_expression_word()? {
712 ParameterExpr::UseDefaultValues { parameter, indirect, test_type, default_value }
713 } /
714 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "=" default_value:parameter_expression_word()? {
715 ParameterExpr::AssignDefaultValues { parameter, indirect, test_type, default_value }
716 } /
717 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "?" error_message:parameter_expression_word()? {
718 ParameterExpr::IndicateErrorIfNullOrUnset { parameter, indirect, test_type, error_message }
719 } /
720 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "+" alternative_value:parameter_expression_word()? {
721 ParameterExpr::UseAlternativeValue { parameter, indirect, test_type, alternative_value }
722 } /
723 "#" parameter:parameter() {
724 ParameterExpr::ParameterLength { parameter, indirect: false }
725 } /
726 indirect:parameter_indirection() parameter:parameter() "%%" pattern:parameter_expression_word()? {
727 ParameterExpr::RemoveLargestSuffixPattern { parameter, indirect, pattern }
728 } /
729 indirect:parameter_indirection() parameter:parameter() "%" pattern:parameter_expression_word()? {
730 ParameterExpr::RemoveSmallestSuffixPattern { parameter, indirect, pattern }
731 } /
732 indirect:parameter_indirection() parameter:parameter() "##" pattern:parameter_expression_word()? {
733 ParameterExpr::RemoveLargestPrefixPattern { parameter, indirect, pattern }
734 } /
735 indirect:parameter_indirection() parameter:parameter() "#" pattern:parameter_expression_word()? {
736 ParameterExpr::RemoveSmallestPrefixPattern { parameter, indirect, pattern }
737 } /
738 non_posix_extensions_enabled() e:non_posix_parameter_expression() { e } /
740 indirect:parameter_indirection() parameter:parameter() {
741 ParameterExpr::Parameter { parameter, indirect }
742 }
743
744 rule parameter_test_type() -> ParameterTestType =
745 colon:":"? {
746 if colon.is_some() {
747 ParameterTestType::UnsetOrNull
748 } else {
749 ParameterTestType::Unset
750 }
751 }
752
753 rule non_posix_parameter_expression() -> ParameterExpr =
754 "!" variable_name:variable_name() "[*]" {
755 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: true }
756 } /
757 "!" variable_name:variable_name() "[@]" {
758 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: false }
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() ":" offset:substring_offset() length:(":" l:substring_length() { l })? {
767 ParameterExpr::Substring { parameter, indirect, offset, length }
768 } /
769 indirect:parameter_indirection() parameter:parameter() "@" op:non_posix_parameter_transformation_op() {
770 ParameterExpr::Transform { parameter, indirect, op }
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::Prefix }
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::Suffix }
777 } /
778 indirect:parameter_indirection() parameter:parameter() "//" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
779 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Anywhere }
780 } /
781 indirect:parameter_indirection() parameter:parameter() "/" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
782 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::FirstOccurrence }
783 } /
784 indirect:parameter_indirection() parameter:parameter() "^^" pattern:parameter_expression_word()? {
785 ParameterExpr::UppercasePattern { parameter, indirect, pattern }
786 } /
787 indirect:parameter_indirection() parameter:parameter() "^" pattern:parameter_expression_word()? {
788 ParameterExpr::UppercaseFirstChar { parameter, indirect, pattern }
789 } /
790 indirect:parameter_indirection() parameter:parameter() ",," pattern:parameter_expression_word()? {
791 ParameterExpr::LowercasePattern { parameter, indirect, pattern }
792 } /
793 indirect:parameter_indirection() parameter:parameter() "," pattern:parameter_expression_word()? {
794 ParameterExpr::LowercaseFirstChar { parameter, indirect, pattern }
795 }
796
797 rule parameter_indirection() -> bool =
798 non_posix_extensions_enabled() "!" { true } /
799 { false }
800
801 rule non_posix_parameter_transformation_op() -> ParameterTransformOp =
802 "U" { ParameterTransformOp::ToUpperCase } /
803 "u" { ParameterTransformOp::CapitalizeInitial } /
804 "L" { ParameterTransformOp::ToLowerCase } /
805 "Q" { ParameterTransformOp::Quoted } /
806 "E" { ParameterTransformOp::ExpandEscapeSequences } /
807 "P" { ParameterTransformOp::PromptExpand } /
808 "A" { ParameterTransformOp::ToAssignmentLogic } /
809 "K" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: false } } /
810 "a" { ParameterTransformOp::ToAttributeFlags } /
811 "k" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: true } }
812
813
814 rule unbraced_parameter() -> Parameter =
815 p:unbraced_positional_parameter() { Parameter::Positional(p) } /
816 p:special_parameter() { Parameter::Special(p) } /
817 p:variable_name() { Parameter::Named(p.to_owned()) }
818
819 pub(crate) rule parameter() -> Parameter =
821 p:positional_parameter() { Parameter::Positional(p) } /
822 p:special_parameter() { Parameter::Special(p) } /
823 non_posix_extensions_enabled() p:variable_name() "[@]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: false } } /
824 non_posix_extensions_enabled() p:variable_name() "[*]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: true } } /
825 non_posix_extensions_enabled() p:variable_name() "[" index:$(arithmetic_word(<"]">)) "]" {?
826 Ok(Parameter::NamedWithIndex { name: p.to_owned(), index: index.to_owned() })
827 } /
828 p:variable_name() { Parameter::Named(p.to_owned()) }
829
830 rule positional_parameter() -> u32 =
831 n:$(['1'..='9'](['0'..='9']*)) {? n.parse().or(Err("u32")) }
832 rule unbraced_positional_parameter() -> u32 =
833 n:$(['1'..='9']) {? n.parse().or(Err("u32")) }
834
835 rule special_parameter() -> SpecialParameter =
836 "@" { SpecialParameter::AllPositionalParameters { concatenate: false } } /
837 "*" { SpecialParameter::AllPositionalParameters { concatenate: true } } /
838 "#" { SpecialParameter::PositionalParameterCount } /
839 "?" { SpecialParameter::LastExitStatus } /
840 "-" { SpecialParameter::CurrentOptionFlags } /
841 "$" { SpecialParameter::ProcessId } /
842 "!" { SpecialParameter::LastBackgroundProcessId } /
843 "0" { SpecialParameter::ShellName }
844
845 rule variable_name() -> &'input str =
846 $(!['0'..='9'] ['_' | '0'..='9' | 'a'..='z' | 'A'..='Z']+)
847
848 pub(crate) rule command_substitution() -> WordPiece =
849 "$(" c:command() ")" { WordPiece::CommandSubstitution(c.to_owned()) } /
850 "`" c:backquoted_command() "`" { WordPiece::BackquotedCommandSubstitution(c) }
851
852 pub(crate) rule command() -> &'input str =
853 $(command_piece()*)
854
855 pub(crate) rule command_piece() -> () =
856 word_piece(<[')']>, true ) {} /
857 ([' ' | '\t'])+ {}
858
859 rule backquoted_command() -> String =
860 chars:(backquoted_char()*) { chars.into_iter().collect() }
861
862 rule backquoted_char() -> char =
863 "\\`" { '`' } /
864 [^'`']
865
866 rule arithmetic_expansion() -> WordPiece =
867 "$((" e:$(arithmetic_word(<"))">)) "))" { WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value: e.to_owned() } ) }
868
869 rule substring_offset() -> ast::UnexpandedArithmeticExpr =
870 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
871
872 rule substring_length() -> ast::UnexpandedArithmeticExpr =
873 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
874
875 rule parameter_replacement_str() -> String =
876 "/" s:$(word(<['}']>)) { s.to_owned() }
877
878 rule parameter_search_pattern() -> String =
879 s:$(word(<['}' | '/']>)) { s.to_owned() }
880
881 rule parameter_expression_word() -> String =
882 s:$(word(<['}']>)) { s.to_owned() }
883
884 rule extglob_enabled() -> () =
885 &[_] {? if parser_options.enable_extended_globbing { Ok(()) } else { Err("no extglob") } }
886
887 rule non_posix_extensions_enabled() -> () =
888 &[_] {? if !parser_options.sh_mode { Ok(()) } else { Err("posix") } }
889
890 rule tilde_parsing_enabled() -> () =
891 &[_] {? if parser_options.tilde_expansion { Ok(()) } else { Err("no tilde expansion") } }
892 }
893}
894
895#[cfg(test)]
896mod tests {
897 use super::*;
898 use anyhow::Result;
899 use assert_matches::assert_matches;
900
901 #[test]
902 fn parse_command_substitution() -> Result<()> {
903 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
904 super::expansion_parser::command_piece("hi", &ParserOptions::default())?;
905 super::expansion_parser::command("echo hi", &ParserOptions::default())?;
906 super::expansion_parser::command_substitution("$(echo hi)", &ParserOptions::default())?;
907
908 let parsed = super::parse("$(echo hi)", &ParserOptions::default())?;
909 assert_matches!(
910 &parsed[..],
911 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == "echo hi"
912 );
913
914 Ok(())
915 }
916
917 #[test]
918 fn parse_command_substitution_with_embedded_quotes() -> Result<()> {
919 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
920 super::expansion_parser::command_piece(r#""hi""#, &ParserOptions::default())?;
921 super::expansion_parser::command(r#"echo "hi""#, &ParserOptions::default())?;
922 super::expansion_parser::command_substitution(
923 r#"$(echo "hi")"#,
924 &ParserOptions::default(),
925 )?;
926
927 let parsed = super::parse(r#"$(echo "hi")"#, &ParserOptions::default())?;
928 assert_matches!(
929 &parsed[..],
930 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == r#"echo "hi""#
931 );
932
933 Ok(())
934 }
935
936 #[test]
937 fn parse_command_substitution_with_embedded_extglob() -> Result<()> {
938 let parsed = super::parse("$(echo !(x))", &ParserOptions::default())?;
939 assert_matches!(
940 &parsed[..],
941 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == "echo !(x)"
942 );
943
944 Ok(())
945 }
946
947 #[test]
948 fn parse_backquoted_command() -> Result<()> {
949 let parsed = super::parse("`echo hi`", &ParserOptions::default())?;
950 assert_matches!(
951 &parsed[..],
952 [WordPieceWithSource { piece: WordPiece::BackquotedCommandSubstitution(s), .. }] if s == "echo hi"
953 );
954
955 Ok(())
956 }
957
958 #[test]
959 fn parse_backquoted_command_in_double_quotes() -> Result<()> {
960 let parsed = super::parse(r#""`echo hi`""#, &ParserOptions::default())?;
961 assert_matches!(
962 &parsed[..],
963 [WordPieceWithSource { piece: WordPiece::DoubleQuotedSequence(inner), .. }]
964 if matches!(
965 &inner[..],
966 [WordPieceWithSource { piece: WordPiece::BackquotedCommandSubstitution(s), .. }] if s == "echo hi"
967 )
968 );
969
970 Ok(())
971 }
972
973 #[test]
974 fn parse_extglob_with_embedded_parameter() -> Result<()> {
975 let parsed = super::parse("+([$var])", &ParserOptions::default())?;
976 assert_matches!(
977 &parsed[..],
978 [WordPieceWithSource { piece: WordPiece::Text(s1), .. },
979 WordPieceWithSource { piece: WordPiece::ParameterExpansion(ParameterExpr::Parameter { parameter: Parameter::Named(s2), .. }), ..},
980 WordPieceWithSource { piece: WordPiece::Text(s3), .. }] if s1 == "+([" && s2 == "var" && s3 == "])"
981 );
982
983 Ok(())
984 }
985
986 #[test]
987 fn parse_arithmetic_expansion() -> Result<()> {
988 const EXPECTED_RESULT: &str = "0";
989
990 let parsed = super::parse("$((0))", &ParserOptions::default())?;
991 assert_matches!(
992 &parsed[..],
993 [WordPieceWithSource { piece: WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value }), .. }] if value == EXPECTED_RESULT
994 );
995
996 Ok(())
997 }
998
999 #[test]
1000 fn parse_arithmetic_expansion_with_parens() -> Result<()> {
1001 const EXPECTED_RESULT: &str = "((1+2)*3)";
1002
1003 let parsed = super::parse("$((((1+2)*3)))", &ParserOptions::default())?;
1004 assert_matches!(
1005 &parsed[..],
1006 [WordPieceWithSource { piece: WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value }), .. }] if value == EXPECTED_RESULT
1007 );
1008
1009 Ok(())
1010 }
1011
1012 #[test]
1013 fn test_arithmetic_word_parsing() {
1014 let options = ParserOptions::default();
1015
1016 assert!(super::expansion_parser::is_arithmetic_word("a", &options).is_ok());
1017 assert!(super::expansion_parser::is_arithmetic_word("b", &options).is_ok());
1018 assert!(super::expansion_parser::is_arithmetic_word(" a + b ", &options).is_ok());
1019 assert!(super::expansion_parser::is_arithmetic_word("(a)", &options).is_ok());
1020 assert!(super::expansion_parser::is_arithmetic_word("((a))", &options).is_ok());
1021 assert!(super::expansion_parser::is_arithmetic_word("(((a)))", &options).is_ok());
1022 assert!(super::expansion_parser::is_arithmetic_word("(1+2)", &options).is_ok());
1023 assert!(super::expansion_parser::is_arithmetic_word("(1+2)*3", &options).is_ok());
1024 assert!(super::expansion_parser::is_arithmetic_word("((1+2)*3)", &options).is_ok());
1025 }
1026
1027 #[test]
1028 fn test_arithmetic_word_piece_parsing() {
1029 let options = ParserOptions::default();
1030
1031 assert!(super::expansion_parser::is_arithmetic_word_piece("a", &options).is_ok());
1032 assert!(super::expansion_parser::is_arithmetic_word_piece("b", &options).is_ok());
1033 assert!(super::expansion_parser::is_arithmetic_word_piece(" a + b ", &options).is_ok());
1034 assert!(super::expansion_parser::is_arithmetic_word_piece("(a)", &options).is_ok());
1035 assert!(super::expansion_parser::is_arithmetic_word_piece("((a))", &options).is_ok());
1036 assert!(super::expansion_parser::is_arithmetic_word_piece("(((a)))", &options).is_ok());
1037 assert!(super::expansion_parser::is_arithmetic_word_piece("(1+2)", &options).is_ok());
1038 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2))", &options).is_ok());
1039 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2)*3)", &options).is_ok());
1040 assert!(super::expansion_parser::is_arithmetic_word_piece("(a", &options).is_err());
1041 assert!(super::expansion_parser::is_arithmetic_word_piece("(a))", &options).is_err());
1042 assert!(super::expansion_parser::is_arithmetic_word_piece("((a)", &options).is_err());
1043 }
1044}