//
//  Misc.
//
WHITESPACE = _{
    " " | "\t" | "\r"
    // A trailing backslash at the end of a line.
    | "\\\n"
}

COMMENT = _{ "#" ~ (!("\n") ~ ANY)* }
word_char = _{
    !(
        "|" | "&" | ";" | " " | "\t" | "\r" | "\n" | "`"
        | "{" | "}" | "<" | ">" | "(" | ")" | "$" | "\"" | "'" | "*" | "?"
    )
    ~ ANY
}
var_name_chars = _{
    "_" | ASCII_ALPHANUMERIC
}

/// --prefix=~/usr
//  ^^^^^^^^
assign_like_prefix_chars = _{
    "-" | "_" | ASCII_ALPHANUMERIC
}

wsnl = _{ (WHITESPACE | COMMENT | newline)+ }
var_name = ${ var_name_chars+ }
assign_like_prefix_var_name = ${ assign_like_prefix_chars+ }
username = ${ (!("/") ~ word_char)+ }
special_var_name = _{ "?" | "$" | "!" | "*" | "@" | "#" | "-" | ASCII_DIGIT }

index = { ("[" ~ expr ~ "]")? }
num = ${ ASCII_DIGIT+ }

//
//  Expr
//
expr = !{ assign ~ (comp_op ~ expr)? }
comp_op = { "==" | "!=" | ">=" | ">" | "<=" | "<" }
assign =
       { (var_name ~ assign_op ~ assign)
       | arith
       }
assign_op = { "=" }
arith = { term ~ (arith_op ~ arith)? }
arith_op = { "+" | "-" }
term = { factor ~ (factor_op ~ term)? }
factor_op = { "*" | "/" }
factor = { sign ~ primary ~ postfix_incdec }
sign = { ("+" | "-")? }
postfix_incdec = { ("++" | "--")? }
primary = _{ num | ("$"? ~ var_name) | param_ex_span |  ("(" ~ expr ~ ")") }

//
//  Word
//
word = ${ assign_like_prefix? ~ (tilde_span | span) ~ span* }
assign_like_prefix = { assign_like_prefix_var_name ~ "=" }
span = _{
    double_quoted_span
    | single_quoted_span
    | any_string_span
    | any_char_span
    | expr_span
    | command_span
    | proc_subst_span
    | backtick_span
    | param_ex_span
    | param_span
    | literal_span
}
escaped_char = { "\\" ~ ANY }
unescaped_char = { word_char }
literal_span = ${ (escaped_char | unescaped_char)+ }
tilde_span = { "~" ~ username? }
any_string_span = { "*" }
any_char_span = { "?" }
command_span = !{ "$(" ~ compound_list ~ ")" }
proc_subst_direction = { "<(" | ">(" }
proc_subst_span = !{ proc_subst_direction ~ compound_list ~ ")" }
backtick_span = !{ "`" ~ compound_list ~ "`" }
expr_span = !{ "$((" ~ expr ~ "))" }
expandable_var_name = { var_name | special_var_name }
length_op ={ "#"? }
param_op = { ":=" | ":-" | "-"  | "=" | "//" | "/" }
param_opt = { param_op ~ word? }
param_ex_span = { "$" ~ "{" ~ length_op ~ expandable_var_name ~ index ~ param_opt? ~ "}" }
param_span = { "$" ~ expandable_var_name }

double_quoted_span = { "\"" ~ double_quoted_span_inner* ~ "\"" }
double_quoted_span_inner = _{
    expr_span
    | backtick_span
    | command_span
    | param_span
    | param_ex_span
    | literal_in_double_quoted_span
}

single_quoted_span = { "'" ~ single_quoted_span_inner* ~ "'" }
single_quoted_span_inner = _{
    literal_in_single_quoted_span
}

literal_in_double_quoted_span = ${
    (
        !("\"" | "\\" | "`" | "$") ~ ANY
        | "\\" ~ ANY
        | WHITESPACE
    )+
}

literal_in_single_quoted_span = ${ ( "\\'"  | !("\'") ~ ANY | WHITESPACE)+ }

//
// Assignment
//
initializer = { array_initializer | string_initializer }
string_initializer = { word }
array_initializer = { ("(" ~ word* ~ ")") }

assignment = { var_name ~ index ~ "=" ~ initializer ~ WHITESPACE? }
assignment_command = { assignment+ }

//
// Simple Command
//
fd = { ASCII_DIGIT* }
redirect_direction = { !("<(" | ">(" | "<<") ~ ("<" | ">>" | ">") }
redirect_to_fd = ${ "&" ~ fd }
redirect = { fd ~ redirect_direction ~ (word | redirect_to_fd) }
word_or_redirect = _{ redirect | word | heredoc }
reserved_word = ${
    (
        "break"
        | "case"
        | "continue"
        | "done"
        | "do"
        | "elif"
        | "else"
        | "esac"
        | "fi"
        | "for"
        | "function"
        | "if"
        | "in"
        | "local"
        | "return"
        | "then"
        | "while"
        | "[["
    ) ~ !(word_char)
}
argv0 = { !reserved_word ~ word }
args = { word_or_redirect* }
assignments = { assignment* }
simple_command = { assignments ~ argv0 ~ args }

heredoc = { "<<" ~ PUSH(heredoc_marker) }
newline = {
    // Entering a here document. Use `PEEK_ALL` instead of `PEEK` to
    // prevent a panic on an empty stack. `(PEEK_ALL)` succeeds if
    // the stack is empty since PEEK_ALLS equals "". `!(PEEK_ALL)` suceeeds
    // otherwise (i.e., there is a here document thus the stack is not empty).
    //
    // TODO: support multiple here documents for example:
    //
    //  $ cat << EOF1; cat << EOF2
    //  hello1
    //  EOF1
    //  hello2
    //  EOF2
    //
    !(PEEK_ALL) ~ "\n" ~ heredoc_body ~ POP
    // No here documents. Just consume a newline character.
    | (PEEK_ALL) ~ normal_newline
}

normal_newline = { "\n" }
heredoc_marker = ${ word_char+ }
heredoc_body = { heredoc_line* }
heredoc_line = { !(PEEK) ~ (word *) ~ "\n" }

//
//  If Command
//
if_command = {
    "if" ~ compound_list ~
    "then" ~ compound_list ~
    elif_part* ~
    else_part? ~
    "fi"
}
elif_part = { "elif" ~ compound_list ~ "then" ~ compound_list }
else_part = { "else" ~ compound_list }

//
//  While Command
//
while_command = {
    "while" ~ compound_list ~ "do" ~ compound_list ~ "done"
}

//
//  For Command
//
word_list = { (!reserved_word ~ word)* }
for_command = {
    "for" ~ var_name ~ "in" ~ word_list ~ (";" | wsnl)+ ~ "do" ~ compound_list ~ "done"
}

arith_for_exprs = { "((" ~ expr ~";" ~ expr ~ ";" ~ expr ~ "))" }
arith_for_command = {
    "for" ~ arith_for_exprs ~ (";" | wsnl)+ ~ "do" ~ compound_list ~ "done"
}

//
//  Case Command
//
patterns = { word ~ ("|" ~ word)* }
case_item = {
    !("esac") ~ patterns ~ ")" ~ compound_list ~ ";;"
}

case_command = {
    "case" ~ word ~ "in" ~ (wsnl | case_item)* ~ "esac"
}

//
//  Function Definition
//
function_definition = {
    ("function")? ~ var_name ~ "()" ~ wsnl? ~ command
}

//
//  Local variable definition
//
local_definition = { "local" ~ (assignment | var_name)+ }

//
//  Return/Break/Continue Command
//
return_command = { "return" ~ num? }
break_command = { "break" }
continue_command = { "continue" }

//
//  Group (`{...}')
//
group = { "{" ~ compound_list ~ "}" }

//
//  Subshell Group (`(...)')
//
subshell_group = { "(" ~ compound_list ~ ")" }

//
//  Condition Command, Extended. (`[[ ]]')
//
cond_ex = { "[[" ~ cond_expr ~ "]]" }
cond_expr =  _{ cond_or }
cond_or = { cond_and ~ ("||" ~ cond_or)? }
cond_and = { cond_term ~ ("&&" ~ cond_and)? }
cond_term = { cond_primary ~ (cond_op ~ cond_term)? }
cond_op = { "==" | "=" | "!=" | "-gt" | "-lt" | "-ge" | "-le" | "-eq" | "-ne" }
cond_primary = { cond_primary_inner }
cond_primary_inner = _{
    word
    | "(" ~ cond_expr ~ ")"
}

//
//  Command
//
command = {
    if_command
    | case_command
    | while_command
    | arith_for_command
    | for_command
    | break_command
    | continue_command
    | return_command
    | local_definition
    | function_definition
    | group
    | subshell_group
    | cond_ex
    | simple_command
    | assignment_command
}

//
//  Pipeline `|'
//
pipeline = { command ~ ((!("||") ~ "|") ~ wsnl? ~ command)* }

//
//  And/Or List (`&&' or `||')
//
and_or_list = { pipeline ~ (and_or_list_sep ~ wsnl? ~ and_or_list)* }
and_or_list_sep = { "||" | "&&" }

//
//  Compound List (`;' or `&')
//
compound_list = { compound_list_inner ~ (compound_list_sep ~ wsnl? ~ compound_list)* }
compound_list_sep = { (!(";;") ~ seq_sep) | !("&&") ~ background | newline }
seq_sep = { ";" }
background = { "&" }
empty_line = { "" }
compound_list_inner = _{ and_or_list | empty_line }

//
//  Script
//
script = _{ SOI ~ compound_list ~ EOI }
