diff --git a/crates/deno_task_shell/src/grammar.pest b/crates/deno_task_shell/src/grammar.pest index 600cedd..69c2f51 100644 --- a/crates/deno_task_shell/src/grammar.pest +++ b/crates/deno_task_shell/src/grammar.pest @@ -1,7 +1,6 @@ -// grammar.pest - // Whitespace and comments -WHITESPACE = _{ " " | "\t" | ("\\" ~ WHITESPACE* ~ NEWLINE) } +WHITESPACE = _{ " " | "\t" | ESCAPED_NEWLINE } +ESCAPED_NEWLINE = _{ "\\" ~ NEWLINE } COMMENT = _{ "#" ~ (!NEWLINE ~ ANY)* } // Basic tokens @@ -25,11 +24,13 @@ QUOTED_PENDING_WORD = ${ ( QUOTED_ESCAPE_CHAR | SUB_COMMAND | ("$" ~ VARIABLE) | - QUOTED_CHAR + QUOTED_CHAR | + NEWLINE | + ESCAPED_NEWLINE )* } UNQUOTED_ESCAPE_CHAR = ${ ("\\" ~ "$" | "$" ~ !"(" ~ !VARIABLE) | "\\" ~ (" " | "`" | "\"" | "(" | ")")* } -QUOTED_ESCAPE_CHAR = ${ "\\" ~ "$" | "$" ~ !"(" ~ !VARIABLE | "\\" ~ ("`" | "\"" | "(" | ")" | "'")* } +QUOTED_ESCAPE_CHAR = @{ "\\" ~ ("$" | "`" | "\"" | "(" | ")" | "'" | "\n" | "\r\n") } UNQUOTED_CHAR = ${ ("\\" ~ " ") | !("(" | ")" | "{" | "}" | "<" | ">" | "|" | "&" | ";" | "\"" | "'" | "$") ~ ANY } QUOTED_CHAR = ${ !"\"" ~ ANY } @@ -38,12 +39,11 @@ VARIABLE = ${ (ASCII_ALPHANUMERIC | "_")+ } SUB_COMMAND = { "$(" ~ complete_command ~ ")" } DOUBLE_QUOTED = @{ "\"" ~ QUOTED_PENDING_WORD ~ "\"" } -SINGLE_QUOTED = @{ "'" ~ (!"'" ~ ANY)* ~ "'" } +SINGLE_QUOTED = @{ "'" ~ (!"'" ~ (NEWLINE | ESCAPED_NEWLINE | ANY))* ~ "'" } NAME = ${ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } ASSIGNMENT_WORD = { NAME ~ "=" ~ UNQUOTED_PENDING_WORD? } IO_NUMBER = @{ ASCII_DIGIT+ } - // Special tokens AND_IF = { "&&" } OR_IF = { "||" } @@ -60,7 +60,6 @@ CLOBBER = { ">|" } AMPERSAND = { "&" } EXIT_STATUS = ${ "$?" } - // Operators OPERATOR = _{ AND_IF | OR_IF | DSEMI | DLESS | DGREAT | LESSAND | GREATAND | LESSGREAT | DLESSDASH | CLOBBER | @@ -203,8 +202,8 @@ filename = _{ FILE_NAME_PENDING_WORD } io_here = !{ (DLESS | DLESSDASH) ~ here_end } here_end = @{ ("\"" ~ UNQUOTED_PENDING_WORD ~ "\"") | UNQUOTED_PENDING_WORD } -newline_list = _{ NEWLINE+ } -linebreak = _{ NEWLINE* } +newline_list = _{ (NEWLINE | ESCAPED_NEWLINE)+ } +linebreak = _{ (NEWLINE | ESCAPED_NEWLINE)* } separator_op = { "&" | ";" } separator = _{ separator_op ~ linebreak | newline_list } sequential_sep = !{ ";" ~ linebreak | newline_list } diff --git a/crates/deno_task_shell/src/parser.rs b/crates/deno_task_shell/src/parser.rs index 27919df..42f2629 100644 --- a/crates/deno_task_shell/src/parser.rs +++ b/crates/deno_task_shell/src/parser.rs @@ -677,7 +677,6 @@ fn parse_subshell(pair: Pair) -> Result { fn parse_word(pair: Pair) -> Result { let mut parts = Vec::new(); - match pair.as_rule() { Rule::UNQUOTED_PENDING_WORD => { for part in pair.into_inner() { @@ -748,6 +747,7 @@ fn parse_word(pair: Pair) -> Result { for part in pair.into_inner() { match part.as_rule() { Rule::UNQUOTED_ESCAPE_CHAR => { + println!("part: {:?}", part); if let Some(WordPart::Text(ref mut text)) = parts.last_mut() { text.push(part.as_str().chars().next().unwrap()); } else { @@ -803,10 +803,14 @@ fn parse_quoted_word(pair: Pair) -> Result { match part.as_rule() { Rule::EXIT_STATUS => parts.push(WordPart::Text("$?".to_string())), Rule::QUOTED_ESCAPE_CHAR => { + let val = &part.as_str()[1..]; + if val == "\n" { + continue; + } if let Some(WordPart::Text(ref mut s)) = parts.last_mut() { - s.push_str(part.as_str()); + s.push_str(val); } else { - parts.push(WordPart::Text(part.as_str().to_string())); + parts.push(WordPart::Text(val.to_string())); } } Rule::SUB_COMMAND => { @@ -990,7 +994,6 @@ mod test { .map_err(|e| anyhow::Error::msg(e.to_string()))? .next() .unwrap(); - // println!("pairs: {:?}", pairs); parse_complete_command(pairs) }; diff --git a/scripts/multiline.sh b/scripts/multiline.sh index b49ca34..516f999 100644 --- a/scripts/multiline.sh +++ b/scripts/multiline.sh @@ -2,4 +2,6 @@ echo "foo \ bla" echo "foo - bla" \ No newline at end of file + bla" + +echo "foo \" bar" \ No newline at end of file