#!/bin/bash


declare -A PowscriptLib

powscript_require() {
  local lib="$1"
  local req="PowscriptLib[$lib]=$(printf '%q\n' "$(cat "$PowscriptLibDirectory/$lib.pow")")"

  ${RequireOp-eval} "$req"
}
# FILE: "$PowscriptLibDirectory/require.bash"
# powscript_source
#
# include source relatively from the powscript source code directory

powscript_source() {
  source "$PowscriptSourceDirectory/$1"
}


# printf_seq $start $end $format
#
# calls printf on each element of $(seq $start $end)
# the specifier %N is used to reference the current number.

printf_seq() {
  local start="$1" end="$2" format="$3"
  for n in $(seq $start $end); do
    printf "${format//%N/$n}"
  done
}

pop() {
  setvar "$2" "${*:$(($1 + 3))}"
}


# setvar $varname $value
#
# dynamic variable assignation

setvar() {
  if [ -n "$1" ]; then
    printf -v "$1" '%s' "$2"
  else
    echo "$2"
  fi
}

# noshadow name ${argnumber:-0} ${varnumber:-1}
#
# wrap the function so that the name of the out variables
# (assumed to be the last arguments) do not conflict
# with the local variables declared within the function.
#
# Passing @ instead of a number means the number of arguments
# is variable. You may have a argnumber or varnumber be @, but
# not both.

ClearShadowingCounter=0

noshadow() {
  local name="$1"
  local argnumber="${2:-0}"
  local varnumber="${3:-1}"
  local arguments set_variables intermediary_variables intermediary_definition
  local prefix="__noshadow_${ClearShadowingCounter}_"

  case $argnumber in
    '@')
      arguments="\"\${@:1:\$((\$# - $varnumber))}\""

      set_variables="shift \$((\$# - $varnumber))
        $(printf_seq 1 $varnumber\
          "setvar \"\$%N\" \"\$$prefix%N\"\n")"

      intermediary_variables="$(printf_seq 1 $varnumber "$prefix%N")"
      intermediary_definition="local $intermediary_variables"
      ;;
    *)
      arguments="$(printf_seq 1 $argnumber '"$%N" ')"

      case $varnumber in
        '@')
          local argshift="shift $((argnumber-1))"
          [ $argnumber = 0 ] && argshift=

          set_variables="#
          $argshift
          for ${prefix}n in \$(seq $((argnumber+1)) \$#); do
            setvar \"\$$((argnumber+1))\" \"\${!${prefix}all[\$${prefix}n]}\"
            shift
          done"

          intermediary_variables="\"\${${prefix}all[@]}\""
          intermediary_definition="declare -A ${prefix}all
          for ${prefix}n in \$(seq $((argnumber+1)) \$#); do
            ${prefix}all[\$${prefix}n]=${prefix}\$${prefix}n
          done"
          ;;
        *)
          set_variables="$(printf_seq $((argnumber+1)) $((varnumber+argnumber)) "setvar \"\$%N\" \"\$$prefix%N\"\n")"
          intermediary_variables="$(printf_seq $((argnumber+1)) $((varnumber+argnumber)) "$prefix%N ")"
          intermediary_definition="local $intermediary_variables"
          ;;
      esac
      ;;
  esac

  ${ShadowingOp-eval} "
__shadowing_$name() $(${ShadowingGetFunc-declare} -f $name | tail -n +2)

$name() {
  if [ -z \${$prefix+x} ]; then
    local $prefix
    $intermediary_definition
  fi
  __shadowing_$name $arguments $intermediary_variables
  $set_variables
}
"
  ClearShadowingCounter=$(($ClearShadowingCounter+1))
}

# FILE: "$PowscriptSourceDirectory/helper.bash"

#!/bin/bash

declare -gA Stream

stream:init() {
  Stream[line]=""
  Stream[index]=0
  Stream[linenumber]=0
  Stream[eof]=false

  stream:next-character
}

stream:get-character() {
  if stream:end; then
    (>&2 echo 'Tried to get an character after the end of file!')
    exit 1
  elif [ ${Stream[index]} = ${#Stream[line]} ]; then
    setvar "$1" $'\n'
  else
    setvar "$1" "${Stream[line]:${Stream[index]}:1}"
  fi
}

stream:next-character() {
  local line
  if [ ${Stream[index]} = ${#Stream[line]} ]; then
    if IFS='' read -r line || [ -n "$line" ]; then
      Stream[line]="$line"
      Stream[index]=0
      Stream[linenumber]=$((${Stream[linenumber]}+1))
    else
      Stream[eof]=true
    fi
  else
    Stream[index]=$((${Stream[index]}+1))
  fi
}

stream:require-string() {
  local req="$1" str="" c
  while [ ${#req} -gt ${#str} ]; do
    if stream:end; then
      >&2 echo "ERROR: end of input before the required string '$req' was found"
    fi
    stream:get-character c
    stream:next-character
    str+=$c
  done
  if [ "$str" = "$req" ]; then
    return 0
  else
    >&2 echo "ERROR:$(
      stream:get-line-number):$(
      stream:get-collumn):Found '$str' found instead of the required '$req'"
    return 1
  fi
}


stream:register-escaped-newline() {
  Stream[line]=" ${Stream[line]}"
  Stream[index]=$((${Stream[index]}+1))
}


__shadowing_stream:get-rest-of-line() {
  local line collumn out="$1"
  line="${Stream[line]}"
  stream:get-collumn collumn
  setvar "$out" "${line:$collumn}"
}

stream:get-rest-of-line() {
  if [ -z ${__noshadow_0_+x} ]; then
    local __noshadow_0_
    local __noshadow_0_1
  fi
  __shadowing_stream:get-rest-of-line  __noshadow_0_1
  setvar "$1" "$__noshadow_0_1"
}



stream:end() {
  ${Stream[eof]}
}

stream:line-start() {
  [ ${Stream[index]} = 0 ]
}

stream:jump-to-collumn() {
  Stream[index]=$1
}

stream:get-line-number() {
  setvar "$1" ${Stream[linenumber]}
}

stream:get-collumn() {
  setvar "$1" ${Stream[index]}
}

# FILE: lexer/stream.bash
declare -gA Tokens

Tokens[index]=0
Tokens[length]=0

TokenMark=0


__shadowing_token:store() {
  local idvar="$8"
  local index="${Tokens[length]}"

  Tokens[value-$index]="$1"
  Tokens[class-$index]="$2"
  Tokens[glued-$index]="$3"
  Tokens[linenumber_start-$index]="$4"
  Tokens[linenumber_end-$index]="$5"
  Tokens[collumn_start-$index]="$6"
  Tokens[collumn_end-$index]="$7"
  Tokens[index]=$(($index+1))
  Tokens[length]=$(($index+1))

  setvar "$idvar" $index
}

token:store() {
  if [ -z ${__noshadow_1_+x} ]; then
    local __noshadow_1_
    local __noshadow_1_8
  fi
  __shadowing_token:store "$1" "$2" "$3" "$4" "$5" "$6" "$7"  __noshadow_1_8
  setvar "$8" "$__noshadow_1_8"
}


token:from() {
  setvar "$3" "${Tokens[${2}-${1}]}"
}

token:all-from() {
  local __token="${@:$#}"

  while [ $# -gt 1 ]; do
    case "$1" in
      '-v'|'--value')
        token:from $__token value "$2"
        shift 2
        ;;
      '-c'|'--class')
        token:from $__token class "$2"
        shift 2
        ;;
      '-ls'|'--line-start')
        token:from $__token linenumber_start "$2"
        shift 2
        ;;
      '-le'|'--line-end')
        token:from $__token linenumber_end "$2"
        shift 2
        ;;
      '-g'|'--glued')
        token:from $__token glued "$2"
        shift 2
        ;;
      '-cs'|'--collumn-start')
        token:from $__token collumn_start "$2"
        shift 2
        ;;
      '-ce'|'--collumn-end')
        token:from $__token collumn_end "$2"
        shift 2
        ;;
      '-i'|'--id')
        setvar "$2" $__token
        shift 2
        ;;
      *)
        parse_error "unexpected argument $1, expecting -(v|c|ls|le|cs|ce|g|i), on line %line"
        ;;
    esac
  done
}


token:get-selected() {
  setvar "$1" ${Tokens[index]}
}

token:clear() {
  Tokens[length]=$((${Tokens[index]}-$1))
  if [ ${Tokens[index]} -gt ${Tokens[length]} ]; then
    Tokens[index]=${Tokens[length]}
  fi
}

token:clear-all() {
  unset Tokens
  declare -gA Tokens
  Tokens[index]=0
  Tokens[length]=0
}

token:move-back-index() {
  Tokens[index]=$((${Tokens[index]}-1))
}

token:forward() {
  Tokens[index]=$((${Tokens[index]}+1))
}

token:in-topmost() {
  [ ${Tokens[index]} = ${Tokens[length]} ]
}


token:mark-position() {
  setvar "$1" ${Tokens[index]}
}

token:return-to-mark() {
  Tokens[index]="$1"
}



__shadowing_token:find-by() {
  local field="$1"
  local value="$2"
  local token="${Tokens[index]}"
  local tokenvar="$3"
  local tvalue

  while [ $token -ge 0 ]; do
    token:from $token $field tvalue
    if [[ "$tvalue" =~ ^$value$ ]]; then
      setvar "$tokenvar" $token
      return
    else
      token=$(($token-1))
    fi
  done
  setvar "$tokenvar" '-1'
}

token:find-by() {
  if [ -z ${__noshadow_2_+x} ]; then
    local __noshadow_2_
    local __noshadow_2_3
  fi
  __shadowing_token:find-by "$1" "$2"  __noshadow_2_3
  setvar "$3" "$__noshadow_2_3"
}


# FILE: lexer/tokens.bash
declare -gA States

States[index]=0

token:push-state() {
  States[${States[index]}]=$1
  States[index]=$((${States[index]}+1))
}

token:pop-state() {
  local index=$((${States[index]}-1))
  States[index]=$index
  setvar "$1" ${States[$index]}
}

token:in-topmost-state() {
  [ ${States[index]} = 1 ]
}

token:clear-states() {
  unset States
  declare -gA States
  States[index]=0
  token:push-state top
}

token:push-state top
# FILE: lexer/states.bash

__shadowing_token:parse-unicode-utf-8() {
  local out="$1"
  local c byte='' unicode='' final_value
  local state='get-byte' gotten=0 needed=1
  stream:next-character

  while [ $needed -gt $gotten ]; do
    if stream:end; then
      token:error "Premature end of input while parsing utf-8 unicode character"
    fi
    stream:get-character c

    case "$state" in
      'get-byte')
        case "$c" in
          [0-9abcdefABCDEF])
            stream:next-character
            byte+="$c"
            if [ ${#byte} = 2 ]; then
              state='test-byte'
            fi
            ;;
          *)
            if [ "${#byte}:$gotten" = "1:0" ]; then
              unicode="\\x$byte"
              gotten=1
            else
              token:error "Invalid utf-8 unicode character: $unicode\\x$byte (incomplete byte)"
            fi
            ;;
        esac
        ;;
      'test-byte')
        case "$gotten" in
          0)
            needed="$((
              0x$byte < 0x80 ? 1 :
              0x$byte < 0xe0 ? 2 :
              0x$byte < 0xf0 ? 3 :
              0x$byte < 0xf8 ? 4 : -1))"
            if [ "$needed" = -1 ]; then
              token:error "Invalid utf-8 unicode character: \\x$byte"
            fi
            ;;
          *)
            if [ $((0x$byte)) -lt $((0x80)) ] ||
               [ $((0x$byte)) -gt $((0xbf)) ]; then
              token:error "Invalid utf-8 unicode character: $unicode\\x$byte"
            fi
            ;;
        esac
        state='get-byte'
        gotten=$((gotten+1))
        unicode+="\\x$byte"
        byte=''
        if [ $gotten -lt $needed ]; then
          if ! stream:require-string '\x'; then
            token:error "^^ while parsing a utf-8 unicode character"
          fi
        fi
        ;;
    esac
  done
  printf -v "$out" "$unicode"
}

token:parse-unicode-utf-8() {
  if [ -z ${__noshadow_3_+x} ]; then
    local __noshadow_3_
    local __noshadow_3_1
  fi
  __shadowing_token:parse-unicode-utf-8  __noshadow_3_1
  setvar "$1" "$__noshadow_3_1"
}




__shadowing_token:parse-unicode-utf-16() {
  local out="$1"
  local c unicode1='' unicode2='' final_value
  local state='unicode1'
  stream:next-character

  while [ -z "${!out}" ]; do
    if stream:end; then
      token:error "Premature end of input while parsing utf-16 unicode character"
    fi
    stream:get-character c

    case "$state" in
      'unicode1')
        case "$c" in
          [0-9abcdefABCDEF])
            unicode1+="$c"
            stream:next-character
            if [ "${#unicode1}" = 4 ]; then
              if [ "$((0x${unicode1}))" -gt $((0xd7ff)) ] &&
                 [ "$((0x${unicode1}))" -lt $((0xdbff)) ]; then
                state='unicode2'
                if ! stream:require-string '\u'; then
                  token:error "^^ while parsing a utf-16 unicode character"
                fi
              elif [ "$((0x${unicode1}))" -gt $((0xdbff)) ] &&
                   [ "$((0x${unicode1}))" -lt $((0xdc00)) ]; then
                token:error "Invalid utf-16 unicode character: \\u${unicode1}$c"
              else
                printf -v "$out" "\\u$unicode1"
              fi
            fi
            ;;
          *)
            if [ "${#unicode1}" -gt 0 ]; then
              printf -v "$out" "\\u$unicode1"
            else
              token:error "Invalid utf-16 unicode: \\u"
            fi
            ;;
        esac
        ;;
      'unicode2')
        case "$c" in
          [0-9abcdefABCDEF])
            unicode2+="$c"
            stream:next-character
            if [ "${#unicode2}" = 4 ]; then
              if [ "$((0x${unicode2}))" -gt $((0xdc00)) ] &&
                 [ "$((0x${unicode2}))" -lt $((0xdfff)) ]; then
                final_value="$((
                  ((0x${unicode1} - 0xd800) * 0x0400) +
                  ((0x${unicode2} - 0xdc00) + 0x10000)))"

                printf -v final_value '%x' "$final_value"
                printf -v "$out" "\U$final_value"
              else
                token:error "Invalid utf-16 low surrogate: \\u$unicode2"
              fi
            fi
            ;;
          *)
            token:error "Invalid utf-16 low surrogate: \\u$unicode2"
            ;;
        esac
        ;;
    esac
  done
}

token:parse-unicode-utf-16() {
  if [ -z ${__noshadow_4_+x} ]; then
    local __noshadow_4_
    local __noshadow_4_1
  fi
  __shadowing_token:parse-unicode-utf-16  __noshadow_4_1
  setvar "$1" "$__noshadow_4_1"
}



__shadowing_token:parse-unicode-utf-32() {
  local out="$1"
  local unicode="" c
  stream:next-character

  while [ -z "${!out}" ]; do
    if stream:end; then
      token:error "Premature end of input while parsing unicode"
    fi
    stream:get-character c

    case "$c" in
      [0-9abcdefABCDEF])
        unicode+="$c"
        stream:next-character
        if [ ${#unicode} = 8 ]; then
          printf -v "$out" "\\U$unicode"
        fi
        ;;
      *)
        if [ -n "$unicode" ]; then
          printf -v "$out" "\\U$unicode"
        else
          token:error "Invalid utf-32 unicode character: \\U"
        fi
        ;;
    esac
  done
}

token:parse-unicode-utf-32() {
  if [ -z ${__noshadow_5_+x} ]; then
    local __noshadow_5_
    local __noshadow_5_1
  fi
  __shadowing_token:parse-unicode-utf-32  __noshadow_5_1
  setvar "$1" "$__noshadow_5_1"
}

# FILE: lexer/unicode.bash


# token:parse varname
# read a token from input and place it's id in the given variable
#
# e.g.
#
#  $ echo "abc" | {
#  >   stream:init
#  >   token:get -v value -c class
#  >   echo "v=$value c=$class"
#  > }
#  v=abc c=name


__shadowing_token:parse() {
  local token_id_var="$1"   # variables where the token id will be stored

  local linenumber_start    # tokens store starting and ending line and collumn
  local linenumber_end      # numbers for debugging purposes.
  local collumn_start       #
  local collumn_end         #

  local state               # describes the parsing context, e.g. if in double quotes or parentheses
  local class=undefined     # token type
  local token=""            # token value
  local c=''                # character being parsed

  local move=true           # if true, the stream will move to the next character after the parsing of the current one.
  local skip_term=true      # if true, the terminating character will not be part of the next token

  local belongs=true        # if false, the current character does not belong to the current token and the latter is finished
  local next_state=none     # if not none, the values of state and next_state are pushed to the stack, in that order
  local next_class=none     # if not none, after finishing the current token, the next one will be given this class
  local state_end=false     # if true, the current state will be not be pushed in the stack at the end of parsing

  local glued=true          # true if the token is glued to the previous one

  stream:get-line-number linenumber_start
  stream:get-collumn     collumn_start

  # states are stored in a stack, to allow strings like 'a b$(echo c)d e'
  # to be handled by the lexer, where the tokens will be
  # 'a b', $(, echo, c, ), 'd e'.
  token:pop-state state

  # the token is done when its class is identified
  while [ $class = undefined ] && ! stream:end; do
    move=true
    stream:get-character c

    case $state in
      quoted-escape)
        # if the escaped character is special, expand it before
        # putting it in the token, otherwise just put the character
        local e
        case "$c" in
          [bfnrtv]) printf -v e "\\$c" "$token"  ;;
          x)        token:parse-unicode-utf-8  e ; move=false ;;
          u)        token:parse-unicode-utf-16 e ; move=false ;;
          U)        token:parse-unicode-utf-32 e ; move=false ;;
          \\)       e='\' ;;
          *)        e="\\$c" ;;
        esac
        token+="$e"
        state=double-quotes
        ;;

      unquoted-escape)
        # for escaped spaces and newlines
        case "$c" in
          $'\n')
            state=esc-newline
            ;;
          *)
            token+="$c"
            token:pop-state state
            ;;
        esac
        ;;

      esc-newline)
        token:pop-state state
        stream:register-escaped-newline
        move=false
        ;;

      single-quotes)
        # accept all characters until an closing single quote is found
        if [ "$c" = "'" ]; then
          class=string
          state_end=true
        else
          token+="$c"
        fi
        ;;

      double-quotes)
        # characters can be escaped, and expressions substituted in.
        # on substitutions, break the string and restart it after the
        # substitution is finished
        case "$c" in
          '\')
            state=quoted-escape
            ;;
          '$')
            class=string
            next_state=substitution
            ;;
          '"')
            class=string
            state_end=true
            ;;
          "'")
            if [ -z "$token" ]; then
              token="'"
            else
              move=false
            fi
            class=string
            ;;
          *)
            token+="$c"
            ;;
        esac
        ;;

      whitespace)
        # indentation
        if [ "$c" = ' ' ]; then
          token+="$c"
        else
          token=${#token}
          move=false
          class=whitespace
          state_end=true
        fi
        ;;

      substitution)
        token='$'
        class=special
        state_end=true
        move=false
        case "$c" in
          '(')
            token='$('
            move=true
            next_state=parentheses
            ;;
          '[')
            token='$['
            move=true
            next_state=brackets
            ;;
          '{')
            token='${'
            move=true
            next_state=curly-braces
            ;;
          '#')
            token='$#'
            move=true
            ;;

          [0-9a-zA-Z_~])
            next_state=variable
            ;;

          @|\*)
            next_state=special-substitution
            ;;

          *)
            class=name
            ;;
        esac
        ;;

      special-substitution)
        token="$c"
        class=special
        state_end=true
        ;;

      variable)
        case "$c" in
          [0-9a-zA-Z_])
            token+="$c"
            ;;
          *)
            class=name
            state_end=true
            move=false
            ;;
        esac
        ;;

      comment)
        # ignore all until newline
        if [ "$c" = $'\n' ]; then
          token="$c"
          class=newline
          state_end=true
        fi
        ;;

      equals)
        case "$c" in
          [~=])
            token="=$c"
            ;;
          *)
            move=false
            ;;
        esac
        state_end=true
        class=special
        ;;

      special)
        case "$c" in
          ':'|';'|','|'@'|'+'|'-'|'*'|'/'|'^'|'%'|'&')
            token+="$c"
            ;;
          *)
            move=false
            state_end=true
            class=special
            ;;
        esac
        ;;

      *)
        # all other contexts follow similar parsing rules,
        # the only difference being what token ends it
        case "$c" in
          "'"|'"'|'('|'['|'{')
            case "$c" in
              "'") next_state=single-quotes ;;
              '"') next_state=double-quotes ;;
              '(') next_state=parentheses;  skip_term=false; next_class=special ;;
              '[') next_state=brackets;     skip_term=false; next_class=special ;;
              '{') next_state=curly-braces; skip_term=false; next_class=special ;;
            esac
            belongs=false
            ;;

          ')'|']'|'}')
            if { { [ "$state" = parentheses  ] && [ ! "$c" = ')' ]; } ||
                 { [ "$state" = brackets     ] && [ ! "$c" = ']' ]; } ||
                 { [ "$state" = curly-braces ] && [ ! "$c" = '}' ]; } ;}; then
              token:error "unexpected $c on line %line. ${state/-/ }"
            else
              belongs=false
              state_end=true
              skip_term=false
              next_class=special
            fi
            ;;

          ':'|';'|','|'@'|'+'|'-'|'*'|'/'|'^'|'%'|'&')
            belongs=false
            skip_term=false
            [ -z "$token" ] && next_state=special
            ;;

          '=')
            if [[ "$token" =~ ^[a-zA-Z_][a-zA-Z_0-9]*$ ]]; then
              belongs=false
              skip_term=false
              next_class=special
            elif [[ "$token" =~ ^[\&+*-\<\>\!/]$ ]]; then
              token+="$c"
              class=special
            elif [ -z "$token" ]; then
              belongs=false
              skip_term=false
              next_state='equals'
            else
              token+="$c"
            fi
            ;;
          '\')
            token:push-state "$state"
            state='unquoted-escape'
            ;;


          '$')
            belongs=false
            next_state=substitution
            skip_term=false
            ;;

          $'\n')
            [ -z "$token" ] && glued=false
            belongs=false
            next_class=newline
            ;;

          ' ')
            if stream:line-start; then
              belongs=false
              skip_term=false
              next_state=whitespace
            elif [ -n "$token" ]; then
              belongs=false
            else
              glued=false
            fi
            ;;

          '#')
            belongs=false
            next_state=comment
            ;;

          *)
            token+="$c"
            ;;
        esac
        # this is still part of the '*)' state case
        if ! $belongs; then
          # found a terminating character while parsing
          if [ -z "$token" ]; then
            # if the current token is empty, don't return
            # any token, instead restart the loop.

            stream:get-line-number linenumber_start # update starting position
            stream:get-collumn     collumn_start    #

            # we skip the terminating character by not
            # adding it to the current empty token
            if $skip_term; then
              token=""
            else
              token="$c"
              skip_term=true
            fi

            # having a next_class means the terminating character
            # forms a token on their own, e.g. '('. It will be
            # a single character token.
            if [ ! $next_class = none ]; then
              class=$next_class
            fi

            if [ ! $next_state = none ]; then
              token:push-state $state
              state=$next_state
              next_state=none
            fi
          else
            # if the token has any characters, return the token
            # and forget the terminating character.
            class=name
            state_end=false
            next_state=none

            move=false # ensures the terminating character is read again next call
          fi
        fi
        ;;
    esac
    $move && stream:next-character
  done

  $state_end || token:push-state $state
  [ $next_state = none ] || token:push-state $next_state

  if stream:end; then
    if token:in-topmost-state; then
      if [ -n "$token" ] && [ "$class" = unidentified ]; then
        class=name
      else
        token=eof
        class=eof
        glued=false
      fi
    else
      if ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
        case "$state" in
          parentheses)   state='('; ;;
          brackets)      state='['; ;;
          curly-braces)  state='{'; ;;
          esc-newline)   state='\'; ;;
        esac
        POWSCRIPT_INCOMPLETE_STATE="$state"
        exit
      else
        token:unfinished-input-error "$state" "$linenumber_start" "$collumn_start"
      fi
    fi
  fi

  stream:get-collumn     collumn_end
  stream:get-line-number linenumber_end

  token:store\
    "$token" "$class" "$glued"\
    "$linenumber_start" "$linenumber_end"\
    "$collumn_start" "$collumn_end"\
    "$token_id_var"
}

token:parse() {
  if [ -z ${__noshadow_6_+x} ]; then
    local __noshadow_6_
    local __noshadow_6_1
  fi
  __shadowing_token:parse  __noshadow_6_1
  setvar "$1" "$__noshadow_6_1"
}


token:get-id() {
  if token:in-topmost; then
    token:parse "$1"
  else
    token:get-selected "$1"
    token:forward
  fi
}

token:peek-id() {
  if token:in-topmost; then
    token:parse "$1"
    token:backtrack
  else
    token:get-selected "$1"
  fi
}

token:skip() {
  local _
  if token:in-topmost; then
    token:parse _
  else
    token:forward
  fi
}


token:get() {
  local __get_token__token
  token:get-id __get_token__token
  token:all-from "$@" $__get_token__token
}


token:peek() {
  local __peek_token__token
  token:peek-id __peek_token__token
  token:all-from "$@" $__peek_token__token
}


token:next-is() {
  local value class glued res=false
  token:peek -v value -c class -g glued
  case $# in
    1)
      [ "$class" = "$1" ] && res=true
      ;;
    2)
      [[ "$class" = "$1" ]] && [[ "$value" = "$2" ]] && res=true
      ;;
    3)
      [[ "$class" = "$1" ]] && [[ "$value" = "$2" ]] && [[ "$glued" = "$3" ]] && res=true
  esac
  $res
}


token:ignore-whitespace() {
  if token:next-is whitespace; then
    token:skip
  fi
}


token:backtrack() {
  token:move-back-index
}


__shadowing_token:get-specific() {
  local value class required="$1" out="$2"
  token:get -v value -c class
  if [ ! $class = $required ]; then
    token:error "Wrong token: found a $class of value $value when a $required was required"
  else
    setvar "$out" "$value"
  fi
}

token:get-specific() {
  if [ -z ${__noshadow_7_+x} ]; then
    local __noshadow_7_
    local __noshadow_7_2
  fi
  __shadowing_token:get-specific "$1"  __noshadow_7_2
  setvar "$2" "$__noshadow_7_2"
}


token:require() {
  local req_class="$1" req_value="$2"
  local value class

  token:get -v value -c class
  if [ ! "$req_class $req_value" = "$class $value" ]; then
    token:error "Wrong token: found a $class of value $value when a $req_class of value $req_value was required"
  fi
}


token:unfinished-input-error() {
  local token
  local opener=
  local state="$1"
  local line
  local collumn
  case "$state" in
    parentheses)    opener='\$?\('  ;;
    brackets)       opener='\['     ;;
    curly-braces)   opener='\$?\{'  ;;
  esac
  if [ -n "$opener" ]; then
    token:find-by value "$opener" token
    if [ ! $token = -1 ]; then
      token:from $token linenumber_start line
      token:from $token collumn_start collumn
      token:error "unclosed ${state/-/ }, last open one found in line $line, collumn $collumn"
    else
      token:error "unclosed ${state/-/ }"
    fi
  else
    if [[ "$state" =~ quotes ]]; then
      line="$2"
      collumn="$3"
      token:error "unfinished ${state/-/ }, starting in line $line, collumn $collumn"
    else
      token:error "unexpected eof"
    fi
  fi
}

token:error() {
  local message="error: ${1//%line/$(stream:get-line-number)}"
  if ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
    if ${POWSCRIPT_SHOW_INCOMPLETE_MESSAGE-false}; then
      >&2 echo "$message"
    fi
    POWSCRIPT_INCOMPLETE_STATE="$message"
    exit
  else
    >&2 echo "$message"
    exit 1
  fi
}

token:to-json() {
  local token
  stream:init

  echo '{'
  while ! stream:end; do
    token:get -v value -c class -g glued
    echo "    'value': '$value'"
    echo "    'class': '$class'"
    echo "    'glued': $glued"
    echo '  }'
  done
  echo '}'
}
# FILE: lexer/lexer.bash
declare -gA Asts

Asts[index]=0
Asts[length]=0
Asts[required-indent]=0

declare -gA IndentStack

IndentStack[index]=0
IndentStack[starting-block]=false
IndentStack[0]=0


ast:new-indentation() {
  local indent

  ast:indentation-required indent

  ast:push-indentation $((indent+1))
  IndentStack[starting-block]=true
}



__shadowing_ast:test-indentation() {
  local value="$1" class="$2" out="$3"
  local req found result temp_result
  ast:indentation-required req
  ast:count-indentation "$value" $class found

  if ${IndentStack[starting-block]}; then
    if [ $found -ge $req ]; then
      IndentStack[starting-block]=false
      IndentStack[${IndentStack[index]}]=$found
      result=ok
    else
      result=error-start
    fi
  else
    if [ $found -eq -1 ]; then
      result=error-eof

    elif [ $found -eq $req ]; then
      result=ok

    elif [ $found -lt $req ]; then
      result=end
    else
      result=error-exact
    fi
  fi
  setvar "$out" $result
}

ast:test-indentation() {
  if [ -z ${__noshadow_8_+x} ]; then
    local __noshadow_8_
    local __noshadow_8_3
  fi
  __shadowing_ast:test-indentation "$1" "$2"  __noshadow_8_3
  setvar "$3" "$__noshadow_8_3"
}



ast:update-indentation() {
  IndentStack[${IndentStack[index]}]=$1
}

ast:pop-indentation() {
  IndentStack[index]=$((${IndentStack[index]}-1))
}

ast:push-indentation() {
  IndentStack[index]=$((${IndentStack[index]}+1))
  ast:update-indentation $1
}

ast:indentation-required() {
  setvar "$1" ${IndentStack[${IndentStack[index]}]}
}

ast:count-indentation() {
  case "$2" in
    whitespace) setvar "$3" "$1" ;;
    eof)        setvar "$3" -1   ;;
    *)          setvar "$3" 0    ;;
  esac
}

ast:indentation-layers() {
  setvar "$1" ${IndentStack[index]}
}

# FILE: ast/indent.bash
declare -gA AstStates

AstStates[index]=0
AstStates[0]=top

ast:push-state() {
  local index=$((${AstStates[index]}+1))

  AstStates[index]=$index
  AstStates[$index]=$1
}

ast:pop-state() {
  AstStates[index]=$((${AstStates[index]}-1))
}

ast:last-state() {
  setvar "$1" "${AstStates[${AstStates[index]}]}"
}

ast:state-is() {
  local state
  ast:last-state state
  [ "$state" = "$1" ]
}

ast:clear-states() {
  unset AstStates
  declare -gA AstStates
  AstStates[index]=0
  AstStates[0]=top
}

ast:ends-state() {
  local expr="$1"
  local state expr_value

  ast:last-state state

  if ast:is $expr name; then
    ast:from $expr value expr_value

    case "$state $expr_value" in
      "( )"|"[ ]"|"{ }")
        return 0
        ;;
      *)
        return 1
        ;;
    esac
  else
    return 1
  fi
}


# FILE: ast/states.bash


__shadowing_ast:new() {
  local index="${Asts[index]}"
  local length="${Asts[length]}"

  setvar "$1" "$index"

  Asts[head-$index]=
  Asts[value-$index]=
  Asts[children-$index]=

  if [ ! $index = $length ]; then
    Asts[index]=$(($index+1))
  else
    Asts[index]=$(($length+1))
  fi
  Asts[length]=$(($length+1))
}

ast:new() {
  if [ -z ${__noshadow_9_+x} ]; then
    local __noshadow_9_
    local __noshadow_9_1
  fi
  __shadowing_ast:new  __noshadow_9_1
  setvar "$1" "$__noshadow_9_1"
}


ast:make() {
  local __newast __newchild
  ast:new __newast
  ast:set "$__newast" head  "$2"
  ast:set "$__newast" value "$3"
  for __newchild in ${@:4}; do
    ast:push-child "$__newast" $__newchild
  done
  setvar "$1" "$__newast"
}

ast:make-from-string() {
  local __mkstr_out
  ast:_make-from-string __mkstr_out "$2"
  setvar "$1" "$__mkstr_out"
}

ast:_make-from-string() {
  local out="$1" string="$2"
  local ast child
  local trimmed incomplete_marker marker_head marker info head value
  declare -a children

  while IFS="" read -r line; do
    trimmed="${line#${line%%[^" "]*}}"
    incomplete_marker="${trimmed%%[^-]*}"
    marker_head="${trimmed:${#incomplete_marker}:1}"
    if [[ ! "$marker_head" = '+' ]]; then
      marker_head=""
    fi
    marker="$incomplete_marker$marker_head"
    info="${trimmed#$marker}"
    info="${info#${info%%[^" "]*}}"

    case "$marker" in
      *+)
        ast="$info"
        ;;
      *)
        head="${info%% *}"
        if [ -n "$head" ]; then
          value="${info#*$head}"
          value="${value# }"
          ast:make ast $head "$value"
          children[${#marker}]=$ast
        fi
        ;;
    esac
    if [ ${#marker} -gt 0 ]; then
      ast:push-child ${children[${#marker}-1]} "$ast"
    fi
  done <<<"$string"
  setvar "$out" "${children[0]}"
}

ast:from() {
  setvar "$3" "${Asts["$2-$1"]}"
}

ast:all-from() {
  local __af_expr="$1"
  shift

  while [ $# -gt 0 ]; do
    case "$1" in
      -e|--expr)
        setvar "$2" $__af_expr
        shift 2
        ;;
      -v|--value)
        ast:from $__af_expr value "$2"
        shift 2
        ;;
      -h|--head)
        ast:from $__af_expr head "$2"
        shift 2
        ;;
      -c|--children)
        ast:from $__af_expr children "$2"
        shift 2
        ;;
      -@|--@children)
        ast:children $__af_expr "${@:2}"
        shift $#
        ;;
      *)
        ast:error "Invalid flag $1, expected -[evhc@]"
        ;;
    esac
  done
}

ast:set() {
  Asts["$2-$1"]="$3"
}

ast:is() {
  local ast_head ast_value res=false
  ast:from $1 head  ast_head
  ast:from $1 value ast_value

  case $# in
    2) case $ast_head  in $2) res=true ;; esac
       ;;
    3) case $ast_head  in $2)
       case $ast_value in $3) res=true ;; esac ;; esac
       ;;
  esac
  $res
}


ast:push-child() {
  Asts["children-$1"]="${Asts["children-$1"]} $2"
}

ast:unshift-child() {
  Asts["children-$1"]="$2 ${Asts["children-$1"]}"
}

ast:shift-child() {
  setvar "$2" "${Asts["children-$1"]%% *}"
  Asts["children-$1"]="${Asts["children-$1"]#* }"
}

ast:pop-child() {
  setvar "$2" "${Asts["children-$1"]##* }"
  Asts["children-$1"]="${Asts["children-$1"]% *}"
}


__shadowing_ast:children() {
  local ast="$1"
  local ast_children children_array child i

  ast:from $ast children ast_children
  children_array=( $ast_children )

  i=0
  for child_name in ${@:2}; do
    setvar "$child_name" ${children_array[$i]}
    i=$((i+1))
  done
}

ast:children() {
  if [ -z ${__noshadow_10_+x} ]; then
    local __noshadow_10_
    declare -A __noshadow_10_all
          for __noshadow_10_n in $(seq 2 $#); do
            __noshadow_10_all[$__noshadow_10_n]=__noshadow_10_$__noshadow_10_n
          done
  fi
  __shadowing_ast:children "$1"  "${__noshadow_10_all[@]}"
  #
          shift 0
          for __noshadow_10_n in $(seq 2 $#); do
            setvar "$2" "${!__noshadow_10_all[$__noshadow_10_n]}"
            shift
          done
}



ast:clear() {
  unset Asts["value-$1"]
  unset Asts["head-$1"]
  unset Asts["children-$1"]
}

ast:clear-all() {
  unset Asts
  declare -gA Asts

  Asts[index]=0
  Asts[length]=0
  Asts[required-indent]=0
}

# FILE: ast/heap.bash
# ast:error $message
#
# Informs an AST parsing error and exits.
#

ast:error() {
  local message="$1"

  if ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
    POWSCRIPT_INCOMPLETE_STATE="error: while parsing the AST: $message"
    if ${POWSCRIPT_SHOW_INCOMPLETE_MESSAGE-false}; then
      >&2 echo "$message"
    fi
  else
    >&2 echo "$message"
  fi
  exit
}


# ast:parse:require-newline
#
# Parses next ast expression, errors if it's not
# a newline, otherwise consume it. If it's the
# end-of-file and we are in interactive mode,
# ignore the error.
#

ast:parse:require-newline() {
  local nl nl_head
  ast:parse:expr nl
  ast:from $nl head nl_head
  case $nl_head in
    newline)
      ;;
    eof)
      if ! ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
        ast:error "unexpected end of file after $1"
      fi
      ;;
    *)
      ast:error "trailing expression after ${1}: $(ast:print $nl) :: $(ast:from $nl head)"
      ;;
  esac
}


# ast:is-flag $expr
#
# Check if the given AST $expr is of the form -name
#

ast:is-flag() {
  local expr="$1"
  local minus name extra

  if ast:is $expr cat; then
    ast:children $expr minus name extra
    if ast:is $minus name '-' && ast:is $name name && [ -z "$extra" ]; then
      return 0
    else
      return 1
    fi
  else
    return 1
  fi
}

# ast:to-double-string
#
# Change a normal string to a double quoted string.
#

ast:to-double-string() {
  local str="$1"
  local str_value cat_child kittens

  if ast:is $str string; then
    ast:from $str value str_value
    str_value="${str_value//\\/\\\\}"
    str_value="${str_value//\"/\\\"}"
    str_value="${str_value//\$/\\\$}"
    ast:set $str head  double-string
    ast:set $str value "$str_value"

  elif ast:is $str cat; then
    ast:from $str children kittens

    for cat_child in $kittens; do
      ast:to-double-string $cat_child
    done
  fi
}

# FILE: ast/helper.bash

__shadowing_ast:parse:expand() {
  local out="$1"
  local block

  ast:parse:require-newline "expand"
  ast:parse:block "ex" block
  ast:make "$out" expand "" $block
}

ast:parse:expand() {
  if [ -z ${__noshadow_11_+x} ]; then
    local __noshadow_11_
    local __noshadow_11_1
  fi
  __shadowing_ast:parse:expand  __noshadow_11_1
  setvar "$1" "$__noshadow_11_1"
}

# FILE: ast/expand.bash

__shadowing_ast:parse:require() {
  local out="$1"
  local file

  ast:parse:expr file
  ast:parse:require-newline 'require'

  ast:make "$out" require '' $file
}

ast:parse:require() {
  if [ -z ${__noshadow_12_+x} ]; then
    local __noshadow_12_
    local __noshadow_12_1
  fi
  __shadowing_ast:parse:require  __noshadow_12_1
  setvar "$1" "$__noshadow_12_1"
}

# FILE: ast/require.bash

__shadowing_ast:parse:list() {
  local out="$1"
  local list_expr

  ast:make list_expr list
  ast:parse:sequence $list_expr

  setvar "$out" $list_expr
}

ast:parse:list() {
  if [ -z ${__noshadow_13_+x} ]; then
    local __noshadow_13_
    local __noshadow_13_1
  fi
  __shadowing_ast:parse:list  __noshadow_13_1
  setvar "$1" "$__noshadow_13_1"
}


ast:parse:sequence() {
  local __discard__
  ast:parse:sequence_ "$1" "$2" "${3:-__discard__}"
}


__shadowing_ast:parse:sequence_() {
  local seq="$1" predicate="${2:-true}" out="$3"
  local expr state

  ast:last-state state

  while true; do
    ast:parse:expr expr

    if ast:is $expr newline; then
      ast:parse:sequence:multiline $state || break

    elif ast:is $expr eof; then
      if ${POWSCRIPT_ALLOW_INCOMPLETE-false} || [ $state = top ]; then
        break
      else
        ast:error "unexpected end-of-file while parsing '$state' sequence"
      fi

    elif ast:parse:sequence:multiline-end $expr $state; then
      break

    elif ! ${predicate//%/$expr}; then
      setvar "$out" "$expr"
      break

    else
      ast:push-child $seq $expr

    fi
  done

  if [ -z "${!out}" ]; then
    setvar "$out" '-1'
  fi
}

ast:parse:sequence_() {
  if [ -z ${__noshadow_14_+x} ]; then
    local __noshadow_14_
    local __noshadow_14_3
  fi
  __shadowing_ast:parse:sequence_ "$1" "$2"  __noshadow_14_3
  setvar "$3" "$__noshadow_14_3"
}


ast:parse:sequence:multiline() {
  local state="$1"

  case $state in
    '('|'['|'{') return 0 ;;
    *)           return 1 ;;
  esac
}

ast:parse:sequence:multiline-end() {
  local expr="$1" state="$2"

  case $state in
    '(') ast:is $expr name ')' && return 0 ;;
    '[') ast:is $expr name ']' && return 0 ;;
    '{') ast:is $expr name '}' && return 0 ;;
  esac
  return 1
}
# FILE: ast/sequence.bash
# ast:parse:assign $expr
#
# Parse an AST representing the attribution
# of a value to a name.
# Can be of the type:
#  - var=single-value
#  - var=(...)
#  - var=[...]
#  - var={}
# The given $expr is an undefined AST
# with the var name as a child, which
# will be transformed into the assign.
#

ast:parse:assign() {
  local expr="$1"
  local assigned_value head
  local value class

  token:peek -v value -c class
  if [ "$class" = special ]; then
    case "$value" in
      '('|'[') head=list-assign ;;
      '{')     head=associative-assign ;;
      *)       head=assign ;;
    esac
  else
    head=assign
  fi
  ast:set $expr head $head
  ast:parse:expr assigned_value
  ast:push-child $expr $assigned_value
}


# ast:parse:special-assign $name $out
#
# Parse an special assingment of the form:
#
# * unset=    <expr>
# * set=      <expr>
# * empty=    <expr>
# * nonempty= <expr>
# * ref=      <expr>
#


__shadowing_ast:parse:conditional-assign() {
  local name="$1" out="$2"
  local assign_type assign_expr tclass

  token:get -v assign_type -c tclass
  case "$assign_type:$tclass" in
    'ref:name')      ;;
    'set:name')      ;;
    'unset:name')    ;;
    'empty:name')    ;;
    'nonempty:name') ;;
    *)
      ast:error "Invalid conditional assign: $assign_type:$tclass"
      ;;
  esac

  token:require special '='
  ast:parse:expr assign_expr

  if [ "$assign_type" = ref ]; then
    ast:make "$out" assign-ref '' $name $assign_expr
  else
    ast:make "$out" assign-conditional "$assign_type" $name $assign_expr
  fi
}

ast:parse:conditional-assign() {
  if [ -z ${__noshadow_15_+x} ]; then
    local __noshadow_15_
    local __noshadow_15_2
  fi
  __shadowing_ast:parse:conditional-assign "$1"  __noshadow_15_2
  setvar "$2" "$__noshadow_15_2"
}


# ast:parse:math-assign $expr $name $op
#
# Parse an math AST to be attributed to a
# variable.
#

ast:parse:math-assign() {
  local expr="$1" name="$2" op="$3"
  local right_side

  ast:push-state math
  ast:parse:expr right_side
  ast:pop-state

  ast:set $expr head math-assign
  ast:set $expr value "$op"
  ast:set $expr children "$name $right_side"
}


# ast:parse:push-assign $expr $name
#
# Parse an AST to be pushed at the
# end of an array.
#

ast:parse:push-assign() {
  local expr="$1" name="$2"
  local name_value index value

  ast:from $name value name_value

  ast:make index array-length "$name_value"

  ast:parse:expr value
  ast:set $expr children "$name $index $value"
  ast:set $expr head indexing-assign
}

# ast:parse:concat-assign $expr $name
#
# Parse an AST to be concatenated to a variable
#

ast:parse:concat-assign() {
  local expr="$1" name="$2"
  local name_value value subst concat

  ast:from $name value name_value

  ast:parse:expr value

  ast:set $expr children "$name $value"
  ast:set $expr head concat-assign
}


# ast:parse:assign-sequence $first $out
#
# parse a sequence of assignments, followed by either a newline or command call
#


__shadowing_ast:parse:assign-sequence() {
  local first=$1 out="$2"
  local predicate seq extra cmd

  ast:make seq assign-sequence '' $first

  if ast:state-is ==; then
    predicate='ast:is-assign % && ast:parse:not-binary-conditional %'
  else
    predicate='ast:is-assign %'
  fi
  ast:parse:sequence $seq "$predicate" extra

  if ast:state-is == && ! ast:parse:not-binary-conditional $extra; then
    setvar "$out" $seq

  elif [ "$extra" = '-1' ]; then
    setvar "$out" $seq

  else
    ast:parse:command-call-with-cmd $seq $extra cmd
    setvar "$out" $cmd
  fi
}

ast:parse:assign-sequence() {
  if [ -z ${__noshadow_16_+x} ]; then
    local __noshadow_16_
    local __noshadow_16_2
  fi
  __shadowing_ast:parse:assign-sequence "$1"  __noshadow_16_2
  setvar "$2" "$__noshadow_16_2"
}


ast:is-assign() {
  local expr="$1"
  local expr_head

  ast:from $expr head expr_head

  case $expr_head in
    *assign) return 0 ;;
    *)       return 1 ;;
  esac
}

# FILE: ast/assign.bash
# ast:parse:substitution $out
#
# Parse expressions of the form $variable or $array[index]
#


__shadowing_ast:parse:substitution() {
  local subst curly="$1" out="$2"
  local expr value head varname aftervar
  local index lb rb aft
  local postcat
  local cat_children cat_array dollar

  ast:parse:expr expr
  ast:all-from "$expr" -v value -h head -@ varname aftervar

  case "$head" in
    name)
      if $curly && ! token:next-is special '}'; then
        if token:next-is special ':'; then
          token:require special ':'
          ast:make postcat cat ''
          ast:parse:parameter-substitution $postcat "$value" subst
        elif [ "$value" = "@:" ]; then
          ast:make postcat cat ''
          ast:parse:parameter-substitution $postcat "@" subst
        else
          local sym class
          token:peek -v sym -c class
          ast:error "unimplemented variable substitution (found $sym::$class instead of [ or :)"
        fi
      else
        ast:make subst simple-substitution "$value"
      fi
      ;;
    cat)
      if ast:is $varname name; then
        if ast:is $aftervar name '['; then
          ast:children "$expr" varname lb index rb aft
          ast:from $varname value value
          ast:make subst indexing-substitution "$value" $index

          if [ -n "$aft" ]; then
            if ${AST_MATH_MODE-false}; then
              ast:error "invalid math expression: $(ast:print $expr)"

            elif $curly; then
              if ast:is $aft name ':'; then
                local param
                ast:from $expr children cat_children
                cat_array=( $cat_children )
                ast:make postcat cat '' "${cat_array[@]:5}"
                ast:parse:parameter-substitution $postcat "@" param

                if ast:is $param indirect-indexing-substitution; then
                  ast:error "unimplemented variable substitution (keys used after indexing)"
                else
                  if ast:is $param string-test; then
                    ast:make subst array-test '' $subst $param
                  else
                    ast:make subst array-operation '' $subst $param
                  fi
                fi

              else
                ast:error "unimplemented variable substitution (found $(ast:print $aft) while looking for } or :)"
              fi

            else
              ast:from $expr children cat_children
              cat_array=( $cat_children )
              ast:make subst cat '' $subst "${cat_array[@]:4}"
            fi
          elif token:next-is special ':'; then
            local param
            token:skip
            ast:make postcat cat ''
            ast:parse:parameter-substitution $postcat "@" param

            if ast:is $param indirect-indexing-substitution; then
              ast:error "unimplemented variable substitution (keys used after indexing)"
            else
              if ast:is $param string-test; then
                ast:make subst array-test '' $subst $param
              else
                ast:make subst array-operation '' $subst $param
              fi
            fi

          fi
        elif $curly; then
           ast:from $expr children cat_children
           cat_array=( $cat_children )

          if ast:is $aftervar name ':'; then
            ast:make postcat cat '' "${cat_array[@]:2}"
            ast:from $varname value value
            ast:parse:parameter-substitution $postcat "$value" subst

          elif ast:is $varname name "@:"; then
            ast:make postcat cat '' "${cat_array[@]:1}"
            ast:parse:parameter-substitution $postcat "@" subst

          else
            ast:error "unimplemented variable substitution ($(ast:print $varname) directly followed by $(ast:print $aftervar) instead of : or [)"
          fi
        else
          ast:set $varname head simple-substitution
          subst=$expr
        fi
      else
        ast:make dollar name '$'
        ast:unshift-child $expr $dollar
        subst=$expr
      fi
      ;;
    *)
      ast:error "unimplemented variable substitution (not a name nor a cat)"
      ;;
  esac
  setvar "$out" $subst
}

ast:parse:substitution() {
  if [ -z ${__noshadow_17_+x} ]; then
    local __noshadow_17_
    local __noshadow_17_2
  fi
  __shadowing_ast:parse:substitution "$1"  __noshadow_17_2
  setvar "$2" "$__noshadow_17_2"
}



# ast:parse:curly-substitution
#
# Parse expressions of the form ${}, ${variable} or ${array[index]}
#


__shadowing_ast:parse:curly-substitution() {
  local out="$1"
  local subst

  if token:next-is special '}'; then
    ast:make subst empty-substitution ''
  else
    ast:push-state '{'
    ast:parse:substitution true subst
    ast:pop-state
  fi

  token:require special '}'
  setvar "$out" $subst
}

ast:parse:curly-substitution() {
  if [ -z ${__noshadow_18_+x} ]; then
    local __noshadow_18_
    local __noshadow_18_1
  fi
  __shadowing_ast:parse:curly-substitution  __noshadow_18_1
  setvar "$1" "$__noshadow_18_1"
}


# ast:parse:parameter-substitution $postcat $varname $out
#
# Parse string substitution commands, of the form:
#
# unset <expr>
# unset= <expr>
# empty <expr>
# empty= <expr>
# set <expr>
# set! <expr>
# nonempty <expr>
# nonempty! <expr>
# length
# index <math expression>
# from <math expression> to <math expression>
# slice <math expression> length <math expression>
# suffix <pattern>
# prefix <pattern>
# suffix* <pattern>
# prefix* <pattern>
# replace <pattern> by <expr>
# replace* <pattern> by <expr>
# uppercase <pattern>
# lowercase <pattern>
# uppercase* <pattern>
# lowercase* <pattern>
# indirect
# keys
# ref
#


__shadowing_ast:parse:parameter-substitution() {
  local postcat="$1" varname="$2" out="$3"
  local child_a child_b child_c
  local opname opclass postname postclass
  local modifier mclass
  local glued

  ast:children $postcat child_a child_b child_c
  if [ -z "$child_a" ]; then
    token:get -v opname -c opclass
    [ $opclass = 'name' ] || ast:error "Invalid string operation: $opname::$opclass"
  elif ast:is $child_a name; then
    ast:from $child_a value opname
  else
    ast:error "Expected a name for a string operation, got: $(ast:print $child_b)"
  fi

  case "$opname" in
    unset|empty)   modifier='='; mclass='string' ;;
    *fix|replace)  modifier='*'  mclass='name'   ;;
    *case)         modifier='*'  mclass='name'   ;;
  esac


  case "$opname" in
    unset|empty|*fix|*case|replace)
      if [ -n "$child_b" ]; then
        if ast:is $child_b "$mclass" "$modifier"; then
          opname="$opname$modifier"
        else
          ast:error "trailling expression after operation name: $(ast:print $child_b)"
        fi
      else
        if token:next-is special "$modifier"; then
          token:peek -g glued
          if $glued; then
            opname="$opname$modifier"
            token:skip
          fi
        else
          token:peek -v postname -g glued
          if $glued && ! token:next-is special '}'; then
            ast:error "Invalid string operation: $opname$postname"
          fi
        fi
      fi
      ;;

    *)
      if [ -n "$child_b" ]; then
        ast:error "trailling expression after operation name: $(ast:print $child_b)"
      else
        token:peek -v postname -g glued
        if $glued && ! token:next-is special '}'; then
          ast:error "Invalid string operation: $opname$postname"
        fi
      fi
      ;;
  esac

  case "$opname" in
    length|indirect)
      ast:make "$out" "string-$opname" "$varname"
      ;;
    keys)
      local at
      ast:make at name '@'
      ast:make "$out" indirect-indexing-substitution "$varname" $at
      ;;
    slice)
      local start len
      NOBC=true ast:parse:math start
      if token:next-is name length; then
        token:require name length
        NOBC=true ast:parse:math len
        ast:make "$out" string-slice "$varname" $start $len
      else
        ast:make "$out" string-slice-from "$varname" $start
      fi
      ;;
    from)
      local from to
      NOBC=true ast:parse:math from
      token:require name to
      NOBC=true ast:parse:math to
      ast:make "$out" string-from "$varname" $from $to
      ;;
    index)
      local index
      NOBC=true ast:parse:math index
      ast:make "$out" string-index "$varname" $index
      ;;
    *fix*|*case*)
      local pattern op opval optype
      case "$opname" in
        suffix)      opval='%'  optype='removal';;
        prefix)      opval='#'  optype='removal';;
        suffix\*)    opval='%%' optype='removal';;
        prefix\*)    opval='##' optype='removal';;
        lowercase)   opval=','  optype='case'   ;;
        uppercase)   opval='^'  optype='case'   ;;
        lowercase\*) opval=',,' optype='case'   ;;
        uppercase\*) opval='^^' optype='case'   ;;
        *)
          ast:error "Invalid string operation: $opname"
          ;;
      esac
      ast:make op name "$opval"
      ast:parse:pattern 'string-op' pattern
      ast:make "$out" "string-$optype" "$varname" $pattern $op
      ;;
    replace*)
      local pattern by op opval
      case "$opname" in
        replace)   opval='/'  ;;
        replace\*) opval='//' ;;
        *)
          ast:error "Invalid string operation: $opname"
          ;;
      esac
      ast:make op name "$opval"
      ast:parse:pattern 'replace' pattern
      token:require name by
      ast:parse:pattern 'string-op' by
      ast:make "$out" string-replace "$varname" $pattern $by $op
      ;;
    *set\?|*empty\?)
      local expr op opval def defval
      ast:conditional-exp-operators "$opname" opval defval
      ast:make op  name "$opval"
      ast:make def name "$defval"
      ast:make "$out" string-test "$varname" $op $def
      ;;
    unset*|empty*|set*|nonempty*)
      local expr op opval
      case "$opname" in
        unset)     opval='-'  ;;
        unset=)    opval='='  ;;
        empty)     opval=':-' ;;
        empty=)    opval=':=' ;;
        set)       opval='+'  ;;
        nonempty)  opval=':+' ;;
        set!)      opval='?'  ;;
        nonempty!) opval=':?' ;;
        *)
          ast:error "Invalid string operation: $opname"
          ;;
      esac
      ast:make op name "$opval"
      if [ -n "$child_c" ]; then
        expr=$child_c
      else
        ast:parse:expr expr
      fi
      ast:to-double-string $expr
      ast:make "$out" string-default "$varname" $expr $op
      ;;

    ref)
      ast:make "$out" variable-reference "$varname"
      ;;
    *)
      ast:error "Invalid string operation: $opname"
      ;;
  esac

}

ast:parse:parameter-substitution() {
  if [ -z ${__noshadow_19_+x} ]; then
    local __noshadow_19_
    local __noshadow_19_3
  fi
  __shadowing_ast:parse:parameter-substitution "$1" "$2"  __noshadow_19_3
  setvar "$3" "$__noshadow_19_3"
}



__shadowing_ast:conditional-exp-operators() {
  local opname="$1" op="$2" def="$3"
  local opval defval
  case "$opname" in
    unset\?)    opval='+1'   defval='0';;
    set\?)      opval='+-1'  defval='1';;
    empty\?)    opval=':+1'  defval='0';;
    nonempty\?) opval=':+-1' defval='1';;
  *)
    ast:error "Invalid string operation: $opname"
    ;;
  esac
  setvar "$op"  "$opval"
  setvar "$def" "$defval"
}

ast:conditional-exp-operators() {
  if [ -z ${__noshadow_20_+x} ]; then
    local __noshadow_20_
    local __noshadow_20_2 __noshadow_20_3
  fi
  __shadowing_ast:conditional-exp-operators "$1"  __noshadow_20_2 __noshadow_20_3
  setvar "$2" "$__noshadow_20_2"
setvar "$3" "$__noshadow_20_3"
}


# ast:parse:command-substitution
#
# Parse expressions of the form $(command ...) or $(math <math expression>)
#


__shadowing_ast:parse:command-substitution() {
  local out="$1"
  local subst cmd call assigns

  ast:push-state '('
  ast:parse:command-call '' call
  ast:pop-state

  ast:make subst command-substitution '' $call
  setvar "$out" $subst
}

ast:parse:command-substitution() {
  if [ -z ${__noshadow_21_+x} ]; then
    local __noshadow_21_
    local __noshadow_21_1
  fi
  __shadowing_ast:parse:command-substitution  __noshadow_21_1
  setvar "$1" "$__noshadow_21_1"
}


# FILE: ast/substitutions.bash

# ast:parse:expr $out
#
# Basic expression, which can be a name, string,
# list, assign, substitution or concatenation.
#


__shadowing_ast:parse:expr() {
  local out="$1" allowcat=${2-true}
  local value class glued
  local root root_head=undefined
  local expression exprnum=0 last_expression

  case "$allowcat" in
    --nocat) allowcat=false ;;
    --cat)   allowcat=true ;;
  esac

  ast:new root

  while [ $root_head = undefined ]; do
    token:ignore-whitespace
    token:get -v value -c class -g glued

    if ${AST_MATH_MODE-false} && [ $class = special ]; then
      case "$value" in
        '+'|'-'|'/'|'*'|'^') glued=true ;;
      esac
    fi

    while ast:state-is '(' && [[ $class =~ (newline|whitespace) ]]; do
      token:get -v value -c class -g glued
    done

    if { $glued && $allowcat; } || [ $exprnum = 0 ]; then
      case $class in
        name|string)
          ast:make expression $class "$value"
          ;;
        special)
          case "$value" in
            '$')
              ast:parse:substitution false expression
              ;;

            '${')
              ast:parse:curly-substitution expression
              ;;

            '$(')
              ast:parse:command-substitution expression
              ;;
            '$#')
              local next_value next_class
              token:peek -v next_value -c next_class

              if [ $next_class = "name" ] && [[ "$next_value" =~ [a-zA-Z_][a-zA-Z_0-9]* ]]; then
                ast:set $root value $next_value
                root_head=array-length
                token:skip
              else
                ast:make root name '$#'
                root_head=name
              fi
              ;;

            '('|'{')
              if [ $exprnum -gt 0 ]; then
                root_head=determinable
              else
                ast:push-state $value
                if ${AST_MATH_MODE-false}; then
                  ast:parse:list expression
                else
                  ast:parse:list root
                  root_head=list
                fi
                ast:pop-state
              fi
              ;;

            ')'|']'|'}')
              local opener
              case $value in
                ')') opener='('; ;;
                ']') opener='['; ;;
                '}') opener='{'; ;;
              esac
              if [ $exprnum -gt 0 ] && { ast:state-is $opener || ${AST_MATH_MODE-false}; }; then
                root_head=determinable
              else
                root_head=name
                ast:clear $root
                ast:make root name "$value"
              fi
              ;;

            ':')
              if ! ast:state-is '{' && [ $exprnum = 1 ] && ast:is $last_expression name; then
                local stream_position pcolon_value pcolon_class pcolon_glued
                token:mark-position stream_position
                token:skip
                token:get -v pcolon_value -c pcolon_class -g pcolon_glued
                token:return-to-mark $stream_position

                if [ "$pcolon_value:$pcolon_class:$pcolon_glued" = "=:special:true" ]; then
                  ast:clear $root
                  ast:parse:conditional-assign $last_expression root
                  ast:from $root head root_head
                else
                  ast:make expression name ":"
                fi
              else
                ast:make expression name ":"
              fi
              ;;

            '=')
              if [ $exprnum = 1 ] && ast:is $last_expression name; then
                exprnum=2
                ast:parse:assign $root
                ast:from $root head root_head

              elif [ $exprnum = 2 ] && ast:is $last_expression name; then
                local name op value
                ast:children $root name op
                ast:from $op value op

                case "$op" in
                  '+'|'-'|'*'|'/'|'^'|'%')
                    ast:parse:math-assign $root $name "$op"
                    root_head=math-assign
                    ;;
                  '@')
                    ast:parse:push-assign $root $name
                    root_head=indexing-assign
                    ;;
                  '&')
                    ast:parse:concat-assign $root $name
                    root_head=concat-assign
                    ;;
                  *)
                    ast:make expression string "="
                    ;;
                esac

              else
                ast:make expression string "="
              fi
              ;;
            '[')
              if [ $exprnum = 1 ] && ast:is $last_expression name; then
                local index
                ast:push-state '['
                ast:parse:expr index
                token:require special ']'
                ast:pop-state

                if token:next-is special '='; then
                  token:skip
                  ast:parse:expr expression
                  root_head=indexing-assign
                  ast:push-child $root $index
                  ast:push-child $root $expression
                else
                  local left_bracket right_bracket
                  ast:make left_bracket  name '['
                  ast:make right_bracket name ']'

                  ast:push-child $root $left_bracket
                  ast:push-child $root $index

                  expression=$right_bracket
                  exprnum=3
                fi
              elif [ $exprnum -gt 0 ]; then
                root=determinable
              else
                ast:push-state '['
                if ${AST_MATH_MODE-false}; then
                  ast:parse:list expression
                else
                  ast:parse:list expression
                  root_head=list
                fi
                ast:pop-state
              fi
              ;;

            '+'|'-'|'*'|'/'|'^'|'%')
              if ${AST_MATH_MODE-false}; then

                if [ $exprnum = 0 ]; then
                  if [ $value = '+' ] || [ $value = '-' ]; then
                    ast:parse:math-unary $root $value
                  else
                    ast:error "$value is not an unary operator"
                  fi

                elif [ $exprnum = 1 ]; then
                  ast:parse:math-binary $root $last_expression "$value"

                else
                  ast:error "trailling $value in math expression"
                fi
                ast:from $root head root_head

              else
                if [ "$value" = '-' ] && [ "$exprnum" = 0 ]; then
                  ast:parse:flag "$value" expression
                else
                  ast:make expression name "$value"
                fi
              fi
              ;;

            '--')
               ast:parse:flag "$value" expression
               ;;

            *)
              if [ $value = '-' ]; then
                ast:parse:flag expression
              else
                ast:make expression name "$value"
              fi
              ;;
          esac
          ;;
        newline|eof)
          root_head=$class
          ;;
        *)
          ast:error "token of class $class found when parsing an expression ast"
          ;;
      esac

      if [ $root_head = undefined ]; then
        ast:push-child $root $expression
        exprnum=$((exprnum+1))
        last_expression=$expression
      fi
    else
      root_head=determinable
    fi

    if [ $root_head = determinable ]; then
      if [ $exprnum = 1 ]; then
        ast:clear $root
        root=$last_expression
        ast:from $root head root_head
      else
        root_head=cat
      fi
      token:backtrack
    fi
  done
  ast:set $root head $root_head

  setvar "$out" $root
}

ast:parse:expr() {
  if [ -z ${__noshadow_22_+x} ]; then
    local __noshadow_22_
    local __noshadow_22_1
  fi
  __shadowing_ast:parse:expr  __noshadow_22_1
  setvar "$1" "$__noshadow_22_1"
}



# ast:parse:specific-expr $required $out
#
# Parse an expression, errors out if it's
# not the required one, otherwise put it
# in $out.
#


__shadowing_ast:parse:specific-expr() {
  local expr expr_head required="$1" out="$2"
  ast:parse:expr expr
  ast:from $expr head expr_head

  if [ $expr_head = $required ]; then
    setvar "$out" $expr
  else
   ast:error "Wrong expression: Found a $expr_head when a $required was required"
 fi
}

ast:parse:specific-expr() {
  if [ -z ${__noshadow_23_+x} ]; then
    local __noshadow_23_
    local __noshadow_23_2
  fi
  __shadowing_ast:parse:specific-expr "$1"  __noshadow_23_2
  setvar "$2" "$__noshadow_23_2"
}

# FILE: ast/expressions.bash
# ast:parse:math $out
#
# Entry point for parsing a math expression.
#


__shadowing_ast:parse:math() {
  local out="$1"
  local expr math_expr
  local float_precision math_type

  if ast:state-is topmath; then
    math_type="top"
  else
    math_type="expr"
  fi


  AST_MATH_MODE=true ast:parse:expr expr

  ast:parse:validate-math-operand $expr

  if token:next-is name && ! ${NOBC-false}; then
    token:get -v float_precision
    if [[ "$float_precision" =~ [0-9]+ ]]; then
      ast:make math_expr math-float "$float_precision" $expr
    else
      ast:error "expected number for floating point precision in math expression, got $float_precision"
    fi
  else
    ast:make math_expr "math-$math_type" '' $expr
  fi

  setvar "$out" $math_expr
}

ast:parse:math() {
  if [ -z ${__noshadow_24_+x} ]; then
    local __noshadow_24_
    local __noshadow_24_1
  fi
  __shadowing_ast:parse:math  __noshadow_24_1
  setvar "$1" "$__noshadow_24_1"
}



# ast:parse:math-unary $expr $op
#
# Parse math expressions of the forms -expr or +expr.
#

ast:parse:math-unary() {
  local expr="$1" op="$2"
  local operand value head

  ast:parse:expr operand
  ast:parse:validate-math-operand $operand
  ast:push-child $expr $operand

  ast:set $expr value "$op"
  ast:set $expr head  math
}

# ast:parse:math-binary $expr $left $op
#
# Parse math expressions of the form expr op expr.
#

ast:parse:math-binary() {
  local expr="$1" left="$2" op="$3"
  local right class

  token:peek -c class
  while [[ $class =~ (newline|whitespace) ]]; do
    token:skip
    token:peek -c class
  done
  if [ $class = eof ] && ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
    POWSCRIPT_INCOMPLETE_STATE="m$op"
    exit
  fi
  ast:parse:expr right
  ast:parse:validate-math-operand $left
  ast:parse:validate-math-operand $right
  ast:push-child $expr $right

  ast:set $expr value "$op"
  ast:set $expr head  math
}


# ast:parse:validate-math-operand $operand
#
# Checks that the operand is a valid math expression, which can be
#  - var, becoming $var
#  - $var, $(command ...), $(math expr), expr
#  - (expr), [expr], {expr}
# Anything else is an error.
#

ast:parse:validate-math-operand() {
  local operand="$1" value head

  ast:all-from "$operand" -v value -h head

  case "$head" in
    name)
      if [[ "$value" =~ ^[~]?([a-zA-Z_][a-zA-Z_0-9]*|@)$ ]]; then
        local default_op default_value
        ast:make default_op    name ":-"
        ast:make default_value name "0"
        ast:set $operand head string-default
        ast:set $operand children "$default_value $default_op"
      elif [[ "$value" =~ ^(0x[0-9a-fA-F]+|[0-9]+)$ ]]; then
        true
      else
        ast:error "invalid variable name '$value' in math expression"
      fi
      ;;
    *substitution|string-*|math)
      ;;
    list)
      local element elements count=0
      ast:from $operand children elements
      for element in $elements; do
        ast:parse:validate-math-operand $element
        count=$((count+1))
      done
      if [ ! $count = 1 ]; then
        ast:error "invalid math expression $(ast:print operand): trailling expressions in parentheses"
      fi
      ;;
    *)
      ast:error "not a valid math expression: $(ast:print operand) :: $head"
      ;;
  esac
}

# FILE: ast/math.bash

__shadowing_ast:parse:flag() {
  local dash="$1" out="$2"
  local expr_value='' class glued
  local flag_type only


  case "$dash" in
    '-')  flag_type=single-dash ;;
    '--') flag_type=double-dash ;;
  esac

  token:peek -v value -c class -g glued
  if $glued; then
    case "$class" in
      'name')
         token:skip
         expr_value="$value"
         only=""
         ;;
      *)
        only=yes
        ;;
    esac
  else
    only=yes
  fi
  ast:make "$out" "flag-$flag_type${only:+-only}" "$value"
}

ast:parse:flag() {
  if [ -z ${__noshadow_25_+x} ]; then
    local __noshadow_25_
    local __noshadow_25_2
  fi
  __shadowing_ast:parse:flag "$1"  __noshadow_25_2
  setvar "$2" "$__noshadow_25_2"
}

# FILE: ast/flag.bash
# ast:parse:command-call $command_ast $out
#
# parse an AST of the form: command expr1 expr2...
#


__shadowing_ast:parse:command-call() {
  local assigns="$1" out="$2"
  local cmd

  if [ -z "$assigns" ]; then
    ast:make assigns 'assign-sequence' ''
  fi

  ast:parse:expr cmd
  ast:parse:command-call-with-cmd "$assigns" "$cmd" "$out"
}

ast:parse:command-call() {
  if [ -z ${__noshadow_26_+x} ]; then
    local __noshadow_26_
    local __noshadow_26_2
  fi
  __shadowing_ast:parse:command-call "$1"  __noshadow_26_2
  setvar "$2" "$__noshadow_26_2"
}



__shadowing_ast:parse:command-call-with-cmd() {
  local assigns=$1 command_ast=$2 out="$3"

  if ast:is $command_ast name math; then
    local state
    ast:parse:math "$out"
    ast:last-state state
    case "$state" in
      '(') token:require special ')' ;;
      '[') token:require special ']' ;;
      '{') token:require special '}' ;;
    esac

  else
    local expression child predicate
    ast:make expression call '' $assigns $command_ast

    if ast:state-is ==; then
      predicate='ast:parse:not-binary-conditional %'
    else
      predicate='true'
    fi
    ast:parse:sequence $expression "$predicate"
    setvar "$out" $expression
  fi
}

ast:parse:command-call-with-cmd() {
  if [ -z ${__noshadow_27_+x} ]; then
    local __noshadow_27_
    local __noshadow_27_3
  fi
  __shadowing_ast:parse:command-call-with-cmd "$1" "$2"  __noshadow_27_3
  setvar "$3" "$__noshadow_27_3"
}



ast:parse:not-binary-conditional() {
  local expr="$1"
  local name

  if ast:is $expr name; then
    ast:from $expr value name
    case "$name" in
      and|or|'&&'|'||') return 1 ;;
      *)                return 0 ;;
    esac
  fi
  return 0
}
# FILE: ast/commands.bash

__shadowing_ast:parse:declare() {
  local out="$1"
  local glob='' type

  if token:next-is name global; then
    glob="global "
    token:skip
  fi

  token:get -v type

  case "$type" in
    integer|string|array|map) ;;
    *) ast:error "invalid type $type" ;;
  esac

  type="$glob$type"

  ast:make "$out" declare
  ast:set "${!out}" value "$type"
  ast:parse:sequence "${!out}" 'ast:is % name'
}

ast:parse:declare() {
  if [ -z ${__noshadow_28_+x} ]; then
    local __noshadow_28_
    local __noshadow_28_1
  fi
  __shadowing_ast:parse:declare  __noshadow_28_1
  setvar "$1" "$__noshadow_28_1"
}

# FILE: ast/declare.bash
# ast:parse:block
#
# Parses an indent-based sequence of expressions divided by newlines.
# e.g.
#   block-starting-expr
#     x=1
#     echo $(math x + 1)
#


__shadowing_ast:parse:block() {
  local state=$1 out="$2"
  local expr child indent_state indent_layers
  local value class token_line ln="0"

  ast:make expr block

  ast:push-state $state
  ast:new-indentation

  ast:indentation-layers indent_layers
  ast:set $expr value $indent_layers

  indent_state=ok

  while [ ! $indent_state = end ]; do
    token:peek -v value -c class -ls token_line

    ast:test-indentation "$value" $class indent_state

    case $indent_state in
      ok)
        ast:parse:top child
        if [ ! "$child" = -1 ]; then
          ast:push-child $expr $child
          ln="$token_line"
        fi
        ;;
      error*)
        if [ $class = eof ] && ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
          POWSCRIPT_INCOMPLETE_STATE="$(ast:last-state)"
          exit
        fi

        local req found or_more=""
        [ $indent_state = error-start ] && or_more=" or more"

        ast:indentation-required req
        ast:count-indentation "$value" $class found
        ast:error "$indent_state : indentation error at line $token_line, expected $req spaces$or_more, found $found."
        ;;
    esac
  done

  ast:pop-state
  ast:pop-indentation
  setvar "$out" $expr
}

ast:parse:block() {
  if [ -z ${__noshadow_29_+x} ]; then
    local __noshadow_29_
    local __noshadow_29_2
  fi
  __shadowing_ast:parse:block "$1"  __noshadow_29_2
  setvar "$2" "$__noshadow_29_2"
}



# FILE: ast/blocks.bash
# ast:parse:pattern
#
# Parse a pattern to be used by match or case.
#


__shadowing_ast:parse:pattern() {
  local pattern_type="$1" out="$2"
  local nclass class value glued
  local pattern unfinished=true nesting=0 escaped=false
  local pattern_expr midpat_expansion pattern_cat catted=false

  finish() {
    unfinished=false
    token:backtrack
  }

  ast:make pattern_cat cat ''
  pattern=""

  while $unfinished; do
    token:get -v value -c class -g glued

    if { [ -n "$pattern" ] || $catted; } && ! $glued; then
      pattern+=" "
    fi

    if $escaped; then
      escaped=false
      case "$class" in
        name|special) pattern+="\\$value"   ;;
        string)       pattern+="\\'$value'" ;;
      esac
      continue
    fi

    case "$class" in
      newline|eof)
        finish
        ;;
      name)
        case "$pattern_type:$value" in
          ==:or)       finish ;;
          ==:and)      finish ;;
          replace:by)  finish ;;
          *:\\)        pattern+='\\' ;;
          *)           pattern+="$value" ;;
        esac
        ;;
      special)
        case "$pattern_type:$value:$nesting" in
          string-op:[{]:*)
            nesting=$((nesting+1))
            pattern+="$value"
            ;;

          string-op:[}]:0)
            finish
            ;;

          string-op:[}]:*)
            pattern+="$value"
            nesting=$((nesting-1))
            ;;

          *:\\:*)
            escape=true
            ;;

          *:[$]*:*)
            token:backtrack
            ast:make pattern_expr pattern "$pattern"
            ast:parse:expr midpat_expansion --nocat
            ast:push-child $pattern_cat $pattern_expr
            ast:push-child $pattern_cat $midpat_expansion
            pattern=''
            catted=true
            ;;

          *)
            pattern+="$value"
            ;;
          esac
        ;;
      string)
        pattern+="'$value'"
        ;;
    esac
  done

  if $catted; then
    if [ -n "$pattern" ]; then
      ast:make pattern_expr pattern "$pattern"
      ast:push-child $pattern_cat $pattern_expr
    fi
    setvar "$out" $pattern_cat
  else
    ast:make "$out" pattern "$pattern"
  fi
}

ast:parse:pattern() {
  if [ -z ${__noshadow_30_+x} ]; then
    local __noshadow_30_
    local __noshadow_30_2
  fi
  __shadowing_ast:parse:pattern "$1"  __noshadow_30_2
  setvar "$2" "$__noshadow_30_2"
}


# FILE: ast/patterns.bash
# ast:parse:if $block_type $out
#
# Parses an expression of the form:
#   if <conditional>
#     <block>
#   elif <conditional> # optional, as many as we want
#     <block>
#   else # optional, only one allowed
#     <block>
#


__shadowing_ast:parse:if() {
  local block_type=$1 out="$2"
  local expr conditional block

  ast:new expr
  case "$block_type" in
    if) ast:set $expr head if; ;;
    ef) ast:set $expr head elif; ;;
    *)  ast:error "invalid block type for if expression";;
  esac

  ast:parse:conditional conditional
  ast:parse:require-newline "condition in if statement"
  ast:parse:block $block_type block

  ast:push-child $expr $conditional
  ast:push-child $expr $block

  ast:parse:post-if $expr

  setvar "$out" $expr
}

ast:parse:if() {
  if [ -z ${__noshadow_31_+x} ]; then
    local __noshadow_31_
    local __noshadow_31_2
  fi
  __shadowing_ast:parse:if "$1"  __noshadow_31_2
  setvar "$2" "$__noshadow_31_2"
}



# ast:parse:post-if $if_ast
#
# Checks if the if expressions is continued in
# an elif or else and parses accordingly.
#

ast:parse:post-if() {
  local if_ast="$1"
  local value class clause_type

  token:peek -v value -c class
  if [ $class = whitespace ]; then
    token:skip
    token:peek -v value -c class
    token:backtrack
  fi

  if [ $class = name ]; then
    clause_type="$value"
  else
    clause_type=none
  fi

  case "$clause_type" in
    else)
      local else_ast else_block
      ast:make else_ast else

      token:ignore-whitespace
      token:skip
      ast:parse:require-newline "else statement"
      ast:parse:block el else_block

      ast:push-child $else_ast $else_block
      ast:push-child $if_ast $else_ast
      ;;
    elif)
      local elif_ast
      token:ignore-whitespace
      token:skip
      ast:parse:if ef elif_ast

      ast:push-child $if_ast $elif_ast
      ;;
    *)
      local end_ast
      ast:new end_ast
      ast:set $end_ast head end_if

      ast:push-child $if_ast $end_ast
      ;;
  esac
}


# ast:parse:while $out
#
# Parses and expression of the form:
#   while <expr>
#     <block>
#


__shadowing_ast:parse:while() {
  local out="$1"
  local condition block

  ast:parse:conditional condition
  ast:parse:require-newline 'while statement'
  ast:parse:block wh block

  ast:make "$out" 'while' '' $condition $block
}

ast:parse:while() {
  if [ -z ${__noshadow_32_+x} ]; then
    local __noshadow_32_
    local __noshadow_32_1
  fi
  __shadowing_ast:parse:while  __noshadow_32_1
  setvar "$1" "$__noshadow_32_1"
}



# ast:parse:for $out
#
# Parses an expression of the form:
#   for <var> in <expr>
#     <block>
#
# or
#   for <var> of <expr>
#     <block>
#
# or
#  for <var>,<var> of <expr>
#    <block>
#
# or
#  for <var> from <expr>
#    <block>
#


__shadowing_ast:parse:for() {
  local out="$1"
  local value value2 class class2 result
  local var_ast var2_ast elements_ast file_ast block_ast

  token:get -v value -c class

  if [ ! "$class" = name ]; then
    ast:error "Expected a variable name in 'for' expression, found a $class token instead."
  else
    if token:next-is name 'in'; then
      token:skip

      ast:make var_ast name "$value"

      ast:make elements_ast elements
      ast:parse:sequence $elements_ast

      ast:parse:block 'for' block_ast

      ast:make result 'for' '' $var_ast $elements_ast $block_ast

    elif token:next-is name 'of'; then
      token:skip

      ast:make var_ast name "$value"

      ast:parse:expr elements_ast
      ast:parse:require-newline 'for-of'

      ast:parse:block 'for' block_ast

      ast:make result 'for-of' '' $var_ast $elements_ast $block_ast

    elif token:next-is name 'from'; then
      token:skip

      ast:make var_ast name "$value"

      ast:parse:expr file_ast
      ast:parse:require-newline 'for-from'

      ast:parse:block 'for' block_ast

      ast:make result 'for-from' '' $var_ast $file_ast $block_ast

    elif token:next-is special ','; then
      token:skip
      token:get -v value2 -c class2

      if [ ! "$class" = name ]; then
        ast:error "Expected a variable name in 'for-of' expression, found a $class2 token instead."
      fi

      token:require name 'of'

      ast:make var_ast  name "$value"
      ast:make var2_ast name "$value2"

      ast:parse:expr elements_ast
      ast:parse:require-newline 'for-of'

      ast:parse:block 'for' block_ast

      ast:make result 'for-of-map' '' $var_ast $var2_ast $elements_ast $block_ast
    else
      ast:error "expected 'in', 'of' or ',' after variable name in for expression, found a ${class}: $value"
    fi
  fi

  setvar "$out" $result
}

ast:parse:for() {
  if [ -z ${__noshadow_33_+x} ]; then
    local __noshadow_33_
    local __noshadow_33_1
  fi
  __shadowing_ast:parse:for  __noshadow_33_1
  setvar "$1" "$__noshadow_33_1"
}



# ast:parse:switch $out
#
# Parses expressions of the form:
#  switch <expr>
#    case <expr> # at least one
#      <block>
#


__shadowing_ast:parse:switch() {
  local out="$1"
  local value cases_block case_ast cases
  local count

  ast:parse:expr value
  ast:parse:require-newline "switch statement"
  ast:parse:block swt cases_block

  ast:from $cases_block children cases

  for case_ast in $cases; do
    if ! ast:is $case_ast 'case'; then
      ast:error "expected case block in switch statement, found $(ast:from $case_ast head)"
    fi
  done

  ast:make "$out" switch '' $value $cases_block
}

ast:parse:switch() {
  if [ -z ${__noshadow_34_+x} ]; then
    local __noshadow_34_
    local __noshadow_34_1
  fi
  __shadowing_ast:parse:switch  __noshadow_34_1
  setvar "$1" "$__noshadow_34_1"
}



# ast:parse:case $out
#
# Parses the case blocks from switch statements
#


__shadowing_ast:parse:case() {
  local out="$1"
  local pattern block

  if ! ast:state-is swt; then
    ast:error "case blocks must be inside switch blocks"
  fi

  ast:parse:pattern 'case' pattern
  ast:parse:require-newline "case statement"
  ast:parse:block cs block

  ast:make "$out" 'case' '' $pattern $block
}

ast:parse:case() {
  if [ -z ${__noshadow_35_+x} ]; then
    local __noshadow_35_
    local __noshadow_35_1
  fi
  __shadowing_ast:parse:case  __noshadow_35_1
  setvar "$1" "$__noshadow_35_1"
}


# ast:parse:assert $out
#
# Parses an assert expression of the form:
#
# assert <condition> "<message>"
#
# which does nothing if the condition is true
# and exits with an error if the condition is
# false.


__shadowing_ast:parse:assert() {
  local out="$1"
  local condition message

  ast:parse:conditional condition

  if token:next-is newline || token:next-is eof; then
    ast:make message print_condition '' $condition
  else
    ast:parse:expr message
  fi

  ast:parse:require-newline "assert"
  ast:make "$out" assert '' $condition $message
}

ast:parse:assert() {
  if [ -z ${__noshadow_36_+x} ]; then
    local __noshadow_36_
    local __noshadow_36_1
  fi
  __shadowing_ast:parse:assert  __noshadow_36_1
  setvar "$1" "$__noshadow_36_1"
}



# ast:parse:test $out
#
# Parses an expression of the form:
#   test <condition>
#


__shadowing_ast:parse:test() {
  local out="$1"
  ast:parse:conditional "$1"
  ast:parse:require-newline "test"
}

ast:parse:test() {
  if [ -z ${__noshadow_37_+x} ]; then
    local __noshadow_37_
    local __noshadow_37_1
  fi
  __shadowing_ast:parse:test  __noshadow_37_1
  setvar "$1" "$__noshadow_37_1"
}



# ast:parse:conditional $out
#
# Parses an expression of the form:
#   * -flag <expr>
#   * <expr> <comparison> <expr>
#   * not <conditional>
#   * <conditional> and/or <conditional>
#


__shadowing_ast:parse:conditional() {
  local out="$1"
  local condition operator

  ast:parse:seek-conditional-operator operator

  if [ "$operator" = "not" ]; then
    token:skip
    ast:parse:negated-conditional condition

  elif [ "$operator" = '-' ]; then
    local flag
    ast:parse:expr flag
    ast:parse:flag-conditional $flag condition

  elif [ -n "$operator" ]; then
    local left right
    case "$operator" in
      '>'|'<'|'<='|'>='|'==')
        NOBC=true ast:parse:math left
        token:skip
        NOBC=true ast:parse:math right
        ;;
      match)
        ast:parse:expr left
        token:skip
        ast:parse:pattern '==' right
        ;;
      is|isnt|'='|'!=')
        ast:parse:expr left
        token:skip
        ast:parse:expr right
        ;;
    esac
    ast:make condition condition "$operator" $left $right

  else
    local initial
    ast:parse:expr initial

    if ast:is $initial string-test || ast:is $initial array-test; then
      local zero
      ast:make zero name '0'
      ast:make condition condition 'is' $initial $zero
    else
      ast:parse:command-conditional $initial condition
    fi
  fi
  ast:parse:composite-conditional $condition condition
  setvar "$out" $condition
}

ast:parse:conditional() {
  if [ -z ${__noshadow_38_+x} ]; then
    local __noshadow_38_
    local __noshadow_38_1
  fi
  __shadowing_ast:parse:conditional  __noshadow_38_1
  setvar "$1" "$__noshadow_38_1"
}



# ast:parse:seek-conditional-operator $out
#
# Look ahead and look for an conditional operator
#


__shadowing_ast:parse:seek-conditional-operator() {
  local out="$1"
  local value class glued stream_position discard_expr

  token:mark-position stream_position

  token:peek -v value -c class
  case "$value:$class" in
    "not:name")
      setvar "$out" 'not'
      ;;
    "-:special")
      token:skip
      token:peek -v value -c class -g glued
      case "$value:$class:$glued" in
        [a-z]:name:true)
          token:skip
          token:peek -g glued
          if ! $glued; then
            setvar "$out" '-'
          else
            token:backtrack
            token:backtrack
          fi
          ;;
        *)
          token:backtrack
          ;;
      esac
      ;;
  esac

  while [ -z "${!out}" ]; do
    token:get -v value -c class -g glued
    case "$class" in
      name|special)
        case "$value" in
          is|isnt|'>'|'<'|'<='|'>='|'!='|'='|'=='|match)
            if ! $glued; then
              setvar "$out" "$value"
            fi
            ;;
          and|or|"&&"|"||")
            break
            ;;
          \$)
            token:backtrack
            ast:parse:expr discard_expr
            ;;
        esac
        ;;
      newline|eof)
        break
        ;;
    esac
  done
  token:return-to-mark $stream_position
}

ast:parse:seek-conditional-operator() {
  if [ -z ${__noshadow_39_+x} ]; then
    local __noshadow_39_
    local __noshadow_39_1
  fi
  __shadowing_ast:parse:seek-conditional-operator  __noshadow_39_1
  setvar "$1" "$__noshadow_39_1"
}



# ast:parse:negated-conditional $out
#
# Parses a conditional and returns a negation of it.
#


__shadowing_ast:parse:negated-conditional() {
  local condition out="$1"

  ast:push-state '!'
  ast:parse:conditional condition
  ast:pop-state

  ast:make "$out" condition not $condition
}

ast:parse:negated-conditional() {
  if [ -z ${__noshadow_40_+x} ]; then
    local __noshadow_40_
    local __noshadow_40_1
  fi
  __shadowing_ast:parse:negated-conditional  __noshadow_40_1
  setvar "$1" "$__noshadow_40_1"
}



# ast:parse:flag-conditional
#
# Parses a conditional of the form -flag <expr>
#


__shadowing_ast:parse:flag-conditional() {
  local flag="$1" out="$2"
  local minus name flagname expr

  ast:from $flag value flagname

  ast:parse:expr expr
  ast:make "$out" condition "-$flagname" $expr
}

ast:parse:flag-conditional() {
  if [ -z ${__noshadow_41_+x} ]; then
    local __noshadow_41_
    local __noshadow_41_2
  fi
  __shadowing_ast:parse:flag-conditional "$1"  __noshadow_41_2
  setvar "$2" "$__noshadow_41_2"
}



# ast:parse:command-conditional
#
# Parses a command call as a conditional
#


__shadowing_ast:parse:command-conditional() {
  local cmd="$1" cmd_ast out="$2"
  local cmd_head assigns value class

  ast:from $cmd head cmd_head

  ast:push-state '=='
  case $cmd_head in
    *assign)
      ast:parse:assign-sequence $cmd cmd_ast
      ;;
    *)
      ast:make assigns assign-sequence
      ast:parse:command-call-with-cmd $assigns $cmd cmd_ast
      ;;
  esac
  ast:pop-state

  ast:make "$out" condition 'command' $cmd_ast
  token:backtrack
}

ast:parse:command-conditional() {
  if [ -z ${__noshadow_42_+x} ]; then
    local __noshadow_42_
    local __noshadow_42_2
  fi
  __shadowing_ast:parse:command-conditional "$1"  __noshadow_42_2
  setvar "$2" "$__noshadow_42_2"
}



# ast:parse:composite-conditional $condition_left $out
#
# Parses a conditional of the form <conditional> and/or <conditional>
#


__shadowing_ast:parse:composite-conditional() {
  local condition_left="$1" condition_right out="$2"
  local value class success=false

  token:peek -v value -c class

  if [ "$class" = name ]; then
    case "$value" in
      "and"|"or"|"&&"|"||")
        if ! ast:state-is '!'; then
          token:skip
          ast:parse:conditional condition_right
          success=true
        elif [[ "$value" =~ ('&&'|'||') ]]; then
          token:skip
          ast:parse:negated-conditional condition_right
          success=true
        fi
      ;;
    esac
  fi
  if $success; then
    ast:make "$out" condition $value $condition_left $condition_right
  else
    setvar "$out" $condition_left
  fi
}

ast:parse:composite-conditional() {
  if [ -z ${__noshadow_43_+x} ]; then
    local __noshadow_43_
    local __noshadow_43_2
  fi
  __shadowing_ast:parse:composite-conditional "$1"  __noshadow_43_2
  setvar "$2" "$__noshadow_43_2"
}


# FILE: ast/conditionals.bash
# ast:parse:function-definition $name $out
#
# Parses an expression of the form:
#   name(...vars...)
#     <block>
#


__shadowing_ast:parse:function-definition() {
  local name=$1 out="$2"
  local expr args block

  ast:make expr function-def

  ast:parse:specific-expr list args
  ast:parse:require-newline "function definition"
  ast:parse:block fn block

  ast:push-child $expr $name
  ast:push-child $expr $args
  ast:push-child $expr $block

  setvar "$out" $expr
}

ast:parse:function-definition() {
  if [ -z ${__noshadow_44_+x} ]; then
    local __noshadow_44_
    local __noshadow_44_2
  fi
  __shadowing_ast:parse:function-definition "$1"  __noshadow_44_2
  setvar "$2" "$__noshadow_44_2"
}


# FILE: ast/functions.bash

__shadowing_ast:parse:await() {
  local out="$1"
  local expr expr_head cmd last_arg then_block done_block var

  ast:parse:command-call '' cmd
  ast:pop-child $cmd last_arg

  if ast:is $last_arg name '|'; then
    ast:pop-child $cmd last_arg
    expr_head='await-pipe'
  elif ast:is $last_arg name 'then'; then
    expr_head='await-then'
  else
    expr_head='await-for'
    var=$last_arg

    ast:pop-child $cmd last_arg
    if ast:is $last_arg name 'for'; then
      ast:pop-child $cmd last_arg
    else
      ast:error "expected 'for' or 'then' after await command"
    fi
  fi

  if ! ast:is $last_arg name 'then'; then
    ast:error "expected 'then' after await command"
  else
    ast:parse:block awt then_block
    if [ ! "$expr_head" = 'await-then' ] && token:next-is 'name' 'when'; then
      ast:parse:when-done done_block
    fi

    ast:make expr $expr_head "" $cmd $var $then_block $done_block
    setvar "$out" $expr
  fi
}

ast:parse:await() {
  if [ -z ${__noshadow_45_+x} ]; then
    local __noshadow_45_
    local __noshadow_45_1
  fi
  __shadowing_ast:parse:await  __noshadow_45_1
  setvar "$1" "$__noshadow_45_1"
}


ast:parse:when-done() {
  token:require name 'when'
  token:require name 'done'
  ast:parse:require-newline "when done"

  ast:parse:block wdn "$1"
}
# FILE: ast/parallel.bash

__shadowing_ast:lower() {
  local expr="$1" out="$2"

  local VarsInScope=''
  declare -i CurrentScope
  declare -A VarTypes
  CurrentScope=0

  typing:scan $expr

  CurrentScope=0
  ast:lower-scanned $expr "$out"
}

ast:lower() {
  if [ -z ${__noshadow_46_+x} ]; then
    local __noshadow_46_
    local __noshadow_46_2
  fi
  __shadowing_ast:lower "$1"  __noshadow_46_2
  setvar "$2" "$__noshadow_46_2"
}



__shadowing_ast:lower-scanned() {
  local expr="$1" out="$2"
  local result
  local expr_head

  ast:from $expr head expr_head

  case $expr_head in
    for-of)
      local var array block lowblock
      local array_name at elements

      ast:children $expr var array block

      ast:lower-scanned $block lowblock

      if ! ast:is $array name; then
        ast:error "for-of expression expected an array's name, got a $(ast:from $array head)"
      fi
      ast:from $array value array_name

      ast:make at name '@'
      ast:make elements indexing-substitution "$array_name" $at

      ast:make result 'for' '' $var $elements $lowblock
      ;;
    for-of-map)
      local key val array block lowblock
      local array_name at elements
      local val_assign val_local val_get key_name key_subst
      local newblock block_value block_children

      ast:children $expr key val array block

      ast:lower-scanned $block lowblock

      if ! ast:is $array name; then
        ast:error "for-of expression expected an array's name, got a $(ast:from $array head)"
      fi
      ast:from $array value array_name

      ast:make at name '@'
      ast:make elements indirect-indexing-substitution "$array_name" $at

      ast:from $key value key_name
      ast:make key_subst simple-substitution "$key_name"
      ast:make val_get indexing-substitution "$array_name" $key_subst
      ast:make val_assign assign '' $val $val_get
      ast:make val_local local '' $val_assign

      ast:all-from $lowblock -v block_value -c block_children
      ast:make newblock block "$block_value" $val_local $block_children

      ast:make result 'for' '' $key $elements $newblock
      ;;
    for-from)
      local var file block lowblock
      local while_block readline

      ast:children $expr var file block

      ast:lower-scanned $block lowblock

      ast:make readline 'readline' '' $var
      ast:make while_block 'while' '' $readline $lowblock
      ast:make result 'file-input' '' $while_block $file
      ;;
    await-then)
      local cmd then
      local set_async var t block
      local lowblock

      ast:children $expr cmd then

      ast:make var 'name' 'ASYNC'
      ast:make t   'name' '1'
      ast:make set_async 'assign' '' $var $t

      ast:make block 'block' '' $cmd $then
      ast:lower-scanned $block lowblock

      ast:make result 'and' '' $lowblock $set_async
      ;;
    await-pipe)
      local cmd then done_block
      local set_async var t block pipe
      local lowblock

      ast:children $expr cmd then done_block

      ast:make var 'name' 'ASYNC'
      ast:make t   'name' '1'
      ast:make set_async 'assign' '' $var $t

      ast:make pipe  'pipe'  '' $cmd  $then
      ast:make block 'block' '' $pipe $done_block
      ast:lower-scanned $block lowblock

      ast:make result 'and' '' $lowblock $set_async
      ;;
    await-for)
      local cmd var then done_block
      local set_async avar t block
      local while_block readline pipe
      local lowblock

      ast:children $expr cmd var then done_block

      ast:make avar 'name' 'ASYNC'
      ast:make t    'name' '1'
      ast:make set_async 'assign' '' $avar $t

      ast:make readline 'readline' '' $var

      ast:make while_block 'while' '' $readline $then

      ast:make pipe  'pipe'  '' $cmd  $while_block
      ast:make block 'block' '' $pipe $done_block
      ast:lower-scanned $block lowblock

      ast:make result 'and' '' $lowblock $set_async
      ;;
    math-assign)
      local var varname value op type

      ast:from $expr value op
      ast:children $expr var value

      ast:from $var value varname
      typing:var-type "$varname" type

      if [ "$PowscriptBackend" = 'bash' ] && [ "$op" = '+' ] && [ "$type" = 'integer' ]; then
        local math_expr

        ast:make math_expr math-assigned "" $value
        ast:make result add-assign "" $var $math_expr

      else
        local math_expr math_assigned subst varname

        ast:from $var value varname

        ast:make subst simple-substitution "$varname"
        ast:make math_expr math "$op" $subst $value
        ast:make math_assigned math-assigned '' $math_expr

        ast:make result assign '' $var $math_assigned
      fi
      ;;
    concat-assign)
      local var varname value type

      ast:children $expr var value

      ast:from $var value varname
      typing:var-type "$varname" type

      if [ "$PowscriptBackend" = 'bash' ] && [ "$type" = string ]; then
        ast:make result add-assign '' $var $value
      else
        result=$expr
      fi
      ;;
    function-def)
      local name args args_extract block block_lowered

      typing:start-scope
      ast:children $expr name args block

      ast:extract-function-arguments $args args_extract

      ast:lower-scanned $block block_lowered
      ast:unshift-child $block_lowered $args_extract
      ast:make result function-def '' $name $args $block_lowered

      typing:end-scope
      ;;
    name|math*|string)
      result=$expr
      ;;
    assign-conditional)
      local assign_type var varname value
      local op def
      ast:from $expr value assign_type
      ast:children $expr var value

      ast:from $var value varname
      ast:conditional-exp-operators "$assign_type?" op def
      ast:make-from-string result "
        if
        - condition is
        -- string-test $varname
        --- name $op
        --- name $def
        -- name 0
        - block
        -- assign
        --+ $var
        --+ $value
        - end_if
      "
      ;;

    assign-ref)
      local var value varname
      local lowvalue

      ast:children $expr var value
      ast:lower-scanned $value lowvalue

      ast:from $var value varname
      ast:make-from-string result "
        block
        - light-assert
        -- condition not
        --- condition is
        ---- string-removal $varname
        ----- pattern __powscript_gensym_reference_variable_
        ----- name #
        ---- simple-substitution $varname
        -- cat
        --- string ERROR:
        --- simple-substitution $varname
        --- string  is not a reference
        - expand
        -- block
        --- assign
        ---- name ~$varname
        ---+ $lowvalue
      "
      ;;

    call)
      local assigns cmd arguments arg arg_head arg_value
      local low_assigns low_cmd low_arguments=""
      local ref_assigns="" ref_returns=""
      ast:children $expr assigns cmd
      ast:from $expr children arguments
      pop 2 arguments $arguments

      ast:lower-scanned $assigns low_assigns
      ast:lower-scanned $cmd     low_cmd

      for arg in $arguments; do
        ast:all-from $arg -v arg_value -h arg_head
        case "$arg_head" in
          variable-reference)
            local refassign refret refvar refname
            ast:gensym refvar reference
            ast:from $refvar value refname
            ast:make-from-string refassign "
              local
              + $refvar
            "
            ast:make-from-string refret "
              assign
              - name $arg_value
              - simple-substitution $refname
            "
            ref_assigns+=" $refassign"
            ref_returns+=" $refret"
            low_arguments+=" $refvar"
            ;;
          *)
            low_arguments+=" $arg"
            ;;
        esac
      done

      if [ -z "$ref_assigns" ]; then
        ast:make result call '' $low_assigns $low_cmd $low_arguments
      else
        ast:make-from-string result "
          block
          + $ref_assigns
          - call
          -+ $low_assigns $low_cmd $low_arguments
          + $ref_returns
        "
      fi
      ;;
    *)
      local expr_value expr_children child lowered_child

      ast:from $expr value expr_value
      ast:from $expr children expr_children

      ast:make result "$expr_head" "$expr_value"

      for child in $expr_children; do
        ast:lower-scanned $child lowered_child
        ast:push-child $result $lowered_child
      done
      ;;
  esac

  setvar "$out" $result
}

ast:lower-scanned() {
  if [ -z ${__noshadow_47_+x} ]; then
    local __noshadow_47_
    local __noshadow_47_2
  fi
  __shadowing_ast:lower-scanned "$1"  __noshadow_47_2
  setvar "$2" "$__noshadow_47_2"
}



__shadowing_ast:extract-function-arguments() {
  local args_expr="$1" out="$2"
  local positionals="" keywords=""
  local arg args arg_head arg_value
  local kw=false has_rest_pos=false has_rest_kw=false
  local rest_keyword rest_positional
  declare -i positional_count keyword_count

  positional_count=0
  keyword_count=0

  ast:from $args_expr children args

  for arg in $args; do
    ast:from $arg head arg_head
    case "$arg_head" in
      name)
        if { $has_rest_pos && ! $kw; } || $has_rest_kw; then
          ast:error "'rest' argument must be the last of it's kind in the list ($arg_value)"

        elif $kw; then
          keyword_count+=1
          ast:from $arg value arg_value
          ast:make arg name $arg_value
          keywords+=" $arg"

        else
          positional_count+=1
          positionals+=" $arg"
        fi
        ;;

      cat)
        local first second more
        ast:children $arg first second more

        if ast:is $first name '@' && ast:is $second name && [ -z "$more" ]; then
          ast:from $second value arg_value

          if { $has_rest_pos && ! $kw; } || $has_rest_kw; then
            ast:error "'rest' argument must be the last of it's kind in the list ($arg_value)"
          elif $kw; then
            has_rest_kw=true
            rest_keyword="$arg_value"
          else
            has_rest_pos=true
            rest_positional="$arg_value"
          fi
        else
          ast:error "Invalid name used for function argument (ast:print $arg)"
        fi
        ;;

      flag-double-dash-only)
        if $kw; then
          ast:error 'can only have one '--' in argument lists'
        else
          kw=true
        fi
        ;;
      *)
        ast:error "invalid function argument: $(ast:print $arg), $arg_head"
        ;;
    esac
  done

  case "$positional_count:$kw" in
    0:false)
      if $has_rest_pos; then
        ast:make-from-string "$out" "
        block
        - declare array
        -- name $rest_positional
        - assign
        -- name $rest_positional
        -- list
        --- simple-substitution @
        "
      else
        ast:make "$out" nothing
      fi
      ;;
    *:false)
      local locals test isset assign subst
      positional_count=1
      ast:make locals local '' $positionals
      ast:make "$out" block '' $locals
      if $has_rest_pos; then
        ast:make-from-string test "
          declare array
          - name $rest_positional
        "
        ast:push-child "${!out}" $test
      fi

      for arg in $positionals; do
        ast:make-from-string test "
          condition and
          - condition >=
          -- name \$#
          -- name $positional_count
          - assign
          -+ $arg
          -- simple-substitution $positional_count
        "
        ast:push-child "${!out}" $test
        positional_count+=1
      done
      if $has_rest_pos; then
        ast:make-from-string test "
          condition and
          - condition >=
          -- name \$#
          -- name $positional_count
          - assign
          -- name $rest_positional
          -- list
          --- string-slice-from @
          ---- name $positional_count
        "
        ast:push-child "${!out}" $test
      fi
      ;;
    *:true)
      local argvar keyvar posvar locals restdecs arg_set arg_test key_assign
      local key keyname keylocals="" keylocal

      ast:gensym argvar keyword_arg
      ast:gensym keyvar keyword_key
      ast:gensym posvar keyword_pos

      for key in $keywords; do
        ast:from $key value keyname
        ast:make keylocal name $keyname
        keylocals+=" $keylocal"
      done

      ast:make locals local '' $argvar $keyvar $posvar $positionals $keylocals

      ast:make-argument-test  "$argvar" "$keyvar" "$posvar" "$positionals" "$rest_positional" arg_test
      ast:make-keyword-assign "$argvar" "$keyvar" "$posvar" "$keywords"    "$rest_keyword"    key_assign
      ast:make-argument-set   "$argvar" "$keyvar" "$posvar" "$arg_test" "$key_assign" arg_set

      if $has_rest_pos || $has_rest_kw; then
        ast:make-from-string restdecs "
          block
          ${rest_positional:+"
            - declare array
            -- name $rest_positional
          "}
          ${rest_keyword:+"
            - declare map
            -- name $rest_keyword
          "}
        "
      fi
      ast:make "$out" block '' $locals $restdecs $arg_set
      ;;
  esac
}

ast:extract-function-arguments() {
  if [ -z ${__noshadow_48_+x} ]; then
    local __noshadow_48_
    local __noshadow_48_2
  fi
  __shadowing_ast:extract-function-arguments "$1"  __noshadow_48_2
  setvar "$2" "$__noshadow_48_2"
}


RANDOM=$(date '+%s')
GENSYM_ID="$RANDOM"
declare -gi GensymCount=0
ast:gensym() {
  GensymCount+=1
  ast:make "$1" name "__powscript_gensym_${2}_variable_${GENSYM_ID}_${GensymCount}_$RANDOM"
}


__shadowing_ast:make-argument-set() {
  local argvar="$1" keyvar="$2" posvar="$3" arg_test="$4" key_assign="$5" out="$6"
  local keyvar_name

  ast:from $keyvar value keyvar_name

  ast:make-from-string "$out" "
    block
    - local
    -- assign
    --+ $posvar
    --- string 1
    - for
    -+ $argvar
    -- simple-substitution @
    -- block
    --- if
    ---- condition is
    ----- simple-substitution $keyvar_name
    ----- string
    ---- block
    ----+ $arg_test
    ---- else
    ----- block
    -----+ $key_assign
  "
}

ast:make-argument-set() {
  if [ -z ${__noshadow_49_+x} ]; then
    local __noshadow_49_
    local __noshadow_49_6
  fi
  __shadowing_ast:make-argument-set "$1" "$2" "$3" "$4" "$5"  __noshadow_49_6
  setvar "$6" "$__noshadow_49_6"
}



__shadowing_ast:make-argument-test() {
  local argvar="$1" keyvar="$2" posvar="$3" positionals="$4" rpos="$5" out="$6"
  local arg argcase cases=""
  local argvar_name posvar_name emptycases
  declare -i poscount=1

  ast:from $argvar value argvar_name
  ast:from $posvar value posvar_name

  for arg in $positionals; do
    ast:make-from-string argcase "
      case
      - name $poscount
      - block
      -- assign
      --+ $arg
      --- simple-substitution $argvar_name
      -- assign
      --+ $posvar
      --- math-expr
      ---- math +
      ----+ $posvar
      ----- name 1
    "
    cases+=" $argcase"
    poscount+=1
  done

  if [ -n "$rpos" ]; then
    ast:make-from-string argcase "
      case
      - pattern *
      - block
      -- indexing-assign
      --- name $rpos
      --- array-length $rpos
      --- simple-substitution $argvar_name
      --+ $posvar
      --- math-expr
      ---- math +
      ----+ $posvar
      ----- name 1
    "
    cases+=" $argcase"
  fi

  if [ -z "$cases" ]; then
    emptycases="---- name true"
  fi

  ast:make-from-string "$out" "
    switch
    - simple-substitution $argvar_name
    - block
    -- case
    --- pattern -*
    --- block
    ---- assign
    ----+ $keyvar
    ----- simple-substitution $argvar_name
    -- case
    --- pattern *
    --- block
  ${cases:+"
    ---- switch
    ----- simple-substitution $posvar_name
    ----- block
    -----+ $cases
    "}
    $emptycases
  "
}

ast:make-argument-test() {
  if [ -z ${__noshadow_50_+x} ]; then
    local __noshadow_50_
    local __noshadow_50_6
  fi
  __shadowing_ast:make-argument-test "$1" "$2" "$3" "$4" "$5"  __noshadow_50_6
  setvar "$6" "$__noshadow_50_6"
}



__shadowing_ast:make-keyword-assign() {
  local argvar="$1" keyvar="$2" posvar="$3" keywords="$4" rkey="$5" out="$6"
  local cases="" keycase key keyname keyshort
  local keyvar_name argvar_name

  ast:from $keyvar value keyvar_name
  ast:from $argvar value argvar_name

  for key in $keywords; do
    ast:from $key value keyname
    keyshort=${keyname:0:1}

    ast:make-from-string keycase "
      case
      - pattern --$keyname|-$keyshort
      - block
      -- assign
      --+ $key
      --- simple-substitution $argvar_name
    "
    cases+=" $keycase"
  done

  if [ -n "$rkey" ]; then
    ast:make-from-string keycase "
    case
    - pattern *
    - block
    -- assign
    --+ $keyvar
    --- string-slice-from $keyvar_name
    ---- name 1
    -- indexing-assign
    --- name $rkey
    --- string-removal $keyvar_name
    ---- pattern -
    ---- name #
    --- simple-substitution $argvar_name
    "
    cases+=" $keycase"
  else
    ast:make-from-string keycase "
    case
    - pattern *
    - block
    -- call
    --- assign-sequence
    --- name :
    "
    cases+=" $keycase"
  fi

  ast:make-from-string "$out" "
    block
    - switch
    -- simple-substitution $keyvar_name
    -- block
    --+ $cases
    - assign
    -+ $keyvar
    -- string
  "
}

ast:make-keyword-assign() {
  if [ -z ${__noshadow_51_+x} ]; then
    local __noshadow_51_
    local __noshadow_51_6
  fi
  __shadowing_ast:make-keyword-assign "$1" "$2" "$3" "$4" "$5"  __noshadow_51_6
  setvar "$6" "$__noshadow_51_6"
}


typing:start-scope() {
  CurrentScope+=1
}

typing:end-scope() {
  return
}


typing:set-type() {
  local var="$1" type="$2"
  VarTypes["${var}|$CurrentScope"]=$type
}

typing:var-type() {
  setvar "$2" "${VarTypes[${1}|$CurrentScope]}"
}

typing:declare-all() {
  local expr="$1" type="$2"
  local var child expr_children

  ast:from $expr children expr_children

  for child in $expr_children; do
    typing:declared-name $child var
    typing:set-type $var $type
  done
}


__shadowing_typing:declared-name() {
  local expr="$1" out="$2"
  local expr_head

  ast:from $expr head expr_head
  case $expr_head in
    name|flag-double-dash-only|cat)
      ;;
    *assign)
      ast:children $expr expr
      ;;
    *)
      ast:error "invalid expression in local/declare: $(ast:print $expr)"
      ;;
  esac

  ast:from $expr value "$out"
}

typing:declared-name() {
  if [ -z ${__noshadow_52_+x} ]; then
    local __noshadow_52_
    local __noshadow_52_2
  fi
  __shadowing_typing:declared-name "$1"  __noshadow_52_2
  setvar "$2" "$__noshadow_52_2"
}



typing:scan() {
  local expr="$1"
  local expr_head

  ast:from $expr head expr_head

  case $expr_head in
    declare)
      local type

      ast:from $expr value type
      type="${type#global }"

      typing:declare-all $expr $type
      ;;

    local)
      typing:declare-all $expr string
      ;;

    function-def)
      local _ args block
      ast:children $expr _ args block

      typing:start-scope

      typing:declare-all $args string
      typing:scan $block

      typing:end-scope
      ;;

    *)
      local child expr_children

      ast:from $expr children expr_children

      for child in $expr_children; do
        typing:scan $child
      done
      ;;
  esac
}

# FILE: ast/lowerer.bash
ast:print() {
  printf '`'
  ast:print-child "$1" "$2"
  echo '`'
}

ast:print-child() {
  local ast=$1 indent=
  local ast_head ast_value ast_children
  ast:from $ast head     ast_head
  ast:from $ast value    ast_value
  ast:from $ast children ast_children

  local child_array=( $ast_children )

  case $ast_head in
    name)
      printf "%s" "$ast_value"
      ;;
    cat)
      local child
      for child in ${child_array[@]:0:$((${#child_array[@]}-1))}; do
        ast:print-child $child
      done
      ast:print-child ${child_array[${#child_array[@]}-1]}
      ;;
    string)
      printf "'%s'" "$ast_value"
      ;;
    call)
      local command=${child_array[1]}
      local argument

      ast:print-child $command
      for argument in ${child_array[@]:2}; do
        printf ' '
        ast:print-child $argument
      done
      ;;
    assign)
      local name=${child_array[0]} value=${child_array[1]}
      ast:print-child $name
      printf '='
      ast:print-child $value
      ;;
    and)
      ast:print-child ${child_array[0]}
      printf " and "
      ast:print-child ${child_array[1]}
      ;;
    indexing-assign)
      local name=${child_array[0]} index=${child_array[1]} value=${child_array[2]}
      ast:print-child $name
      printf '['
      ast:print-child $index
      printf ']='
      ast:print-child $value
      ;;
    math-assign)
      ast:print-child ${child_array[0]}
      printf '%s=' $ast_value
      ast:print-child ${child_array[1]}
      ;;
    concat-assign)
      ast:print-child ${child_array[0]}
      printf '&='
      ast:print-child ${child_array[1]}
      ;;
    simple-substitution)
      printf '$%s' "$ast_value"
      ;;
    indexing-substitution)
      printf '${%s[' "$ast_value"
      ast:print-child ${child_array[0]}
      printf ']}'
      ;;
    indirect-indexing-substitution)
      printf '${!%s[' "$ast_value"
      ast:print-child ${child_array[0]}
      printf ']}'
      ;;
    command-substitution)
      printf '$('
      ast:print-child ${child_array[0]}
      printf ')'
      ;;
    math-top|math-expr)
      printf 'math '
      ast:print-child ${child_array[0]}
      ;;
    math)
      if [ -n "${child_array[1]}" ]; then
        ast:print-child ${child_array[0]}
        printf '%s' "$ast_value"
        ast:print-child ${child_array[1]}
      else
        printf '%s' "$ast_value"
        ast:print-child ${child_array[0]}
      fi
      ;;
    math-assigned)
      ast:print-child ${child_array[0]}
      ;;
    array-length)
      printf '$#%s' "$ast_value"
      ;;
    function-def)
      local name=${child_array[0]} args=${child_array[1]} block=${child_array[2]}

      ast:print-child $name
      ast:print-child $args
      echo
      ast:print-child $block
      ;;
    if|elif)
      printf '%s ' $ast_head
      ast:print-child ${child_array[0]}
      echo
      ast:print-child ${child_array[1]}
      ast:print-child ${child_array[2]}
      ;;
    else)
      printf 'else\n'
      ast:print-child ${child_array[0]}
      ast:print-child ${child_array[1]}
      ;;
    end_if)
      ;;
    condition)
      case $ast_value in
        command)
          ast:print-child ${child_array[0]}
          ;;
        not|-*)
          printf '%s ' "$ast_value"
          ast:print-child ${child_array[0]}
          ;;
        *)
          ast:print-child ${child_array[0]}
          printf ' %s ' "$ast_value"
          ast:print-child ${child_array[1]}
          ;;
      esac
      ;;
    for)
      printf 'for '
      ast:print-child ${child_array[0]}
      printf ' in '
      ast:print-child ${child_array[1]}
      echo
      ast:print-child ${child_array[2]}
      ;;
    for-of)
      printf 'for '
      ast:print-child ${child_array[0]}
      printf ' of '
      ast:print-child ${child_array[1]}
      echo
      ast:print-child ${child_array[2]}
      ;;
    for-of-map)
      printf 'for '
      ast:print-child ${child_array[0]}
      printf ','
      ast:print-child ${child_array[1]}
      printf ' of '
      ast:print-child ${child_array[2]}
      echo
      ast:print-child ${child_array[3]}
      ;;
    switch|case|while)
      printf '%s' "$ast_head "
      ast:print-child ${child_array[0]}
      echo
      ast:print-child ${child_array[1]}
      ;;
    expand)
      printf 'expand'
      echo
      ast:print-child ${child_array[0]}
      ;;
    local)
      local child
      printf 'local'
      for child in ${child_array[@]}; do
        printf ' '
        ast:print-child $child
      done
      ;;
    string-length)
      printf '%s' "\${$ast_value:length}"
      ;;
    string-indirect)
      printf '%s' "\${$ast_value:indirect}"
      ;;
    string-removal|string-case|string-default)
      local op
      printf '%s' "\${$ast_value:"
      ast:from ${child_array[1]} value op
      case "$op" in
        '#')  printf "prefix "      ;;
        '%')  printf "suffix "      ;;
        '##') printf "prefix* "     ;;
        '%%') printf "suffix* "     ;;
        '^')  printf "uppercase "   ;;
        ',')  printf "lowercase "   ;;
        '^^') printf "uppercase* "  ;;
        ',,') printf "lowercase* "  ;;
        '-')  printf "unset "       ;;
        '=')  printf "unset= "      ;;
        ':-') printf "empty "       ;;
        ':=') printf "empty= "      ;;
        '+')  printf "set "         ;;
        ':+') printf "nonempty "    ;;
        '?')  printf "set! "        ;;
        ':?') printf "nonempty! "   ;;
      esac
      ast:print-child ${child_array[0]}
      printf "}"
      ;;
    string-replace)
      printf "\${$ast_value:"
      printf 'replace '
      ast:print-child ${child_array[0]}
      printf ' by '
      ast:print-child ${child_array[1]}
      printf '}'
      ;;
    string-from)
      printf "\${$ast_value:"
      printf 'from $('
      ast:print-child ${child_array[0]}
      printf ') to $('
      ast:print-child ${child_array[1]}
      printf ')}'
      ;;
    string-slice)
      printf "\${$ast_value:"
      printf 'slice $('
      ast:print-child ${child_array[0]}
      printf ') length $('
      ast:print-child ${child_array[1]}
      printf ')}'
      ;;
    string-index)
      printf "\${$ast_value:"
      printf 'index $('
      ast:print-child ${child_array[0]}
      printf ')}'
      ;;
    string-test)
      local op
      printf "\${$ast_value:"
      ast:from ${child_array[1]} value op

      case "$op" in
        '+1')  printf  'unset?}'    ;;
        '+-1') printf  'set?}'      ;;
        ':+1')  printf 'empty?}'    ;;
        ':+-1') printf 'nonempty?}' ;;
      esac
      ;;
    array-operation)
      local var index subst
      ast:from ${child_array[0]} value var
      ast:children ${child_array[0]} index
      index="$(ast:print-child $index)"
      subst="$(ast:print-child ${child_array[1]})"

      printf '%s' "${subst/@/$var[$index]}"
      ;;
    pattern)
      local op
      printf '%s' "$ast_value"
      ;;
    flag-double-dash*)
      printf '%s' "--$ast_value"
      ;;
    flag-single-dash*)
      printf '%s' "-$ast_value"
      ;;
    await*)
      local then

      printf 'await '
      ast:print-child ${child_array[0]}
      printf ' then'

      case "$ast_head" in
        await-for)
          then=2
          printf ' for '
          ast:print-child ${child_array[1]}
          echo
          ;;
        await-pipe)
          then=1
          printf ' |\n'
          ;;
        await-then)
          then=1
          echo
          ;;
      esac

      ast:print-child ${child_array[$then]}

      if [ -n "${child_array[$((then+1))]}" ]; then
        printf 'when done\n'
        ast:print-child ${child_array[$((then+1))]}
      fi
      ;;
    pipe)
      ast:print-child ${child_array[0]}
      printf " | "
      ast:print-child ${child_array[1]}
      ;;
    list)
      local element

      printf '( '
      for element in "${child_array[@]}"; do
        ast:print-child $element
        printf ' '
      done
      printf ')'
      ;;
    elements)
      local element

      for element in "${child_array[@]}"; do
        ast:print-child $element
        printf ' '
      done
      ;;
    block)
      local statement

      for statement in "${child_array[@]}"; do
        printf "%$((ast_value*2)).s" ''
        ast:print-child $statement
        echo
      done
      ;;
  esac
}


# FILE: ast/print.bash

# ast:parse:try
#
# Try parsing an ast expression from the input,
# printing 'top' on success or the last
# parser state on failure.

ast:parse:try() {
  (
    local ast
    POWSCRIPT_INCOMPLETE_STATE=

    trap '
      if [ -n "$POWSCRIPT_INCOMPLETE_STATE" ]; then
        echo "$POWSCRIPT_INCOMPLETE_STATE"
      else
        ast:last-state
      fi
      exit' EXIT

    POWSCRIPT_ALLOW_INCOMPLETE=true ast:parse ast
    exit
  )
}


# ast:parse $out
#
# Parse an ast expression from the input,
# storing it in $out.

ast:parse() {
  ast:parse:linestart "$1"
}


# ast:parse:linestart $out
#
# Test that there is no indentation before proceeding
# to parse the expression.


__shadowing_ast:parse:linestart() {
  local value class line

  token:get -v value -c class -ls line

  if [ "$class" = whitespace ]; then
    ast:error "indentation error at line $line, unexpected indentation of $value."
  else
    token:backtrack
    ast:parse:top "$1"
  fi
}

ast:parse:linestart() {
  if [ -z ${__noshadow_53_+x} ]; then
    local __noshadow_53_
    local __noshadow_53_1
  fi
  __shadowing_ast:parse:linestart  __noshadow_53_1
  setvar "$1" "$__noshadow_53_1"
}



# ast:parse:top $out
#
# Analyze first expression and dispatch to the
# appropriate function appropriate for it.


__shadowing_ast:parse:top() {
  local out="$1"
  local expr expr_head assigns

  ast:parse:expr expr
  ast:from $expr head expr_head

  case $expr_head in
    name)
      local expr_value
      ast:from $expr value expr_value
      if token:next-is special '(' true; then
        case "$expr_value" in
          'if'|'for'|'math'|'case'|'while'|'switch'|\
          'switch'|'await'|'assert'|'require'|'expand'|\
          'declare'|'test')
            ast:error "'(' not allowed after reserved name '$expr_value'"
            ;;
        esac
      fi
      case "$expr_value" in
        'if')      ast:parse:if 'if' "$out" ;;
        'for')     ast:parse:for     "$out" ;;
        'case')    ast:parse:case    "$out" ;;
        'while')   ast:parse:while   "$out" ;;
        'switch')  ast:parse:switch  "$out" ;;
        'await')   ast:parse:await   "$out" ;;
        'assert')  ast:parse:assert  "$out" ;;
        'require') ast:parse:require "$out" ;;
        'expand')  ast:parse:expand  "$out" ;;
        'declare') ast:parse:declare "$out" ;;
        'test')    ast:parse:test    "$out" ;;
        'math')
          ast:push-state 'topmath'
          ast:parse:math "$out"
          ast:pop-state
          ;;
        *)
          if token:next-is special '(' true; then
            ast:parse:function-definition $expr "$out"
          else
            ast:make assigns assign-sequence
            ast:parse:command-call-with-cmd $assigns $expr "$out"
          fi
          ;;
      esac
      ;;
    *assign)
      ast:parse:assign-sequence $expr "$out"
      ;;
    newline)
      setvar "$out" -1
      ;;
    cat)
      if token:next-is special '(' true; then
         ast:parse:function-definition $expr "$out"
      else
         ast:make assigns assign-sequence
         ast:parse:command-call-with-cmd $assigns $expr "$out"
       fi
       ;;
    *)
      ast:make assigns assign-sequence
      ast:parse:command-call-with-cmd $assigns $expr "$out"
      ;;
  esac
}

ast:parse:top() {
  if [ -z ${__noshadow_54_+x} ]; then
    local __noshadow_54_
    local __noshadow_54_1
  fi
  __shadowing_ast:parse:top  __noshadow_54_1
  setvar "$1" "$__noshadow_54_1"
}


# FILE: ast/parse.bash

# FILE: ast/ast.bash
declare -gA PowscriptBackends

backend:error() {
  local message="$1"
  >&2 echo "$message"
  if ! powscript:is-interactive; then
    exit 1
  fi
}

backend:compile-children() {
  local expr="$1"
  local __ast __asts

  ast:from $expr children __asts

  for __ast in $__asts; do
    shift
    backend:compile $__ast "$1"
  done
}

# FILE: lang/common.bash
bash:interactive() {
  local wfifo="$1"
  local rfifo="$2"
  local end="$3"
  local code="__PowscriptCompiledCode__"
  local line="__PowscriptCodeLine__"
  local result="__PowscriptResultLine__"
  bash -c "
    trap '{ echo \"#<<END.$end>>\" >>\"$rfifo\"; exit; }' EXIT ERR
    $code=
    $line=
    $result=
    while [ -p '$wfifo' ]; do
      IFS= read -r $line <'$wfifo'
      if [ \"\$$line\" = '#<<END>>' ] ; then
        2>&1 eval \"\$$code\" >>'$rfifo' || true
        echo '#<<END.$end>>' >>'$rfifo'
        $code=
      else
        $code=\"\$$code\"\$'\n'\"\$$line\"
      fi
    done
  " 2>/dev/null
}

# FILE: lang/bash/interactive.bash

bash:run() {
  bash -c "$1"
}


__shadowing_bash:compile() {
  local expr=$1 out="$2"
  local expr_head expr_value expr_children

  ast:from $expr head expr_head

  set_substitution() {
    if ${NO_QUOTING-false}; then
      setvar "$out" "$1"
    else
      setvar "$out" "\"$1\""
    fi
  }

  case "$expr_head" in
    name|string|assign|cat|if|elif|else|end_if|call|for|\
    while|expand|command-substitution|switch|case|require|\
    pattern|and|pipe|elements|simple-substitution|*assert|\
    function-def|local|block|math|math-top|math-float|\
    math-assigned|math-expr|assign-sequence|readline|file-input|\
    string-length|string-removal|string-default|string-test|\
    string-indirect|double-string|nothing|empty-substitution|flag*)

      sh:compile $expr "$out"
      ;;

    declare)
      local result type child_ast child expr_children

      ast:from $expr value type
      ast:from $expr children expr_children

      case "$type" in
        integer) result="declare -i" ;;
        array)   result="declare -a" ;;
        map)     result="declare -A" ;;
        string)  result="declare" ;;
        'global integer') result="declare -gi" ;;
        'global array')   result="declare -ga" ;;
        'global map')     result="declare -gA" ;;
        'global string')  result="declare -g" ;;
      esac

      for child_ast in $expr_children; do
        bash:compile $child_ast child
        result+=" $child"
      done

      setvar "$out" "$result"
      ;;

    indexing-substitution)
      local name index expr_children

      ast:from $expr children expr_children
      expr_children=( $expr_children )

      ast:from $expr value name
      bash:compile ${expr_children[0]} index

      setvar "$out" "\"\${$name[$index]}\""
      ;;

    indirect-indexing-substitution)
      local name expr_children

      ast:from $expr children expr_children
      expr_children=( $expr_children )

      ast:from $expr value name
      bash:compile ${expr_children[0]} index

      setvar "$out" "\"\${!$name[$index]}\""
      ;;

    indexing-assign)
      local name index value
      ast:from $expr children expr_children
      expr_children=( $expr_children )

      bash:compile ${expr_children[0]} name
      bash:compile ${expr_children[1]} index
      bash:compile ${expr_children[2]} value

      setvar "$out" "$name[$index]=$value"
      ;;

    list-assign)
      local name_ast list_ast name list

      ast:children $expr name_ast list_ast

      sh:compile   $name_ast name
      bash:compile $list_ast list

      setvar "$out" "$name=$list"
      ;;

    add-assign)
      local name_ast value_ast name value

      ast:children $expr name_ast value_ast

      bash:compile $name_ast  name
      bash:compile $value_ast value

      setvar "$out" "$name+=$value"
      ;;

    associative-assign)
      local name_ast name value_ast value_children

      ast:children $expr name_ast value_ast
      ast:from $value_ast children value_children

      backend:compile $name_ast name

      if [ -n "$value_children" ]; then
        >&2 echo "warning: Associative arrays with elements aren't implemented yet. Ignoring elements."
      fi
      setvar "$out" "declare -A $name"
      ;;

    array-length)
      local name
      ast:from $expr value name

      setvar "$out" "\${#$name[@]}"
      ;;

    concat-assign)
      local name_ast value_ast
      local name value

      ast:children $expr name_ast value_ast

      backend:compile $name_ast  name
      backend:compile $value_ast value

      setvar "$out" "$name=\"\${$name}\"$value"
      ;;

    list)
      local expr_children child_ast child result

      ast:from $expr children expr_children

      result="( "
      for child_ast in $expr_children; do
        bash:compile $child_ast child
        result="$result$child "
      done

      setvar "$out" "$result)"
      ;;

    string-index)
      local name index_ast
      local index

      ast:from $expr value name
      ast:children $expr index_ast

      backend:compile $index_ast index

      set_substitution "\${$name:$index:1}"
      ;;

    string-slice-from)
      local name start_ast
      local start

      ast:from $expr value name
      ast:children $expr start_ast

      backend:compile $start_ast start

      set_substitution "\${$name:$start}"
      ;;

    string-slice)
      local name start_ast len_ast
      local start len

      ast:from $expr value name
      ast:children $expr start_ast len_ast

      backend:compile $start_ast start
      backend:compile $len_ast   len

      set_substitution "\${$name:$start:$len}"
      ;;

    string-from)
      local name from_ast to_ast
      local from to from_cond to_cond len

      ast:from $expr value name
      ast:children $expr from_ast to_ast

      backend:compile $from_ast from
      backend:compile $to_ast   to

      from="${from#\$(( }"
      from="${from% ))}"

      to="${to#\$(( }"
      to="${to% ))}"

      from="\$(($from < 0 ? 0 : $from))"
      to="\$(($to < 0 ? -1 : $to))"
      len="\$(($to < $from ? 0 : $to-$from+1))"

      set_substitution "\${$name:$from:$len}"
      ;;

    string-case)
      local name pattern_ast op_ast
      local pattern op

      ast:from $expr value name
      ast:children $expr pattern_ast op_ast

      backend:compile $pattern_ast pattern
      backend:compile $op_ast op

      set_substitution "\${$name$op$pattern}"
      ;;

    string-replace)
      local name pattern_ast by_ast op_ast
      local pattern by op

      ast:from $expr value name
      ast:children $expr pattern_ast by_ast op_ast

      backend:compile $pattern_ast pattern
      backend:compile $by_ast      by
      backend:compile $op_ast      op

      pattern="${pattern//\//\\\/}"
      by="${by//\//\\\/}"

      pattern="${pattern% }"

      set_substitution "\${$name$op$pattern/$by}"
      ;;

    array-operation|array-test)
      local subst_ast param_ast
      local subst param

      ast:children $expr subst_ast param_ast

      backend:compile $subst_ast subst
      backend:compile $param_ast param

      subst="${subst#\"}"
      subst="${subst#\$\{}"
      subst="${subst%\"}"
      subst="${subst%\}}"

      setvar "$out" "${param/@/$subst}"
      ;;

    condition)
      local op left right quoted=no
      ast:from $expr value op
      ast:from $expr children expr_children
      expr_children=( $expr_children )

      case "$op" in
        command)
          bash:compile ${expr_children[0]} left
          setvar "$out" "$left"
          ;;
        not)
          bash:compile ${expr_children[0]} right
          setvar "$out" "! $right"
          ;;
        -*)
          bash:compile ${expr_children[0]} right
          setvar "$out" "[ $op $right ]"
          ;;
        *)
          bash:compile ${expr_children[0]} left
          bash:compile ${expr_children[1]} right

          case "$op" in
            'is'|'=')     op='='    quoted=single ;;
            'isnt'|'!=')  op='!='   quoted=single ;;
            '==')         op='-eq'  quoted=single ;;
            '>')          op='-gt'  quoted=single ;;
            '>=')         op='-ge'  quoted=single ;;
            '<')          op='-lt'  quoted=single ;;
            '<=')         op='-le'  quoted=single ;;
            'match')      op='=~'   quoted=double ;;
            'and'|'&&')   op='&&' ;;
            'or'|'||')    op='||' ;;
          esac

          case $quoted in
            double) setvar "$out" "[[ $left $op $right ]]" ;;
            single) setvar "$out"  "[ $left $op $right ]"  ;;
            no)     setvar "$out"    "$left $op $right"    ;;
          esac
          ;;
      esac
      ;;
    newline|eof|'')
      ;;
    *)
      backend:error "unimplemented: '$expr_head'"
      ;;
  esac
}

bash:compile() {
  if [ -z ${__noshadow_55_+x} ]; then
    local __noshadow_55_
    local __noshadow_55_2
  fi
  __shadowing_bash:compile "$1"  __noshadow_55_2
  setvar "$2" "$__noshadow_55_2"
}

# FILE: lang/bash/compile.bash
sh:interactive() {
  local wfifo="$1"
  local rfifo="$2"
  local end="$3"
  local code="__PowscriptCompiledCode__"
  local line="__PowscriptCodeLine__"
  local result="__PowscriptResultLine__"
  local newline=$'\n'
  sh -c "
    trap '{ [ -p \"$rfifo\" ] && echo \"#<<END.$end>>\" >>\"$rfifo\"; exit; }' INT TERM QUIT EXIT
    $code=
    $line=
    $result=
    while [ -p '$wfifo' ]; do
      IFS= read -r $line <'$wfifo'
      if [ \"\$$line\" = '#<<END>>' ] ; then
        2>&1 eval \"\$$code\" >>'$rfifo' || true
        echo '#<<END.$end>>' >>'$rfifo'
        $code=
      else
        $code=\"\$$code$newline\$$line\"
      fi
    done
  "
}
# FILE: lang/sh/interactive.bash

sh:run() {
  sh -c "$1"
}


__shadowing_sh:compile() {
  local expr="$1" out="$2"
  local expr_head

  ast:from $expr head expr_head

  set_substitution() {
    if ${NO_QUOTING-false}; then
      setvar "$out" "$1"
    else
      setvar "$out" "\"$1\""
    fi
  }

  case "$expr_head" in
    name)
      ast:from $expr value "$out"
      ;;

    string)
      local string
      ast:from $expr value string
      if [ "$string" = "'" ]; then
        setvar "$out" "\"'\""
      else
        setvar "$out" "'$string'"
      fi
      ;;

    double-string)
      local string
      ast:from $expr value string
      setvar "$out" "\"$string\""
      ;;

    nothing)
      setvar "$out" ""
      ;;

    flag*)
      local dash='' name
      ast:from $expr value name

      case "$expr_head" in
        *single-dash) dash='-'  ;;
        *double-dash) dash='--' ;;
      esac

      setvar "$out" "$dash$name"
      ;;

    cat)
      local child compiled result=""
      ast:from $expr children expr_children
      for child in $expr_children; do
        backend:compile $child compiled
        result="$result$compiled"
      done
      setvar "$out" "$result"
      ;;

    require)
      local file_ast ofile file code compiled_file
      ast:from $expr children file_ast

      bash:compile $file_ast file
      file="$(eval "echo $file")"
      ofile="$file"

      case "$file" in
        /*) ;;
        *)  file="${POWCOMP_DIR-$PWD}/$file" ;;
      esac

      if [ -f "$file" ]; then
        code="$(cat "$file")"$'\n\n'
        compiled_file="$(POWCOMP_DIR="$(dirname "$file")" files:compile-file <<<"$code")"
      else
        compiled_file="$(cache:library $ofile)"
        if [ -z "$compiled_file" ]; then
          echo "ERROR: $file not found, and $ofile is not a library" >&2
          if ${POWSCRIPT_ALLOW_INCOMPLETE-false}; then
            return 1
          else
            exit 1
          fi
        fi
      fi

      setvar "$out" "$compiled_file"$'\n'
      ;;

    assert|light-assert)
      local condition_ast message_ast condition message
      local NL=$'\n' onfailure

      ast:children $expr condition_ast message_ast

      backend:compile $condition_ast condition

      if ast:is $message_ast print_condition; then
        message="Assertation failed: $(ast:print $condition_ast)"
        message="cat <<'ASSERT_EOF'$NL$message${NL}ASSERT_EOF$NL"
      else
        backend:compile $message_ast message
        message="echo $message"
      fi

      case "$expr_head" in
        assert) onfailure="exit 1" ;;
        light-assert)
          if ${INSIDE_FUNCTION-false}; then
            onfailure="return 1"
          else
            onfailure="false"
          fi
          ;;
      esac
      setvar "$out" "if $condition; then :; else >&2 $message$NL $onfailure; fi"
      ;;

    and|pipe|file-input)
      local left_ast right_ast
      local left right op

      ast:children $expr left_ast right_ast

      backend:compile $left_ast  left
      backend:compile $right_ast right

      case "$expr_head" in
        and)        op='&' ;;
        pipe)       op='|' ;;
        file-input) op='<' ;;
      esac

      setvar "$out" "$left $op $right"
      ;;

    assign)
      local var_ast value_ast
      local var value

      ast:children $expr var_ast value_ast

      backend:compile $var_ast   var
      backend:compile $value_ast value

      setvar "$out" "$var=$value"
      ;;

    assign-sequence)
      local assign_ast expr_children
      local result="" assign

      ast:from $expr children expr_children

      for assign_ast in $expr_children; do
        backend:compile $assign_ast assign
        result+="$assign "
      done
      setvar "$out" "$result"
      ;;

    readline)
      local var_ast
      local var

      ast:children $expr var_ast

      backend:compile $var_ast var

      setvar "$out" "IFS= read $var || [ -n \"\$$var\" ]"
      ;;
    math-assigned)
      local value_ast value

      ast:from $expr children value_ast

      NO_QUOTING=true backend:compile $value_ast value

      setvar "$out" "\$(( $value ))"
      ;;

    concat-assign)
      local var_ast value_ast
      local var value

      ast:children $expr var_ast value_ast

      backend:compile $var_ast   var
      backend:compile $value_ast value

      setvar "$out" "$var=\"\${$var}\"$value"
      ;;

    call)
      local children arg_ast
      local result assigns cmd arg

      ast:from $expr children children
      children=( $children )

      backend:compile ${children[0]} assigns
      backend:compile ${children[1]} cmd

      result="$assigns$cmd"
      for arg_ast in "${children[@]:2}"; do
        backend:compile $arg_ast arg
        result="$result $arg"
      done
      setvar "$out" "$result"
      ;;

    empty-substitution)
      set_substitution "\${NOTHING:+}"
      ;;

    command-substitution)
      local call_ast call

      ast:from $expr children call_ast

      backend:compile $call_ast call

      if ast:is $call_ast math-expr; then
        setvar "$out" "$call"
      else
        set_substitution "\$( $call )"
      fi
      ;;

    if|elif)
      local condition block after
      backend:compile-children $expr condition block after

      setvar "$out" "$expr_head $condition; then"$'\n'"${block:2:-2}"$'\n'"$after"
      ;;

    else)
      local block
      backend:compile-children $expr block

      setvar "$out" "else"$'\n'"${block:2:-2}"$'\n'"fi"
      ;;

    end_if)
      setvar "$out" "fi"
      ;;

    for)
      local varname loc=''
      local elements block

      backend:compile-children $expr varname elements block

      ${INSIDE_FUNCTION-false} && loc="local $varname; "

      setvar "$out" "${loc}for $varname in $elements; do"$'\n'"${block:2:-2}"$'\ndone'
      ;;

    while)
      local condition block
      backend:compile-children $expr condition block

      setvar "$out" "while $condition; do"$'\n'"${block:2:-2}"$'\ndone'
      ;;
    switch)
      local value cases
      backend:compile-children $expr value cases

      setvar "$out" "case $value in"$'\n'"${cases:2:-2}"$'\n'"esac"
      ;;

    case)
      local pattern block
      backend:compile-children $expr pattern block

      setvar "$out" "$pattern)"$'\n'"${block:2:-2}"$'\n;;'
      ;;

    elements)
      local e elements r result

      ast:from $expr children elements

      for e in $elements; do
        backend:compile $e r
        result="$result$r "
      done

      setvar "$out" "${result:0:-1}"
      ;;

    simple-substitution)
      local name
      ast:from $expr value name
      set_substitution "\${$name}"
      ;;

    string-length)
      local name
      ast:from $expr value name
      set_substitution "\${#$name}"
      ;;

    string-indirect)
      local name
      ast:from $expr value name
      set_substitution "\${!$name}"
      ;;

    string-removal)
      local name pattern_ast op_ast
      local pattern op

      ast:from $expr value name
      ast:children $expr pattern_ast op_ast

      backend:compile $pattern_ast pattern
      backend:compile $op_ast op

      set_substitution "\${$name$op$pattern}"
      ;;

    string-default)
      local name default_ast op_ast
      local default op

      ast:from $expr value name
      ast:children $expr default_ast op_ast

      backend:compile $default_ast default
      backend:compile $op_ast op

      set_substitution "\${$name$op$default}"
      ;;

    string-test)
      local name op_ast def_ast
      local op def

      ast:from $expr value name
      ast:children $expr op_ast def_ast

      backend:compile $op_ast  op
      backend:compile $def_ast def

      set_substitution "\$(( \${$name$op}+$def ))"
      ;;

    math-float)
      local precision child_ast child

      ast:from $expr value precision
      ast:from $expr children child_ast
      NO_QUOTING=true FLOAT_MATH=true backend:compile $child_ast child

      setvar "$out" "echo \"{scale=$precision; $child}\" | bc"
      ;;
    math-top)
      local child_ast child

      ast:from $expr children child_ast
      NO_QUOTING=true backend:compile $child_ast child

      setvar "$out" "echo \$(( $child ))"
      ;;

    math-expr)
      local child_ast child

      ast:from $expr children child_ast
      NO_QUOTING=true backend:compile $child_ast child

      setvar "$out" "\$(( $child ))"
      ;;

    math)
      local left_ast right_ast op
      local left right

      ast:all-from $expr -v op -@ left_ast right_ast

      if [ "$op" = "^" ] && ! ${FLOAT_MATH-false}; then
        case $PowscriptBackend in
          sh)
            backend:error "unimplemented: ^ operator in math"
            return
            ;;
          bash)
            op="**"
            ;;
        esac
      fi

      backend:compile $left_ast left

      if [ -n "$right_ast" ]; then
        backend:compile $right_ast right
        setvar "$out" "$left$op$right"
      else
        setvar "$out" "$op$left"
      fi
      ;;

    function-def)
      local name_ast args_ast block_ast
      local name block

      ast:children $expr name_ast args_ast block_ast

      backend:compile $name_ast name
      INSIDE_FUNCTION=true backend:compile $block_ast block

      setvar "$out" "$name() $block"
      ;;

    local)
      local result child_ast child
      ast:from $expr children expr_children

      if ${INSIDE_FUNCTION-false}; then
        result="local"
      else
        result=":"
      fi

      for child_ast in $expr_children; do
        sh:compile $child_ast child
        if ${INSIDE_FUNCTION-false}; then
          result+=" $child"
        else
          if ast:is $child assign; then
            result+="; : $child"
          fi
        fi
      done

      setvar "$out" "$result;"
      ;;

    declare)
      local result
      ast:set $expr head local
      sh:compile $expr result
      ast:set $expr head declare

      setvar "$out" "$result"
      ;;

    block)
      local child_ast child result
      ast:from $expr children expr_children

      result='{'
      for child_ast in $expr_children; do
        backend:compile $child_ast child
        [ -n "$child" ] && result="$result"$'\n'"$child"
      done
      result="${result}"$'\n}'

      setvar "$out" "$result"
      ;;

    pattern)
      ast:from $expr value "$out"
      ;;

    condition)
      local op left right quoted=no
      ast:from $expr value op
      ast:from $expr children expr_children
      expr_children=( $expr_children )

      case "$op" in
        command)
          sh:compile ${expr_children[0]} left
          setvar "$out" "$left"
          ;;
        not)
          sh:compile ${expr_children[0]} right
          setvar "$out" "! $right"
          ;;
        -*)
          bash:compile ${expr_children[0]} right
          setvar "$out" "[ $op $right ]"
          ;;
        *)
          sh:compile ${expr_children[0]} left
          sh:compile ${expr_children[1]} right

          case "$op" in
            'is'|'=')     op='='    quoted=single ;;
            'isnt'|'!=')  op='!='   quoted=single ;;
            '==')         op='-eq'  quoted=single ;;
            '>')          op='-gt'  quoted=single ;;
            '>=')         op='-ge'  quoted=single ;;
            '<')          op='-lt'  quoted=single ;;
            '<=')         op='-le'  quoted=single ;;
            'and'|'&&')   op='&&' ;;
            'or'|'||')    op='||' ;;
            *) backend:error "unimplemented: condition: $op" ;;
          esac

          case $quoted in
            single) setvar "$out"  "[ $left $op $right ]"  ;;
            no)     setvar "$out"    "$left $op $right"    ;;
          esac
          ;;
      esac
      ;;
    expand)
      local block_ast block quoted_block
      ast:from $expr children block_ast
      backend:compile $block_ast block

      setvar quoted_block "${block//\\/\\\\}"
      setvar quoted_block "${block//\"/\\\"}"
      setvar quoted_block "${quoted_block//\$/\\\$}"
      setvar quoted_block "${quoted_block//\~/\$}"
      setvar "$out" "eval \"$quoted_block\""
      ;;
    newline|eof|'')
      ;;
    *)
      backend:error "unimplemented: '$expr_head'"
      ;;
  esac
}

sh:compile() {
  if [ -z ${__noshadow_56_+x} ]; then
    local __noshadow_56_
    local __noshadow_56_2
  fi
  __shadowing_sh:compile "$1"  __noshadow_56_2
  setvar "$2" "$__noshadow_56_2"
}

# FILE: lang/sh/compile.bash

backend:select() {
  eval "
    backend:compile     () { ${1}:compile     \"\$@\"; }
    backend:interactive () { ${1}:interactive \"\$@\"; }
    backend:run         () { ${1}:run         \"\$@\"; }
  "
}

# FILE: lang/backends.bash

PowscriptTempDirectory="$(mkdir "$(mktemp -u).powscript")"

powscript:temp-name() {
  local suffix=".powscript$1"
  setvar "$2" "$(mktemp -u -p "$PowscriptTempDirectory")$suffix"
}

powscript:make-temp() {
  powscript:temp-name "$1" "$2"
  touch "${!2}"
}

powscript:make-fifo() {
  powscript:temp-name "$1" "$2"
  mkfifo "${!2}"
}

powscript:clean-up() {
  local exit_code=$?
  POWSCRIPT_CDEBUG_STOP=false
  [ -d "$PowscriptTempDirectory" ] && rm -r "$PowscriptTempDirectory"
  [ -n "$PowscriptGuestProcess"  ] && pgrep -P "$PowscriptGuestProcess" >/dev/null && kill -QUIT "$PowscriptGuestProcess"
  exit $exit_code
}

trap 'powscript:clean-up' TERM INT QUIT ABRT EXIT

# FILE: compiler/temp.bash
powscript:help() {
  echo '
  usage: powscript [options...] input_files...

  options:
    -h|--help             Display this message and exit.

    -v|--version          Display version number and exit.

    -i|--interactive      Launch interactive mode.
                          Default if no input files are given.

    -c|--compile          Force compilation mode.

    -o|--output $file     Compile all input files into $file.
                          If this option is not given, the compilation
                          is sent to the standard output.

    -e|--evaluate $expr   Evaluate a powscript expression.

    --no-std              Don'"'"'t import the standard library,
                          decreasing startup time.

    -d|--debug            Debug mode for developers.
                          In bash, using "source ./powscript --debug"
                          will make all internal functions available
                          for testing.

    --to sh|bash          Select target language for compilation.

    --no-cache            Don'"'"'t use cache'"'"'d compiled files.

    --update-cache        Force cache files to be recompiled.
  '
}
# FILE: compiler/helptext.bash
POWSCRIPT_VERSION=1.1.15

version:number() {
  echo "$POWSCRIPT_VERSION"
}

version:up-to-date() {
  [ "$(version:major-of "$1")" -ge "$(version:major-of "$POWSCRIPT_VERSION")" ] ||
  [ "$(version:minor-of "$1")" -ge "$(version:minor-of "$POWSCRIPT_VERSION")" ] ||
  [ "$(version:patch-of "$1")" -ge "$(version:patch-of "$POWSCRIPT_VERSION")" ]
}

version:major-of() {
  echo "${1%%.*}"
}

version:minor-of() {
  local minor_patch="${1#*.}"
  echo "${minor_patch%.*}"
}

version:patch-of() {
  echo "${1##*.}"
}
# FILE: compiler/version.bash
PowscriptDirectory="$HOME/.powscript"
PowscriptCacheDirectory="$PowscriptDirectory/cache"

declare -gA PowscriptLibCache

cache:init() {
  for lib in "${!PowscriptLib[@]}"; do
    if [ -f "$(cache:file "$lib")" ]; then
      PowscriptLibCache[$lib]="$(<"$(cache:file "$lib")")"
    fi
  done
}

cache:file() {
  echo "$PowscriptCacheDirectory/lib/$1.$PowscriptBackend"
}

cache:library() {
  if ${POWSCRIPT_NO_CACHE-false}; then
    files:compile-file <<<"${PowscriptLib[$1]}"$'\n\n'
  else
    cache:update
    echo "${PowscriptLibCache[$1]}"
  fi
}

cache:remove() {
  rm -r "$PowscriptCacheDirectory"
}

cache:update() {
  if ! ${POWSCRIPT_NO_CACHE:-false}; then
    if cache:init-directory || ! cache:up-to-date; then
      cache:update-libraries
      cache:update-version
    elif [ "${#PowscriptLibCache[@]}" = 0 ]; then
      cache:init
    fi
  fi
}

cache:up-to-date() {
  version:up-to-date "$(cache:version)" || return 1
  for lib in "${!PowscriptLib[@]}"; do
    [ -f "$(cache:file "$lib")" ] || return 1
  done
}

cache:update-version() {
  echo "$(version:number)" >"$PowscriptCacheDirectory/version"
}

cache:version() {
  if [ -f "$PowscriptCacheDirectory/version" ]; then
    cat "$PowscriptCacheDirectory/version"
  else
    echo "0.0.0"
  fi
}

cache:update-libraries() {
  local lib code
  backend:select "$PowscriptBackend"
  echo -e "\\033[1mCompiling libraries..."
  for lib in "${!PowscriptLib[@]}"; do
    echo "* compiling: $lib"
    code="${PowscriptLib[$lib]}"$'\n\n'
    PowscriptLibCache[$lib]="$(files:compile-file '/dev/stdout' <<<"$code")"
    echo "${PowscriptLibCache[$lib]}" >"$(cache:file "$lib")"
  done
  echo -e "\033[0m"
}

cache:init-directory() {
  if [ ! -d "$PowscriptDirectory" ]; then
    mkdir "$PowscriptDirectory"
  fi
  if [ ! -d "$PowscriptCacheDirectory" ]; then
    mkdir "$PowscriptCacheDirectory"
    mkdir "$PowscriptCacheDirectory/lib"
    cache:update-version
    return 0
  else
    return 1
  fi
}
# FILE: compiler/cache.bash
PowscriptBackend=bash
PowscriptInteractiveMode=nofile
PowscriptCompileFile=false
PowscriptOutput='/dev/stdout'
PowscriptIncludeStd=true

declare -gA PowscriptFiles
PowscriptFileNumber=0

powscript:parse-options() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      '-h'|'--help')
        powscript:help
        exit
        ;;
      '-o'|'--output')
        PowscriptOutput="$2"
        shift 2
        ;;
      '-i'|'--interactive')
        PowscriptInteractiveMode=yes
        shift
        ;;
      '-d'|'--debug')
        PowscriptInteractiveMode=false
        PowscriptCompileFile=false
        POWSCRIPT_DEBUG=true
        shift $#
        ;;
      '--to')
        shift
        case "$1" in
          bash|sh)
            PowscriptBackend="$1"
            shift
            ;;
          *)
            >&2 echo "Invalid powscript backend $1"
            exit 1
            ;;
        esac
        ;;
      '--no-std')
        shift
        PowscriptIncludeStd=false
        ;;
      '--update-cache')
        shift
        cache:remove
        ;;
      '--no-cache')
        shift
        POWSCRIPT_NO_CACHE=true
        ;;
      '-c'|'--compile')
        PowscriptCompileFile=true
        shift
        ;;

      '-e'|'--evaluate')
        shift
        backend:select bash
        backend:run "$(files:compile-file '/dev/stdout' <<<"$1"$'\n\n')"
        if [ ! $PowscriptInteractiveMode = yes ]; then
          PowscriptInteractiveMode=no
        fi
        shift
        ;;

      '-v'|'--version')
        shift
        version:number
        exit 0
        ;;

      '-'*)
        >&2 echo "Invalid powscript option $1"
        exit 1
        ;;

      *)
        PowscriptFiles[$PowscriptFileNumber]="$1"
        PowscriptFileNumber=$((PowscriptFileNumber+1))
        shift
        ;;
    esac
  done

  if $PowscriptCompileFile && [ "$PowscriptFileNumber" -eq 0 ]; then
    >&2 echo "No input files given"
  fi

  case $PowscriptInteractiveMode in
    no)  PowscriptInteractiveMode=false; ;;
    yes) PowscriptInteractiveMode=true; ;;
    nofile)
      if [ "$PowscriptFileNumber" -gt 0 ]; then
        PowscriptInteractiveMode=false
      else
        PowscriptInteractiveMode=true
      fi
      ;;
  esac
}

powscript:is-interactive() {
  $PowscriptInteractiveMode
}

powscript:in-compile-mode() {
  $PowscriptCompileFile
}
# FILE: compiler/options.bash
PowscriptStartFile='
'
# FILE: extra/start.bash
PowscriptEndFile='
if $ASYNC is 1
  wait

'
# FILE: extra/end.bash

files:compile() {
  local output="${1-/dev/stdout}"
  shift

  files:start-code    "$output"
  files:compile-files "$output" "$@"
  files:end-code      "$output"
}

files:compile-files() {
  local output="$1"
  shift

  for file in "$@"; do
    POWCOMP_DIR="$(dirname "$file")"
    files:compile-file "$output" <<<"$(<"$file")"$'\n\n'
  done
}


files:compile-file() {
  local output="${1-/dev/stdout}"
  local ast ast_lowered

  stream:init
  interactive:clear-compilation
  while ! stream:end; do
    ast:parse ast
    ast:lower $ast ast_lowered
    backend:compile $ast_lowered 1>>"$output"
  done
}

files:start-code() {
  files:compile-file "$1" <<<"$PowscriptFileStart"$'\n\n'

  if ${PowscriptIncludeStd-true}; then
    echo "$(cache:library std)"$'\n\n' >>"$1"
  fi
}

files:end-code() {
  files:compile-file "$1" <<<"$PowscriptEndFile"$'\n\n'
}


# FILE: compiler/files.bash
InteractiveFileLineNumber=0

interactive:start() {
  local ast code compiled_code line="" state=none
  local proc rfifo wfifo end_token result
  local powhistory="${POWSCRIPT_HISTORY_FILE-$HOME/.powscript_history}"
  local extra_line=''
  local compile_flag=false ast_flag=false echo_flag=false incomplete_flag=false lower_flag=false

  [ ! -f "$powhistory" ] && echo >"$powhistory"
  history -c
  history -r "$powhistory"

  powscript:make-fifo ".interactive.wfifo" wfifo
  powscript:make-fifo ".interactive.rfifo" rfifo
  powscript:temp-name ".end" end_token

  backend:interactive "$wfifo" "$rfifo" "$end_token" &
  proc="$!"
  PowscriptGuestProcess="$proc"

  exec 3<>"$wfifo"
  exec 4<>"$rfifo"

  if ${PowscriptIncludeStd-true}; then
    echo "$(cache:library std)" >>"$wfifo"
  fi

  if [ -f "$HOME/.powrc" ]; then
    files:compile-file "$wfifo" <"$HOME/.powrc"
  fi

  while pgrep -P $proc >/dev/null; do
    result=

    if [ -n "${extra_line// /}" ]; then
      line="$extra_line"
      extra_line=""
    else
      interactive:read-powscript top line
    fi
    code="$line"

    case "$code" in
      '.compile')
        interactive:toggle-flag compile_flag
        ;;
      '.ast')
        interactive:toggle-flag ast_flag
        ;;
      '.lower')
        interactive:toggle-flag lower_flag
        ;;
      '.echo')
        interactive:toggle-flag echo_flag
        ;;
      '.incomplete')
        interactive:toggle-flag incomplete_flag
        ;;
      '.show '*)
        interactive:show-ast "${code//.show /}"
        echo
        ;;
      '.tokens '*)
        interactive:show-tokens "${code//.tokens /}"
        ;;
      '.help'*)
        interactive:help
        ;;
      *)
        state=none
        while [ ! "$state" = top ]; do
          interactive:clear-compilation
          state="$( { stream:init; POWSCRIPT_SHOW_INCOMPLETE_MESSAGE=$incomplete_flag ast:parse:try; } <<< "$code" )"
          [ -z "$line" ] && state=top
          case "$state" in
            top)
              interactive:clear-compilation
              { stream:init; ast:parse ast; } <<< "$code"$'\n'
              ;;
            error*)
              >&2 echo "$state"
              state=none
              code=
              line=
              ;;
            *)
              interactive:read-powscript "$state" line
              code="$code"$'\n'"$line"
              ;;
          esac
        done

        while IFS= read -r codeline; do
          [ -n "$codeline" ] && history -s "$codeline"
        done <<<"$code"

        if $echo_flag; then
          echo "---- CODE ECHO -----"
          echo "$code"
          echo "---------------------"
         fi

        if ! stream:end; then
          interactive:get-remaining-input extra_line
          code="${code:0:$(($# - ${#extra_line}))}"
        fi

        if $ast_flag; then
          echo "---- SYNTAX TREE ----"
          interactive:show-ast $ast
          echo "---------------------"
        fi
        ast:lower $ast ast
        if $lower_flag; then
          echo "---- LOWERED TREE ---"
          interactive:show-ast $ast
          echo "---------------------"
        fi
        backend:compile $ast compiled_code
        if $compile_flag; then
          echo "--- COMPILED CODE ---"
          echo "$compiled_code"
          echo "---------------------"
        fi
        echo "$compiled_code" >>"$wfifo"
        echo "#<<END>>" >>"$wfifo"
        while [ ! "$result" = "#<<END.$end_token>>" ]; do
          IFS= read -r result <"$rfifo"
          [ ! "$result" = "#<<END.$end_token>>" ] && echo "$result"
        done
        echo
        ;;
    esac
  done
  history -w "$powhistory"

  [ -p "$wfifo" ] && rm $wfifo
  [ -p "$rfifo" ] && rm $rfifo
}

interactive:help() {
  echo '
  Special Commands:
    .help        Display this message

    .ast         Toggle the display of the abstract syntax tree

    .lower       Toggle the display of the lowered ast

    .compile     Toggle the display of compilated code

    .tokens t*   Display information about the given tokens

    .echo        Toggle echoing the code

    .incomplete  Toggle allowing incomplete code

    .show ast    Display information about the ast with the given ID
  '
}


__shadowing_interactive:get-remaining-input() {
  local collumn out="$1"
  token:peek -cs collumn <<< ""
  stream:jump-to-collumn $collumn
  stream:get-rest-of-line "$out"
}

interactive:get-remaining-input() {
  if [ -z ${__noshadow_57_+x} ]; then
    local __noshadow_57_
    local __noshadow_57_1
  fi
  __shadowing_interactive:get-remaining-input  __noshadow_57_1
  setvar "$1" "$__noshadow_57_1"
}


interactive:clear-compilation() {
  token:clear-all
  token:clear-states
  ast:clear-all
  ast:clear-states
}

interactive:show-ast() {
  echo "id:       $1"
  echo "head:     $(ast:from $1 head)"
  echo "value:    $(ast:from $1 value)"
  echo "children: $(ast:from $1 children)"
  ast:print $1
}

interactive:show-tokens() {
  local value class
  {
    interactive:clear-compilation
    stream:init
    while ! stream:end; do
      token:get -v value -c class
      echo "-----------------"
      echo "$value :: $class"
    done
  } <<< "$1"
  echo
}

interactive:toggle-flag() {
  if ${!1}; then
    setvar "$1" false
  else
    setvar "$1" true
  fi
}

interactive:read-powscript() {
  IFS="" read -r -e -p "$(interactive:format-powscript-prompt "$1")" "$2"
  InteractiveFileLineNumber=$((InteractiveFileLineNumber+1))
}

interactive:format-powscript-prompt() {
  local state_name=$1 state

  case $state_name in
    top) state="--" ;;
    double-quotes) state='""' ;;
    single-quotes) state="''" ;;
    *) state="$state_name" ;;

  esac

  local default_prompt='pow[%L]%S> '
  local prompt="${POWSCRIPT_PS1-$default_prompt}"

  prompt="${prompt//%L/$(printf '%.3d' $InteractiveFileLineNumber)}"
  prompt="${prompt//%S/$(printf '%4s'  $state)}"

  echo "$prompt"
}


# FILE: compiler/interactive.bash
# FILE: compiler/compiler.bash

PowscriptLib[std]=$'map(A f)\n  shift 2\n  expand\n    for k,v of ~A\n      ~f $@ $k $v\n\nmappipe(f)\n  shift\n  while read -r line\n    $f $@ $line\n\ncurry(out f x)\n  expand\n    ~out()\n      ~f \'~x\' $@\n\nkeys(x y)\n  echo $x\n\nvalues(x y)\n  echo $y\n\nset?(s)\n  expand\n    return ${~s:set?}\n\nunset?(s)\n  expand\n    return ${~s:unset?}\n\nempty?(s)\n  expand\n    return ${~s:empty?}\n\nnonempty?(s)\n  expand\n    return ${~s:nonempty?}\n\nbool(int)\n  return $int\n\nfilter(A p)\n  shift 2\n  expand\n    for k,v of ~A\n      if ~p $@ $k $v\n        echo $v\n\ncompose(fg f g)\n  expand\n    ~fg()\n      ~f $(~g $@)\n\npick(A k)\n  if -z $k\n    return 1\n  expand\n    local output=${~A[~k]}\n  if -n $output\n    echo $output\n\nfirst(A)\n  if unset? A\n    return 1\n  expand\n    echo ${~A[0]}\n\nlast(A)\n  if unset? A\n    return 1\n  expand\n    echo ${~A[-1]}\n\nrequire_env(var)\n  expand\n    pass ${~var:set! "Required ENV-variable ~var not set (hint: \'~var=yourvalue ./yourscript\')."}\n\nrequire_cmd(var)\n  if which $var 1>/dev/null 2>/dev/null\n    return 0\n  expand\n    pass ${~var:set! "Please install the required command \'~var\' (was not found)."}\n\nismap?(x)\n  local result k1=__ismap_dummy1 k2=__ismap_dummy2\n  if empty? x\n    return 1\n  expand\n    local ~k1=0 ~k2=0\n    local v\n    test ${~x[~k1]:set?} and v=${~x[~k1]}\n    ~x[~k1]=a\n    ~x[~k2]=b\n    if ${~x[~k1]} is a\n      unset ~x[~k1]\n      unset ~x[~k2]\n      return 0\n    else\n      test ${v:set?} and ~x=$v or unset ~x\n      return 1\n\ncopy_map(__A __B)\n  if not ismap? $__A\n    echo "ERROR: in copy_map: $__A is not a map"\n    return 1\n  if not ismap? $__B\n    echo "ERROR: in copy_map: $__B is not a map"\n    return 1\n  expand\n    for __k,__v of ~__A\n      ~__B[$__k]=$__v\n\ncopy_array(__A __B)\n  expand\n    for __k,__v of ~__A\n      if not $__k match ^[1-9]+$\n        echo "ERROR: not a valid array key: $__k"\n        return 1\n      ~__B[$__k]=$__v\n\nclear_array(__A)\n  expand\n    for __k in ${~__A:keys}\n      unset ~__A[$__k]\n\nNL="\n"\n\npass()\n  true'
PowscriptLib[unicode]=$'unicode_parsing_error(utf_type msg)\n  echo "ERROR: While parsing a $utf_type unicode character: $msg" >&2\n\nunicode_test_eof(utf_type string i)\n  if i > ${string:length}\n    unicode_parsing_error $utf_type \'Premature end of input\'\n    return 1\n\nunicode_eat_delimiter(type string delim i iref)\n  if ${string:slice i length ${delim:length}} isnt $delim\n    unicode_parsing_error $type "required delimiter \'$delim\' not found after byte $gotten"\n    return 1\n  else\n    iref:ref= $(math i+${delim:length})\n\n\nparse_utf8(string -- result offset start delim prefix)\n  local c byte=\'\' unicode=\'\' final_value\n  local state=\'get-byte\'\n  declare integer i gotten needed\n  i=${state:set 0}\n  gotten=0\n  needed=1\n  if ${delim:unset?}\n    prefix:unset=\'\\x\'\n  delim:unset=\'\\x\'\n  #\n  unicode_eat_delimiter utf-8 $string $prefix $i ${i:ref} || return 1\n  while gotten < needed\n    unicode_test_eof utf-8 $string $i || return 1\n    #\n    c=${string:index i}\n    switch $c\n      case [0-9abcdefABCDEF]\n        i+=1\n        byte&=$c\n      case *\n        if "${byte:length}:$gotten" is \'1:0\'\n          unicode="\\\\x$byte"\n          gotten=1\n        else\n          unicode_parsing_error utf-8 "Invalid unicode $unicode\\\\x$byte"\n          return 1\n    if ${byte:length} is 2\n      switch $gotten\n        case 0\n          local byte_value=0x$byte\n          if byte_value < 0x80\n            needed=1\n          elif byte_value < 0xe0\n            needed=2\n          elif byte_value < 0xf0\n            needed=3\n          elif byte_value < 0xf8\n            needed=4\n          else\n            unicode_parsing_error utf-8 "Invalid byte 1: $byte"\n        case *\n          local byte_value=0x$byte\n          if byte_value < 0x80 or byte_value > 0xbf\n            unicode_parsing_error utf-8 "Invalid byte $(math gotten+1): $byte"\n            return 1\n      state=\'get-byte\'\n      gotten+=1\n      unicode&="\\\\x$byte"\n      byte=\'\'\n      if gotten < needed\n        unicode_eat_delimiter utf-8 $string $delim $i ${i:ref} || return 1\n  printf -v unicode "$unicode"\n  if ${result:set?}\n    result:ref= $unicode\n  else\n    echo $unicode\n  #\n  if ${offset:set?}\n    offset:ref= $i\n\n\nparse_utf16(string -- result start offset delim prefix)\n  local c unicode1=\'\' unicode2=\'\' final_value\n  local state=\'unicode1\' result\n  declare integer i\n  i=${start:unset 0}\n  #\n  if ${delim:unset?}\n    prefix=${prefix:unset \'\\u\'}\n  delim=${delim:unset \'\\u\'}\n  #\n  unicode_eat_delimiter utf-16 $string $prefix $i ${i:ref} || return 1\n  while ${final_value:unset?}\n    unicode_test_eof utf-16 $string $i || return 1\n    #\n    c=${string:index i}\n    switch $state\n      case unicode1\n        switch $c\n          case [0-9abcdefABCDEF]\n            unicode1&=$c\n            i+=1\n            if ${unicode1:length} is 4\n              local value=0x$unicode1\n              if value > 0xd7ff and value < 0xdbff\n                state=\'unicode2\'\n                unicode_eat_delimiter utf-16 $string $delim $i ${i:ref} || return 1\n              elif value > 0xdbff and value < 0xdc00\n                unicode_parsing_error utf-16 "Invalid character: \\\\u$unicode1"\n              else\n                printf -v final_value "\\\\u$unicode1"\n          case *\n            if ${unicode1:length} > 0\n              printf -v final_value "\\\\u$unicode1"\n            else\n              unicode_parsing_error utf-16 "Invalid character: \\\\u$unicode1"\n      case unicode2\n        switch $c\n          case [0-9abcdefABCDEF]\n            unicode2&=$c\n            i+=1\n            if ${unicode2:length} is 4\n              local value=0x$unicode2\n              if value > 0xdc00 and value < 0xdfff\n                local high=0x$unicode1 low=$value\n                final_value=$(math\n                  ((high - 0xd800) * 0x0400) +\n                  ((low  - 0xdc00) + 0x10000))\n                printf -v final_value \'%x\' $final_value\n                printf -v final_value "\\\\U$final_value"\n              else\n                unicode_parsing_error utf-16 "Invalid low surrogate: \\\\u$unicode2"\n          case *\n            unicode_parsing_error utf-16 "Invalid low surrogate: \\\\u$unicode2"\n  if ${result:set?}\n    result:ref= "$final_value"\n  else\n    echo "$final_value"\n  if ${offset:set?}\n    offset:ref= $i\n\n\nparse_utf32(string -- result start offset prefix)\n  local unicode="" final_value c\n  declare integer i\n  i=${start:unset 0}\n  prefix:unset=\'\\U\'\n  unicode_eat_delimiter utf-32 $string $prefix $i ${i:ref} || return 1\n  #\n  while ${final_value:unset?}\n    unicode_test_eof utf-32 $string $i || return 1\n    #\n    c=${string:index i}\n    switch $c\n      case [0-9abcdefABCDEF]\n        unicode&=$c\n        i+=1\n        if ${unicode:length} is 8\n          printf -v final_value "\\\\U$unicode"\n      case *\n        if ${unicode:nonempty?}\n          printf -v final_value "\\\\U$unicode"\n        else\n          unicode_parsing_error utf-32 "Invalid character: \\\\U"\n          return 1\n  if ${result:set?}\n    result:ref= "$final_value"\n  else\n    echo "$final_value"\n  if ${offset:set?}\n    offset:ref= $i'
PowscriptLib[json]=$'# usage: echo \'{"foo":"bar"}\' | json_tokenize | json_parse\n\njson_print()\n  local value\n  json_value ${value:ref} $@ || return 1\n  echo $value\n\njson_print_type()\n  local type\n  json_type ${type:ref} $@ || return 1\n  echo $type\n\njson_value()\n  json_property value $@\n\njson_type()\n  json_property type $@\n\n\njson_property(kind out start_id values)\n  shift 3\n  local id type value\n  if $start_id match ^[0-9]+$\n    shift\n  else\n    values=$start_id\n    start_id=1\n  json_get_id ${id:ref} $start_id $values $@ || return 1\n  expand\n    type=${~values[$id:type]}\n    value=${~values[$id:value]}\n  switch $kind\n    case type\n      out:ref=$type\n    case value\n      switch $type\n        case object|array\n          out:ref=$id\n        case *\n          out:ref=$value\n\njson_get_id(out id values)\n  shift 3\n  if not ismap? $values\n    echo "ERROR: in json_get: $values is not a json object"\n    return 1\n  expand\n    for key in $@\n      id=${~values[$id-$key]}\n  out:ref=$id\n\njson_parse(__out __string)\n  declare map __objects\n  test _json_parse $__out $__string or return 1\n  copy_map __objects $__out\n\n_json_parse(out string)\n  #\n  local index=0\n  local c\n  local state="top"\n  #\n  local unicode1 unicode2\n  #\n  local key=\'\'\n  local value=\'\'\n  declare array value_id_stack\n  declare integer value_stack_pos\n  declare integer value_id\n  declare integer previous_id\n  declare integer next_id\n  #\n  value_id_stack[0]=0\n  value_stack_pos=0\n  value_id=0\n  next_id=1\n  #\n  declare integer state_depth\n  declare array state_list\n  state_depth=0\n  #\n  declare integer line\n  declare integer collumn\n  line=0\n  collumn=0\n  #\n  state_pop()\n    if $state_depth > 0\n      state_depth-=1\n      state=$state_list[$state_depth]\n  #\n  state_push(v)\n    state_list[$state_depth]=$state\n    state_depth+=1\n    state=$v\n  #\n  state_replace(v)\n    state_pop\n    state_push $v\n  #\n  set_type(t)\n    __objects["$value_id:type"]=$t\n  #\n  add_value_to_object()\n    __objects["$previous_id-$key"]=$value_id\n  #\n  add_value_to_array()\n    local index=$__objects[$previous_id:length]\n    __objects["$previous_id-$index"]=$value_id\n    __objects["$previous_id:length"]=$(math index+1)\n  #\n  start_value()\n    previous_id=$value_id\n    value_id=$next_id\n    next_id+=1\n    value_stack_pos+=1\n    value_id_stack[$value_stack_pos]=$value_id\n    state_push \'value\'\n  #\n  end_value()\n    __objects["$value_id:value"]="$value"\n    value=\n    value_stack_pos-=1\n    value_id=$previous_id\n    previous_id=$value_id_stack[$(math value_stack_pos-1)]\n    state_pop\n  #\n  start_array()\n    __objects["$value_id:length"]=0\n  #\n  error(msg)\n    echo "ERROR:$line:$collumn: while parsing json: $msg" >&2\n  #\n  if not ismap? $out\n    error "expected an associative array as an argument, but $out isn\'t one"; return 1\n  #\n  start_value\n  while $index < ${string:length}\n    c=${string:index index}\n    index+=1\n    #\n    if $c is $NL\n      newline+=1\n      collumn=0\n    else\n      collumn+=1\n    #\n    switch $state\n      case \'value\'\n        switch $c\n          case [[:space:]]\n            pass\n          case \'{\'\n            state_push \'object\'\n            state_push \'object-key\'\n          case \'[\'\n            start_array\n            state_push \'array-start\'\n          case \'"\'\n            state_push \'string\'\n          case \'-\'\n            state_push \'negative\'\n            value=\'-\'\n          case \'0\'\n            state_push \'after-integer\'\n            value=\'0\'\n          case [1-9]\n            value&=$c\n            set_type \'number\'\n            state_push \'integer\'\n          case \'t\'\n            value=${string:slice index-1 length 5}\n            switch $value\n              case \'true\'|\'true\'[[:space:]]\n                set_type \'bool\'\n                value=\'true\'\n                end_value\n                index+=4\n              case \'true,\'|\'true]\'|\'true}\'\n                set_type \'bool\'\n                value=\'true\'\n                end_value\n                index+=3\n              case *\n                error "unexpected character $c: didn\'t lead to \'true\' ($value...)"; return 1\n          case \'f\'\n            value=${string:slice index-1 length 6}\n            switch $value\n              case \'false\'|\'false\'[[:space:]]\n                set_type \'bool\'\n                value=\'false\'\n                end_value\n                index+=5\n              case \'false,\'|\'false]\'|\'false}\'\n                set_type \'bool\'\n                value=\'false\'\n                end_value\n                index+=4\n              case *\n                error "unexpected character $c: didn\'t lead to \'false\' ($value...)"; return 1\n          case *\n            error "unexpected character $c"; return 1\n      case \'object-key\'\n        switch $c\n          case [[:space:]]\n            pass\n          case \'"\'\n            state_pop\n            state_push \'object-colon\'\n            state_push \'string\'\n          case \'}\'\n            state_pop\n            end_value\n          case *\n            error "unexpected character $c: expected a string as a object key"; return 1\n      case \'object-colon\'\n        switch $c\n          case [[:space:]]\n            pass\n          case \':\'\n            state_pop\n            start_value\n            add_value_to_object\n          case *\n            error "unexpected character $c: expected a colon (:) after the object key"; return 1\n      case \'object\'\n        switch $c\n          case [[:space:]]\n            pass\n          case \',\'\n            state_push \'object-key\'\n          case \'}\'\n            set_type \'object\'\n            state_pop\n            end_value\n          case *\n            error "unexpected character $c: expected \',\' or \'}\' in object"; return 1\n      case \'array-start\'\n        switch $c\n          case \']\'\n            set_type \'array\'\n            state_pop\n            end_value\n          case *\n            index-=1\n            state_replace \'array\'\n            start_value\n            add_value_to_array\n      case \'array\'\n        switch $c\n          case [[:space:]]\n            pass\n          case \',\'\n             start_value\n             add_value_to_array\n          case \']\'\n            set_type \'array\'\n            state_pop\n            end_value\n          case *\n            error "expected \',\' or \'}\' in array"; return 1\n      case \'string\'\n        switch $c\n          case \'"\'\n            set_type \'string\'\n            state_pop\n            if $state is \'value\'\n              end_value\n            elif $state is \'object-colon\'\n              key="$value"\n              value=\'\'\n          case \'\\\'\n            state_push \'escape\'\n          case *\n            value&=$c\n      case \'escape\'\n        state_pop\n        switch $c\n          case \'"\'|\'\\\'|\'/\'\n            value&=$c\n          case [bfnrt]\n            local v\n            printf -v v "\\\\$c"\n            value&=$v\n          case u\n            local unicode offset\n            parse_utf16 -r ${unicode:ref} -o ${offset:ref} ${string:slice index+1 length 8} || return 1\n            value&=$unicode\n            index+=$offset\n          case *\n            error "invalid escape character: $c"; return 1\n      case \'negative\'\n        switch $c\n          case 0\n            state_replace \'after-integer\'\n            value&=0\n          case [1-9]\n            state_replace \'integer\'\n            value&=$c\n          case *\n             error "unexpected character $c: expected a digit from 0 to 9"; return 1\n      case \'integer\'\n        switch $c\n          case [0-9]\n            value&=$c\n          case \'.\'\n            value&=\'.\'\n            state_replace \'floating-point\'\n          case [[:space:]]\n            state_pop\n            set_type \'number\'\n            end_value\n          case \',\'|\']\'|\'}\'\n            state_pop\n            set_type \'number\'\n            end_value\n            index-=1\n          case *\n             error "unexpected character $c: expected a digit from 0 to 9, . or whitespace"; return 1\n      case \'after-integer\'\n        switch $c\n          case \'.\'\n            value&=\'.\'\n            state_replace \'floating-point-start\'\n          case [[:space:]]\n            state_pop\n            set_type \'number\'\n            end_value\n          case \',\'|\']\'|\'}\'\n            state_pop\n            set_type \'number\'\n            end_value\n            index-=1\n          case *\n             error "unexpected character $c: expected . or whitespace"; return 1\n      case \'floating-point-start\'\n        switch $c\n          case [0-9]\n            value&=$c\n            state_replace \'floating-point\'\n          case *\n             error "unexpected character $c: expected a digit from 0 to 9 after ."; return 1\n      case \'floating-point\'\n        switch $c\n          case [0-9]\n            value&=$c\n          case [eE]\n            value&=e\n            state_replace \'float-e\'\n          case [[:space:]]\n            state_pop\n            set_type \'number\'\n            end_value\n          case \',\'|\']\'|\'}\'\n            state_pop\n            set_type \'number\'\n            end_value\n            index-=1\n          case *\n             error "unexpected character $c: expected a digit from 0 to 9, e/E or whitespace."; return 1\n      case \'float-e\'\n        switch $c\n          case [0-9]|+|-\n            value&=$c\n            state_replace \'float-e-value\'\n          case *\n             error "unexpected character $c: expected a digit from 0 to 9, + or -."; return 1\n      case \'float-e-value\'\n        switch $c\n          case [0-9]\n            value&=$c\n          case [[:space:]]\n            state_pop\n            set_type \'number\'\n            end_value\n  while $state isnt \'top\'\n    switch $state\n      case \'integer\'|\'floating-point\'|\'float-e-value\'\n        state_pop\n        end_value\n      case *\n        error "premature end of input. ($state)"\n        return 1'

powscript:parse-options "$@"
backend:select $PowscriptBackend

cache:update

powscript:compile() {
  printf '' >"$PowscriptOutput"
  files:compile "$PowscriptOutput" "${PowscriptFiles[@]}"
}

if powscript:is-interactive; then
  interactive:start
elif powscript:in-compile-mode; then
  powscript:compile
elif ! ${POWSCRIPT_DEBUG-false}; then
  backend:run "$(powscript:compile)" || exit 1
fi

if ! ${POWSCRIPT_DEBUG-false}; then
  powscript:clean-up
fi
