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 pub fn generate(self) -> Box<dyn Iterator<Item = String>> {
422 match self {
423 BraceExpressionMember::NumberSequence {
424 low,
425 high,
426 increment,
427 } => Box::new(
428 (low..=high)
429 .step_by(increment as usize)
430 .map(|n| n.to_string()),
431 ),
432 BraceExpressionMember::CharSequence {
433 low,
434 high,
435 increment,
436 } => Box::new(
437 (low..=high)
438 .step_by(increment as usize)
439 .map(|c| c.to_string()),
440 ),
441 BraceExpressionMember::Text(text) => Box::new(std::iter::once(text)),
442 }
443 }
444}
445
446pub fn parse(
453 word: &str,
454 options: &ParserOptions,
455) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
456 cacheable_parse(word.to_owned(), options.to_owned())
457}
458
459#[cached::proc_macro::cached(size = 64, result = true)]
460fn cacheable_parse(
461 word: String,
462 options: ParserOptions,
463) -> Result<Vec<WordPieceWithSource>, error::WordParseError> {
464 tracing::debug!(target: "expansion", "Parsing word '{}'", word);
465
466 let pieces = expansion_parser::unexpanded_word(word.as_str(), &options)
467 .map_err(|err| error::WordParseError::Word(word.to_owned(), err))?;
468
469 tracing::debug!(target: "expansion", "Parsed word '{}' => {{{:?}}}", word, pieces);
470
471 Ok(pieces)
472}
473
474pub fn parse_parameter(
481 word: &str,
482 options: &ParserOptions,
483) -> Result<Parameter, error::WordParseError> {
484 expansion_parser::parameter(word, options)
485 .map_err(|err| error::WordParseError::Parameter(word.to_owned(), err))
486}
487
488pub fn parse_brace_expansions(
495 word: &str,
496 options: &ParserOptions,
497) -> Result<Option<Vec<BraceExpressionOrText>>, error::WordParseError> {
498 expansion_parser::brace_expansions(word, options)
499 .map_err(|err| error::WordParseError::BraceExpansion(word.to_owned(), err))
500}
501
502peg::parser! {
503 grammar expansion_parser(parser_options: &ParserOptions) for str {
504 rule traced<T>(e: rule<T>) -> T =
506 &(input:$([_]*) {
507 #[cfg(feature = "debug-tracing")]
508 println!("[PEG_INPUT_START]\n{}\n[PEG_TRACE_START]", input);
509 })
510 e:e()? {?
511 #[cfg(feature = "debug-tracing")]
512 println!("[PEG_TRACE_STOP]");
513 e.ok_or("")
514 }
515
516 pub(crate) rule unexpanded_word() -> Vec<WordPieceWithSource> = traced(<word(<![_]>)>)
517
518 rule word<T>(stop_condition: rule<T>) -> Vec<WordPieceWithSource> =
519 tilde:tilde_prefix_with_source()? pieces:word_piece_with_source(<stop_condition()>, false )* {
520 let mut all_pieces = Vec::new();
521 if let Some(tilde) = tilde {
522 all_pieces.push(tilde);
523 }
524 all_pieces.extend(pieces);
525 all_pieces
526 }
527
528 pub(crate) rule brace_expansions() -> Option<Vec<BraceExpressionOrText>> =
529 pieces:(brace_expansion_piece()+) { Some(pieces) } /
530 [_]* { None }
531
532 rule brace_expansion_piece() -> BraceExpressionOrText =
533 expr:brace_expr() {
534 BraceExpressionOrText::Expr(expr)
535 } /
536 text:$(non_brace_expr_text()+) { BraceExpressionOrText::Text(text.to_owned()) }
537
538 rule non_brace_expr_text() -> () =
539 !"{" word_piece(<['{']>, false) {} /
540 !brace_expr() "{" {}
541
542 rule brace_expr() -> BraceExpression =
543 "{" inner:brace_expr_inner() "}" { inner }
544
545 rule brace_expr_inner() -> BraceExpression =
546 brace_text_list_expr() /
547 seq:brace_sequence_expr() { vec![seq] }
548
549 rule brace_text_list_expr() -> BraceExpression =
550 members:(brace_text_list_member() **<2,> ",") {
551 members.into_iter().flatten().collect()
552 }
553
554 rule brace_text_list_member() -> BraceExpression =
555 &[',' | '}'] { vec![BraceExpressionMember::Text(String::new())] } /
556 brace_expr() /
557 text:$(word_piece(<[',' | '}']>, false)) {
558 vec![BraceExpressionMember::Text(text.to_owned())]
559 }
560
561 rule brace_sequence_expr() -> BraceExpressionMember =
562 low:number() ".." high:number() increment:(".." n:number() { n })? {
563 BraceExpressionMember::NumberSequence { low, high, increment: increment.unwrap_or(1) }
564 } /
565 low:character() ".." high:character() increment:(".." n:number() { n })? {
566 BraceExpressionMember::CharSequence { low, high, increment: increment.unwrap_or(1) }
567 }
568
569 rule number() -> i64 = n:$(['0'..='9']+) { n.parse().unwrap() }
570 rule character() -> char = ['a'..='z' | 'A'..='Z']
571
572 pub(crate) rule is_arithmetic_word() =
573 arithmetic_word(<![_]>)
574
575 rule arithmetic_word<T>(stop_condition: rule<T>) =
578 arithmetic_word_piece(<stop_condition()>)* {}
579
580 pub(crate) rule is_arithmetic_word_piece() =
581 arithmetic_word_piece(<![_]>)
582
583 rule arithmetic_word_piece<T>(stop_condition: rule<T>) =
588 "(" arithmetic_word_plus_right_paren() {} /
593 !"(" word_piece(<param_rule_or_open_paren(<stop_condition()>)>, false ) {}
597
598 rule param_rule_or_open_paren<T>(stop_condition: rule<T>) -> () =
600 stop_condition() {} /
601 "(" {}
602
603 rule arithmetic_word_plus_right_paren() =
605 arithmetic_word(<[')']>) ")" /
606
607 rule word_piece_with_source<T>(stop_condition: rule<T>, in_command: bool) -> WordPieceWithSource =
608 start_index:position!() piece:word_piece(<stop_condition()>, in_command) end_index:position!() {
609 WordPieceWithSource { piece, start_index, end_index }
610 }
611
612 rule word_piece<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
613 arithmetic_expansion() /
614 command_substitution() /
615 parameter_expansion() /
616 unquoted_text(<stop_condition()>, in_command)
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 unquoted_text<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
626 s:double_quoted_sequence() { WordPiece::DoubleQuotedSequence(s) } /
627 s:single_quoted_literal_text() { WordPiece::SingleQuotedText(s.to_owned()) } /
628 s:ansi_c_quoted_text() { WordPiece::AnsiCQuotedText(s.to_owned()) } /
629 normal_escape_sequence() /
630 unquoted_literal_text(<stop_condition()>, in_command)
631
632 rule double_quoted_sequence() -> Vec<WordPieceWithSource> =
633 "\"" i:double_quoted_sequence_inner()* "\"" { i }
634
635 rule double_quoted_sequence_inner() -> WordPieceWithSource =
636 start_index:position!() piece:double_quoted_word_piece() end_index:position!() {
637 WordPieceWithSource {
638 piece,
639 start_index,
640 end_index
641 }
642 }
643
644 rule single_quoted_literal_text() -> &'input str =
645 "\'" inner:$([^'\'']*) "\'" { inner }
646
647 rule ansi_c_quoted_text() -> &'input str =
648 "$\'" inner:$([^'\'']*) "\'" { inner }
649
650 rule unquoted_literal_text<T>(stop_condition: rule<T>, in_command: bool) -> WordPiece =
651 s:$(unquoted_literal_text_piece(<stop_condition()>, in_command)+) { WordPiece::Text(s.to_owned()) }
652
653 rule unquoted_literal_text_piece<T>(stop_condition: rule<T>, in_command: bool) =
655 is_true(in_command) extglob_pattern() /
656 is_true(in_command) subshell_command() /
657 !stop_condition() !normal_escape_sequence() [^'$' | '\'' | '\"'] {}
658
659 rule is_true(value: bool) = &[_] {? if value { Ok(()) } else { Err("not true") } }
660
661 rule extglob_pattern() =
662 ("@" / "!" / "?" / "+" / "*") "(" extglob_body_piece()* ")" {}
663
664 rule extglob_body_piece() =
665 word_piece(<[')']>, true ) {}
666
667 rule subshell_command() =
668 "(" command() ")" {}
669
670 rule double_quoted_text() -> WordPiece =
671 s:double_quote_body_text() { WordPiece::Text(s.to_owned()) }
672
673 rule double_quote_body_text() -> &'input str =
674 $((!double_quoted_escape_sequence() [^'$' | '\"'])+)
675
676 rule normal_escape_sequence() -> WordPiece =
677 s:$("\\" [c]) { WordPiece::EscapeSequence(s.to_owned()) }
678
679 rule double_quoted_escape_sequence() -> WordPiece =
680 s:$("\\" ['$' | '`' | '\"' | '\'' | '\\']) { WordPiece::EscapeSequence(s.to_owned()) }
681
682 rule tilde_prefix_with_source() -> WordPieceWithSource =
683 start_index:position!() piece:tilde_prefix() end_index:position!() {
684 WordPieceWithSource {
685 piece,
686 start_index,
687 end_index
688 }
689 }
690
691 rule tilde_prefix() -> WordPiece =
693 tilde_parsing_enabled() "~" cs:$((!['/' | ':' | ';'] [c])*) { WordPiece::TildePrefix(cs.to_owned()) }
694
695 rule parameter_expansion() -> WordPiece =
698 "${" e:parameter_expression() "}" {
699 WordPiece::ParameterExpansion(e)
700 } /
701 "$" parameter:unbraced_parameter() {
702 WordPiece::ParameterExpansion(ParameterExpr::Parameter { parameter, indirect: false })
703 } /
704 "$" !['\''] {
705 WordPiece::Text("$".to_owned())
706 }
707
708 rule parameter_expression() -> ParameterExpr =
709 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "-" default_value:parameter_expression_word()? {
710 ParameterExpr::UseDefaultValues { parameter, indirect, test_type, default_value }
711 } /
712 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "=" default_value:parameter_expression_word()? {
713 ParameterExpr::AssignDefaultValues { parameter, indirect, test_type, default_value }
714 } /
715 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "?" error_message:parameter_expression_word()? {
716 ParameterExpr::IndicateErrorIfNullOrUnset { parameter, indirect, test_type, error_message }
717 } /
718 indirect:parameter_indirection() parameter:parameter() test_type:parameter_test_type() "+" alternative_value:parameter_expression_word()? {
719 ParameterExpr::UseAlternativeValue { parameter, indirect, test_type, alternative_value }
720 } /
721 "#" parameter:parameter() {
722 ParameterExpr::ParameterLength { parameter, indirect: false }
723 } /
724 indirect:parameter_indirection() parameter:parameter() "%%" pattern:parameter_expression_word()? {
725 ParameterExpr::RemoveLargestSuffixPattern { parameter, indirect, pattern }
726 } /
727 indirect:parameter_indirection() parameter:parameter() "%" pattern:parameter_expression_word()? {
728 ParameterExpr::RemoveSmallestSuffixPattern { parameter, indirect, pattern }
729 } /
730 indirect:parameter_indirection() parameter:parameter() "##" pattern:parameter_expression_word()? {
731 ParameterExpr::RemoveLargestPrefixPattern { parameter, indirect, pattern }
732 } /
733 indirect:parameter_indirection() parameter:parameter() "#" pattern:parameter_expression_word()? {
734 ParameterExpr::RemoveSmallestPrefixPattern { parameter, indirect, pattern }
735 } /
736 non_posix_extensions_enabled() e:non_posix_parameter_expression() { e } /
738 indirect:parameter_indirection() parameter:parameter() {
739 ParameterExpr::Parameter { parameter, indirect }
740 }
741
742 rule parameter_test_type() -> ParameterTestType =
743 colon:":"? {
744 if colon.is_some() {
745 ParameterTestType::UnsetOrNull
746 } else {
747 ParameterTestType::Unset
748 }
749 }
750
751 rule non_posix_parameter_expression() -> ParameterExpr =
752 "!" variable_name:variable_name() "[*]" {
753 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: true }
754 } /
755 "!" variable_name:variable_name() "[@]" {
756 ParameterExpr::MemberKeys { variable_name: variable_name.to_owned(), concatenate: false }
757 } /
758 "!" prefix:variable_name() "*" {
759 ParameterExpr::VariableNames { prefix: prefix.to_owned(), concatenate: true }
760 } /
761 "!" prefix:variable_name() "@" {
762 ParameterExpr::VariableNames { prefix: prefix.to_owned(), concatenate: false }
763 } /
764 indirect:parameter_indirection() parameter:parameter() ":" offset:substring_offset() length:(":" l:substring_length() { l })? {
765 ParameterExpr::Substring { parameter, indirect, offset, length }
766 } /
767 indirect:parameter_indirection() parameter:parameter() "@" op:non_posix_parameter_transformation_op() {
768 ParameterExpr::Transform { parameter, indirect, op }
769 } /
770 indirect:parameter_indirection() parameter:parameter() "/#" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
771 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Prefix }
772 } /
773 indirect:parameter_indirection() parameter:parameter() "/%" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
774 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Suffix }
775 } /
776 indirect:parameter_indirection() parameter:parameter() "//" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
777 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::Anywhere }
778 } /
779 indirect:parameter_indirection() parameter:parameter() "/" pattern:parameter_search_pattern() replacement:parameter_replacement_str()? {
780 ParameterExpr::ReplaceSubstring { parameter, indirect, pattern, replacement, match_kind: SubstringMatchKind::FirstOccurrence }
781 } /
782 indirect:parameter_indirection() parameter:parameter() "^^" pattern:parameter_expression_word()? {
783 ParameterExpr::UppercasePattern { parameter, indirect, pattern }
784 } /
785 indirect:parameter_indirection() parameter:parameter() "^" pattern:parameter_expression_word()? {
786 ParameterExpr::UppercaseFirstChar { parameter, indirect, pattern }
787 } /
788 indirect:parameter_indirection() parameter:parameter() ",," pattern:parameter_expression_word()? {
789 ParameterExpr::LowercasePattern { parameter, indirect, pattern }
790 } /
791 indirect:parameter_indirection() parameter:parameter() "," pattern:parameter_expression_word()? {
792 ParameterExpr::LowercaseFirstChar { parameter, indirect, pattern }
793 }
794
795 rule parameter_indirection() -> bool =
796 non_posix_extensions_enabled() "!" { true } /
797 { false }
798
799 rule non_posix_parameter_transformation_op() -> ParameterTransformOp =
800 "U" { ParameterTransformOp::ToUpperCase } /
801 "u" { ParameterTransformOp::CapitalizeInitial } /
802 "L" { ParameterTransformOp::ToLowerCase } /
803 "Q" { ParameterTransformOp::Quoted } /
804 "E" { ParameterTransformOp::ExpandEscapeSequences } /
805 "P" { ParameterTransformOp::PromptExpand } /
806 "A" { ParameterTransformOp::ToAssignmentLogic } /
807 "K" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: false } } /
808 "a" { ParameterTransformOp::ToAttributeFlags } /
809 "k" { ParameterTransformOp::PossiblyQuoteWithArraysExpanded { separate_words: true } }
810
811
812 rule unbraced_parameter() -> Parameter =
813 p:unbraced_positional_parameter() { Parameter::Positional(p) } /
814 p:special_parameter() { Parameter::Special(p) } /
815 p:variable_name() { Parameter::Named(p.to_owned()) }
816
817 pub(crate) rule parameter() -> Parameter =
819 p:positional_parameter() { Parameter::Positional(p) } /
820 p:special_parameter() { Parameter::Special(p) } /
821 non_posix_extensions_enabled() p:variable_name() "[@]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: false } } /
822 non_posix_extensions_enabled() p:variable_name() "[*]" { Parameter::NamedWithAllIndices { name: p.to_owned(), concatenate: true } } /
823 non_posix_extensions_enabled() p:variable_name() "[" index:$(arithmetic_word(<"]">)) "]" {?
824 Ok(Parameter::NamedWithIndex { name: p.to_owned(), index: index.to_owned() })
825 } /
826 p:variable_name() { Parameter::Named(p.to_owned()) }
827
828 rule positional_parameter() -> u32 =
829 n:$(['1'..='9'](['0'..='9']*)) {? n.parse().or(Err("u32")) }
830 rule unbraced_positional_parameter() -> u32 =
831 n:$(['1'..='9']) {? n.parse().or(Err("u32")) }
832
833 rule special_parameter() -> SpecialParameter =
834 "@" { SpecialParameter::AllPositionalParameters { concatenate: false } } /
835 "*" { SpecialParameter::AllPositionalParameters { concatenate: true } } /
836 "#" { SpecialParameter::PositionalParameterCount } /
837 "?" { SpecialParameter::LastExitStatus } /
838 "-" { SpecialParameter::CurrentOptionFlags } /
839 "$" { SpecialParameter::ProcessId } /
840 "!" { SpecialParameter::LastBackgroundProcessId } /
841 "0" { SpecialParameter::ShellName }
842
843 rule variable_name() -> &'input str =
844 $(!['0'..='9'] ['_' | '0'..='9' | 'a'..='z' | 'A'..='Z']+)
845
846 pub(crate) rule command_substitution() -> WordPiece =
847 "$(" c:command() ")" { WordPiece::CommandSubstitution(c.to_owned()) } /
848 "`" c:backquoted_command() "`" { WordPiece::BackquotedCommandSubstitution(c) }
849
850 pub(crate) rule command() -> &'input str =
851 $(command_piece()*)
852
853 pub(crate) rule command_piece() -> () =
854 word_piece(<[')']>, true ) {} /
855 ([' ' | '\t'])+ {}
856
857 rule backquoted_command() -> String =
858 chars:(backquoted_char()*) { chars.into_iter().collect() }
859
860 rule backquoted_char() -> char =
861 "\\`" { '`' } /
862 [^'`']
863
864 rule arithmetic_expansion() -> WordPiece =
865 "$((" e:$(arithmetic_word(<"))">)) "))" { WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value: e.to_owned() } ) }
866
867 rule substring_offset() -> ast::UnexpandedArithmeticExpr =
868 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
869
870 rule substring_length() -> ast::UnexpandedArithmeticExpr =
871 s:$(arithmetic_word(<[':' | '}']>)) { ast::UnexpandedArithmeticExpr { value: s.to_owned() } }
872
873 rule parameter_replacement_str() -> String =
874 "/" s:$(word(<['}']>)) { s.to_owned() }
875
876 rule parameter_search_pattern() -> String =
877 s:$(word(<['}' | '/']>)) { s.to_owned() }
878
879 rule parameter_expression_word() -> String =
880 s:$(word(<['}']>)) { s.to_owned() }
881
882 rule extglob_enabled() -> () =
883 &[_] {? if parser_options.enable_extended_globbing { Ok(()) } else { Err("no extglob") } }
884
885 rule non_posix_extensions_enabled() -> () =
886 &[_] {? if !parser_options.sh_mode { Ok(()) } else { Err("posix") } }
887
888 rule tilde_parsing_enabled() -> () =
889 &[_] {? if parser_options.tilde_expansion { Ok(()) } else { Err("no tilde expansion") } }
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896 use anyhow::Result;
897 use assert_matches::assert_matches;
898
899 #[test]
900 fn parse_command_substitution() -> Result<()> {
901 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
902 super::expansion_parser::command_piece("hi", &ParserOptions::default())?;
903 super::expansion_parser::command("echo hi", &ParserOptions::default())?;
904 super::expansion_parser::command_substitution("$(echo hi)", &ParserOptions::default())?;
905
906 let parsed = super::parse("$(echo hi)", &ParserOptions::default())?;
907 assert_matches!(
908 &parsed[..],
909 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == "echo hi"
910 );
911
912 Ok(())
913 }
914
915 #[test]
916 fn parse_command_substitution_with_embedded_quotes() -> Result<()> {
917 super::expansion_parser::command_piece("echo", &ParserOptions::default())?;
918 super::expansion_parser::command_piece(r#""hi""#, &ParserOptions::default())?;
919 super::expansion_parser::command(r#"echo "hi""#, &ParserOptions::default())?;
920 super::expansion_parser::command_substitution(
921 r#"$(echo "hi")"#,
922 &ParserOptions::default(),
923 )?;
924
925 let parsed = super::parse(r#"$(echo "hi")"#, &ParserOptions::default())?;
926 assert_matches!(
927 &parsed[..],
928 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == r#"echo "hi""#
929 );
930
931 Ok(())
932 }
933
934 #[test]
935 fn parse_command_substitution_with_embedded_extglob() -> Result<()> {
936 let parsed = super::parse("$(echo !(x))", &ParserOptions::default())?;
937 assert_matches!(
938 &parsed[..],
939 [WordPieceWithSource { piece: WordPiece::CommandSubstitution(s), .. }] if s.as_str() == "echo !(x)"
940 );
941
942 Ok(())
943 }
944
945 #[test]
946 fn parse_extglob_with_embedded_parameter() -> Result<()> {
947 let parsed = super::parse("+([$var])", &ParserOptions::default())?;
948 assert_matches!(
949 &parsed[..],
950 [WordPieceWithSource { piece: WordPiece::Text(s1), .. },
951 WordPieceWithSource { piece: WordPiece::ParameterExpansion(ParameterExpr::Parameter { parameter: Parameter::Named(s2), .. }), ..},
952 WordPieceWithSource { piece: WordPiece::Text(s3), .. }] if s1 == "+([" && s2 == "var" && s3 == "])"
953 );
954
955 Ok(())
956 }
957
958 #[test]
959 fn parse_arithmetic_expansion() -> Result<()> {
960 let parsed = super::parse("$((0))", &ParserOptions::default())?;
961 const EXPECTED_RESULT: &str = "0";
962
963 assert_matches!(
964 &parsed[..],
965 [WordPieceWithSource { piece: WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value }), .. }] if value == EXPECTED_RESULT
966 );
967
968 Ok(())
969 }
970
971 #[test]
972 fn parse_arithmetic_expansion_with_parens() -> Result<()> {
973 let parsed = super::parse("$((((1+2)*3)))", &ParserOptions::default())?;
974 const EXPECTED_RESULT: &str = "((1+2)*3)";
975
976 assert_matches!(
977 &parsed[..],
978 [WordPieceWithSource { piece: WordPiece::ArithmeticExpression(ast::UnexpandedArithmeticExpr { value }), .. }] if value == EXPECTED_RESULT
979 );
980
981 Ok(())
982 }
983
984 #[test]
985 fn test_arithmetic_word_parsing() {
986 let options = ParserOptions::default();
987
988 assert!(super::expansion_parser::is_arithmetic_word("a", &options).is_ok());
989 assert!(super::expansion_parser::is_arithmetic_word("b", &options).is_ok());
990 assert!(super::expansion_parser::is_arithmetic_word(" a + b ", &options).is_ok());
991 assert!(super::expansion_parser::is_arithmetic_word("(a)", &options).is_ok());
992 assert!(super::expansion_parser::is_arithmetic_word("((a))", &options).is_ok());
993 assert!(super::expansion_parser::is_arithmetic_word("(((a)))", &options).is_ok());
994 assert!(super::expansion_parser::is_arithmetic_word("(1+2)", &options).is_ok());
995 assert!(super::expansion_parser::is_arithmetic_word("(1+2)*3", &options).is_ok());
996 assert!(super::expansion_parser::is_arithmetic_word("((1+2)*3)", &options).is_ok());
997 }
998
999 #[test]
1000 fn test_arithmetic_word_piece_parsing() {
1001 let options = ParserOptions::default();
1002
1003 assert!(super::expansion_parser::is_arithmetic_word_piece("a", &options).is_ok());
1004 assert!(super::expansion_parser::is_arithmetic_word_piece("b", &options).is_ok());
1005 assert!(super::expansion_parser::is_arithmetic_word_piece(" a + b ", &options).is_ok());
1006 assert!(super::expansion_parser::is_arithmetic_word_piece("(a)", &options).is_ok());
1007 assert!(super::expansion_parser::is_arithmetic_word_piece("((a))", &options).is_ok());
1008 assert!(super::expansion_parser::is_arithmetic_word_piece("(((a)))", &options).is_ok());
1009 assert!(super::expansion_parser::is_arithmetic_word_piece("(1+2)", &options).is_ok());
1010 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2))", &options).is_ok());
1011 assert!(super::expansion_parser::is_arithmetic_word_piece("((1+2)*3)", &options).is_ok());
1012 assert!(super::expansion_parser::is_arithmetic_word_piece("(a", &options).is_err());
1013 assert!(super::expansion_parser::is_arithmetic_word_piece("(a))", &options).is_err());
1014 assert!(super::expansion_parser::is_arithmetic_word_piece("((a)", &options).is_err());
1015 }
1016}