basic_escape_char = @{ "\\\\" | "\\'" } escape_char = @{ "\\" ~ ANY } bareword_char = @{ escape_char | !("|" | ";" | "\"" | "'" | "$" | "{" | "(" | ")" | WHITESPACE | COMMENT) ~ ANY } single_string_char = @{ basic_escape_char | (!"'" ~ ANY) } double_string_char = @{ escape_char | (!("\"" | "$") ~ ANY) } var = @{ ("$" ~ XID_START ~ XID_CONTINUE*) | ("$" ~ ("?" | "$" | "*" | ASCII_DIGIT)) | ("${" ~ (!"}" ~ ANY)+ ~ "}") } bareword = @{ bareword_char+ } single_string = @{ single_string_char+ } double_string = @{ double_string_char+ } alternation_bareword_char = @{ !("," | "}") ~ bareword_char } alternation_bareword = @{ alternation_bareword_char+ } alternation_word_part = ${ var | alternation_bareword | "'" ~ single_string? ~ "'" | "\"" ~ (var | double_string)* ~ "\"" } alternation_word = ${ alternation_word_part* } alternation = ${ "{" ~ alternation_word ~ ("," ~ alternation_word)* ~ "}" } substitution = ${ "$(" ~ w? ~ commands ~ w? ~ ")"} word_part = ${ alternation | substitution | var | bareword | "'" ~ single_string? ~ "'" | "\"" ~ (substitution | var | double_string)* ~ "\"" } word = ${ word_part+ } redir_prefix = @{ ("in" | "out" | "err" | ASCII_DIGIT*) ~ (">>" | ">" | "<") } redirect = ${ redir_prefix ~ w? ~ word } exe = ${ (redirect | word) ~ (w ~ (redirect | word))* } subshell = ${ "(" ~ w? ~ commands ~ w? ~ ")" ~ (w? ~ redirect ~ (w ~ redirect)*)? } list = ${ word ~ (w ~ word)* } pipeline = ${ (subshell | exe) ~ (w? ~ "|" ~ w? ~ (subshell | exe))* } control_if = ${ "if" ~ w ~ pipeline } control_while = ${ "while" ~ w ~ pipeline } control_for = ${ "for" ~ w ~ bareword ~ w ~ "in" ~ w ~ list } control_else = ${ "else" ~ (w ~ "if" ~ w ~ pipeline)? } control_end = ${ "end" } control = ${ control_if | control_while | control_for | control_else | control_end } command = ${ control | pipeline } commands = ${ command ~ (w? ~ ";" ~ w? ~ command)* } line = ${ SOI ~ w? ~ commands ~ w? ~ EOI } w = _{ (WHITESPACE | COMMENT)+ } WHITESPACE = _{ (" " | "\t" | "\n") } COMMENT = _{ "#" ~ ANY* }