11// Copyright 2018-2025 the Deno authors. MIT license.
22
3- use std:: borrow:: Cow ;
4-
53use anyhow:: Result ;
64use anyhow:: bail;
75use monch:: * ;
@@ -694,72 +692,81 @@ fn parse_single_quoted_string(input: &str) -> ParseResult<&str> {
694692}
695693
696694fn parse_double_quoted_string ( input : & str ) -> ParseResult < Vec < WordPart > > {
697- // https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_02_03
698- parse_surrounded_expression (
699- input,
700- '"' ,
701- "Expected closing double quote." ,
702- parse_word_parts ( ParseWordPartsMode :: DoubleQuotes ) ,
703- )
704- }
695+ fn parse_words_within ( input : & str ) -> ParseResult < Vec < WordPart > > {
696+ match parse_word_parts ( ParseWordPartsMode :: DoubleQuotes ) ( input) {
697+ Ok ( ( result_input, parts) ) => {
698+ if !result_input. is_empty ( ) {
699+ return ParseError :: fail (
700+ input,
701+ format ! (
702+ "Failed parsing within double quotes. Unexpected character: {}" ,
703+ result_input
704+ ) ,
705+ ) ;
706+ }
707+ Ok ( ( result_input, parts) )
708+ }
709+ Err ( err) => ParseError :: fail (
710+ input,
711+ format ! (
712+ "Failed parsing within double quotes. {}" ,
713+ match & err {
714+ ParseError :: Backtrace => "Could not determine expression." ,
715+ ParseError :: Failure ( parse_error_failure) =>
716+ parse_error_failure. message. as_str( ) ,
717+ }
718+ ) ,
719+ ) ,
720+ }
721+ }
705722
706- fn parse_surrounded_expression < ' a , TResult > (
707- input : & ' a str ,
708- surrounded_char : char ,
709- fail_message : & str ,
710- parse : impl Fn ( & str ) -> ParseResult < TResult > ,
711- ) -> ParseResult < ' a , TResult > {
723+ // https://pubs.opengroup.org/onlinepubs/009604499/utilities/xcu_chap02.html#tag_02_02_03
712724 let start_input = input;
713- let ( input, _) = ch ( surrounded_char ) ( input) ?;
725+ let ( mut input, _) = ch ( '"' ) ( input) ?;
714726 let mut was_escape = false ;
715- for ( index, c) in input. char_indices ( ) {
727+ let mut pending_parts = Vec :: new ( ) ;
728+ let mut iter = input. char_indices ( ) . peekable ( ) ;
729+ while let Some ( ( index, c) ) = iter. next ( ) {
716730 match c {
717- c if c == surrounded_char && !was_escape => {
731+ c if c == '$'
732+ && !was_escape
733+ && iter. peek ( ) . map ( |( _, c) | * c) == Some ( '(' ) =>
734+ {
735+ let previous_input = & input[ ..index] ;
736+ pending_parts. extend ( parse_words_within ( previous_input) ?. 1 ) ;
737+ let next_input = & input[ index..] ;
738+ let ( next_input, sequence) = with_error_context (
739+ parse_command_substitution,
740+ "Failed parsing command substitution in double quoted string." ,
741+ ) ( next_input) ?;
742+ pending_parts. push ( WordPart :: Command ( sequence) ) ;
743+ iter = next_input. char_indices ( ) . peekable ( ) ;
744+ input = next_input;
745+ }
746+ c if c == '`' && !was_escape => {
747+ let previous_input = & input[ ..index] ;
748+ pending_parts. extend ( parse_words_within ( previous_input) ?. 1 ) ;
749+ let next_input = & input[ index..] ;
750+ let ( next_input, sequence) = with_error_context (
751+ parse_backticks_command_substitution,
752+ "Failed parsing backticks in double quoted string." ,
753+ ) ( next_input) ?;
754+ pending_parts. push ( WordPart :: Command ( sequence) ) ;
755+ iter = next_input. char_indices ( ) . peekable ( ) ;
756+ input = next_input;
757+ }
758+ c if c == '"' && !was_escape => {
718759 let inner_input = & input[ ..index] ;
719- let inner_input =
720- if surrounded_char == '`' && inner_input. contains ( "\\ `" ) {
721- Cow :: Owned ( inner_input. replace ( "\\ `" , "`" ) )
722- } else {
723- Cow :: Borrowed ( inner_input)
724- } ;
725- let parts = match parse ( & inner_input) {
726- Ok ( ( result_input, parts) ) => {
727- if !result_input. is_empty ( ) {
728- return ParseError :: fail (
729- input,
730- format ! (
731- "Failed parsing within {}. Unexpected character: {}" ,
732- if c == '`' {
733- "backticks"
734- } else {
735- "double quotes"
736- } ,
737- result_input
738- ) ,
739- ) ;
740- }
760+ let ( _, parts) = parse_words_within ( inner_input) ?;
761+ return Ok ( (
762+ & input[ index + 1 ..] ,
763+ if pending_parts. is_empty ( ) {
741764 parts
742- }
743- Err ( err) => {
744- return ParseError :: fail (
745- input,
746- format ! (
747- "Failed parsing within {}. {}" ,
748- if c == '`' {
749- "backticks"
750- } else {
751- "double quotes"
752- } ,
753- match & err {
754- ParseError :: Backtrace => "Could not determine expression." ,
755- ParseError :: Failure ( parse_error_failure) =>
756- parse_error_failure. message. as_str( ) ,
757- }
758- ) ,
759- ) ;
760- }
761- } ;
762- return Ok ( ( & input[ index + 1 ..] , parts) ) ;
765+ } else {
766+ pending_parts. extend ( parts) ;
767+ pending_parts
768+ } ,
769+ ) ) ;
763770 }
764771 '\\' => {
765772 was_escape = true ;
@@ -770,7 +777,7 @@ fn parse_surrounded_expression<'a, TResult>(
770777 }
771778 }
772779
773- ParseError :: fail ( start_input, fail_message )
780+ ParseError :: fail ( start_input, "Expected closing double quote." )
774781}
775782
776783#[ derive( Clone , Copy , PartialEq , Eq ) ]
@@ -941,12 +948,58 @@ fn parse_command_substitution(input: &str) -> ParseResult<SequentialList> {
941948fn parse_backticks_command_substitution (
942949 input : & str ,
943950) -> ParseResult < SequentialList > {
944- parse_surrounded_expression (
945- input,
946- '`' ,
947- "Expected closing backtick." ,
948- parse_sequential_list,
949- )
951+ let start_input = input;
952+ let ( input, _) = ch ( '`' ) ( input) ?;
953+ let mut was_escape = false ;
954+ for ( index, c) in input. char_indices ( ) {
955+ match c {
956+ c if c == '`' && !was_escape => {
957+ let inner_input = & input[ ..index] ;
958+ let inner_input = inner_input. replace ( "\\ `" , "`" ) ;
959+ let parts = match parse_sequential_list ( & inner_input) {
960+ Ok ( ( result_input, parts) ) => {
961+ if !result_input. is_empty ( ) {
962+ return ParseError :: fail (
963+ input,
964+ format ! (
965+ "Failed parsing within backticks. Unexpected character: {}" ,
966+ result_input
967+ ) ,
968+ ) ;
969+ }
970+ parts
971+ }
972+ Err ( err) => {
973+ return ParseError :: fail (
974+ input,
975+ format ! (
976+ "Failed parsing within {}. {}" ,
977+ if c == '`' {
978+ "backticks"
979+ } else {
980+ "double quotes"
981+ } ,
982+ match & err {
983+ ParseError :: Backtrace => "Could not determine expression." ,
984+ ParseError :: Failure ( parse_error_failure) =>
985+ parse_error_failure. message. as_str( ) ,
986+ }
987+ ) ,
988+ ) ;
989+ }
990+ } ;
991+ return Ok ( ( & input[ index + 1 ..] , parts) ) ;
992+ }
993+ '\\' => {
994+ was_escape = true ;
995+ }
996+ _ => {
997+ was_escape = false ;
998+ }
999+ }
1000+ }
1001+
1002+ ParseError :: fail ( start_input, "Expected closing backtick." )
9501003}
9511004
9521005fn parse_subshell ( input : & str ) -> ParseResult < SequentialList > {
@@ -1066,8 +1119,10 @@ mod test {
10661119 . unwrap( )
10671120 . to_string( ) ,
10681121 concat!(
1069- "Failed parsing within double quotes. Expected closing parenthesis for command substitution.\n " ,
1070- " test$(echo testing\" \n " ,
1122+ "Failed parsing command substitution in double quoted string.\n " ,
1123+ "\n " ,
1124+ "Expected closing double quote.\n " ,
1125+ " \" \n " ,
10711126 " ~"
10721127 ) ,
10731128 ) ;
@@ -1528,7 +1583,9 @@ mod test {
15281583 run_test (
15291584 parse_quoted_string,
15301585 r#""asdf`""# ,
1531- Err ( "Failed parsing within double quotes. Expected closing backtick." ) ,
1586+ Err (
1587+ "Failed parsing backticks in double quoted string.\n \n Expected closing backtick." ,
1588+ ) ,
15321589 ) ;
15331590
15341591 run_test_with_end (
@@ -1537,6 +1594,53 @@ mod test {
15371594 Ok ( vec ! [ WordPart :: Text ( "test" . to_string( ) ) ] ) ,
15381595 " asdf" ,
15391596 ) ;
1597+
1598+ run_test (
1599+ parse_quoted_string,
1600+ r#""test $(deno eval 'console.info("test")') test `backticks "test"` test""# ,
1601+ Ok ( vec ! [
1602+ WordPart :: Text ( "test " . to_string( ) ) ,
1603+ WordPart :: Command ( SequentialList {
1604+ items: Vec :: from( [ SequentialListItem {
1605+ is_async: false ,
1606+ sequence: Sequence :: Pipeline ( Pipeline {
1607+ negated: false ,
1608+ inner: PipelineInner :: Command ( Command {
1609+ redirect: None ,
1610+ inner: CommandInner :: Simple ( SimpleCommand {
1611+ env_vars: vec![ ] ,
1612+ args: Vec :: from( [
1613+ Word :: new_word( "deno" ) ,
1614+ Word :: new_word( "eval" ) ,
1615+ Word :: new_string( "console.info(\" test\" )" ) ,
1616+ ] ) ,
1617+ } ) ,
1618+ } ) ,
1619+ } ) ,
1620+ } ] ) ,
1621+ } ) ,
1622+ WordPart :: Text ( " test " . to_string( ) ) ,
1623+ WordPart :: Command ( SequentialList {
1624+ items: Vec :: from( [ SequentialListItem {
1625+ is_async: false ,
1626+ sequence: Sequence :: Pipeline ( Pipeline {
1627+ negated: false ,
1628+ inner: PipelineInner :: Command ( Command {
1629+ redirect: None ,
1630+ inner: CommandInner :: Simple ( SimpleCommand {
1631+ env_vars: vec![ ] ,
1632+ args: Vec :: from( [
1633+ Word :: new_word( "backticks" ) ,
1634+ Word :: new_string( "test" ) ,
1635+ ] ) ,
1636+ } ) ,
1637+ } ) ,
1638+ } ) ,
1639+ } ] ) ,
1640+ } ) ,
1641+ WordPart :: Text ( " test" . to_string( ) ) ,
1642+ ] ) ,
1643+ ) ;
15401644 }
15411645
15421646 #[ test]
0 commit comments