COMMENT = _{ "#" ~ (!"#" ~ ANY)* }
WHITESPACE = _{ " " | "\t" }
id = _{ ASCII_ALPHANUMERIC | "_" }
id_env = _{ ASCII_ALPHA_UPPER | ASCII_DIGIT | "_" }
id_starbase = _{ ALPHABETIC | ASCII_DIGIT | JOIN_CONTROL | "_" | "-" | "." | "/" | "\\" }
// SYNTAX:
// https://www.gnu.org/software/bash/manual/html_node/Definitions.html
// https://fishshell.com/docs/current/language.html#table-of-operators
blank = _{ " " | "\t" }
whitespace = _{ blank | "\n" | "\r" }
boundary = _{ whitespace | EOI }
meta_char = _{ whitespace | "|" | "&" | ";" }
escape_char = _{ "\\" | "/" | "b" | "f" | "n" | "r" | "t" }
fd_char = _{ ASCII_DIGIT | "-" | "out+err" | "o+e" | "out" | "o" | "err" | "e" }
// OPERATORS:
// https://www.gnu.org/software/bash/manual/html_node/Redirections.html
control_operator = {
";"
| "&&"
| "||"
| "--"
}
redirect_operator = {
"<>"
| ">>>"
| ">>"
| "<<<"
| "<<"
| "&>>"
| "&>"
| ">&"
| ">?"
| ">^"
| ">|"
| "<&"
| "<?"
| "<^"
| "<|"
| "|>"
| "|<"
| ">"
| "<"
}
redirect_operator_with_fd = @{
(fd_char ~ redirect_operator ~ fd_char)
| (fd_char ~ redirect_operator)
| (redirect_operator ~ fd_char)
}
operator = _{
control_operator
| redirect_operator_with_fd
| redirect_operator
}
// VALUES:
value_quote_inner = _{
"\\" ~ ((^"x" | ^"u") ~ ASCII_HEX_DIGIT+)
}
value_double_quote_inner = _{
!("\"" | "\\") ~ ANY
| "\\" ~ ("\"" | escape_char)
| value_quote_inner
}
value_double_quote = @{ "$"? ~ "\"" ~ value_double_quote_inner* ~ "\"" }
value_single_quote_inner = _{
!("'" | "\\") ~ ANY
| "\\" ~ ("'" | escape_char)
| value_quote_inner
}
value_single_quote = @{ "$"? ~ "'" ~ value_single_quote_inner* ~ "'" }
value_murex_brace_quote = @{ "%(" ~ (!")" ~ ANY)+ ~ ")" }
value_nu_raw_quote = @{ "r#'" ~ value_single_quote_inner* ~ "'#" }
value_unquoted_inner = _{ !(meta_char | operator) ~ ANY }
value_unquoted = @{ value_unquoted_inner+ }
value = _{ value_murex_brace_quote | value_nu_raw_quote | value_double_quote | value_single_quote | value_unquoted }
value_dynamic = _{ expansion | substitution | value }
// EXPANSIONS:
// https://www.gnu.org/software/bash/manual/html_node/Arithmetic-Expansion.html
// https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
arithmetic_expansion = { "$((" ~ (!"))" ~ ANY)+ ~ "))" }
brace_expansion = { "{" ~ (!"}" ~ ANY)+ ~ "}" }
parameter_expansion = { "${" ~ (!"}" ~ ANY)+ ~ "}" ~ boundary }
tilde_expansion = { "~" ~ ("+" | "-")? ~ ASCII_DIGIT? ~ ANY* }
moon_token_expansion = @{ "@" ~ id_starbase+ ~ "(" ~ id_starbase+ ~ ")" }
expansion = _{ arithmetic_expansion | parameter_expansion | brace_expansion | tilde_expansion | moon_token_expansion }
// SUBSTITUTION:
// https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html
// https://www.gnu.org/software/bash/manual/html_node/Process-Substitution.html
command_substitution = { "$(" ~ (!")" ~ ANY)+ ~ ")" | "!(" ~ (!")" ~ ANY)+ ~ ")" | "(" ~ (!")" ~ ANY)+ ~ ")" | "`" ~ (!"`" ~ ANY)+ ~ "`" }
process_substitution = { "<(" ~ (!")" ~ ANY)+ ~ ")" | ">(" ~ (!")" ~ ANY)+ ~ ")" }
substitution = _{ process_substitution | command_substitution }
// ARGUMENTS:
env_var_namespace = { ^"$e:" | ^"$env::" | ^"$env:" | ^"$env." }
env_var_name = @{ id_env+ }
env_var = ${ env_var_namespace? ~ env_var_name ~ "=" ~ value_dynamic }
flag_group = @{ "-" ~ ASCII_ALPHA{2, } }
flag = @{ "-" ~ ASCII_ALPHA }
option = @{ ("&" | "--") ~ ASCII_ALPHA ~ (id | "-" | ".")* }
option_with_value = ${ option ~ "=" ~ value_dynamic }
param_special = @{ "$" ~ ("#" | "*" | "@" | "?" | "-" | "$" | "!") }
param = @{ ("$" | "@") ~ (((ASCII_ALPHA | "_") ~ id*) | ASCII_DIGIT+) ~ boundary }
argument = _{ expansion | substitution | env_var | param_special | param | flag_group | flag | option_with_value | option | value }
// COMMAND LINE:
// https://www.gnu.org/software/bash/manual/html_node/Shell-Commands.html
command = { argument+ }
command_terminator = { ";" | "&-" | "&!" | "&" | "2>&1" | "\n" | "--" }
command_list = { command ~ (operator ~ command)* ~ command_terminator? }
pipeline_negated = { "!" }
pipeline_operator = { "|&" | "&|" | "^|" | "|" | "->" | "=>" | "?" }
pipeline = { pipeline_negated? ~ command_list ~ (pipeline_operator ~ command_list)* }
// PARSERS:
command_line = _{ SOI ~ pipeline ~ EOI }
unquoted_expansion_or_substitution = _{ SOI ~ (expansion | substitution) ~ EOI }