# Single quoted string
syntax .sh-sq

state string
    char "'" END string
    eat this

# Escape inside double quotes
syntax .sh-dq-escape

state special
    char '"$\`'"\n" END special
    recolor string 1
    eat END

# Escape outside double quotes
syntax .sh-command-escape

state special
    eat END special

# ${var}, $var, $1 etc. ($ already eaten)
syntax .sh-var

state variable
    char '{' brace1
    char '*@#?$!0-9-' END variable
    char a-zA-Z_ name
    noeat END

state name variable
    char a-zA-Z_0-9 this
    noeat END

state brace1 variable
    char } END variable
    str '${' brace2
    eat this

state brace2 variable
    char } brace1 variable
    str '${' brace3
    eat this

state brace3 variable
    char } brace2 variable
    str '${' END error
    eat this

# $((expr)) "$((" already eaten
syntax .sh-expression

state expression special
    # FIXME: there can be almost anything in $(( ... ))
    str '))' END special
    eat this

# Double quoted string. $(cmd) and $((expr) not properly parsed
syntax .sh-simple-dq

state string
    char \" END string
    char \\ .sh-dq-escape:this
    char \$ special
    eat this

state special
    char '(' paren
    recolor variable 1
    noeat .sh-var:string

state paren special
    char '(' .sh-expression:string
    noeat command

state command
    char ')' string special
    char -n "\n" this
    noeat string

# $(cmd)
syntax .sh-pcommand

state command
    char ')' END special
    char \' .sh-sq:this
    char \" .sh-simple-dq:this
    char \\ .sh-command-escape:this
    char \$ .sh-var:this
    eat this

# $var, $(command), $((expression))
syntax .sh-dollar

state special
    char '(' paren
    recolor variable 1
    noeat .sh-var:END

state paren special
    char '(' .sh-expression:END
    noeat .sh-pcommand:END

# `command`
syntax .sh-backtick

state special
    # NOTE: you can't put "`" inside `...`, it's not possible
    char \' .sh-sq:this
    char \" .sh-simple-dq:this
    char \\ .sh-command-escape:this
    char \$ .sh-var:this
    char ` END special
    eat this

syntax .sh-dq

# Double quoted string
state string
    char '"' END string
    char \\ .sh-dq-escape:this
    char ` .sh-backtick:this
    char \$ .sh-dollar:this
    eat this

# Skip redirect etc. after <<EOF
# e.g. cat <<EOF > /tmp/file
syntax .sh-heredoc-skip-line

state start
    char "\n" END
    eat this code

# Common to <<"EOF" and <<'EOF'
syntax .sh-heredoc-common

state start
    noeat .sh-heredoc-skip-line:bol

state bol string
    heredocend match
    noeat string

state string
    char "\n" bol
    eat this

state match special
    char "\n" END
    # Length clamped to bol
    recolor string 1000
    noeat string

# << EOF
syntax .sh-heredoc

state start code
    noeat .sh-heredoc-skip-line:bol

state bol string
    heredocend match
    noeat string

state string
    char \\ esc
    char "\n" bol
    char \` .sh-backtick:this
    char \$ .sh-dollar:this
    eat this

state esc special
    char "\n\\`$" string special
    recolor error 1
    noeat string

state match special
    char "\n" END
    # Length clamped to bol
    recolor string 1000
    noeat string

# << "EOF"
syntax .sh-heredoc-dq

state start string
    char -n \" END error
    eat .sh-heredoc-common:END

# << 'EOF'
syntax .sh-heredoc-sq

state start string
    char -n \' END error
    eat .sh-heredoc-common:END

# Commands can be at beginning of line or after any of && || ; & | { ` $(
# also beginning of line can contain ( after which command is allowed
syntax sh

state start code
    char -b a-zA-Z0-9_/.- command
    char "\t " this
    char '({' this special
    noeat args

state command code
    # Eat all garbage to distinguish test/run.sh from builtin test
    char -b a-zA-Z0-9_/.- this
    inlist keyword1 start keyword
    inlist keyword2 args keyword
    inlist builtin args
    bufis for for
    char \( function
    noeat args

state function special
    # Don't try to validate syntax because there can be comments
    # between "()" and "{"
    char \) start special
    eat start error

state for keyword
    char " \t" this
    char a-zA-Z_ forvar
    noeat forerror

state forvar ident
    char a-zA-Z_0-9 this
    char " \t" forspace
    noeat forerror

state forspace code
    char " \t" this
    str in in
    noeat forerror

state in keyword
    char " \t" args
    noeat forerror

state forerror code
    char a-zA-Z_0-9 args error
    noeat args

state args code
    char # comment
    char "'" .sh-sq:this
    char '"' .sh-dq:this
    char \\ .sh-command-escape:this
    char ` .sh-backtick:this
    char \$ .sh-dollar:this
    char "\n" start
    # && is same as two &, same for |. does not matter
    char ';&|' start special
    # This might be error
    char ()} this special
    char < lt
    eat this

state comment
    char "\n" start
    eat this

state lt special
    char < lt2
    noeat args

state lt2 special
    char < lt3
    char -- - heredoc-start special
    noeat heredoc-start

state lt3 special
    # cat <<<"string $var"
    # cat <<< $(echo $USER)
    noeat args

state heredoc-start special
    char " \t" this
    char \" heredoc-dq
    char \' heredoc-sq
    char "\n" start error
    noeat heredoc

state heredoc-dq string
    char -bn "\"\n" this
    heredocbegin .sh-heredoc-dq start

state heredoc-sq string
    char -bn "\'\n" this
    heredocbegin .sh-heredoc-sq start

state heredoc special
    char -bn " \t\n;|>" heredoc
    heredocbegin .sh-heredoc start

# Command allowed after these
list keyword1 \
    do else elif exec if then until while

# Command not allowed after these
list keyword2 \
    break case continue done esac exit export fi function in return shift

list builtin \
    alias bg bind cd caller command compgen complete test kill source \
    time declare typeset dirs disown echo enable eval fc fg getopts hash \
    help history jobs let local logout popd printf pushd pwd read \
    readonly shopt set trap type ulimit umask unalias unset wait "."

default special expression
