Skip to content

Commit

Permalink
Merge branch 'main' into 16-prompt
Browse files Browse the repository at this point in the history
  • Loading branch information
Hofer-Julian authored Sep 6, 2024
2 parents f41c9ed + d2a8cd6 commit 7564639
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 42 deletions.
46 changes: 46 additions & 0 deletions .github/workflows/rust-linting.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: Rust Linting

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
fmt:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true

- name: Run cargo fmt
run: cargo fmt -- --check

clippy:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true

- name: Run cargo clippy
run: cargo clippy --all-targets --workspace -- -D warnings
30 changes: 30 additions & 0 deletions .github/workflows/rust-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Rust

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
tests:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true

- name: Run tests
run: cargo test --manifest-path crates/shell/Cargo.toml --all-targets
72 changes: 36 additions & 36 deletions crates/deno_task_shell/src/grammar.pest
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ QUOTED_PENDING_WORD = ${ (
QUOTED_CHAR
)* }

UNQUOTED_ESCAPE_CHAR = ${ ("\\" ~ "$" | "$" ~ !"(" ~ !VARIABLE) | "\\" ~ ("`" | "\"" | "(" | ")")* }
UNQUOTED_ESCAPE_CHAR = ${ ("\\" ~ "$" | "$" ~ !"(" ~ !VARIABLE) | "\\" ~ (" " | "`" | "\"" | "(" | ")")* }
QUOTED_ESCAPE_CHAR = ${ "\\" ~ "$" | "$" ~ !"(" ~ !VARIABLE | "\\" ~ ("`" | "\"" | "(" | ")" | "'")* }

UNQUOTED_CHAR = ${ ("\\" ~ " ") | !(WHITESPACE | "~" | "(" | ")" | "{" | "}" | "<" | ">" | "|" | "&" | ";" | "\"" | "'") ~ ANY }
UNQUOTED_CHAR = ${ ("\\" ~ " ") | !("~" | "(" | ")" | "{" | "}" | "<" | ">" | "|" | "&" | ";" | "\"" | "'" | "$") ~ ANY }
QUOTED_CHAR = ${ !"\"" ~ ANY }

VARIABLE = ${ (ASCII_ALPHANUMERIC | "_")+ }
Expand Down Expand Up @@ -64,7 +64,7 @@ EXIT_STATUS = ${ "$?" }
// Operators
OPERATOR = _{
AND_IF | OR_IF | DSEMI | DLESS | DGREAT | LESSAND | GREATAND | LESSGREAT | DLESSDASH | CLOBBER |
"(" | ")" | "{" | "}" | ";" | "&" | "|" | "<" | ">" | "!"
"(" | ")" | "{" | "}" | ";" | "&" | "|" | "<" | ">"
}

// Reserved words
Expand Down Expand Up @@ -96,12 +96,12 @@ RESERVED_WORD = _{

// Main grammar rules
complete_command = { list? ~ (separator+ ~ list)* ~ separator? }
list = { and_or ~ (separator_op ~ and_or)* ~ separator_op? }
and_or = { (pipeline | ASSIGNMENT_WORD+) ~ ((AND_IF | OR_IF) ~ linebreak ~ and_or)? }
pipeline = { Bang? ~ pipe_sequence }
pipe_sequence = { command ~ ((StdoutStderr | Stdout) ~ linebreak ~ pipe_sequence)? }
list = !{ and_or ~ (separator_op ~ and_or)* ~ separator_op? }
and_or = !{ (pipeline | ASSIGNMENT_WORD+) ~ ((AND_IF | OR_IF) ~ linebreak ~ and_or)? }
pipeline = !{ Bang? ~ pipe_sequence }
pipe_sequence = !{ command ~ ((StdoutStderr | Stdout) ~ linebreak ~ pipe_sequence)? }

command = {
command = !{
simple_command |
compound_command ~ redirect_list? |
function_definition
Expand All @@ -117,80 +117,80 @@ compound_command = {
until_clause
}

subshell = { "(" ~ compound_list ~ ")" }
compound_list = { (newline_list? ~ term ~ separator?)+ }
term = { and_or ~ (separator ~ and_or)* }
subshell = !{ "(" ~ compound_list ~ ")" }
compound_list = !{ (newline_list? ~ term ~ separator?)+ }
term = !{ and_or ~ (separator ~ and_or)* }

for_clause = {
For ~ name ~ linebreak ~
(linebreak ~ In ~ wordlist? ~ sequential_sep)? ~
linebreak ~ do_group
}

case_clause = {
case_clause = !{
Case ~ UNQUOTED_PENDING_WORD ~ linebreak ~
linebreak ~ In ~ linebreak ~
(case_list | case_list_ns)? ~
Esac
}

case_list = {
case_list = !{
case_item+
}

case_list_ns = {
case_list_ns = !{
case_item_ns+
}

case_item = {
case_item = !{
"("? ~ pattern ~ ")" ~ (compound_list | linebreak) ~ DSEMI ~ linebreak
}

case_item_ns = {
case_item_ns = !{
"("? ~ pattern ~ ")" ~ compound_list? ~ linebreak
}

pattern = {
pattern = !{
(Esac | UNQUOTED_PENDING_WORD) ~ ("|" ~ UNQUOTED_PENDING_WORD)*
}

if_clause = {
if_clause = !{
If ~ compound_list ~
Then ~ compound_list ~
else_part? ~
Fi
}

else_part = {
else_part = !{
Elif ~ compound_list ~ Then ~ else_part |
Else ~ compound_list
}

while_clause = { While ~ compound_list ~ do_group }
until_clause = { Until ~ compound_list ~ do_group }
while_clause = !{ While ~ compound_list ~ do_group }
until_clause = !{ Until ~ compound_list ~ do_group }

function_definition = { fname ~ "(" ~ ")" ~ linebreak ~ function_body }
function_body = { compound_command ~ redirect_list? }
function_definition = !{ fname ~ "(" ~ ")" ~ linebreak ~ function_body }
function_body = !{ compound_command ~ redirect_list? }

fname = @{ RESERVED_WORD | NAME | ASSIGNMENT_WORD | UNQUOTED_PENDING_WORD }
name = @{ NAME }

brace_group = { Lbrace ~ compound_list ~ Rbrace }
do_group = { Do ~ compound_list ~ Done }
brace_group = !{ Lbrace ~ compound_list ~ Rbrace }
do_group = !{ Do ~ compound_list ~ Done }

simple_command = {
cmd_prefix ~ WHITESPACE* ~ cmd_word ~ WHITESPACE* ~ cmd_suffix? |
(!cmd_prefix ~ cmd_name ~ WHITESPACE* ~ cmd_suffix?)
simple_command = !{
cmd_prefix ~ cmd_word ~ cmd_suffix? |
(!cmd_prefix ~ cmd_name ~ cmd_suffix?)
}

cmd_prefix = { (io_redirect | ASSIGNMENT_WORD)+ }
cmd_suffix = { (io_redirect | QUOTED_WORD | UNQUOTED_PENDING_WORD)+ }
cmd_prefix = !{ (io_redirect | ASSIGNMENT_WORD)+ }
cmd_suffix = !{ (io_redirect | UNQUOTED_PENDING_WORD)+ }
cmd_name = @{ (RESERVED_WORD | UNQUOTED_PENDING_WORD) }
cmd_word = @{ (ASSIGNMENT_WORD | UNQUOTED_PENDING_WORD) }

redirect_list = { io_redirect+ }
io_redirect = { (IO_NUMBER | AMPERSAND)? ~ (io_file | io_here) }
io_file = {
redirect_list = !{ io_redirect+ }
io_redirect = !{ (IO_NUMBER | AMPERSAND)? ~ (io_file | io_here) }
io_file = !{
LESS ~ filename |
GREAT ~ filename |
DGREAT ~ filename |
Expand All @@ -200,16 +200,16 @@ io_file = {
CLOBBER ~ filename
}
filename = _{ FILE_NAME_PENDING_WORD }
io_here = { (DLESS | DLESSDASH) ~ here_end }
io_here = !{ (DLESS | DLESSDASH) ~ here_end }
here_end = @{ ("\"" ~ UNQUOTED_PENDING_WORD ~ "\"") | UNQUOTED_PENDING_WORD }

newline_list = _{ NEWLINE+ }
linebreak = _{ NEWLINE* }
separator_op = { "&" | ";" }
separator = _{ separator_op ~ linebreak | newline_list }
sequential_sep = { ";" ~ linebreak | newline_list }
sequential_sep = !{ ";" ~ linebreak | newline_list }

wordlist = { UNQUOTED_PENDING_WORD+ }
wordlist = !{ UNQUOTED_PENDING_WORD+ }

// Entry point
FILE = { SOI ~ complete_command ~ EOI }
51 changes: 45 additions & 6 deletions crates/deno_task_shell/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,8 @@ struct ShellParser;
pub fn parse(input: &str) -> Result<SequentialList> {
let mut pairs = ShellParser::parse(Rule::FILE, input)?;

// println!("pairs: {:?}", pairs);

parse_file(pairs.next().unwrap())
}

Expand Down Expand Up @@ -402,11 +404,13 @@ fn parse_compound_list(
Rule::newline_list => {
// Ignore newlines
}
Rule::separator_op => {
if let Some(last) = items.last_mut() {
last.is_async = item.as_str() == "&";
}
}
_ => {
return Err(anyhow::anyhow!(
"Unexpected rule in compound_list: {:?}",
item.as_rule()
));
anyhow::bail!("Unexpected rule in compound_list: {:?}", item.as_rule());
}
}
}
Expand Down Expand Up @@ -494,14 +498,24 @@ fn parse_shell_var(pair: Pair<Rule>) -> Result<Sequence> {
}

fn parse_pipeline(pair: Pair<Rule>) -> Result<Sequence> {
let pipeline_str = pair.as_str();
let mut inner = pair.into_inner();

// Check if the first element is Bang (negation)
let first = inner
.next()
.ok_or_else(|| anyhow::anyhow!("Expected pipeline content"))?;
let (negated, pipe_sequence) = if first.as_rule() == Rule::Bang {
// If it's Bang, the next element should be the pipe_sequence
// If it's Bang, check for whitespace
if pipeline_str.len() > 1
&& !pipeline_str[1..2].chars().next().unwrap().is_whitespace()
{
anyhow::bail!(
"Perhaps you meant to add a space after the exclamation point to negate the command?\n ! {}",
pipeline_str
);
}
// Get the actual pipe sequence after whitespace
let pipe_sequence = inner.next().ok_or_else(|| {
anyhow::anyhow!("Expected pipe sequence after negation")
})?;
Expand Down Expand Up @@ -669,13 +683,38 @@ fn parse_word(pair: Pair<Rule>) -> Result<Word> {
for part in pair.into_inner() {
match part.as_rule() {
Rule::EXIT_STATUS => parts.push(WordPart::Variable("?".to_string())),
Rule::UNQUOTED_ESCAPE_CHAR | Rule::UNQUOTED_CHAR => {
Rule::UNQUOTED_CHAR => {
if let Some(WordPart::Text(ref mut text)) = parts.last_mut() {
text.push(part.as_str().chars().next().unwrap());
} else {
parts.push(WordPart::Text(part.as_str().to_string()));
}
}
Rule::UNQUOTED_ESCAPE_CHAR => {
let mut chars = part.as_str().chars();
let mut escaped_char = String::new();
while let Some(c) = chars.next() {
match c {
'\\' => {
let next_char = chars.next().unwrap_or('\0');
escaped_char.push(next_char);
}
'$' => {
escaped_char.push(c);
break;
}
_ => {
escaped_char.push(c);
break;
}
}
}
if let Some(WordPart::Text(ref mut text)) = parts.last_mut() {
text.push_str(&escaped_char);
} else {
parts.push(WordPart::Text(escaped_char));
}
}
Rule::SUB_COMMAND => {
let command =
parse_complete_command(part.into_inner().next().unwrap())?;
Expand Down

0 comments on commit 7564639

Please sign in to comment.