sistema_progs

Programas para customizar o meu entorno de traballo nos meus equipos persoais
Log | Files | Refs

core-syntax.sh (285716B)


      1 #!/bin/bash
      2 #%[release = 0]
      3 #%m main (
      4 
      5 function ble/syntax/util/is-directory {
      6   local path=$1
      7   # Note: #D1168 Cygwin では // で始まるパスの判定は遅い
      8   if [[ ( $OSTYPE == cygwin || $OSTYPE == msys ) && $path == //* ]]; then
      9     [[ $path == // ]]
     10   else
     11     [[ -d $path ]]
     12   fi
     13 }
     14 
     15 ## @fn ble/syntax/urange#update prefix p1 p2
     16 ## @fn ble/syntax/wrange#update prefix p1 [p2]
     17 ##   @param[in]   prefix
     18 ##   @param[in]   p1 p2
     19 ##   @var[in,out] {prefix}umin {prefix}umax
     20 ##
     21 ##   ble/syntax/urange#update に関しては、
     22 ##   ble/urange#update --prefix=prefix p1 p2 に等価である。
     23 ##   ble/syntax/wrange#update に対応するものはない。
     24 ##
     25 function ble/syntax/urange#update {
     26   local prefix=$1
     27   local p1=$2 p2=${3:-$2}
     28   ((0<=p1&&p1<p2)) || return 1
     29   (((${prefix}umin<0||${prefix}umin>p1)&&(${prefix}umin=p1),
     30     (${prefix}umax<0||${prefix}umax<p2)&&(${prefix}umax=p2)))
     31 }
     32 function ble/syntax/wrange#update {
     33   local prefix=$1
     34   local p1=$2 p2=${3:-$2}
     35   ((0<=p1&&p1<=p2)) || return 1
     36   (((${prefix}umin<0||${prefix}umin>p1)&&(${prefix}umin=p1),
     37     (${prefix}umax<0||${prefix}umax<p2)&&(${prefix}umax=p2)))
     38 }
     39 
     40 ## @fn ble/syntax/urange#shift prefix
     41 ## @fn ble/syntax/wrange#shift prefix
     42 ##   @param[in]   prefix
     43 ##   @var[in]     beg end end0 shift
     44 ##   @var[in,out] {prefix}umin {prefix}umax
     45 ##
     46 ##   ble/syntax/urange#shift に関しては、
     47 ##   ble/urange#shift --prefix=prefix "$beg" "$end" "$end0" "$shift" に等価である。
     48 ##   ble/syntax/wrange#shift に対応するものはない。
     49 ##
     50 function ble/syntax/urange#shift {
     51   local prefix=$1
     52   ((${prefix}umin>=end0?(${prefix}umin+=shift):(
     53       ${prefix}umin>=beg&&(${prefix}umin=end)),
     54     ${prefix}umax>end0?(${prefix}umax+=shift):(
     55       ${prefix}umax>beg&&(${prefix}umax=beg)),
     56     ${prefix}umin>=${prefix}umax&&
     57       (${prefix}umin=${prefix}umax=-1)))
     58 }
     59 function ble/syntax/wrange#shift {
     60   local prefix=$1
     61 
     62   # ※以下の不等号について (動作を見ながら)
     63   # もう一度考え直した方が良いかも。
     64   ((${prefix}umin>=end0?(${prefix}umin+=shift):(
     65        ${prefix}umin>beg&&(${prefix}umin=end)),
     66     ${prefix}umax>=end0?(${prefix}umax+=shift):(
     67       ${prefix}umax>=beg&&(${prefix}umax=beg)),
     68     ${prefix}umin==0&&++${prefix}umin,
     69     ${prefix}umin>${prefix}umax&&
     70       (${prefix}umin=${prefix}umax=-1)))
     71 }
     72 
     73 ## @var _ble_syntax_text
     74 ##   解析対象の文字列を保持する。
     75 ## @var _ble_syntax_lang
     76 ##   解析対象の言語を保持する。
     77 ##
     78 ## @var _ble_syntax_stat[i]
     79 ##   文字 #i を解釈しようとする直前の状態を記録する。
     80 ##   各要素は "ctx wlen wtype nlen tclen tplen nparam lookahead" の形式をしている。
     81 ##
     82 ##   @var ctx         = int (stat[0])
     83 ##     現在の文脈。
     84 ##   @var wlen        = int (stat[1])
     85 ##     現在のシェル単語の継続している長さ。
     86 ##   @var wtype       = string (stat[2])
     87 ##     現在のシェル単語の種類。
     88 ##   @var nlen        = int (stat[3])
     89 ##     現在の入れ子状態が継続している長さ。
     90 ##   @var tclen,tplen = int (stat[4], stat[5])
     91 ##     tchild, tprev の負オフセット。
     92 ##   @var nparam      = string (stat[6])
     93 ##     その入れ子レベルに特有のデータ一般を記録する文字列。
     94 ##     ヒアドキュメントの開始情報を記録するのに使用する。
     95 ##     将来的に `{ .. }` や `do .. done` の対応を取るのにも使うかもしれない。
     96 ##     解析変数の nparam が空文字列のときは "none" という値を格納する。
     97 ##   @var lookahead   = int (stat[7])
     98 ##     先読みの文字数を指定します。通常は 1 です。
     99 ##     この _ble_syntax_stat 要素の情報に影響を与えた、
    100 ##     対応する点以降の文字数を格納します。文字列末端も 1 文字と数えます。
    101 ##
    102 ## @var _ble_syntax_nest[inest]
    103 ##   入れ子の情報
    104 ##   各要素は "ctx wlen wtype inest tclen tplen nparam ntype" の形式をしている。
    105 ##   ctx wbegin inest wtype nparam は入れ子を抜けた時の状態を表す。
    106 ##   ntype は入れ子の種類を表す文字列。
    107 ##   nparam は復帰時の入れ子レベルにおける nparam 値を保持する。
    108 ##   nparam 値が空文字列の場合には代わりに none という文字列が格納される。
    109 ##
    110 ## @var _ble_syntax_tree[i-1]
    111 ##   境界 #i で終端した範囲 (単語・入れ子) についての情報を保持する。
    112 ##   同じ位置で複数の階層の範囲が終端した場合は、それらの情報が連結されて格納される。
    113 ##   各要素は "( wtype wlen tclen tplen wattr )*" の形式をしている。
    114 ##   より外側の範囲の情報はより左側に格納される。
    115 ##
    116 ##   tclen tplen を用いて他の _ble_syntax_tree 要素を参照する。
    117 ##   別の位置から或る位置を参照するとき、一番左側の範囲情報を参照する。
    118 ##   或る位置から自分自身を参照するとき、同じ要素の一つ右の範囲情報を参照する。
    119 ##
    120 ##   wtype (ntype)
    121 ##     範囲の種類を保持する。範囲が単語のとき、文脈値を整数で保持する。
    122 ##     範囲が入れ子範囲のとき、整数以外の文字列になる。
    123 ##
    124 ##   wlen (nlen)
    125 ##     範囲の長さを保持する。範囲の開始点は i-wlen である。
    126 ##
    127 ##   tclen
    128 ##     0 以上の時、一つ内側の要素の終端位置までの offset を保持する。
    129 ##     _ble_syntax_tree[i-1-tclen] に子要素の情報が格納されている。
    130 ##     子要素がないとき負の値。
    131 ##
    132 ##   tplen
    133 ##     0 以上の時、一つ前の兄弟要素までの offset を保持する。
    134 ##     _ble_syntax_tree[i-1-tplen] に兄要素の情報が格納されている。
    135 ##     兄要素が同じ位置で終端することはないので必ず正の値になるはず。
    136 ##     兄要素がないとき (自分が長男要素のとき) 負の値。
    137 ##
    138 ##   attr (wattr) または - or --
    139 ##     単語の着色に関する情報を保持する。
    140 ##     以下の何れかの形式を持つ。
    141 ##
    142 ##     "-"
    143 ##       単語の着色が未だ計算されていない事を表す。
    144 ##     g
    145 ##       描画属性値を保持する。
    146 ##     'm' len ':' attr (',' len ':' attr)*
    147 ##       長さ len の部分列と属性の組。
    148 ##       長さ len として '$' を指定した場合は単語終端までを意味する。
    149 ##     'd'
    150 ##       描画属性を削除することを意味する。
    151 ##
    152 ## @var _ble_syntax_TREE_WIDTH
    153 ##   _ble_syntax_tree に格納される一つの範囲情報のフィールドの数。
    154 ##
    155 ## @var _ble_syntax_attr[i]
    156 ##   文脈・属性の情報
    157 _ble_syntax_text=
    158 _ble_syntax_lang=bash
    159 _ble_syntax_stat=()
    160 _ble_syntax_nest=()
    161 _ble_syntax_tree=()
    162 _ble_syntax_attr=()
    163 
    164 _ble_syntax_TREE_WIDTH=5
    165 
    166 #--------------------------------------
    167 # ble/syntax/tree-enumerate proc
    168 # ble/syntax/tree-enumerate-children proc
    169 # ble/syntax/tree-enumerate-in-range beg end proc
    170 
    171 function ble/syntax/tree-enumerate/.add-root-element {
    172   local wtype=$1 wlen=$2 tclen=$3 tplen=$4
    173 
    174   # Note: wtype は単語に格納される時に EndWtype テーブルで変換される。
    175   #  ble/syntax:bash/ctx-command/check-word-end の実装を参照の事。
    176   [[ ! ${wtype//[0-9]} && ${_ble_syntax_bash_command_EndWtype[wtype]} ]] &&
    177     wtype=${_ble_syntax_bash_command_EndWtype[wtype]}
    178 
    179   TE_root="$wtype $wlen $tclen $tplen -- $TE_root"
    180 }
    181 
    182 ## @fn ble/syntax/tree-enumerate/.initialize
    183 ##
    184 ##   @var[in]  iN
    185 ##     文字列の長さ、つまり現在の解析終端位置を指定します。
    186 ##
    187 ##   @var[out] TE_root
    188 ##     ${_ble_syntax_tree[iN-1]} を調整して返します。
    189 ##     閉じていない範囲 (word, nest) を終端位置で閉じたときの値を計算します。
    190 ##
    191 ##   @var[out] TE_i
    192 ##     一番最後の範囲の終端位置を返します。
    193 ##     解析情報がない場合は -1 を返します。
    194 ##
    195 ##   @var[out] TE_nofs
    196 ##     0 に初期化します。
    197 ##
    198 function ble/syntax/tree-enumerate/.initialize {
    199   if [[ ! ${_ble_syntax_stat[iN]} ]]; then
    200     TE_root= TE_i=-1 TE_nofs=0
    201     return 0
    202   fi
    203 
    204   local -a stat nest
    205   ble/string#split-words stat "${_ble_syntax_stat[iN]}"
    206   local wtype=${stat[2]}
    207   local wlen=${stat[1]}
    208   local nlen=${stat[3]} inest
    209   ((inest=nlen<0?nlen:iN-nlen))
    210   local tclen=${stat[4]}
    211   local tplen=${stat[5]}
    212 
    213   TE_root=
    214   ((iN>0)) && TE_root=${_ble_syntax_tree[iN-1]}
    215 
    216   while
    217     if ((wlen>=0)); then
    218       # TE_root に単語ノードを追加
    219       ble/syntax/tree-enumerate/.add-root-element "$wtype" "$wlen" "$tclen" "$tplen"
    220       tclen=0
    221     fi
    222     ((inest>=0))
    223   do
    224     ble/util/assert '[[ ${_ble_syntax_nest[inest]} ]]' "$FUNCNAME/FATAL1" || break
    225 
    226     ble/string#split-words nest "${_ble_syntax_nest[inest]}"
    227 
    228     local olen=$((iN-inest))
    229     tplen=${nest[4]}
    230     ((tplen>=0&&(tplen+=olen)))
    231 
    232     # TE_root にネストノードを追加
    233     ble/syntax/tree-enumerate/.add-root-element "${nest[7]}" "$olen" "$tclen" "$tplen"
    234 
    235     wtype=${nest[2]} wlen=${nest[1]} nlen=${nest[3]} tclen=0 tplen=${nest[5]}
    236     ((wlen>=0&&(wlen+=olen),
    237       tplen>=0&&(tplen+=olen),
    238       nlen>=0&&(nlen+=olen),
    239       inest=nlen<0?nlen:iN-nlen))
    240 
    241     ble/util/assert '((nlen<0||nlen>olen))' "$FUNCNAME/FATAL2" || break
    242   done
    243 
    244   if [[ $TE_root ]]; then
    245     ((TE_i=iN))
    246   else
    247     ((TE_i=tclen>=0?iN-tclen:tclen))
    248   fi
    249   ((TE_nofs=0))
    250 }
    251 
    252 ## @fn ble/syntax/tree-enumerate/.impl command...
    253 ##   @param[in] command...
    254 ##     各ノードについて呼び出すコマンドを指定します。
    255 ##   @var[in] iN
    256 ##   @var[in] TE_root,TE_i,TE_nofs
    257 function ble/syntax/tree-enumerate/.impl {
    258   local islast=1
    259   while ((TE_i>0)); do
    260     local -a node
    261     if ((TE_i<iN)); then
    262       ble/string#split-words node "${_ble_syntax_tree[TE_i-1]}"
    263     else
    264       ble/string#split-words node "${TE_root:-${_ble_syntax_tree[iN-1]}}"
    265     fi
    266 
    267     ble/util/assert '((TE_nofs<${#node[@]}))' "$FUNCNAME(i=$TE_i,iN=$iN,TE_nofs=$TE_nofs,node=${node[*]},command=$@)/FATAL1" || break
    268 
    269     local wtype=${node[TE_nofs]} wlen=${node[TE_nofs+1]} tclen=${node[TE_nofs+2]} tplen=${node[TE_nofs+3]} attr=${node[TE_nofs+4]}
    270     local wbegin=$((wlen<0?wlen:TE_i-wlen))
    271     local tchild=$((tclen<0?tclen:TE_i-tclen))
    272     local tprev=$((tplen<0?tplen:TE_i-tplen))
    273     "$@"
    274 
    275     ble/util/assert '((tprev<TE_i))' "$FUNCNAME/FATAL2" || break
    276 
    277     ((TE_i=tprev,TE_nofs=0,islast=0))
    278   done
    279 }
    280 
    281 ## @var[in] iN
    282 ## @var[in] TE_root,TE_i,TE_nofs
    283 ## @var[in] tchild
    284 function ble/syntax/tree-enumerate-children {
    285   ((0<tchild&&tchild<=TE_i)) || return 1
    286   local TE_nofs=$((TE_i==tchild?TE_nofs+_ble_syntax_TREE_WIDTH:0))
    287   local TE_i=$tchild
    288   ble/syntax/tree-enumerate/.impl "$@"
    289 }
    290 function ble/syntax/tree-enumerate-break { ((tprev=-1)); }
    291 
    292 ## @fn ble/syntax/tree-enumerate command...
    293 ##   現在の解析状態 _ble_syntax_tree に基いて、
    294 ##   指定したコマンド command... を
    295 ##   トップレベルの各ノードに対して末尾にあるノードから順に呼び出します。
    296 ##
    297 ##   @param[in] command...
    298 ##     呼び出すコマンドを指定します。
    299 ##
    300 ##     コマンドは以下のシェル変数を入力・出力とします。
    301 ##     @var[in]     TE_i TE_nofs
    302 ##     @var[in]     wtype wbegin wlen attr tchild
    303 ##     @var[in,out] tprev
    304 ##       列挙を中断する時は ble/syntax/tree-enumerate-break
    305 ##       を呼び出す事によって、tprev=-1 を設定します。
    306 ##
    307 ##     内部で ble/syntax/tree-enumerate-children を呼び出すと、
    308 ##     更に入れ子になった単語について処理を実行する事ができます。
    309 ##
    310 ##   @var[in] iN
    311 ##     解析の起点を指定します。_ble_syntax_stat が設定されている必要があります。
    312 ##     指定を省略した場合は _ble_syntax_stat の末尾が使用されます。
    313 function ble/syntax/tree-enumerate {
    314   local TE_root TE_i TE_nofs
    315   [[ ${iN:+set} ]] || local iN=${#_ble_syntax_text}
    316   ble/syntax/tree-enumerate/.initialize
    317   ble/syntax/tree-enumerate/.impl "$@"
    318 }
    319 
    320 ## @fn ble/syntax/tree-enumerate-in-range beg end proc
    321 ##   入れ子構造に従わず或る範囲内に登録されている節を列挙します。
    322 ##   @param[in] beg,end
    323 ##   @param[in] proc
    324 ##     以下の変数を使用する関数を指定します。
    325 ##     @var[in] wtype wlen wbeg wend wattr
    326 ##     @var[in] node
    327 ##     @var[in] TE_i TE_nofs
    328 function ble/syntax/tree-enumerate-in-range {
    329   local beg=$1 end=$2
    330   local proc=$3
    331   local -a node
    332   local TE_i TE_nofs
    333   for ((TE_i=end;TE_i>=beg;TE_i--)); do
    334     ((TE_i>0)) && [[ ${_ble_syntax_tree[TE_i-1]} ]] || continue
    335     ble/string#split-words node "${_ble_syntax_tree[TE_i-1]}"
    336     local flagUpdateNode=
    337     for ((TE_nofs=0;TE_nofs<${#node[@]};TE_nofs+=_ble_syntax_TREE_WIDTH)); do
    338       local wtype=${node[TE_nofs]} wlen=${node[TE_nofs+1]} wattr=${node[TE_nofs+4]}
    339       local wbeg=$((wlen<0?wlen:TE_i-wlen)) wend=$TE_i
    340       "${@:3}"
    341     done
    342   done
    343 }
    344 
    345 #--------------------------------------
    346 # ble/syntax/print-status
    347 
    348 function ble/syntax/print-status/.graph {
    349   local char=$1
    350   if ble/util/isprint+ "$char"; then
    351     graph="'$char'"
    352     return 0
    353   else
    354     local ret
    355     ble/util/s2c "$char"
    356     local code=$ret
    357     if ((code<32)); then
    358       ble/util/c2s "$((code+64))"
    359       graph="$_ble_term_rev^$ret$_ble_term_sgr0"
    360     elif ((code==127)); then
    361       graph="$_ble_term_rev^?$_ble_term_sgr0"
    362     elif ((128<=code&&code<160)); then
    363       ble/util/c2s "$((code-64))"
    364       graph="${_ble_term_rev}M-^$ret$_ble_term_sgr0"
    365     else
    366       graph="'$char' ($code)"
    367     fi
    368   fi
    369 }
    370 
    371 ## @var[in,out] word
    372 function ble/syntax/print-status/.tree-prepend {
    373   local j=$1
    374   local value=$2${tree[j]}
    375   tree[j]=$value
    376   ((max_tree_width<${#value}&&(max_tree_width=${#value})))
    377 }
    378 
    379 function ble/syntax/print-status/.dump-arrays/.append-attr-char {
    380   if (($?==0)); then
    381     attr="${attr}$1"
    382   else
    383     attr="${attr} "
    384   fi
    385 }
    386 
    387 ## @fn ble/syntax/print-status/ctx#get-text ctx
    388 ##   @var[out] ret
    389 function ble/syntax/print-status/ctx#get-text {
    390   local sgr
    391   ble/syntax/ctx#get-name "$1"
    392   ret=${ret#BLE_}
    393   if [[ ! $ret ]]; then
    394     ble/color/face2sgr syntax_error
    395     ret="${ret}CTX$1$_ble_term_sgr0"
    396   fi
    397 }
    398 ## @fn ble/syntax/print-status/word.get-text index
    399 ##   _ble_syntax_tree[index] の内容を文字列にします。
    400 ##   @param[in] index
    401 ##   @var[out]  word
    402 function ble/syntax/print-status/word.get-text {
    403   local index=$1
    404   ble/string#split-words word "${_ble_syntax_tree[index]}"
    405   local out= ret
    406   if [[ $word ]]; then
    407     local nofs=$((${#word[@]}/_ble_syntax_TREE_WIDTH*_ble_syntax_TREE_WIDTH))
    408     while (((nofs-=_ble_syntax_TREE_WIDTH)>=0)); do
    409       local axis=$((index+1))
    410 
    411       local wtype=${word[nofs]}
    412       if [[ $wtype =~ ^[0-9]+$ ]]; then
    413         ble/syntax/print-status/ctx#get-text "$wtype"; wtype=$ret
    414       elif [[ $wtype =~ ^n* ]]; then
    415         # Note: nest-pop 時の tree-append では prefix n を付けている。
    416         wtype=$sgr_quoted\"${wtype:1}\"$_ble_term_sgr0
    417       else
    418         wtype=$sgr_error${wtype}$_ble_term_sgr0
    419       fi
    420 
    421       local b=$((axis-word[nofs+1])) e=$axis
    422       local sprev=${word[nofs+3]} schild=${word[nofs+2]}
    423       if ((sprev>=0)); then
    424         sprev="@$((axis-sprev-1))>"
    425       else
    426         sprev=
    427       fi
    428       if ((schild>=0)); then
    429         schild=">@$((axis-schild-1))"
    430       else
    431         schild=
    432       fi
    433 
    434       local wattr=${word[nofs+4]}
    435       if [[ $wattr != - ]]; then
    436         wattr="/(wattr=$wattr)"
    437       else
    438         wattr=
    439       fi
    440 
    441       out=" word=$wtype:$sprev$b-$e$schild$wattr$out"
    442       for ((;b<index;b++)); do
    443         ble/syntax/print-status/.tree-prepend "$b" '|'
    444       done
    445       ble/syntax/print-status/.tree-prepend "$index" '+'
    446     done
    447     word=$out
    448   fi
    449 }
    450 ## @fn ble/syntax/print-status/nest.get-text index
    451 ##   _ble_syntax_nest[index] の内容を文字列にします。
    452 ##   @param[in] index
    453 ##   @var[out]  nest
    454 function ble/syntax/print-status/nest.get-text {
    455   local index=$1
    456   ble/string#split-words nest "${_ble_syntax_nest[index]}"
    457   if [[ $nest ]]; then
    458     local ret
    459     ble/syntax/print-status/ctx#get-text "${nest[0]}"; local nctx=$ret
    460 
    461     local nword=-
    462     if ((nest[1]>=0)); then
    463       ble/syntax/print-status/ctx#get-text "${nest[2]}"; local swtype=$ret
    464       local wbegin=$((index-nest[1]))
    465       nword="$swtype:$wbegin-"
    466     fi
    467 
    468     local nnest=-
    469     ((nest[3]>=0)) && nnest="'${nest[7]}':$((index-nest[3]))-"
    470 
    471     local nchild=-
    472     if ((nest[4]>=0)); then
    473       local tchild=$((index-nest[4]))
    474       nchild='$'$tchild
    475       if ! ((0<tchild&&tchild<=index)) || [[ ! ${_ble_syntax_tree[tchild-1]} ]]; then
    476         nchild=$sgr_error$nchild$_ble_term_sgr0
    477       fi
    478     fi
    479 
    480     local nprev=-
    481     if ((nest[5]>=0)); then
    482       local tprev=$((index-nest[5]))
    483       nprev='$'$tprev
    484       if ! ((0<tprev&&tprev<=index)) || [[ ! ${_ble_syntax_tree[tprev-1]} ]]; then
    485         nprev=$sgr_error$nprev$_ble_term_sgr0
    486       fi
    487     fi
    488 
    489     local nparam=${nest[6]}
    490     if [[ $nparam == none ]]; then
    491       nparam=
    492     else
    493       # Note #D1774: bash-3.0 bug "${var//../$'...'}" とすると $'' の引用符が残
    494       #   る問題の回避の為に行を分けて代入する。
    495       nparam=${nparam//$_ble_term_FS/$'\e[7m^\\\e[m'}
    496       nparam=" nparam=$nparam"
    497     fi
    498 
    499     nest=" nest=($nctx w=$nword n=$nnest t=$nchild:$nprev$nparam)"
    500   fi
    501 }
    502 ## @fn ble/syntax/print-status/stat.get-text index
    503 ##   _ble_syntax_stat[index] の内容を文字列にします。
    504 ##   @param[in] index
    505 ##   @var[out]  stat
    506 function ble/syntax/print-status/stat.get-text {
    507   local index=$1
    508   ble/string#split-words stat "${_ble_syntax_stat[index]}"
    509   if [[ $stat ]]; then
    510     local ret
    511     ble/syntax/print-status/ctx#get-text "${stat[0]}"; local stat_ctx=$ret
    512 
    513     local stat_word=-
    514     if ((stat[1]>=0)); then
    515       ble/syntax/print-status/ctx#get-text "${stat[2]}"; local stat_wtype=$ret
    516       stat_word="$stat_wtype:$((index-stat[1]))-"
    517     fi
    518 
    519     local stat_inest=-
    520     if ((stat[3]>=0)); then
    521       local inest=$((index-stat[3]))
    522       stat_inest="@$inest"
    523       if ((inest<0)) || [[ ! ${_ble_syntax_nest[inest]} ]]; then
    524         stat_inest=$sgr_error$stat_inest$_ble_term_sgr0
    525       fi
    526     fi
    527 
    528     local stat_child=-
    529     if ((stat[4]>=0)); then
    530       local tchild=$((index-stat[4]))
    531       stat_child='$'$tchild
    532       if ! ((0<tchild&&tchild<=index)) || [[ ! ${_ble_syntax_tree[tchild-1]} ]]; then
    533         stat_child=$sgr_error$stat_child$_ble_term_sgr0
    534       fi
    535     fi
    536 
    537     local stat_prev=-
    538     if ((stat[5]>=0)); then
    539       local tprev=$((index-stat[5]))
    540       stat_prev='$'$tprev
    541       if ! ((0<tprev&&tprev<=index)) || [[ ! ${_ble_syntax_tree[tprev-1]} ]]; then
    542         stat_prev=$sgr_error$stat_prev$_ble_term_sgr0
    543       fi
    544     fi
    545 
    546     local snparam=${stat[6]}
    547     if [[ $snparam == none ]]; then
    548       snparam=
    549     else
    550       # Note #D1774: bash-3.0 bug "${var//$'...'}" とすると余分な引用符が残る問
    551       #   題を回避する為に行を分けて代入している。
    552       snparam=${snparam//"$_ble_term_FS"/$'\e[7m^\\\e[m'}
    553       snparam=" nparam=$snparam"
    554     fi
    555 
    556     local stat_lookahead=
    557     ((stat[7]!=1)) && stat_lookahead=" >>${stat[7]}"
    558     stat=" stat=($stat_ctx w=$stat_word n=$stat_inest t=$stat_child:$stat_prev$snparam$stat_lookahead)"
    559   fi
    560 }
    561 
    562 ## @var[out] resultA
    563 ## @var[in]  iN
    564 function ble/syntax/print-status/.dump-arrays {
    565   local -a tree char line
    566   tree=()
    567   char=()
    568   line=()
    569 
    570   local ret
    571   ble/color/face2sgr syntax_error; local sgr_error=$ret
    572   ble/color/face2sgr syntax_quoted; local sgr_quoted=$ret
    573 
    574   local i max_tree_width=0
    575   for ((i=0;i<=iN;i++)); do
    576     local attr="  ${_ble_syntax_attr[i]:-|}"
    577     if ((_ble_syntax_attr_umin<=i&&i<_ble_syntax_attr_umax)); then
    578       attr="${attr:${#attr}-2:2}*"
    579     else
    580       attr="${attr:${#attr}-2:2} "
    581     fi
    582 
    583     [[ ${_ble_highlight_layer_syntax1_table[i]} ]] && ble/color/g2sgr "${_ble_highlight_layer_syntax1_table[i]}"
    584     ble/syntax/print-status/.dump-arrays/.append-attr-char "${ret}a${_ble_term_sgr0}"
    585     [[ ${_ble_highlight_layer_syntax2_table[i]} ]] && ble/color/g2sgr "${_ble_highlight_layer_syntax2_table[i]}"
    586     ble/syntax/print-status/.dump-arrays/.append-attr-char "${ret}w${_ble_term_sgr0}"
    587     [[ ${_ble_highlight_layer_syntax3_table[i]} ]] && ble/color/g2sgr "${_ble_highlight_layer_syntax3_table[i]}"
    588     ble/syntax/print-status/.dump-arrays/.append-attr-char "${ret}e${_ble_term_sgr0}"
    589 
    590     [[ ${_ble_syntax_stat_shift[i]} ]]
    591     ble/syntax/print-status/.dump-arrays/.append-attr-char s
    592 
    593     local index=000$i
    594     index=${index:${#index}-3:3}
    595 
    596     local word nest stat
    597     ble/syntax/print-status/word.get-text "$i"
    598     ble/syntax/print-status/nest.get-text "$i"
    599     ble/syntax/print-status/stat.get-text "$i"
    600 
    601     local graph=
    602     ble/syntax/print-status/.graph "${_ble_syntax_text:i:1}"
    603     char[i]="$attr $index $graph"
    604     line[i]=$word$nest$stat
    605   done
    606 
    607   resultA='_ble_syntax_attr/tree/nest/stat?'$'\n'
    608   ble/string#reserve-prototype "$max_tree_width"
    609   for ((i=0;i<=iN;i++)); do
    610     local t=${tree[i]}${_ble_string_prototype::max_tree_width}
    611     resultA="$resultA${char[i]} ${t::max_tree_width}${line[i]}"$'\n'
    612   done
    613 }
    614 
    615 ## @fn ble/syntax/print-status/.dump-tree/proc1
    616 ## @var[out] resultB
    617 ## @var[in]  prefix
    618 ## @var[in]  nl
    619 function ble/syntax/print-status/.dump-tree/proc1 {
    620   local tip="| "; tip=${tip:islast:1}
    621   prefix="$prefix$tip   " ble/syntax/tree-enumerate-children ble/syntax/print-status/.dump-tree/proc1
    622   resultB="$prefix\_ '${_ble_syntax_text:wbegin:wlen}'$nl$resultB"
    623 }
    624 
    625 ## @fn ble/syntax/print-status/.dump-tree
    626 ## @var[out] resultB
    627 ## @var[in]  iN
    628 function ble/syntax/print-status/.dump-tree {
    629   resultB=
    630 
    631   local nl=$_ble_term_nl
    632   local prefix=
    633   ble/syntax/tree-enumerate ble/syntax/print-status/.dump-tree/proc1
    634 }
    635 
    636 function ble/syntax/print-status {
    637   local iN=${#_ble_syntax_text}
    638 
    639   local resultA
    640   ble/syntax/print-status/.dump-arrays
    641 
    642   local resultB
    643   ble/syntax/print-status/.dump-tree
    644 
    645   local result=$resultA$resultB
    646   if [[ $1 == -v && $2 ]]; then
    647     local "${2%%\[*\]}" && ble/util/upvar "$2" "$result"
    648   else
    649     ble/util/print "$result"
    650   fi
    651 }
    652 
    653 function ble/syntax/print-layer-buffer.draw {
    654   local layer_name=$1
    655   local -a keys vals
    656   builtin eval "keys=(\"\${!_ble_highlight_layer_${layer_name}_buff[@]}\")"
    657   builtin eval "vals=(\"\${_ble_highlight_layer_${layer_name}_buff[@]}\")"
    658 
    659   local ret sgr0=$_ble_term_sgr0
    660   ble/color/face2sgr command_builtin; local sgr1=$ret
    661   ble/color/face2sgr syntax_varname; local sgr2=$ret
    662   ble/color/face2sgr syntax_quoted; local sgr3=$ret
    663 
    664   ble/canvas/put.draw "${sgr1}buffer${sgr0} ${sgr2}$layer_name${sgr0}=("
    665   local i count=0
    666   for ((i=0;i<${#keys[@]};i++)); do
    667     local key=${keys[i]} val=${vals[i]}
    668     while ((count++<key)); do
    669       ((count==1)) || ble/canvas/put.draw ' '
    670       ble/canvas/put.draw $'\e[91munset\e[m'
    671     done
    672 
    673     ((count==1)) || ble/canvas/put.draw ' '
    674     ble/string#quote-word "$val" quote-empty:sgrq="$sgr3"
    675     ble/canvas/put.draw "$ret"
    676   done
    677   ble/canvas/put.draw ")$_ble_term_nl"
    678 }
    679 
    680 #--------------------------------------
    681 
    682 function ble/syntax/parse/serialize-stat {
    683   ((ilook<=i&&(ilook=i+1)))
    684   sstat="$ctx $((wbegin<0?wbegin:i-wbegin)) $wtype $((inest<0?inest:i-inest)) $((tchild<0?tchild:i-tchild)) $((tprev<0?tprev:i-tprev)) ${nparam:-none} $((ilook-i))"
    685 }
    686 
    687 
    688 ## @fn ble/syntax/parse/set-lookahead count
    689 ##
    690 ##   @param[in] count
    691 ##     現在位置の何文字先まで参照して動作を決定したかを指定します。
    692 ##   @var[out] i
    693 ##   @var[out] ilook
    694 ##
    695 ##   例えば "a@bcdx" の @ の位置に i があって、
    696 ##   x の文字を見て c 直後までしか読み取らない事を決定したとき、
    697 ##   set-lookahead 4 を実行して i を 2 進めるか、
    698 ##   i を 2 進めてから set-lookahead 2 を実行します。
    699 ##
    700 ##   最終的に i の次の 1 文字までしか参照しない時、
    701 ##   set-lookahead を呼び出す必要はありません。
    702 ##
    703 function ble/syntax/parse/set-lookahead {
    704   ((i+$1>ilook&&(ilook=i+$1)))
    705 }
    706 
    707 # 構文木の管理 (_ble_syntax_tree)
    708 
    709 ## @fn ble/syntax/parse/tree-append
    710 ## 要件 解析位置を進めてから呼び出す必要があります (要件: i>=p1+1)。
    711 function ble/syntax/parse/tree-append {
    712 #%if !release
    713   [[ $debug_p1 ]] && ble/util/assert '((i-1>=debug_p1))' "Wrong call of tree-append: Condition violation (p1=$debug_p1 i=$i iN=$iN)."
    714 #%end
    715   local type=$1
    716   local beg=$2 end=$i
    717   local len=$((end-beg))
    718   ((len==0)) && return 0
    719 
    720   local tchild=$3 tprev=$4
    721 
    722   # 子情報・兄情報
    723   local ochild=-1 oprev=-1
    724   ((tchild>=0&&(ochild=i-tchild)))
    725   ((tprev>=0&&(oprev=i-tprev)))
    726 
    727   [[ $type =~ ^[0-9]+$ ]] && ble/syntax/parse/touch-updated-word "$i"
    728 
    729   # 追加する要素の数は _ble_syntax_TREE_WIDTH と一致している必要がある。
    730   _ble_syntax_tree[i-1]="$type $len $ochild $oprev - ${_ble_syntax_tree[i-1]}"
    731 }
    732 
    733 function ble/syntax/parse/word-push {
    734   wtype=$1 wbegin=$2 tprev=$tchild tchild=-1
    735 }
    736 ## @fn ble/syntax/parse/word-pop
    737 ## 要件 解析位置を進めてから呼び出す必要があります (要件: i>=p1+1)。
    738 # 仮定: 1つ上の level は nest-push による level か top level のどちらかである。
    739 #   この場合に限って ble/syntax/parse/nest-reset-tprev を用いて、tprev
    740 #   を適切な値に復元することができる。
    741 function ble/syntax/parse/word-pop {
    742   ble/syntax/parse/tree-append "$wtype" "$wbegin" "$tchild" "$tprev"
    743   ((wbegin=-1,wtype=-1,tchild=i))
    744   ble/syntax/parse/nest-reset-tprev
    745 }
    746 ## '[[' 専用の関数:
    747 ##   word-push/word-pop と nest-push の順序を反転させる為に。
    748 ##   具体的にどう使われているかは使っている箇所を参照すると良い。
    749 ##   ※本当は [[ が見付かった時点でコマンドとして読み取るのではなく、
    750 ##     特別扱いするべきな気もするが、面倒なので今の実装になっている。
    751 ## 仮定: 一番最後に設置されたのが単語である事。
    752 ##   かつ、キャンセルされる単語は今回の解析ステップで設置された物である事。
    753 function ble/syntax/parse/word-cancel {
    754   local -a word
    755   ble/string#split-words word "${_ble_syntax_tree[i-1]}"
    756   local wlen=${word[1]} tplen=${word[3]}
    757   local wbegin=$((i-wlen))
    758   tchild=$((tplen<0?tplen:i-tplen))
    759   ble/array#fill-range _ble_syntax_tree "$wbegin" "$i" ''
    760 }
    761 
    762 # 入れ子構造の管理
    763 
    764 ## @fn ble/syntax/parse/nest-push newctx ntype
    765 ##   @param[in]     newctx 新しい ctx を指定します。
    766 ##   @param[in,opt] ntype  文法要素の種類を指定します。
    767 ##   @var  [in]     i      現在の位置を指定します。
    768 ##   @var  [in out] inest  親 nest の位置を指定します。新しい nest の位置 (i) を返します。
    769 ##   @var  [in,out] ctx    復帰時の ctx を指定します。新しい ctx (newctx) を返します。
    770 ##   @var  [in,out] wbegin 復帰時の wbegin を指定します。新しい wbegin (-1) を返します。
    771 ##   @var  [in,out] wtype  復帰時の wtype を指定します。新しい wtype (-1) を返します。
    772 ##   @var  [in,out] tchild 復帰時の tchild を指定します。新しい tchild (-1) を返します。
    773 ##   @var  [in,out] tprev  復帰時の tprev を指定します。新しい tprev (tchild) を返します。
    774 ##   @var  [in,out] nparam 復帰時の nparam を指定します。新しい nparam (空文字列) を返します。
    775 function ble/syntax/parse/nest-push {
    776   local wlen=$((wbegin<0?wbegin:i-wbegin))
    777   local nlen=$((inest<0?inest:i-inest))
    778   local tclen=$((tchild<0?tchild:i-tchild))
    779   local tplen=$((tprev<0?tprev:i-tprev))
    780   _ble_syntax_nest[i]="$ctx $wlen $wtype $nlen $tclen $tplen ${nparam:-none} ${2:-none}"
    781   ((ctx=$1,inest=i,wbegin=-1,wtype=-1,tprev=tchild,tchild=-1))
    782   nparam=
    783 }
    784 ## @fn ble/syntax/parse/nest-pop
    785 ## 要件 解析位置を進めてから呼び出す必要があります (要件: i>=p1+1)。
    786 ##   現在の入れ子を閉じます。現在の入れ子情報を記録して、一つ上の入れ子情報を復元します。
    787 ##   @var[   out] ctx      上の入れ子階層の ctx を復元します。
    788 ##   @var[   out] wbegin   上の入れ子階層の wbegin を復元します。
    789 ##   @var[   out] wtype    上の入れ子階層の wtype を復元します。
    790 ##   @var[in,out] inest    記録する入れ子情報を指定します。上の入れ子階層の inest を復元します。
    791 ##   @var[in,out] tchild   記録する入れ子情報を指定します。上の入れ子階層の tchild を復元します。
    792 ##   @var[in,out] tprev    記録する入れ子情報を指定します。上の入れ子階層の tprev を復元します。
    793 ##   @var[   out] nparam   上の入れ子階層の nparam を復元します。
    794 function ble/syntax/parse/nest-pop {
    795   ((inest<0)) && return 1
    796 
    797   local -a parentNest
    798   ble/string#split-words parentNest "${_ble_syntax_nest[inest]}"
    799 
    800   local ntype=${parentNest[7]} nbeg=$inest
    801   ble/syntax/parse/tree-append "n$ntype" "$nbeg" "$tchild" "$tprev"
    802 
    803   local wlen=${parentNest[1]} nlen=${parentNest[3]} tplen=${parentNest[5]}
    804   ((ctx=parentNest[0]))
    805   ((wtype=parentNest[2]))
    806   ((wbegin=wlen<0?wlen:nbeg-wlen,
    807     inest=nlen<0?nlen:nbeg-nlen,
    808     tchild=i,
    809     tprev=tplen<0?tplen:nbeg-tplen))
    810   nparam=${parentNest[6]}
    811   [[ $nparam == none ]] && nparam=
    812 }
    813 function ble/syntax/parse/nest-type {
    814   local _ble_local_var=ntype
    815   [[ $1 == -v ]] && _ble_local_var=$2
    816   if ((inest<0)); then
    817     builtin eval "$_ble_local_var="
    818     return 1
    819   else
    820     builtin eval "$_ble_local_var=\"\${_ble_syntax_nest[inest]##* }\""
    821   fi
    822 }
    823 ## @fn ble/syntax/parse/nest-ctx
    824 ##   @var[out] nctx
    825 function ble/syntax/parse/nest-ctx {
    826   nctx=
    827   ((inest>=0)) || return 1
    828   nctx=${_ble_syntax_nest[inest]%% *}
    829 }
    830 function ble/syntax/parse/nest-reset-tprev {
    831   if ((inest<0)); then
    832     tprev=-1
    833   else
    834     local -a nest
    835     ble/string#split-words nest "${_ble_syntax_nest[inest]}"
    836     local tclen=${nest[4]}
    837     ((tprev=tclen<0?tclen:inest-tclen))
    838   fi
    839 }
    840 ## @fn ble/syntax/parse/nest-equals
    841 ##   現在のネスト状態と前回のネスト状態が一致するか判定します。
    842 ## @var i1                     更新開始点
    843 ## @var i2                     更新終了点
    844 ## @var tail_syntax_stat[i-i2] i2 以降の更新前状態
    845 ## @var _ble_syntax_stat[i]    新しい状態
    846 function ble/syntax/parse/nest-equals {
    847   local parent_inest=$1
    848   while :; do
    849     ((parent_inest<i1)) && return 0 # 変更していない範囲 または -1
    850     ((parent_inest<i2)) && return 1 # 変更によって消えた範囲
    851 
    852     local onest=${tail_syntax_nest[parent_inest-i2]}
    853     local nnest=${_ble_syntax_nest[parent_inest]}
    854     [[ $onest != "$nnest" ]] && return 1
    855 
    856     ble/string#split-words onest "$onest"
    857 #%if !release
    858     ble/util/assert \
    859       '((onest[3]!=0&&onest[3]<=parent_inest))' \
    860       "invalid nest onest[3]=${onest[3]} parent_inest=$parent_inest text=$text" || return 0
    861 #%end
    862     ((parent_inest=onest[3]<0?onest[3]:(parent_inest-onest[3])))
    863   done
    864 }
    865 
    866 # 属性値の変更範囲
    867 
    868 ## @var _ble_syntax_attr_umin, _ble_syntax_attr_umax は更新された文法属性の範囲を記録する。
    869 ## @var _ble_syntax_word_umin, _ble_syntax_word_umax は更新された単語の先頭位置の範囲を記録する。
    870 ##   attr については [_ble_syntax_attr_umin, _ble_syntax_attr_umax) が範囲である。
    871 ##   word については [_ble_syntax_word_umin, _ble_syntax_word_umax] が範囲である。
    872 _ble_syntax_attr_umin=-1 _ble_syntax_attr_umax=-1
    873 _ble_syntax_word_umin=-1 _ble_syntax_word_umax=-1
    874 _ble_syntax_word_defer_umin=-1 _ble_syntax_word_defer_umax=-1
    875 function ble/syntax/parse/touch-updated-attr {
    876   ble/syntax/urange#update _ble_syntax_attr_ "$1" "$(($1+1))"
    877 }
    878 function ble/syntax/parse/touch-updated-word {
    879 #%if !release
    880   ble/util/assert "(($1>0))" "invalid word position $1"
    881 #%end
    882   ble/syntax/wrange#update _ble_syntax_word_ "$1"
    883 }
    884 
    885 #==============================================================================
    886 #
    887 # 文脈値
    888 #
    889 
    890 # 文脈値達 from lib/core-syntax-ctx.def
    891 #%$ sed 's/[[:space:]]*#.*//;/^$/d' lib/core-syntax-ctx.def | awk '$2 ~ /^[0-9]+$/ {print $1 "=" $2;}'
    892 
    893 # for debug
    894 _ble_syntax_bash_ctx_names=(
    895 #%$ sed 's/[[:space:]]*#.*//;/^$/d' lib/core-syntax-ctx.def | awk '$2 ~ /^[0-9]+$/ {print "  [" $2 "]=" $1;}'
    896 )
    897 
    898 ## @fn ble/syntax/ctx#get-name ctx
    899 ##   @param[in] ctx
    900 ##   @var[out] ret
    901 function ble/syntax/ctx#get-name {
    902   ret=${_ble_syntax_bash_ctx_names[$1]#_ble_ctx_}
    903 }
    904 
    905 # @var _ble_syntax_context_proc[]
    906 # @var _ble_syntax_context_end[]
    907 #   以上の二つの配列を通して文法要素は最終的に登録される。
    908 #   (逆に言えば上の二つの配列を弄れば別の文法の解析を実行する事もできる)
    909 _ble_syntax_context_proc=()
    910 _ble_syntax_context_end=()
    911 
    912 #==============================================================================
    913 #
    914 # 空文法
    915 #
    916 #------------------------------------------------------------------------------
    917 
    918 function ble/syntax:text/ctx-unspecified {
    919   ((i+=${#tail}))
    920   return 0
    921 }
    922 _ble_syntax_context_proc[CTX_UNSPECIFIED]=ble/syntax:text/ctx-unspecified
    923 
    924 function ble/syntax:text/initialize-ctx { ctx=$CTX_UNSPECIFIED; }
    925 function ble/syntax:text/initialize-vars { :; }
    926 
    927 #==============================================================================
    928 #
    929 # Bash Script 文法
    930 #
    931 #------------------------------------------------------------------------------
    932 
    933 _ble_syntax_bash_RexSpaces=$'[ \t]+'
    934 _ble_syntax_bash_RexIFSs="[$_ble_term_IFS]+"
    935 _ble_syntax_bash_RexDelimiter="[$_ble_term_IFS;|&<>()]"
    936 _ble_syntax_bash_RexRedirect='((\{[_a-zA-Z][_a-zA-Z0-9]*\}|[0-9]+)?(&?>>?|>[|&]|<[>&]?|<<[-<]?))[ 	]*'
    937 
    938 ## @var _ble_syntax_bash_chars[]
    939 ##   特定の役割を持つ文字の集合。Bracket expression [~] に入れて使う為の物。
    940 ##   histchars に依存しているので変化があった時に更新する。
    941 _ble_syntax_bash_chars=()
    942 _ble_syntax_bashc_seed=
    943 
    944 function ble/syntax:bash/cclass/update/reorder {
    945   builtin eval "local a=\"\${$1}\""
    946 
    947   # Bracket expression として安全な順に並び替える
    948   [[ $a == *']'* ]] && a="]${a//]}"
    949   [[ $a == *'-'* ]] && a="${a//-}-"
    950 
    951   builtin eval "$1=\$a"
    952 }
    953 
    954 ## @fn ble/syntax:bash/cclass/update
    955 ##
    956 ##   @var[in] _ble_syntax_bash_histc12
    957 ##   @var[in,out] _ble_syntax_bashc_seed
    958 ##   @var[in,out] _ble_syntax_bash_chars[]
    959 ##
    960 ##   @exit 更新があった時に正常終了します。
    961 ##     更新の必要がなかった時に 1 を返します。
    962 ##
    963 function ble/syntax:bash/cclass/update {
    964   local seed=$_ble_syntax_bash_histc12
    965   shopt -q extglob && seed=${seed}x
    966   [[ $seed == "$_ble_syntax_bashc_seed" ]] && return 1
    967   _ble_syntax_bashc_seed=$seed
    968 
    969   local key modified=
    970   if [[ $_ble_syntax_bash_histc12 == '!^' ]]; then
    971     for key in "${!_ble_syntax_bash_charsDef[@]}"; do
    972       _ble_syntax_bash_chars[key]=${_ble_syntax_bash_charsDef[key]}
    973     done
    974     _ble_syntax_bashc_simple=$_ble_syntax_bash_chars_simpleDef
    975   else
    976     modified=1
    977 
    978     local histc1=${_ble_syntax_bash_histc12:0:1}
    979     local histc2=${_ble_syntax_bash_histc12:1:1}
    980     for key in "${!_ble_syntax_bash_charsFmt[@]}"; do
    981       local a=${_ble_syntax_bash_charsFmt[key]}
    982       a=${a//@h/"$histc1"}
    983       a=${a//@q/"$histc2"}
    984       _ble_syntax_bash_chars[key]=$a
    985     done
    986 
    987     local a=$_ble_syntax_bash_chars_simpleFmt
    988     a=${a//@h/"$histc1"}
    989     a=${a//@q/"$histc2"}
    990     _ble_syntax_bashc_simple=$a
    991   fi
    992 
    993   if [[ $seed == *x ]]; then
    994     # extglob: ?() *() +() @() !()
    995     local extglob='@+!' # *? は既に登録されている筈
    996     _ble_syntax_bash_chars[CTX_ARGI]=${_ble_syntax_bash_chars[CTX_ARGI]}$extglob
    997     _ble_syntax_bash_chars[CTX_PATN]=${_ble_syntax_bash_chars[CTX_PATN]}$extglob
    998     _ble_syntax_bash_chars[CTX_PWORD]=${_ble_syntax_bash_chars[CTX_PWORD]}$extglob
    999     _ble_syntax_bash_chars[CTX_PWORDE]=${_ble_syntax_bash_chars[CTX_PWORDE]}$extglob
   1000     _ble_syntax_bash_chars[CTX_PWORDR]=${_ble_syntax_bash_chars[CTX_PWORDR]}$extglob
   1001   fi
   1002 
   1003   if [[ $modified ]]; then
   1004     for key in "${!_ble_syntax_bash_chars[@]}"; do
   1005       ble/syntax:bash/cclass/update/reorder _ble_syntax_bash_chars[key]
   1006     done
   1007     ble/syntax:bash/cclass/update/reorder _ble_syntax_bashc_simple
   1008   fi
   1009   return 0
   1010 }
   1011 
   1012 _ble_syntax_bash_charsDef=()
   1013 _ble_syntax_bash_charsFmt=()
   1014 _ble_syntax_bash_chars_simpleDef=
   1015 _ble_syntax_bash_chars_simpleFmt=
   1016 function ble/syntax:bash/cclass/initialize {
   1017   local delimiters="$_ble_term_IFS;|&()<>"
   1018   local expansions="\$\"\`\\'"
   1019   local glob='[*?'
   1020   local tilde='~:'
   1021 
   1022   # _ble_syntax_bash_chars[CTX_ARGI] は以下で使われている
   1023   #   ctx-command (色々)
   1024   #   ctx-redirect (CTX_RDRF, CTX_RDRD, CTX_RDRD2, CTX_RDRS)
   1025   #   ctx-values (CTX_VALI, CTX_VALR, CTX_VALQ)
   1026   #   ctx-conditions (CTX_CONDI, CTX_CONDQ)
   1027   # 更に以下でも使われている
   1028   #   ctx-bracket-expression
   1029   #   ctx-brace-expansion
   1030   #   check-tilde-expansion
   1031 
   1032   # default values
   1033   _ble_syntax_bash_charsDef[CTX_ARGI]="$delimiters$expansions$glob{$tilde^!"
   1034   _ble_syntax_bash_charsDef[CTX_PATN]="$expansions$glob(|)<>{!" # <> はプロセス置換のため。
   1035   _ble_syntax_bash_charsDef[CTX_QUOT]="\$\"\`\\!"         # 文字列 "~" で特別な意味を持つのは $ ` \ " のみ。+履歴展開の ! も。
   1036   _ble_syntax_bash_charsDef[CTX_EXPR]="][}()$expansions!" # ()[] は入れ子を数える為。} は ${var:ofs:len} の為。
   1037   _ble_syntax_bash_charsDef[CTX_PWORD]="}$expansions$glob!" # パラメータ展開 ${~}
   1038   _ble_syntax_bash_charsDef[CTX_PWORDE]="}$expansions$glob!" # パラメータ展開 ${~} エラー
   1039   _ble_syntax_bash_charsDef[CTX_PWORDR]="}/$expansions$glob!" # パラメータ展開 ${~} 置換前
   1040   _ble_syntax_bash_charsDef[CTX_RDRH]="$delimiters$expansions"
   1041   _ble_syntax_bash_charsDef[CTX_HERE1]="\\\$\`$_ble_term_nl!"
   1042 
   1043   # templates
   1044   _ble_syntax_bash_charsFmt[CTX_ARGI]="$delimiters$expansions$glob{$tilde@q@h"
   1045   _ble_syntax_bash_charsFmt[CTX_PATN]="$expansions$glob(|)<>{@h"
   1046   _ble_syntax_bash_charsFmt[CTX_QUOT]="\$\"\`\\@h"
   1047   _ble_syntax_bash_charsFmt[CTX_EXPR]="][}()$expansions@h"
   1048   _ble_syntax_bash_charsFmt[CTX_PWORD]="}$expansions$glob@h"
   1049   _ble_syntax_bash_charsFmt[CTX_PWORDE]="}$expansions$glob@h"
   1050   _ble_syntax_bash_charsFmt[CTX_PWORDR]="}/$expansions$glob@h"
   1051   _ble_syntax_bash_charsFmt[CTX_RDRH]=${_ble_syntax_bash_charsDef[CTX_RDRH]}
   1052   _ble_syntax_bash_charsFmt[CTX_HERE1]="\\\$\`$_ble_term_nl@h"
   1053 
   1054   _ble_syntax_bash_chars_simpleDef="$delimiters$expansions^!"
   1055   _ble_syntax_bash_chars_simpleFmt="$delimiters$expansions@q@h"
   1056 
   1057   _ble_syntax_bash_histc12='!^'
   1058   ble/syntax:bash/cclass/update
   1059 }
   1060 
   1061 ble/syntax:bash/cclass/initialize
   1062 
   1063 #------------------------------------------------------------------------------
   1064 # ble/syntax:bash/simple-word
   1065 
   1066 ## @var _ble_syntax_bash_simple_rex_word
   1067 ## @var _ble_syntax_bash_simple_rex_element
   1068 ##   単純な単語のパターンとその構成要素を表す正規表現
   1069 ##   histchars に依存しているので変化があった時に更新する。
   1070 _ble_syntax_bash_simple_rex_letter=
   1071 _ble_syntax_bash_simple_rex_param=
   1072 _ble_syntax_bash_simple_rex_bquot=
   1073 _ble_syntax_bash_simple_rex_squot=
   1074 _ble_syntax_bash_simple_rex_dquot=
   1075 _ble_syntax_bash_simple_rex_literal=
   1076 _ble_syntax_bash_simple_rex_element=
   1077 _ble_syntax_bash_simple_rex_word=
   1078 _ble_syntax_bash_simple_rex_open_word=
   1079 _ble_syntax_bash_simple_rex_open_dquot=
   1080 _ble_syntax_bash_simple_rex_open_squot=
   1081 _ble_syntax_bash_simple_rex_incomplete_word1=
   1082 _ble_syntax_bash_simple_rex_incomplete_word2=
   1083 
   1084 _ble_syntax_bash_simple_rex_noglob_word1=
   1085 _ble_syntax_bash_simple_rex_noglob_word2=
   1086 
   1087 function ble/syntax:bash/simple-word/update {
   1088   local q="'"
   1089 
   1090   local letter='\[[!^]|[^'${_ble_syntax_bashc_simple}']'
   1091   local param1='\$([-*@#?$!0_]|[1-9][0-9]*|[_a-zA-Z][_a-zA-Z0-9]*)'
   1092   local param2='\$\{(#?[-*@#?$!0]|[#!]?([1-9][0-9]*|[_a-zA-Z][_a-zA-Z0-9]*))\}' # ${!!} ${!$} はエラーになる。履歴展開の所為?
   1093   local param=$param1'|'$param2
   1094   local bquot='\\.'
   1095   local squot=$q'[^'$q']*'$q'|\$'$q'([^'$q'\]|\\.)*'$q
   1096   local dquot='\$?"([^'${_ble_syntax_bash_chars[CTX_QUOT]}']|\\.|'$param')*"'
   1097   _ble_syntax_bash_simple_rex_letter=$letter # 0 groups
   1098   _ble_syntax_bash_simple_rex_param=$param   # 3 groups
   1099   _ble_syntax_bash_simple_rex_bquot=$bquot   # 0 groups
   1100   _ble_syntax_bash_simple_rex_squot=$squot   # 1 groups
   1101   _ble_syntax_bash_simple_rex_dquot=$dquot   # 4 groups
   1102 
   1103   # @var _ble_syntax_bash_simple_rex_element
   1104   # @var _ble_syntax_bash_simple_rex_word
   1105   _ble_syntax_bash_simple_rex_literal='^('$letter')+$'
   1106   _ble_syntax_bash_simple_rex_element='('$bquot'|'$squot'|'$dquot'|'$param'|'$letter')'
   1107   _ble_syntax_bash_simple_rex_word='^'$_ble_syntax_bash_simple_rex_element'+$'
   1108 
   1109   # @var _ble_syntax_bash_simple_rex_open_word
   1110   local open_squot=$q'[^'$q']*|\$'$q'([^'$q'\]|\\.)*'
   1111   local open_dquot='\$?"([^'${_ble_syntax_bash_chars[CTX_QUOT]}']|\\.|'$param')*'
   1112   _ble_syntax_bash_simple_rex_open_word='^('$_ble_syntax_bash_simple_rex_element'*)(\\|'$open_squot'|'$open_dquot')$'
   1113   _ble_syntax_bash_simple_rex_open_squot=$open_squot
   1114   _ble_syntax_bash_simple_rex_open_dquot=$open_dquot
   1115 
   1116   # @var _ble_syntax_bash_simple_rex_incomplete_word1
   1117   # @var _ble_syntax_bash_simple_rex_incomplete_word2
   1118   local letter1='\[[!^]|[^{'${_ble_syntax_bashc_simple}']'
   1119   local letter2='\[[!^]|[^'${_ble_syntax_bashc_simple}']'
   1120   _ble_syntax_bash_simple_rex_incomplete_word1='^('$bquot'|'$squot'|'$dquot'|'$param'|'$letter1')+'
   1121   _ble_syntax_bash_simple_rex_incomplete_word2='^(('$bquot'|'$squot'|'$dquot'|'$param'|'$letter2')*)(\\|'$open_squot'|'$open_dquot')?$'
   1122 
   1123   # @var _ble_syntax_bash_simple_rex_noglob_word{1,2}
   1124   local noglob_letter='[^[?*'${_ble_syntax_bashc_simple}']'
   1125   _ble_syntax_bash_simple_rex_noglob_word1='^('$bquot'|'$squot'|'$dquot'|'$noglob_letter')+$'
   1126   _ble_syntax_bash_simple_rex_noglob_word2='^('$bquot'|'$squot'|'$dquot'|'$param'|'$noglob_letter')+$'
   1127 }
   1128 ble/syntax:bash/simple-word/update
   1129 
   1130 function ble/syntax:bash/simple-word/is-literal {
   1131   [[ $1 =~ $_ble_syntax_bash_simple_rex_literal ]]
   1132 }
   1133 function ble/syntax:bash/simple-word/is-simple {
   1134   [[ $1 =~ $_ble_syntax_bash_simple_rex_word ]]
   1135 }
   1136 function ble/syntax:bash/simple-word/is-simple-or-open-simple {
   1137   [[ $1 =~ $_ble_syntax_bash_simple_rex_word || $1 =~ $_ble_syntax_bash_simple_rex_open_word ]]
   1138 }
   1139 function ble/syntax:bash/simple-word/is-never-word {
   1140   ble/syntax:bash/simple-word/is-simple-or-open-simple && return 1
   1141   local rex=${_ble_syntax_bash_simple_rex_word%'$'}'[ |&;<>()]|^[ |&;<>()]'
   1142   [[ $1 =~ $rex ]]
   1143 }
   1144 function ble/syntax:bash/simple-word/is-simple-noglob {
   1145   [[ $1 =~ $_ble_syntax_bash_simple_rex_noglob_word1 ]] && return 0
   1146   if [[ $1 =~ $_ble_syntax_bash_simple_rex_noglob_word2 ]]; then
   1147     builtin eval -- "local expanded=$1" 2>/dev/null
   1148     local rex='[*?]|\[.+\]|[*?@+!]\(.*\)'
   1149     [[ $expanded =~ $rex ]] || return 0
   1150   fi
   1151   return 1
   1152 }
   1153 
   1154 ## @fn ble/syntax:bash/simple-word/evaluate-last-brace-expansion simple_word
   1155 ##   @param[in] simple_word
   1156 ##   @var[out] ret simple_ibrace
   1157 function ble/syntax:bash/simple-word/evaluate-last-brace-expansion {
   1158   local value=$1
   1159   local bquot=$_ble_syntax_bash_simple_rex_bquot
   1160   local squot=$_ble_syntax_bash_simple_rex_squot
   1161   local dquot=$_ble_syntax_bash_simple_rex_dquot
   1162   local param=$_ble_syntax_bash_simple_rex_param
   1163   local letter='\[[!^]|[^{,}'${_ble_syntax_bashc_simple}']'
   1164   local symbol='[{,}]'
   1165 
   1166   local rex_range_expansion='^(([-+]?[0-9]+)\.\.\.[-+]?[0-9]+|([a-zA-Z])\.\.[a-zA-Z])(\.\.[-+]?[0-9]+)?$'
   1167 
   1168   local rex0='^('$bquot'|'$squot'|'$dquot'|'$param'|'$letter')+'
   1169   local stack; stack=()
   1170   local out= comma= index=0 iopen=0 no_brace_length=0
   1171   while [[ $value ]]; do
   1172     if [[ $value =~ $rex0 ]]; then
   1173       local len=${#BASH_REMATCH}
   1174       ((index+=len,no_brace_length+=len))
   1175       out=$out${value::len}
   1176       value=${value:len}
   1177     elif [[ $value == '{'* ]]; then
   1178       ((iopen=++index,no_brace_length=0))
   1179       value=${value:1}
   1180       ble/array#push stack "$comma:$out"
   1181       out= comma=
   1182     elif ((${#stack[@]})) && [[ $value == '}'* ]]; then
   1183       ((++index))
   1184       value=${value:1}
   1185       ble/array#pop stack
   1186       local out0=${ret#*:} comma0=${ret%%:*}
   1187       if [[ $comma ]]; then
   1188         ((iopen=index,no_brace_length=0))
   1189         out=$out0$out
   1190         comma=$comma0
   1191       elif [[ $out =~ $rex_range_expansion ]]; then
   1192         ((iopen=index,no_brace_length=0))
   1193         out=$out0${2#+}$3
   1194         comma=$comma0
   1195       else
   1196         ((++no_brace_length))
   1197         ble/array#push stack "$comma0:$out0" # cancel pop
   1198         out=$out'}'
   1199       fi
   1200     elif ((${#stack[@]})) && [[ $value == ','* ]]; then
   1201       ((iopen=++index,no_brace_length=0))
   1202       value=${value:1}
   1203       out= comma=1
   1204     else
   1205       ((++index,++no_brace_length))
   1206       out=$out${value::1}
   1207       value=${value:1}
   1208     fi
   1209   done
   1210 
   1211   while ((${#stack[@]})); do
   1212     ble/array#pop stack
   1213     local out0=${ret#*:} comma0=${ret%%:*}
   1214     out=$out0$out
   1215   done
   1216 
   1217   ret=$out simple_ibrace=$iopen:$((${#out}-no_brace_length))
   1218 }
   1219 
   1220 ## @fn ble/syntax:bash/simple-word/reconstruct-incomplete-word
   1221 ##   word について不完全なブレース展開と不完全な引用符を閉じ、
   1222 ##   更にブレース展開を実行して最後の単語を取得します。
   1223 ##
   1224 ##   @param[in] word
   1225 ##     不完全な単語を指定します。
   1226 ##
   1227 ##   @var[out] ret
   1228 ##     word に対して不完全なブレース展開と引用符を閉じ、ブレース展開した結果を返します。
   1229 ##
   1230 ##   @var[out] simple_flags
   1231 ##     引用符 $"..." を閉じた時に simple_flags=I を設定します。
   1232 ##     引用符 "..." を閉じた時に simple_flags=D を設定します。
   1233 ##     引用符 $'...' を閉じた時に simple_flags=E を設定します。
   1234 ##     引用符 '...' を閉じた時に simple_flags=S を設定します。
   1235 ##     不完全な末端 \ があった時に simple_flags=B を設定します。
   1236 ##
   1237 ##   @var[out] simple_ibrace=ibrace:jbrace
   1238 ##     ブレース展開の構造を破壊せずに変更できる最初の位置を返します。
   1239 ##     ibrace には word 内の位置を返し、jbrace には ret 内の位置を返します。
   1240 ##
   1241 ##   @exit
   1242 ##     ブレース展開及び引用符を閉じることによってシェルの完全な単語になる時に成功します。
   1243 ##     それ以外の場合に失敗します。
   1244 ##
   1245 function ble/syntax:bash/simple-word/reconstruct-incomplete-word {
   1246   local word=$1
   1247   ret= simple_flags= simple_ibrace=0:0
   1248 
   1249   [[ $word ]] || return 0
   1250 
   1251   if [[ $word =~ $_ble_syntax_bash_simple_rex_incomplete_word1 ]]; then
   1252     ret=${word::${#BASH_REMATCH}}
   1253     word=${word:${#BASH_REMATCH}}
   1254     [[ $word ]] || return 0
   1255   fi
   1256 
   1257   if [[ $word =~ $_ble_syntax_bash_simple_rex_incomplete_word2 ]]; then
   1258     local out=$ret
   1259 
   1260     local m_brace=${BASH_REMATCH[1]}
   1261     local m_quote=${word:${#m_brace}}
   1262 
   1263     if [[ $m_brace ]]; then
   1264       ble/syntax:bash/simple-word/evaluate-last-brace-expansion "$m_brace"
   1265       simple_ibrace=$((${#out}+${simple_ibrace%:*})):$((${#out}+${simple_ibrace#*:}))
   1266       out=$out$ret
   1267     fi
   1268 
   1269     if [[ $m_quote ]]; then
   1270       case $m_quote in
   1271       ('$"'*) out=$out$m_quote\" simple_flags=I ;;
   1272       ('"'*)  out=$out$m_quote\" simple_flags=D ;;
   1273       ("$'"*) out=$out$m_quote\' simple_flags=E ;;
   1274       ("'"*)  out=$out$m_quote\' simple_flags=S ;;
   1275       ('\')   simple_flags=B ;;
   1276       (*) return 1 ;;
   1277       esac
   1278     fi
   1279 
   1280     ret=$out
   1281     return 0
   1282   fi
   1283 
   1284   return 1
   1285 }
   1286 
   1287 ## @fn ble/syntax:bash/simple-word/extract-parameter-names word
   1288 ##   単純単語に含まれるパラメータ展開のパラメータ名を抽出します。
   1289 ##   @var[in] word
   1290 ##   @var[out] ret
   1291 function ble/syntax:bash/simple-word/extract-parameter-names {
   1292   ret=()
   1293   local letter=$_ble_syntax_bash_simple_rex_letter
   1294   local bquot=$_ble_syntax_bash_simple_rex_bquot
   1295   local squot=$_ble_syntax_bash_simple_rex_squot
   1296   local dquot=$_ble_syntax_bash_simple_rex_dquot
   1297   local param=$_ble_syntax_bash_simple_rex_param
   1298 
   1299   local value=$1
   1300   local rex0='^('$letter'|'$bquot'|'$squot')+'
   1301   local rex1='^('$dquot')'
   1302   local rex2='^('$param')'
   1303   while [[ $value ]]; do
   1304     [[ $value =~ $rex0 ]] && value=${value:${#BASH_REMATCH}}
   1305     if [[ $value =~ $rex1 ]]; then
   1306       value=${value:${#BASH_REMATCH}}
   1307       ble/syntax:bash/simple-word/extract-parameter-names/.process-dquot "$BASH_REMATCH"
   1308     fi
   1309     [[ $value =~ $rex2 ]] || break
   1310     value=${value:${#BASH_REMATCH}}
   1311     local var=${BASH_REMATCH[2]}${BASH_REMATCH[3]}
   1312     [[ $var == [_a-zA-Z]* ]] && ble/array#push ret "$var"
   1313   done
   1314 }
   1315 ## @fn ble/syntax:bash/simple-word/extract-parameter-names/.process-dquot match
   1316 ##   @var[in,out] ret
   1317 function ble/syntax:bash/simple-word/extract-parameter-names/.process-dquot {
   1318   local value=$1
   1319   if [[ $value == '$"'*'"' ]]; then
   1320     value=${value:2:${#value}-3}
   1321   elif [[ $value == '"'*'"' ]]; then
   1322     value=${value:1:${#value}-2}
   1323   else
   1324     return 0
   1325   fi
   1326 
   1327   local rex0='^([^'${_ble_syntax_bash_chars[CTX_QUOT]}']|\\.)+'
   1328   local rex2='^('$param')'
   1329   while [[ $value ]]; do
   1330     [[ $value =~ $rex0 ]] && value=${value:${#BASH_REMATCH}}
   1331     [[ $value =~ $rex2 ]] || break
   1332     value=${value:${#BASH_REMATCH}}
   1333     local var=${BASH_REMATCH[2]}${BASH_REMATCH[3]}
   1334     [[ $var == [_a-zA-Z]* ]] && ble/array#push ret "$var"
   1335   done
   1336 }
   1337 
   1338 function ble/syntax:bash/simple-word/eval/.set-result { __ble_ret=("$@"); }
   1339 function ble/syntax:bash/simple-word/eval/.print-result {
   1340   if (($#>=1000)) && [[ $OSTYPE != cygwin ]]; then
   1341     # ファイル数が少ない場合は fork コストを避ける為に多少遅くても quote&eval
   1342     # でデータを親シェルに転送する。Cygwin では mapfile/read が unbuffered で遅
   1343     # いので、ファイル数が遅くても quote&eval を使う。
   1344 
   1345     if ((_ble_bash>=50200)); then
   1346       printf '%s\0' "$@" >| "$__ble_simple_word_tmpfile"
   1347       ble/util/print 'ble/util/readarray -d "" __ble_ret < "$__ble_simple_word_tmpfile"'
   1348       return 0
   1349     elif ((_ble_bash>=40000)); then
   1350       ret=("$@")
   1351       ble/util/writearray --nlfix ret >| "$__ble_simple_word_tmpfile"
   1352       ble/util/print 'ble/util/readarray --nlfix __ble_ret < "$__ble_simple_word_tmpfile"'
   1353       return 0
   1354     fi
   1355   fi
   1356 
   1357   local ret; ble/string#quote-words "$@"
   1358   ble/util/print "__ble_ret=($ret)"
   1359 }
   1360 ## @fn ble/syntax:bash/simple-word/eval/.impl word opts
   1361 ##   @param[in] word
   1362 ##   @param[in,opt] opts
   1363 ##   @var[out] __ble_ret
   1364 function ble/syntax:bash/simple-word/eval/.impl {
   1365   local __ble_word=$1 __ble_opts=$2 __ble_flags=
   1366 
   1367   # グローバル変数の復元
   1368   local -a ret=()
   1369   ble/syntax:bash/simple-word/extract-parameter-names "$__ble_word"
   1370   if ((${#ret[@]})); then
   1371     local __ble_defs
   1372     ble/util/assign __ble_defs 'ble/util/print-global-definitions --hidden-only "${ret[@]}"'
   1373     builtin eval -- "$__ble_defs" &>/dev/null # 読み取り専用の変数のこともある
   1374   fi
   1375 
   1376   # glob パターンを含む可能性がある時の処理 (Note: is-simple-noglob の
   1377   # 判定で変数を参照するので、グローバル変数の復元よりも後で処理する必
   1378   # 要がある)
   1379   if [[ $- != *f* ]] && ! ble/syntax:bash/simple-word/is-simple-noglob "$1"; then
   1380     if [[ :$__ble_opts: == *:noglob:* ]]; then
   1381       set -f
   1382       __ble_flags=f
   1383     elif ble/util/is-cygwin-slow-glob "$1"; then # Note: #D1168
   1384       if shopt -q failglob &>/dev/null; then
   1385         __ble_ret=()
   1386         return 1
   1387       elif shopt -q nullglob &>/dev/null; then
   1388         __ble_ret=()
   1389         return 0
   1390       else
   1391         # noglob で処理する
   1392         set -f
   1393         __ble_flags=f
   1394       fi
   1395     elif [[ :$__ble_opts: == *:stopcheck:* ]]; then
   1396       ble/decode/has-input && return 148
   1397       if ((_ble_bash>=40000)); then
   1398         __ble_flags=s
   1399       elif shopt -q globstar &>/dev/null; then
   1400         # conditional-sync が使えない場合はせめて globstar だけでも撥ねる
   1401         if builtin eval "[[ $__ble_word == *'**'* ]]"; then
   1402           [[ :$__ble_opts: == *:timeout=*:* ]] && return 142
   1403           return 148
   1404         fi
   1405       fi
   1406     fi
   1407   fi
   1408 
   1409   # Note: failglob 時に一致がないと実行されないので予め __ble_ret=() をする。
   1410   #   また、エラーメッセージが生じるので /dev/null に繋ぐ。
   1411   __ble_ret=()
   1412   if [[ $__ble_flags == *s* ]]; then
   1413     local __ble_sync_command="ble/syntax:bash/simple-word/eval/.print-result $__ble_word"
   1414     local __ble_sync_opts=progressive-weight
   1415     local __ble_sync_weight=$bleopt_syntax_eval_polling_interval
   1416 
   1417     # determine timeout
   1418     local __ble_sync_timeout=$_ble_syntax_bash_simple_eval_timeout
   1419     if [[ $_ble_syntax_bash_simple_eval_timeout_carry ]]; then
   1420       __ble_sync_timeout=0
   1421     elif local __ble_rex=':timeout=([^:]*):'; [[ :$__ble_opts: =~ $__ble_rex ]]; then
   1422       __ble_sync_timeout=${BASH_REMATCH[1]}
   1423     fi
   1424     [[ $__ble_sync_timeout ]] &&
   1425       __ble_sync_opts=$__ble_sync_opts:timeout=$((__ble_sync_timeout))
   1426 
   1427     local _ble_local_tmpfile; ble/util/assign/mktmp
   1428     local __ble_simple_word_tmpfile=$_ble_local_tmpfile
   1429     local __ble_script
   1430     ble/util/assign __ble_script 'ble/util/conditional-sync "$__ble_sync_command" "" "$__ble_sync_weight" "$__ble_sync_opts"' &>/dev/null; local ext=$?
   1431     builtin eval -- "$__ble_script"
   1432     ble/util/assign/rmtmp
   1433   else
   1434     builtin eval "ble/syntax:bash/simple-word/eval/.set-result $__ble_word" &>/dev/null; local ext=$?
   1435     builtin eval : # Note: bash 3.1/3.2 eval バグ対策 (#D1132)
   1436   fi
   1437 
   1438   [[ $__ble_flags == *f* ]] && set +f
   1439   return "$ext"
   1440 }
   1441 
   1442 _ble_syntax_bash_simple_eval_hash=
   1443 ## @fn ble/syntax:bash/simple-word/eval/.cache-clear
   1444 ##   @var[in,out] _ble_syntax_bash_simple_eval
   1445 function ble/syntax:bash/simple-word/eval/.cache-clear {
   1446   ble/gdict#clear _ble_syntax_bash_simple_eval
   1447   ble/gdict#clear _ble_syntax_bash_simple_eval_full
   1448 }
   1449 ## @fn ble/syntax:bash/simple-word/eval/.cache-update
   1450 ##   @var[in,out] _ble_syntax_bash_simple_eval
   1451 ##   @var[in,out] _ble_syntax_bash_simple_eval_hash
   1452 function ble/syntax:bash/simple-word/eval/.cache-update {
   1453   local hash=$-:$BASHOPTS:$_ble_edit_lineno:$_ble_textarea_version:$PWD
   1454   if [[ $hash != "$_ble_syntax_bash_simple_eval_hash" ]]; then
   1455     _ble_syntax_bash_simple_eval_hash=$hash
   1456     ble/syntax:bash/simple-word/eval/.cache-clear
   1457   fi
   1458 }
   1459 ## @fn ble/syntax:bash/simple-word/eval/.save word ext ret...
   1460 ##   @var[in] ext
   1461 ##   @var[in,out] _ble_syntax_bash_simple_eval
   1462 function ble/syntax:bash/simple-word/eval/.cache-save {
   1463   ((ext==148||ext==142)) && return 0
   1464   local ret; ble/string#quote-words "$3"
   1465   ble/gdict#set _ble_syntax_bash_simple_eval "$1" "ext=$2 count=$(($#-2)) ret=$ret"
   1466   local ret; ble/string#quote-words "${@:3}"
   1467   ble/gdict#set _ble_syntax_bash_simple_eval_full "$1" "ext=$2 count=$(($#-2)) ret=($ret)"
   1468 }
   1469 ## @fn ble/syntax:bash/simple-word/eval/.cache-load word opts
   1470 function ble/syntax:bash/simple-word/eval/.cache-load {
   1471   ext= ret=
   1472   if [[ :$2: == *:single:* ]]; then
   1473     ble/gdict#get _ble_syntax_bash_simple_eval "$1" || return 1
   1474   else
   1475     ble/gdict#get _ble_syntax_bash_simple_eval_full "$1" || return 1
   1476   fi
   1477   builtin eval -- "$ret"
   1478   return 0
   1479 }
   1480 
   1481 ## @fn ble/syntax:bash/simple-word/eval word opts
   1482 ##   @param[in] word
   1483 ##   @param[in,opt] opts
   1484 ##     コロン区切りのオプション指定です。
   1485 ##
   1486 ##       noglob
   1487 ##         パス名展開を抑制します。
   1488 ##
   1489 ##     以下は (成功時の) 評価結果に影響を与えないオプションです。
   1490 ##
   1491 ##       single
   1492 ##         最初の展開結果のみを ret に設定します。
   1493 ##       count
   1494 ##         変数 count に展開結果の単語数を返します。
   1495 ##       cached
   1496 ##         展開結果をキャッシュします。
   1497 ##
   1498 ##     以下はパス名展開の起こる可能性にある単語に対して有効です。
   1499 ##
   1500 ##       stopcheck
   1501 ##         ユーザー入力があった場合に中断します。
   1502 ##       timeout=NUM
   1503 ##         stopcheck を指定している時に有効です。timeout を指定します。
   1504 ##       retry-noglob-on-timeout
   1505 ##         timeout した時に noglob で改めて展開を試行します。
   1506 ##       timeout-carry
   1507 ##         timeout が発生した場合に後続の eval にタイムアウトを伝播します。
   1508 ##
   1509 ##   @var[in] _ble_syntax_bash_simple_eval_timeout
   1510 ##     パス名展開のタイムアウトの既定値を指定します。空文字列が指定さ
   1511 ##     れている時、既定でタイムアウトは起こりません。
   1512 ##   @var[in,out] _ble_syntax_bash_simple_eval_timeout_carry
   1513 ##     この値が設定されている時、パス名展開に対して強制的にタイムアウ
   1514 ##     トが起こります。opts に timeout-carry が指定されている時に値が設定されます。
   1515 ##
   1516 ##   @arr[out] ret
   1517 ##     展開結果を返します。複数の単語に評価される場合にはそれを全て返します。
   1518 ##     opts に single が指定されている時、最初の展開結果のみを返します。
   1519 ##   @var[out] count
   1520 ##     opts に count が指定されている時に展開結果の数を返します。
   1521 ##
   1522 ##   @exit
   1523 ##     ユーザー入力により中断した場合は 148 を返します。timeout を起こ
   1524 ##     した場合 142 を返します。例えば failglob など、その他の理由でパ
   1525 ##     ス名展開に失敗した時 0 以外の終了ステータスを返します。
   1526 ##
   1527 _ble_syntax_bash_simple_eval_timeout=
   1528 _ble_syntax_bash_simple_eval_timeout_carry=
   1529 function ble/syntax:bash/simple-word/eval {
   1530   [[ :$2: != *:count:* ]] && local count
   1531   if [[ :$2: == *:cached:* && :$2: != *:noglob:* ]]; then
   1532     ble/syntax:bash/simple-word/eval/.cache-update
   1533     local ext; ble/syntax:bash/simple-word/eval/.cache-load "$1" "$2" && return "$ext"
   1534   fi
   1535 
   1536   local __ble_ret
   1537   ble/syntax:bash/simple-word/eval/.impl "$1" "$2"; local ext=$?
   1538   ret=("${__ble_ret[@]}")
   1539   count=${#ret[@]}
   1540 
   1541   if [[ :$2: == *:cached:* && :$2: != *:noglob:* ]]; then
   1542     ble/syntax:bash/simple-word/eval/.cache-save "$1" "$ext" "${ret[@]}"
   1543   fi
   1544   if ((ext==142)); then
   1545     [[ :$2: == *:timeout-carry:* ]] &&
   1546       _ble_syntax_bash_simple_eval_timeout_carry=1
   1547     if [[ :$2: == *:retry-noglob-on-timeout:* ]]; then
   1548       ble/syntax:bash/simple-word/eval "$1" "$2:noglob"
   1549       return "$?"
   1550     fi
   1551   fi
   1552   return "$ext"
   1553 }
   1554 
   1555 ## @fn ble/syntax:bash/simple-word/get-rex_element sep
   1556 ##   指定した分割文字 (sep) で区切られた単純単語片に一致する正規表現を構築します。
   1557 ##   @var[out] rex_element
   1558 function ble/syntax:bash/simple-word/get-rex_element {
   1559   local sep=$1
   1560   local param=$_ble_syntax_bash_simple_rex_param
   1561   local bquot=$_ble_syntax_bash_simple_rex_bquot
   1562   local squot=$_ble_syntax_bash_simple_rex_squot
   1563   local dquot=$_ble_syntax_bash_simple_rex_dquot
   1564   local letter1='\[[!^]|[^'$sep$_ble_syntax_bashc_simple']'
   1565   rex_element='('$bquot'|'$squot'|'$dquot'|'$param'|'$letter1')+'
   1566 }
   1567 ## @fn ble/syntax:bash/simple-word/evaluate-path-spec path_spec [sep] [opts]
   1568 ##   @param[in] path_spec
   1569 ##   @param[in,opt] sep (default: '/:=')
   1570 ##   @param[in,opt] opts
   1571 ##     noglob
   1572 ##     stopcheck
   1573 ##     timeout=*
   1574 ##     timeout-carry
   1575 ##     cached
   1576 ##       これらは simple-word/eval に対するオプションです。
   1577 ##
   1578 ##     notilde
   1579 ##       評価時にチルダ展開を抑制します。
   1580 ##     after-sep
   1581 ##       分割位置を分割子の前ではなく後に変更します。
   1582 ##     fixlen=LEN
   1583 ##       分割の対象とならない固定接頭辞の長さを指定します。
   1584 ##
   1585 ##   @arr[out] spec
   1586 ##   @arr[out] path
   1587 ##   @arr[out] ret
   1588 ##     path_spec 全体を評価した時の結果を返します。
   1589 ##     パス名展開によって複数のパスに展開された場合に、
   1590 ##     全ての展開結果を含む配列になります。
   1591 ##
   1592 ##   指定した path_spec を sep に含まれる文字で区切ってルートから末端まで順に評価します。
   1593 ##   各階層までの評価の対象を spec に評価の結果を path に追加します。
   1594 ##   例えば path_spec='~/a/b' の時、
   1595 ##     spec=(~ ~/a ~/a/b)
   1596 ##     path=(/home/user /home/user/a /home/user/a/b)
   1597 ##   という結果が得られます。
   1598 ##
   1599 function ble/syntax:bash/simple-word/evaluate-path-spec {
   1600   local word=$1 sep=${2:-'/:='} opts=$3
   1601   ret=() spec=() path=()
   1602   [[ $word ]] || return 0
   1603 
   1604   # read options
   1605   local eval_opts=$opts notilde=
   1606   [[ :$opts: == *:notilde:* ]] && notilde=\'\' # チルダ展開の抑制
   1607   local fixlen
   1608   ble/opts#extract-last-optarg "$opts" fixlen 0
   1609 
   1610   # compose regular expressions
   1611   local rex_element; ble/syntax:bash/simple-word/get-rex_element "$sep"
   1612   local rex='^['$sep']?'$rex_element'|^['$sep']'
   1613   [[ :$opts: == *:after-sep:* ]] &&
   1614     local rex='^'$rex_element'['$sep']?|^['$sep']'
   1615 
   1616   local tail=${word:fixlen} s=${word::fixlen} p= ext=0
   1617   while [[ $tail =~ $rex ]]; do
   1618     local rematch=$BASH_REMATCH
   1619     s=$s$rematch
   1620     ble/syntax:bash/simple-word/eval "$notilde$s" "$eval_opts"; ext=$?
   1621     ((ext==148||ext==142)) && return "$ext"
   1622     p=$ret
   1623     tail=${tail:${#rematch}}
   1624     ble/array#push spec "$s"
   1625     ble/array#push path "$p"
   1626   done
   1627   [[ $tail ]] && return 1
   1628   ((ext)) && return "$ext"
   1629   return 0
   1630 }
   1631 
   1632 ## @fn ble/syntax:bash/simple-word/detect-separated-path word [sep] [opts]
   1633 ##   指定した単語が単一のパス名か sep 区切りのパス名刺低下の判定を行います。
   1634 ##   @param[in] word
   1635 ##   @param[in,opt] sep
   1636 ##   @param[in] opts
   1637 ##     noglob
   1638 ##     stopcheck
   1639 ##     timeout=*
   1640 ##     timeout-carry
   1641 ##     cached
   1642 ##     url
   1643 ##     notilde
   1644 ##   @var[out] ret
   1645 ##     有効な区切り文字の集合を返します。
   1646 function ble/syntax:bash/simple-word/detect-separated-path {
   1647   local word=$1 sep=${2:-':'} opts=$3
   1648   [[ $word ]] || return 1
   1649 
   1650   local rex_url='^[a-z]+://'
   1651   [[ :$opts: == *:url:* && $word =~ $rex_url ]] && return 1
   1652 
   1653   # read eval options
   1654   local eval_opts=$opts notilde=
   1655   [[ :$opts: == *:notilde:* ]] && notilde=\'\' # チルダ展開の抑制
   1656 
   1657   # compose regular expressions
   1658   local rex_element
   1659   ble/syntax:bash/simple-word/get-rex_element /
   1660   local rex='^'$rex_element'/?|^/'
   1661 
   1662   local tail=$word head=
   1663   while [[ $tail =~ $rex ]]; do
   1664     local rematch=$BASH_REMATCH
   1665     ble/syntax:bash/simple-word/locate-filename/.exists "$notilde$head$rematch"; local ext=$?
   1666     ((ext==148)) && return 148
   1667     ((ext==0)) || break
   1668     head=$head$rematch
   1669     tail=${tail:${#rematch}}
   1670   done
   1671 
   1672   ret=
   1673   local i
   1674   for ((i=0;i<${#sep};i++)); do
   1675     local sep1=${sep:i:1}
   1676     ble/syntax:bash/simple-word/get-rex_element "$sep1"
   1677     local rex_nocolon='^('$rex_element')?$'
   1678     local rex_hascolon='^('$rex_element')?['$sep1']'
   1679     [[ $head =~ $rex_nocolon && $tail =~ $rex_hascolon ]] && ret=$ret$sep1
   1680   done
   1681   [[ $ret ]]
   1682 }
   1683 
   1684 ## @fn ble/syntax:bash/simple-word/locate-filename/.exists word opts
   1685 ##   @param[in] word
   1686 ##   @param[in] opts
   1687 ##   @var[in] eval_opts
   1688 ##   @var[in] rex_url
   1689 function ble/syntax:bash/simple-word/locate-filename/.exists {
   1690   local word=$1 ret
   1691   ble/syntax:bash/simple-word/eval "$word" "$eval_opts" || return "$?"
   1692   local path=$ret
   1693   # Note: #D1168 Cygwin では // で始まるパスの判定は遅いので直接文字列で判定する
   1694   if [[ ( $OSTYPE == cygwin || $OSTYPE == msys ) && $path == //* ]]; then
   1695     [[ $path == // ]]
   1696   else
   1697     [[ -e $path || -h $path ]]
   1698   fi || [[ :$opts: == *:url:* && $path =~ $rex_url ]]
   1699 }
   1700 ## @fn ble/syntax:bash/simple-word/locate-filename word [sep] [opts]
   1701 ##   @param[in] word
   1702 ##   @param[in] sep
   1703 ##   @param[in] opts
   1704 ##     exists
   1705 ##       存在する有効なファイル名のみを抽出します。
   1706 ##     greedy
   1707 ##       : を区切りと見做さずに結合したパスが有効であれば、その結合パスを採用します。
   1708 ##     url
   1709 ##       [a-z]+:// で始まるパスを無条件に有効なパスと判定します。
   1710 ##     stopcheck
   1711 ##       時間のかかる可能性のあるパス名展開をユーザ入力により中断します。
   1712 ##     timeout=*
   1713 ##       stopcheck に際して timeout を指定します。
   1714 ##     timeout-carry
   1715 ##       タイムアウトを後続のパス名展開に伝播させます。
   1716 ##     cached
   1717 ##       展開内容をキャッシュします。
   1718 ##
   1719 ##   @arr[out] ret
   1720 ##     偶数個の要素を含みます。偶数 index (0, 2, 4, ...) が範囲の開始で、
   1721 ##     奇数 index (1, 3, 5, ...) が範囲の終了です。
   1722 ##
   1723 function ble/syntax:bash/simple-word/locate-filename {
   1724   local word=$1 sep=${2:-':='} opts=$3
   1725   ret=0
   1726   [[ $word ]] || return 0
   1727 
   1728   # prepare evaluator
   1729   local eval_opts=$opts
   1730 
   1731   # compose regular expressions
   1732   local rex_element; ble/syntax:bash/simple-word/get-rex_element "$sep"
   1733   local rex='^'$rex_element'['$sep']|^['$sep']'
   1734   local rex_url='^[a-z]+://'
   1735 
   1736   local -a seppos=()
   1737   local tail=$word p=0
   1738   while [[ $tail =~ $rex ]]; do
   1739     ((p+=${#BASH_REMATCH}))
   1740     tail=${tail:${#BASH_REMATCH}}
   1741     ble/array#push seppos "$((p-1))"
   1742   done
   1743   ble/syntax:bash/simple-word/is-simple "$tail" &&
   1744     ble/array#push seppos "$((p+${#tail}))"
   1745 
   1746   local -a out=()
   1747   for ((i=0;i<${#seppos[@]};i++)); do
   1748     local j0=$i
   1749     [[ :$opts: == *:greedy:* ]] && j0=${#seppos[@]}-1
   1750     for ((j=j0;j>=i;j--)); do
   1751       local f1=0 f2=${seppos[j]}
   1752       ((i)) && ((f1=seppos[i-1]+1))
   1753 
   1754       if ((j>i)); then
   1755         # もし繋げて存在するファイル名になるのであればそれを採用
   1756         ble/syntax:bash/simple-word/locate-filename/.exists "${word:f1:f2-f1}" "$opts"; local ext=$?
   1757         ((ext==148)) && return 148
   1758         if ((ext==0)); then
   1759           ble/array#push out "$f1" "$f2"
   1760           ((i=j))
   1761         fi
   1762       else
   1763         # ファイルが見つからなかった場合は単一区間を登録
   1764         if [[ :$opts: != *:exists:* ]] ||
   1765              { ble/syntax:bash/simple-word/locate-filename/.exists "${word:f1:f2-f1}" "$opts"
   1766                local ext=$?; ((ext==148)) && return 148; ((ext==0)); }; then
   1767           ble/array#push out "$f1" "$f2"
   1768         fi
   1769       fi
   1770     done
   1771   done
   1772 
   1773   ret=("${out[@]}")
   1774   return 0
   1775 }
   1776 
   1777 ## @fn ble/syntax:bash/simple-word#break-word word sep
   1778 ##   単語を指定した分割子で分割します。評価は行いません。
   1779 ##   progcomp で単語を COMP_WORDBREAKS で分割するのに使います。
   1780 ##   例えば a==b:c\=d に対して ret=(a == b : c=d) という結果を生成します。
   1781 ##
   1782 ##   @param[in] word
   1783 ##     前提: simple-word/is-simple である必要があります。
   1784 ##
   1785 ##   @arr[out] ret
   1786 ##     単語片を含む配列を返します。
   1787 ##     偶数番目の要素は分割子以外の文字列です。
   1788 ##     奇数番目の要素は分割子からなる文字列です。
   1789 ##
   1790 function ble/syntax:bash/simple-word#break-word {
   1791   local word=$1 sep=${2:-':='}
   1792   if [[ ! $word ]]; then
   1793     ret=('')
   1794     return 0
   1795   fi
   1796 
   1797   sep=${sep//[\"\'\$\`]}
   1798 
   1799   # compose regular expressions
   1800   local rex_element; ble/syntax:bash/simple-word/get-rex_element "$sep"
   1801   local rex='^('$rex_element')?['$sep']+'
   1802 
   1803   local -a out=()
   1804   local tail=$word p=0
   1805   while [[ $tail =~ $rex ]]; do
   1806     local rematch1=${BASH_REMATCH[1]}
   1807     ble/array#push out "$rematch1"
   1808     ble/array#push out "${BASH_REMATCH:${#rematch1}}"
   1809     tail=${tail:${#BASH_REMATCH}}
   1810   done
   1811   ble/array#push out "$tail"
   1812   ret=("${out[@]}")
   1813   return 0
   1814 }
   1815 
   1816 #------------------------------------------------------------------------------
   1817 
   1818 function ble/syntax:bash/initialize-ctx {
   1819   ctx=$CTX_CMDX # CTX_CMDX が ble/syntax:bash の最初の文脈
   1820 }
   1821 
   1822 ## @fn ble/syntax:bash/initialize-vars
   1823 ##   @var[in,out] _ble_syntax_bash_histc12
   1824 ##   @var[in,out] _ble_syntax_bash_histstop
   1825 function ble/syntax:bash/initialize-vars {
   1826   # シェル変数 histchars の解釈について
   1827   #
   1828   # - 1文字目 [既定値 !] は履歴展開の開始を表す。
   1829   #   イベント指示子の中に含まれる ! も対象となる。
   1830   #   但し、histchars の 1 文字目が既に別の意味を持っている場合 ([-#?0-9^$%*]) は、
   1831   #   そちらの方が優先される様だ。
   1832   # - 2文字目 [既定値 ^] は履歴展開(置換)の開始を表す。
   1833   #   ^aaa^bbb^ は =aaa=bbb= となる。
   1834   # - 3文字目 [既定値 #] は .bash_history
   1835   #   に時刻を出力する時の区切り文字に使われる。
   1836   #   ここでは関係ない。
   1837   #
   1838   local histc12
   1839   if [[ ${histchars+set} ]]; then
   1840     histc12=${histchars::2}
   1841   else
   1842     histc12='!^'
   1843   fi
   1844   _ble_syntax_bash_histc12=$histc12
   1845 
   1846   if ble/syntax:bash/cclass/update; then
   1847     ble/syntax:bash/simple-word/update
   1848   fi
   1849 
   1850   local histstop=$' \t\n='
   1851   shopt -q extglob && histstop="$histstop("
   1852   _ble_syntax_bash_histstop=$histstop
   1853 }
   1854 
   1855 
   1856 #------------------------------------------------------------------------------
   1857 # 共通の字句の一致判定
   1858 
   1859 ## @fn ble/syntax/highlight/vartype/.impl name [opts [tail]]
   1860 ##   @arr[out] __ble_vartype_ret=(ret [lookahead])
   1861 ##     属性値 ret と先読み文字数 lookahead を返します。
   1862 function ble/syntax/highlight/vartype/.impl {
   1863   if [[ ! $bleopt_highlight_variable ]]; then
   1864     __ble_vartype_ret=$ATTR_VAR
   1865     return 0
   1866   fi
   1867 
   1868   local __ble_name=$1 __ble_opts=$2 __ble_tail=$3
   1869   local __ble_attr; ble/variable#get-attr -v __ble_attr "$__ble_name"
   1870   if [[ ${!__ble_name+set} || $__ble_attr == *[aA]* ]]; then
   1871     local __ble_rex='^-?[0-9]+(#[_a-zA-Z0-9@]*)?$'
   1872     if [[ ${!__ble_name-} && :$__ble_opts: == *:expr:* && ! ( ${!__ble_name} =~ $__ble_rex ) ]]; then
   1873       __ble_vartype_ret=$ATTR_VAR_EXPR
   1874     elif [[ ${!__ble_name+set} && $__ble_attr == *x* ]]; then
   1875       # Note: 配列の場合には第0要素が設定されている時のみ。
   1876       __ble_vartype_ret=$ATTR_VAR_EXPORT
   1877     elif [[ $__ble_attr == *a* ]]; then
   1878       __ble_vartype_ret=$ATTR_VAR_ARRAY
   1879     elif [[ $__ble_attr == *A* ]]; then
   1880       __ble_vartype_ret=$ATTR_VAR_HASH
   1881     elif [[ $__ble_attr == *r* && :$__ble_opts: != *:no-readonly:* ]]; then
   1882       __ble_vartype_ret=$ATTR_VAR_READONLY
   1883     elif [[ $__ble_attr == *i* ]]; then
   1884       __ble_vartype_ret=$ATTR_VAR_NUMBER
   1885     elif [[ $__ble_attr == *[luc]* ]]; then
   1886       __ble_vartype_ret=$ATTR_VAR_TRANSFORM
   1887     elif [[ ! ${!__ble_name} ]]; then
   1888       __ble_vartype_ret=$ATTR_VAR_EMPTY
   1889     else
   1890       __ble_vartype_ret=$ATTR_VAR
   1891     fi
   1892   else
   1893     # set -u のチェック
   1894     if [[ :$__ble_opts: == *:readvar:* && $_ble_bash_set == *u* ]]; then
   1895       if [[ ! $__ble_tail ]] || {
   1896            local __ble_rex='^:?[-+?=]'
   1897            [[ $__ble_tail == :* ]] && __ble_vartype_ret[1]=2
   1898            ! [[ $__ble_tail =~ $__ble_rex ]]; }
   1899       then
   1900         __ble_vartype_ret=$ATTR_ERR
   1901         return 0
   1902       fi
   1903     fi
   1904 
   1905     __ble_vartype_ret=$ATTR_VAR_UNSET
   1906   fi
   1907 }
   1908 function ble/syntax/highlight/vartype/.print {
   1909   if [[ :$2: == *:unset:* ]]; then
   1910     # local readonly で被覆されていて分からない時にここに来る。グローバル変数が
   1911     # 存在するかしないかもわからないのでデフォルトの変数着色にする。
   1912     ble/util/print "$ATTR_VAR"
   1913   else
   1914     local -a __ble_vartype_ret=()
   1915     ble/syntax/highlight/vartype/.impl "$1" "$_ble_highlight_vartype_opts" "$_ble_highlight_vartype_tail"
   1916     ble/util/print "${__ble_vartype_ret[@]}"
   1917   fi
   1918 }
   1919 
   1920 ## @fn ble/syntax/highlight/vartype varname [opts [tail]]
   1921 ##   変数の種類・状態に応じた属性値を決定します。
   1922 ##
   1923 ##   @param[in] varname
   1924 ##     判定対象の変数名を指定します。
   1925 ##   @param[in,opt] opts
   1926 ##     コロン区切りのオプションを指定します。
   1927 ##
   1928 ##     readvar ... ユーザー文脈で "set -u" が指定されていてかつ存在しない変数に
   1929 ##         対してエラー着色を適用します。
   1930 ##
   1931 ##     global ... グローバル変数の状態を参照します。
   1932 ##
   1933 ##     no-readonly ... readonly 状態を無視します。
   1934 ##
   1935 ##   @param[in,opt] tail
   1936 ##     ${var...} 形式の内部を解析している時に変数名に続く文字列を指定します。
   1937 ##
   1938 ##   @var[out] ret
   1939 ##     属性値を返します。
   1940 ##
   1941 ##   @var[out,opt] lookahead
   1942 ##     tail が指定された時にのみ設定されます。属性値を決定する際に参照した tail
   1943 ##     内の先読み文字数を返します。
   1944 ##
   1945 function ble/syntax/highlight/vartype {
   1946   local -a __ble_vartype_ret=()
   1947   if [[ :$2: == *:global:* && $1 != __ble_* ]] && ! ble/variable#is-global "$1"; then
   1948     # Note: readonly を global 変数取得に使っているので readonly は正しく判定で
   1949     #   きない。何れにしてもローカル変数がある時点で global readonly ではないと
   1950     #   考えるのが (ローカル変数を定義した後に declare -gr するのでなければ) 普
   1951     #   通である。
   1952     local _ble_highlight_vartype_name=$1
   1953     local _ble_highlight_vartype_opts=$__ble_opts:no-readonly
   1954     local _ble_highlight_vartype_tail=$__ble_tail
   1955     ble/util/assign-words __ble_vartype_ret 'ble/util/for-global-variables ble/syntax/highlight/vartype/.print "" "$_ble_highlight_vartype_name"'
   1956   else
   1957     ble/syntax/highlight/vartype/.impl "$@"
   1958   fi
   1959   ret=${__ble_vartype_ret:-$ATTR_VAR}
   1960   [[ ${__ble_vartype_ret[1]+set} ]] && lookahead=${__ble_vartype_ret[1]}
   1961   return 0
   1962 }
   1963 
   1964 function ble/syntax:bash/check-plain-with-escape {
   1965   local rex='^('$1'|\\.)' is_quote=$2
   1966   [[ $tail =~ $rex ]] || return 1
   1967   if [[ $BASH_REMATCH == '\'? &&
   1968           ( ! $is_quote || $BASH_REMATCH == '\'[$'\\`$\n"'] ) ]]; then
   1969     ((_ble_syntax_attr[i]=ATTR_QESC))
   1970   else
   1971     ((_ble_syntax_attr[i]=ctx))
   1972   fi
   1973   ((i+=${#BASH_REMATCH}))
   1974   return 0
   1975 }
   1976 
   1977 function ble/syntax:bash/check-dollar {
   1978   [[ $tail == '$'* ]] || return 1
   1979 
   1980   local rex
   1981   if [[ $tail == '${'* ]]; then
   1982     # Note: パラメータ展開の中で許される物:
   1983     #   決まったパターン + 数式や文字列に途中で切り替わる事も。
   1984     # Note: 初めに文字数 ${#param} の形式を試す (失敗するとしても lookahead は設定する)。
   1985     #   その次に ${param...} 及び ${!param...} の形式を試す。
   1986     #   これにより ${#...} の # が文字数か或いは $# か判定する。
   1987     local rex1='^(\$\{#)([-*@#?$!0]\}?|[1-9][0-9]*\}?|[_a-zA-Z][_a-zA-Z0-9]*[[}]?)'
   1988     local rex2='^(\$\{!?)([-*@#?$!0]|[1-9][0-9]*|[_a-zA-Z][_a-zA-Z0-9]*\[?)'
   1989     if
   1990       [[ $tail =~ $rex1 ]] && {
   1991         [[ ${BASH_REMATCH[2]} == *['[}'] || $BASH_REMATCH == "$tail" ]] ||
   1992           { ble/syntax/parse/set-lookahead "$((${#BASH_REMATCH}+1))"; false; } } ||
   1993         [[ $tail =~ $rex2 ]]
   1994     then
   1995       # <parameter> = [-*@#?-$!0] | [1-9][0-9]* | <varname> | <varname> [ ... ] | <varname> [ <@> ]
   1996       # <@> = * | @
   1997       # ${<parameter>} ${#<parameter>} ${!<parameter>}
   1998       # ${<parameter>:-<word>} ${<parameter>:=<word>} ${<parameter>:+<word>} ${<parameter>:?<word>}
   1999       # ${<parameter>-<word>} ${<parameter>=<word>} ${<parameter>+<word>} ${<parameter>?<word>}
   2000       # ${<parameter>:expr} ${<parameter>:expr:expr} etc
   2001       # ${!head<@>} ${!varname[<@>]}
   2002 
   2003       # for bash-3.1 ${#arr[n]} bug
   2004       local rematch1=${BASH_REMATCH[1]}
   2005       local rematch2=${BASH_REMATCH[2]}
   2006       local varname=${rematch2%['[}']}
   2007 
   2008       local ntype='${'
   2009       if ((ctx==CTX_QUOT)); then
   2010         ntype='"${'
   2011       elif ((ctx==CTX_PWORD||ctx==CTX_PWORDE||ctx==CTX_PWORDR||ctx==CTX_EXPR)); then
   2012         local ntype2; ble/syntax/parse/nest-type -v ntype2
   2013         [[ $ntype2 == '"${' ]] && ntype='"${'
   2014       fi
   2015 
   2016       local ret lookahead= tail2=${tail:${#rematch1}+${#varname}}
   2017       ble/syntax/highlight/vartype "$varname" readvar:global "$tail2"; local attr=$ret
   2018 
   2019       ble/syntax/parse/nest-push "$CTX_PARAM" "$ntype"
   2020       ((_ble_syntax_attr[i]=ctx,
   2021         i+=${#rematch1},
   2022         _ble_syntax_attr[i]=attr,
   2023         i+=${#varname}))
   2024       [[ $lookahead ]] && ble/syntax/parse/set-lookahead "$lookahead"
   2025 
   2026       if rex='^\$\{![_a-zA-Z][_a-zA-Z0-9]*[*@]\}?'; [[ $tail =~ $rex ]]; then
   2027         ble/syntax/parse/set-lookahead 2
   2028         if [[ $BASH_REMATCH == *'}' ]]; then
   2029           # ${!head<@>} の時は末尾の @* を個別に読み取る。
   2030           ((i++,ctx=CTX_PWORDE))
   2031         fi
   2032       elif [[ $rematch2 == *'[' ]]; then
   2033         ble/syntax/parse/nest-push "$CTX_EXPR" 'v['
   2034         ((_ble_syntax_attr[i++]=CTX_EXPR))
   2035       fi
   2036       return 0
   2037     elif ((_ble_bash>=50300)) && [[ $tail == '${'[$' \t\n|']* ]]; then
   2038       ((_ble_syntax_attr[i]=CTX_PARAM))
   2039       ble/syntax/parse/nest-push "$CTX_CMDX" 'cmdsub_nofork'
   2040       ((i+=2))
   2041       [[ $tail == '${|'* ]] && ((i++))
   2042       return 0
   2043     else
   2044       ((_ble_syntax_attr[i]=ATTR_ERR,i+=2))
   2045       return 0
   2046     fi
   2047   elif [[ $tail == '$(('* ]]; then
   2048     ((_ble_syntax_attr[i]=CTX_PARAM))
   2049     ble/syntax/parse/nest-push "$CTX_EXPR" '$(('
   2050     ((i+=3))
   2051     return 0
   2052   elif [[ $tail == '$['* ]]; then
   2053     ((_ble_syntax_attr[i]=CTX_PARAM))
   2054     ble/syntax/parse/nest-push "$CTX_EXPR" '$['
   2055     ((i+=2))
   2056     return 0
   2057   elif [[ $tail == '$('* ]]; then
   2058     ((_ble_syntax_attr[i]=CTX_PARAM))
   2059     ble/syntax/parse/nest-push "$CTX_CMDX" '$('
   2060     ((i+=2))
   2061     return 0
   2062   elif rex='^\$([-*@#?$!0_]|[1-9]|[_a-zA-Z][_a-zA-Z0-9]*)' && [[ $tail =~ $rex ]]; then
   2063     local rematch=$BASH_REMATCH rematch1=${BASH_REMATCH[1]}
   2064     ((_ble_syntax_attr[i++]=CTX_PARAM))
   2065 
   2066     if ((_ble_bash<40200)) && local tail=${tail:1} &&
   2067          ble/syntax:bash/starts-with-histchars && ble/syntax:bash/check-history-expansion; then
   2068       # bash-4.1 以下では $!" や $!a 等が履歴展開の対象になる。
   2069       return 0
   2070     else
   2071       local ret; ble/syntax/highlight/vartype "$rematch1" readvar:global
   2072       ((_ble_syntax_attr[i]=ret,i+=${#rematch}-1))
   2073       return 0
   2074     fi
   2075   else
   2076     # if dollar doesn't match any patterns it is treated as a normal character
   2077     ((_ble_syntax_attr[i++]=ctx))
   2078     return 0
   2079   fi
   2080 }
   2081 
   2082 function ble/syntax:bash/check-quotes {
   2083   local rex aqdel=$ATTR_QDEL aquot=$CTX_QUOT
   2084 
   2085   # 字句的に解釈されるが除去はされない場合
   2086   if ((ctx==CTX_EXPR)) && [[ $tail != \`* ]]; then
   2087     local ntype
   2088     ble/syntax/parse/nest-type
   2089     case $ntype in
   2090     ('${')
   2091       # ${var:...} の中では如何なる quote も除去されない (字句的には解釈される)。
   2092       # 除去されない quote は算術式エラーである。Note: bash >= 5.2 では "..."
   2093       # と $"..." は許される。
   2094       if ((_ble_bash<50200)) || [[ $tail == \'* || $tail == \$\'* ]]; then
   2095         ((aqdel=ATTR_ERR,aquot=CTX_EXPR))
   2096       fi ;;
   2097     ('$['|'$(('|expr-paren-ax)
   2098       if [[ $tail == \'* ]] || ((_ble_bash<40400)); then
   2099         ((aqdel=ATTR_ERR,aquot=CTX_EXPR))
   2100       fi ;;
   2101     ('(('|expr-paren|expr-brack)
   2102       if [[ $tail == \'* ]] && ((_ble_bash>=50100)); then
   2103         ((aqdel=ATTR_ERR,aquot=CTX_EXPR))
   2104       fi ;;
   2105     ('a['|'v['|expr-paren-ai|expr-brack-ai)
   2106       if [[ $tail == \'* ]] && ((_ble_bash>=40400)); then
   2107         ((aqdel=ATTR_ERR,aquot=CTX_EXPR))
   2108       fi ;;
   2109     ('"${')
   2110       if ! { [[ $tail == '$'[\'\"]* ]] && shopt -q extquote; }; then
   2111         # "${var:...}" の中では 〈extquote が設定されている時の $'' $""〉 を例
   2112         # 外としてquote は除去されない (字句的には解釈される)。
   2113         ((aqdel=ATTR_ERR,aquot=CTX_EXPR))
   2114       fi ;;
   2115     # ('d['|expr-paren-di|expr-brack-di) ;; # nop
   2116     esac
   2117   elif ((ctx==CTX_PWORD||ctx==CTX_PWORDE||ctx==CTX_PWORDR)); then
   2118     # "${var ~}" の中では $'' $"" は ! shopt -q extquote の時除去されない。
   2119     if [[ $tail == '$'[\'\"]* ]] && ! shopt -q extquote; then
   2120       local ntype
   2121       ble/syntax/parse/nest-type
   2122       if [[ $ntype == '"${' ]]; then
   2123         ((aqdel=ctx,aquot=ctx))
   2124       fi
   2125     fi
   2126   fi
   2127 
   2128   if rex='^`([^`\]|\\(.|$))*(`?)|^'\''[^'\'']*('\''?)' && [[ $tail =~ $rex ]]; then
   2129     ((_ble_syntax_attr[i]=aqdel,
   2130       _ble_syntax_attr[i+1]=aquot,
   2131       i+=${#BASH_REMATCH},
   2132       _ble_syntax_attr[i-1]=${#BASH_REMATCH[3]}||${#BASH_REMATCH[4]}?aqdel:ATTR_ERR))
   2133     return 0
   2134   fi
   2135 
   2136   if ((ctx!=CTX_QUOT)); then
   2137     if rex='^(\$?")([^'"${_ble_syntax_bash_chars[CTX_QUOT]}"']*)("?)' && [[ $tail =~ $rex ]]; then
   2138       local rematch1=${BASH_REMATCH[1]} # for bash-3.1 ${#arr[n]} bug
   2139       if [[ ${BASH_REMATCH[3]} ]]; then
   2140         # 終端まで行った場合
   2141         ((_ble_syntax_attr[i]=aqdel,
   2142           _ble_syntax_attr[i+${#rematch1}]=aquot,
   2143           i+=${#BASH_REMATCH},
   2144           _ble_syntax_attr[i-1]=aqdel))
   2145       else
   2146         # 中に構造がある場合
   2147         ble/syntax/parse/nest-push "$CTX_QUOT"
   2148         if (((ctx==CTX_PWORD||ctx==CTX_PWORDE||ctx==CTX_PWORDR)&&aqdel!=ATTR_QDEL)); then
   2149           # CTX_PWORD (パラメータ展開) でクォート除去が有効でない文脈の場合、
   2150           # 「$」 だけ aqdel で着色し、「" ... "」 は通常通り着色する。
   2151           ((_ble_syntax_attr[i]=aqdel,
   2152             _ble_syntax_attr[i+${#rematch1}-1]=ATTR_QDEL,
   2153             _ble_syntax_attr[i+${#rematch1}]=CTX_QUOT,
   2154             i+=${#BASH_REMATCH}))
   2155         else
   2156           ((_ble_syntax_attr[i]=aqdel,
   2157             _ble_syntax_attr[i+${#rematch1}]=CTX_QUOT,
   2158             i+=${#BASH_REMATCH}))
   2159         fi
   2160       fi
   2161       return 0
   2162     elif rex='^\$'\''(([^'\''\]|\\(.|$))*)('\''?)' && [[ $tail =~ $rex ]]; then
   2163       ((_ble_syntax_attr[i]=aqdel,i+=2))
   2164       local t=${BASH_REMATCH[1]} rematch4=${BASH_REMATCH[4]}
   2165 
   2166       local rex='\\[abefnrtvE"'\''\?]|\\[0-7]{1,3}|\\c.|\\x[0-9a-fA-F]{1,2}'
   2167       ((_ble_bash>=40200)) && rex=$rex'|\\u[0-9a-fA-F]{1,4}|\\U[0-9a-fA-F]{1,8}'
   2168       local rex='^([^'\''\]*)('$rex'|(\\.))'
   2169       while [[ $t =~ $rex ]]; do
   2170         local m1=${BASH_REMATCH[1]} m2=${BASH_REMATCH[2]}
   2171         [[ $m1 ]] && ((_ble_syntax_attr[i]=aquot,i+=${#m1}))
   2172         if [[ ${BASH_REMATCH[3]} ]]; then
   2173           ((_ble_syntax_attr[i]=aquot))
   2174         else
   2175           ((_ble_syntax_attr[i]=ATTR_QESC))
   2176         fi
   2177         ((i+=${#m2}))
   2178         t=${t:${#BASH_REMATCH}}
   2179       done
   2180       [[ $t ]] && ((_ble_syntax_attr[i]=aquot,i+=${#t}))
   2181       if [[ $rematch4 ]]; then
   2182         ((_ble_syntax_attr[i++]=aqdel))
   2183       else
   2184         ((_ble_syntax_attr[i-1]=ATTR_ERR))
   2185       fi
   2186       return 0
   2187     fi
   2188   fi
   2189 
   2190   return 1
   2191 }
   2192 
   2193 function ble/syntax:bash/check-process-subst {
   2194   # プロセス置換
   2195   if [[ $tail == ['<>']'('* ]]; then
   2196     ble/syntax/parse/nest-push "$CTX_CMDX" '('
   2197     ((_ble_syntax_attr[i]=ATTR_DEL,i+=2))
   2198     return 0
   2199   fi
   2200 
   2201   return 1
   2202 }
   2203 
   2204 function ble/syntax:bash/check-comment {
   2205   # コメント
   2206   if shopt -q interactive_comments; then
   2207     if ((wbegin<0||wbegin==i)) && local rex=$'^#[^\n]*' && [[ $tail =~ $rex ]]; then
   2208       # 空白と同様に ctx は変えずに素通り (末端の改行は残す)
   2209       ((_ble_syntax_attr[i]=ATTR_COMMENT,
   2210         i+=${#BASH_REMATCH}))
   2211       return 0
   2212     fi
   2213   fi
   2214 
   2215   return 1
   2216 }
   2217 
   2218 function ble/syntax:bash/check-glob {
   2219   [[ $tail == ['[?*@+!()|']* ]] || return 1
   2220 
   2221   local ntype= force_attr=
   2222   if ((ctx==CTX_VRHS||ctx==CTX_ARGVR||ctx==CTX_ARGER||ctx==CTX_VALR||ctx==CTX_RDRS)); then
   2223     force_attr=$ctx
   2224     ntype="glob_attr=$force_attr"
   2225   elif ((ctx==CTX_FARGX1||ctx==CTX_FARGI1)); then
   2226     # for [xxx] / for a[xxx] の場合
   2227     force_attr=$ATTR_ERR
   2228     ntype="glob_attr=$force_attr"
   2229   elif ((ctx==CTX_PWORD||ctx==CTX_PWORDE||ctx==CTX_PWORDR)); then
   2230     ntype="glob_ctx=$ctx"
   2231   elif ((ctx==CTX_PATN||ctx==CTX_BRAX)); then
   2232     ble/syntax/parse/nest-type
   2233     local exit_attr=
   2234     if [[ $ntype == glob_attr=* ]]; then
   2235       force_attr=${ntype#*=}
   2236       exit_attr=$force_attr
   2237     elif ((ctx==CTX_BRAX)); then
   2238       force_attr=$ctx
   2239       ntype="glob_attr=$force_attr"
   2240     elif ((ctx==CTX_PATN)); then
   2241       ((exit_attr=_ble_syntax_attr[inest]))
   2242 
   2243       # glob_ctx=* の時は ntype は子に継承する
   2244       [[ $ntype != glob_ctx=* ]] && ntype=
   2245     else
   2246       ntype=
   2247     fi
   2248   elif [[ $1 == assign ]]; then
   2249     # $1 == assign の時、arr[... の "[" の位置で呼び出されたことを意味する。
   2250     ntype='a['
   2251   fi
   2252 
   2253   if [[ $tail == ['?*@+!']'('* ]] && shopt -q extglob; then
   2254     ble/syntax/parse/nest-push "$CTX_PATN" "$ntype"
   2255     ((_ble_syntax_attr[i]=${force_attr:-ATTR_GLOB},i+=2))
   2256     return 0
   2257   fi
   2258 
   2259   # 履歴展開の解釈の方が強い
   2260   local histc1=${_ble_syntax_bash_histc12::1}
   2261   [[ $histc1 && $tail == "$histc1"* ]] && return 1
   2262 
   2263   if [[ $tail == '['* ]]; then
   2264     if ((ctx==CTX_BRAX)); then
   2265       # 角括弧式の中の [ or [! はそのまま読み飛ばす。
   2266       ((_ble_syntax_attr[i++]=force_attr))
   2267       [[ $tail == '[!'* ]] && ((i++))
   2268       return 0
   2269     fi
   2270 
   2271     ble/syntax/parse/nest-push "$CTX_BRAX" "$ntype"
   2272     ((_ble_syntax_attr[i++]=${force_attr:-ATTR_GLOB}))
   2273     [[ $tail == '[!'* ]] && ((i++))
   2274     if [[ ${text:i:1} == ']' ]]; then
   2275       ((_ble_syntax_attr[i++]=${force_attr:-CTX_BRAX}))
   2276     elif [[ ${text:i:1} == '[' ]]; then
   2277       # Note: 条件コマンド [[ に変換する為に [[ の連なりは一度に読み取る。
   2278       if [[ ${text:i+1:1} == [:=.] ]]; then
   2279         # Note: glob bracket expression が POSIX 括弧で始まっている時は
   2280         # [[ が一まとまりになっていると困るので除外。
   2281         ble/syntax/parse/set-lookahead 2
   2282       else
   2283         ((_ble_syntax_attr[i++]=${force_attr:-CTX_BRAX}))
   2284         [[ ${text:i:1} == '!'* ]] && ((i++))
   2285       fi
   2286     fi
   2287 
   2288     return 0
   2289   elif [[ $tail == ['?*']* ]]; then
   2290     ((_ble_syntax_attr[i++]=${force_attr:-ATTR_GLOB}))
   2291     return 0
   2292   elif [[ $tail == ['@+!']* ]]; then
   2293     ((_ble_syntax_attr[i++]=${force_attr:-ctx}))
   2294     return 0
   2295   elif ((ctx==CTX_PATN||ctx==CTX_BRAX)); then
   2296     if [[ $tail == '('* ]]; then
   2297       ble/syntax/parse/nest-push "$CTX_PATN" "$ntype"
   2298       ((_ble_syntax_attr[i++]=${force_attr:-ctx}))
   2299       return 0
   2300     elif [[ $tail == ')'* ]]; then
   2301       if ((ctx==CTX_PATN)); then
   2302         ((_ble_syntax_attr[i++]=exit_attr))
   2303         ble/syntax/parse/nest-pop
   2304       else
   2305         ((_ble_syntax_attr[i++]=${force_attr:-ctx}))
   2306       fi
   2307       return 0
   2308     elif [[ $tail == '|'* ]]; then
   2309       ((_ble_syntax_attr[i++]=${force_attr:-ATTR_GLOB}))
   2310       return 0
   2311     fi
   2312   fi
   2313 
   2314   return 1
   2315 }
   2316 
   2317 _ble_syntax_bash_histexpand_RexWord=
   2318 _ble_syntax_bash_histexpand_RexMods=
   2319 _ble_syntax_bash_histexpand_RexEventDef=
   2320 _ble_syntax_bash_histexpand_RexQuicksubDef=
   2321 _ble_syntax_bash_histexpand_RexEventFmt=
   2322 _ble_syntax_bash_histexpand_RexQuicksubFmt=
   2323 function ble/syntax:bash/check-history-expansion/.initialize {
   2324   local spaces=$_ble_term_IFS nl=$'\n'
   2325   local rex_event='-?[0-9]+|[!#]|[^-$^*%:'$spaces'=?!#;&|<>()]+|\?[^?'$nl']*\??'
   2326   _ble_syntax_bash_histexpand_RexEventDef='^!('$rex_event')'
   2327 
   2328   local rex_word1='([0-9]+|[$%^])'
   2329   local rex_wordsA=':('$rex_word1'?-'$rex_word1'?|\*|'$rex_word1'\*?)'
   2330   local rex_wordsB='([$%^]?-'$rex_word1'?|\*|[$^%][*-]?)'
   2331   _ble_syntax_bash_histexpand_RexWord='('$rex_wordsA'|'$rex_wordsB')?'
   2332 
   2333   # ※本当は /s(.)([^\]|\\.)*?\1([^\]|\\.)*?\1/ 等としたいが *? は ERE にない。
   2334   #   仕方がないので ble/syntax:bash/check-history-expansion/.check-modifiers
   2335   #   にて繰り返し正規表現を適用して s?..?..? を読み取る。
   2336   local rex_modifier=':[htrepqx]|:[gGa]?&|:[gGa]?s(/([^\/]|\\.)*){0,2}(/|$)'
   2337   _ble_syntax_bash_histexpand_RexMods='('$rex_modifier')*'
   2338 
   2339   _ble_syntax_bash_histexpand_RexQuicksubDef='\^([^^\]|\\.)*\^([^^\]|\\.)*\^'
   2340 
   2341   # for histchars
   2342   _ble_syntax_bash_histexpand_RexQuicksubFmt='@A([^@C\]|\\.)*@A([^@C\]|\\.)*@A'
   2343   _ble_syntax_bash_histexpand_RexEventFmt='^@A('$rex_event'|@A)'
   2344 }
   2345 ble/syntax:bash/check-history-expansion/.initialize
   2346 
   2347 ## @fn ble/syntax:bash/check-history-expansion/.initialize-event
   2348 ##   @var[out] rex_event
   2349 function ble/syntax:bash/check-history-expansion/.initialize-event {
   2350   local histc1=${_ble_syntax_bash_histc12::1}
   2351   if [[ $histc1 == '!' ]]; then
   2352     rex_event=$_ble_syntax_bash_histexpand_RexEventDef
   2353   else
   2354     local A="[$histc1]"
   2355     [[ $histc1 == '^' ]] && A='\^'
   2356     rex_event=$_ble_syntax_bash_histexpand_RexEventFmt
   2357     rex_event=${rex_event//@A/"$A"}
   2358   fi
   2359 }
   2360 ## @fn ble/syntax:bash/check-history-expansion/.initialize-quicksub
   2361 ##   @var[out] rex_quicksub
   2362 function ble/syntax:bash/check-history-expansion/.initialize-quicksub {
   2363   local histc2=${_ble_syntax_bash_histc12:1:1}
   2364   if [[ $histc2 == '^' ]]; then
   2365     rex_quicksub=$_ble_syntax_bash_histexpand_RexQuicksubDef
   2366   else
   2367     rex_quicksub=$_ble_syntax_bash_histexpand_RexQuicksubFmt
   2368     rex_quicksub=${rex_quicksub//@A/"[$histc2]"}
   2369     rex_quicksub=${rex_quicksub//@C/"$histc2"}
   2370   fi
   2371 }
   2372 function ble/syntax:bash/check-history-expansion/.check-modifiers {
   2373   # check simple modifiers
   2374   [[ ${text:i} =~ $_ble_syntax_bash_histexpand_RexMods ]] &&
   2375     ((i+=${#BASH_REMATCH}))
   2376 
   2377   # check :s?..?..? form modifier
   2378   if local rex='^:[gGa]?s(.)'; [[ ${text:i} =~ $rex ]]; then
   2379     local del=${BASH_REMATCH[1]}
   2380     local A="[$del]" B="[^$del]"
   2381     [[ $del == '^' || $del == ']' ]] && A='\'$del
   2382     [[ $del != '\' ]] && B=$B'|\\.'
   2383 
   2384     local rex_substitute='^:[gGa]?s('$A'('$B')*){0,2}('$A'|$)'
   2385     if [[ ${text:i} =~ $rex_substitute ]]; then
   2386       ((i+=${#BASH_REMATCH}))
   2387       ble/syntax:bash/check-history-expansion/.check-modifiers
   2388       return 0
   2389     fi
   2390   fi
   2391 
   2392   # ErrMsg 'unrecognized modifier'
   2393   if [[ ${text:i} == ':'[gGa]* ]]; then
   2394     ((_ble_syntax_attr[i+1]=ATTR_ERR,i+=2))
   2395   elif [[ ${text:i} == ':'* ]]; then
   2396     ((_ble_syntax_attr[i]=ATTR_ERR,i++))
   2397   fi
   2398 }
   2399 ## @fn ble/syntax:bash/check-history-expansion
   2400 ##   @var[in] i tail
   2401 function ble/syntax:bash/check-history-expansion {
   2402   [[ -o histexpand ]] || return 1
   2403 
   2404   local histc1=${_ble_syntax_bash_histc12:0:1}
   2405   local histc2=${_ble_syntax_bash_histc12:1:1}
   2406   if [[ $histc1 && $tail == "$histc1"[^"$_ble_syntax_bash_histstop"]* ]]; then
   2407 
   2408     # "~" 文字列中では一致可能範囲を制限する。
   2409     if ((_ble_bash>=40300&&ctx==CTX_QUOT)); then
   2410       local tail=${tail%%'"'*}
   2411       [[ $tail == '!' ]] && return 1
   2412     fi
   2413 
   2414     ((_ble_syntax_attr[i]=ATTR_HISTX))
   2415     local rex_event
   2416     ble/syntax:bash/check-history-expansion/.initialize-event
   2417     if [[ $tail =~ $rex_event ]]; then
   2418       ((i+=${#BASH_REMATCH}))
   2419     elif [[ $tail == "$histc1"['-:0-9^$%*']* ]]; then
   2420       ((_ble_syntax_attr[i]=ATTR_HISTX,i++))
   2421     else
   2422       # ErrMsg 'unrecognized event'
   2423       ((_ble_syntax_attr[i+1]=ATTR_ERR,i+=2))
   2424       return 0
   2425     fi
   2426 
   2427     # word-designator
   2428     [[ ${text:i} =~ $_ble_syntax_bash_histexpand_RexWord ]] &&
   2429       ((i+=${#BASH_REMATCH}))
   2430 
   2431     ble/syntax:bash/check-history-expansion/.check-modifiers
   2432     return 0
   2433   elif ((i==0)) && [[ $histc2 && $tail == "$histc2"* ]]; then
   2434     ((_ble_syntax_attr[i]=ATTR_HISTX))
   2435     local rex_quicksub
   2436     ble/syntax:bash/check-history-expansion/.initialize-quicksub
   2437     if [[ $tail =~ $rex_quicksub ]]; then
   2438       ((i+=${#BASH_REMATCH}))
   2439 
   2440       ble/syntax:bash/check-history-expansion/.check-modifiers
   2441       return 0
   2442     else
   2443       # 末端まで
   2444       ((i+=${#tail}))
   2445       return 0
   2446     fi
   2447   fi
   2448 
   2449   return 1
   2450 }
   2451 ## @fn ble/syntax:bash/starts-with-histchars
   2452 ##   @var[in] tail
   2453 function ble/syntax:bash/starts-with-histchars {
   2454   [[ $_ble_syntax_bash_histc12 && $tail == ["$_ble_syntax_bash_histc12"]* ]]
   2455 }
   2456 
   2457 #------------------------------------------------------------------------------
   2458 # 文脈: 各種文脈
   2459 
   2460 _ble_syntax_context_proc[CTX_QUOT]=ble/syntax:bash/ctx-quot
   2461 function ble/syntax:bash/ctx-quot {
   2462   # 文字列の中身
   2463   if ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[CTX_QUOT]}]+" 1; then
   2464     return 0
   2465   elif [[ $tail == '"'* ]]; then
   2466     ((_ble_syntax_attr[i]=ATTR_QDEL,
   2467       i+=1))
   2468     ble/syntax/parse/nest-pop
   2469     return 0
   2470   elif ble/syntax:bash/check-quotes; then
   2471     return 0
   2472   elif ble/syntax:bash/check-dollar; then
   2473     return 0
   2474   elif ble/syntax:bash/starts-with-histchars; then
   2475     ble/syntax:bash/check-history-expansion ||
   2476       ((_ble_syntax_attr[i]=ctx,i++))
   2477     return 0
   2478   fi
   2479 
   2480   return 1
   2481 }
   2482 
   2483 _ble_syntax_context_proc[CTX_CASE]=ble/syntax:bash/ctx-case
   2484 function ble/syntax:bash/ctx-case {
   2485   if [[ $tail =~ ^$_ble_syntax_bash_RexIFSs ]]; then
   2486     ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   2487     return 0
   2488   elif [[ $tail == '('* ]]; then
   2489     ((_ble_syntax_attr[i++]=ATTR_GLOB,ctx=CTX_CPATX))
   2490     return 0
   2491   elif [[ $tail == 'esac'$_ble_syntax_bash_RexDelimiter* || $tail == 'esac' ]]; then
   2492     ((ctx=CTX_CMDX))
   2493     ble/syntax:bash/ctx-command
   2494   else
   2495     ((ctx=CTX_CPATX))
   2496     ble/syntax:bash/ctx-command-case-pattern-expect
   2497   fi
   2498 }
   2499 
   2500 # 文脈 CTX_PATN (extglob/case-pattern)
   2501 _ble_syntax_context_proc[CTX_PATN]=ble/syntax:bash/ctx-globpat
   2502 _ble_syntax_context_end[CTX_PATN]=ble/syntax:bash/ctx-globpat.end
   2503 
   2504 ## @fn ble/syntax:bash/ctx-globpat/get-stop-chars
   2505 ##   @var[out] chars
   2506 function ble/syntax:bash/ctx-globpat/get-stop-chars {
   2507   chars=${_ble_syntax_bash_chars[CTX_PATN]}
   2508   local ntype; ble/syntax/parse/nest-type
   2509   if [[ $ntype == glob_ctx=* ]]; then
   2510     local gctx=${ntype#glob_ctx=}
   2511     if ((gctx==CTX_PWORD||gctx==CTX_PWORDE)); then
   2512       chars=}$chars
   2513     elif ((gctx==CTX_PWORDR)); then
   2514       chars=}/$chars
   2515     fi
   2516   fi
   2517 }
   2518 function ble/syntax:bash/ctx-globpat {
   2519   # glob () の中身 (extglob @(...) や case in (...) の中)
   2520   local chars; ble/syntax:bash/ctx-globpat/get-stop-chars
   2521   if ble/syntax:bash/check-plain-with-escape "[^$chars]+"; then
   2522     return 0
   2523   elif ble/syntax:bash/check-process-subst; then
   2524     return 0
   2525   elif [[ $tail == ['<>']* ]]; then
   2526     ((_ble_syntax_attr[i++]=ctx))
   2527     return 0
   2528   elif ble/syntax:bash/check-quotes; then
   2529     return 0
   2530   elif ble/syntax:bash/check-dollar; then
   2531     return 0
   2532   elif ble/syntax:bash/check-glob; then
   2533     return 0
   2534   elif ble/syntax:bash/check-brace-expansion; then
   2535     return 0
   2536   elif ble/syntax:bash/starts-with-histchars; then
   2537     ble/syntax:bash/check-history-expansion ||
   2538       ((_ble_syntax_attr[i]=ctx,i++))
   2539     return 0
   2540   fi
   2541 
   2542   return 1
   2543 }
   2544 function ble/syntax:bash/ctx-globpat.end {
   2545   local is_end= tail=${text:i}
   2546   local ntype; ble/syntax/parse/nest-type
   2547   if [[ $ntype == glob_ctx=* ]]; then
   2548     local gctx=${ntype#glob_ctx=}
   2549     if ((gctx==CTX_PWORD||gctx==CTX_PWORDE)); then
   2550       [[ ! $tail || $tail == '}'* ]] && is_end=1
   2551     elif ((gctx==CTX_PWORDR)); then
   2552       [[ ! $tail || $tail == ['/}']* ]] && is_end=1
   2553     fi
   2554   fi
   2555 
   2556   if [[ $is_end ]]; then
   2557     ble/syntax/parse/nest-pop
   2558     ble/syntax/parse/check-end
   2559     return 0
   2560   fi
   2561 
   2562   return 0
   2563 }
   2564 
   2565 # 文脈 CTX_BRAX (bracket expression)
   2566 _ble_syntax_context_proc[CTX_BRAX]=ble/syntax:bash/ctx-bracket-expression
   2567 _ble_syntax_context_end[CTX_BRAX]=ble/syntax:bash/ctx-bracket-expression.end
   2568 function ble/syntax:bash/ctx-bracket-expression {
   2569   local nctx; ble/syntax/parse/nest-ctx
   2570   if ((nctx==CTX_PATN)); then
   2571     local chars; ble/syntax:bash/ctx-globpat/get-stop-chars
   2572   elif ((nctx==CTX_PWORD||nctx==CTX_PWORDE||nctx==CTX_PWORDR)); then
   2573     local chars=${_ble_syntax_bash_chars[nctx]}
   2574   else
   2575     # 以下の文脈では ctx-command と同様の処理で問題ない。
   2576     #
   2577     #   ctx-command (色々)
   2578     #   ctx-redirect (CTX_RDRF CTX_RDRD CTX_RDRD2 CTX_RDRS)
   2579     #   ctx-values (CTX_VALI, CTX_VALR, CTX_VALQ)
   2580     #   ctx-conditions (CTX_CONDI, CTX_CONDQ)
   2581     #     この文脈では例外として && || < > など一部の演算子で delimiters
   2582     #     が単語中に許されるが、この例外は [...] を含む単語には当てはまらない。
   2583     #
   2584     # is-delimiters の時に [... は其処で不完全終端する。
   2585     local chars=${_ble_syntax_bash_chars[CTX_ARGI]//'~'}
   2586   fi
   2587   chars="][${chars#']'}"
   2588 
   2589   local ntype; ble/syntax/parse/nest-type
   2590   local force_attr=; [[ $ntype == glob_attr=* ]] && force_attr=${ntype#*=}
   2591 
   2592   local rex
   2593   if [[ $tail == ']'* ]]; then
   2594     ((_ble_syntax_attr[i++]=${force_attr:-ATTR_GLOB}))
   2595     ble/syntax/parse/nest-pop
   2596 
   2597     # 通常引数が配列代入の形式を持つとき、以降でチルダ展開が有効
   2598     # 例: echo arr[i]=... arr[i]+=...
   2599     if [[ $ntype == 'a[' ]]; then
   2600       local is_assign=
   2601       if [[ $tail == ']='* ]]; then
   2602         ((_ble_syntax_attr[i++]=ctx,is_assign=1))
   2603       elif [[ $tail == ']+'* ]]; then
   2604         ble/syntax/parse/set-lookahead 2
   2605         [[ $tail == ']+='* ]] && ((_ble_syntax_attr[i]=ctx,i+=2,is_assign=1))
   2606       fi
   2607 
   2608       if [[ $is_assign ]]; then
   2609         ble/util/assert '[[ ${_ble_syntax_bash_command_CtxAssign[ctx]} ]]'
   2610         ((ctx=_ble_syntax_bash_command_CtxAssign[ctx]))
   2611         if local tail=${text:i}; [[ $tail == '~'* ]]; then
   2612           ble/syntax:bash/check-tilde-expansion rhs
   2613         fi
   2614       fi
   2615     fi
   2616     return 0
   2617   elif [[ $tail == '['* ]]; then
   2618     rex='^\[@([^'$chars']+(@\]?)?)?'
   2619     rex=${rex//@/:}'|'${rex//@/'\.'}'|'${rex//@/=}'|^\['
   2620     [[ $tail =~ $rex ]]
   2621     ((_ble_syntax_attr[i]=${force_attr:-ctx},
   2622       i+=${#BASH_REMATCH}))
   2623     return 0
   2624   elif ctx=${force_attr:-$ctx} ble/syntax:bash/check-plain-with-escape "[^$chars]+"; then
   2625     return 0
   2626   elif ble/syntax:bash/check-process-subst; then
   2627     return 0
   2628   elif ble/syntax:bash/check-quotes; then
   2629     return 0
   2630   elif ble/syntax:bash/check-dollar; then
   2631     return 0
   2632   elif ble/syntax:bash/check-glob; then
   2633     return 0
   2634   elif ble/syntax:bash/check-brace-expansion; then
   2635     return 0
   2636   elif ble/syntax:bash/check-tilde-expansion; then
   2637     return 0
   2638   elif ble/syntax:bash/starts-with-histchars; then
   2639     ble/syntax:bash/check-history-expansion ||
   2640       ((_ble_syntax_attr[i++]=${force_attr:-ctx}))
   2641     return 0
   2642   elif ((nctx==CTX_PATN)) && [[ $tail == ['<>']* ]]; then
   2643     ((_ble_syntax_attr[i++]=${force_attr:-ctx}))
   2644     return 0
   2645   fi
   2646 
   2647   return 1
   2648 }
   2649 function ble/syntax:bash/ctx-bracket-expression.end {
   2650   local is_end=
   2651 
   2652   local tail=${text:i}
   2653   if [[ ! $tail ]]; then
   2654     is_end=1
   2655   else
   2656     local nctx; ble/syntax/parse/nest-ctx
   2657     local external_ctx=$nctx
   2658     if ((nctx==CTX_PATN)); then
   2659       local ntype; ble/syntax/parse/nest-type
   2660       [[ $ntype == glob_ctx=* ]] &&
   2661         external_ctx=${ntype#glob_ctx=}
   2662     fi
   2663 
   2664     if ((external_ctx==CTX_PATN)); then
   2665       [[ $tail == ')'* ]] && is_end=1
   2666     elif ((external_ctx==CTX_PWORD||external_ctx==CTX_PWORDE)); then
   2667       [[ $tail == '}'* ]] && is_end=1
   2668     elif ((external_ctx==CTX_PWORDR)); then
   2669       [[ $tail == ['}/']* ]] && is_end=1
   2670     else
   2671       # 外側は ctx-command など。
   2672       if ble/syntax:bash/check-word-end/is-delimiter; then
   2673         is_end=1
   2674       elif [[ $tail == ':'* && ${_ble_syntax_bash_command_IsAssign[ctx]} ]]; then
   2675         is_end=1
   2676       fi
   2677     fi
   2678   fi
   2679 
   2680   if [[ $is_end ]]; then
   2681     ble/syntax/parse/nest-pop
   2682     ble/syntax/parse/check-end
   2683     return "$?"
   2684   fi
   2685 
   2686   return 0
   2687 }
   2688 
   2689 _ble_syntax_context_proc[CTX_PARAM]=ble/syntax:bash/ctx-param
   2690 _ble_syntax_context_proc[CTX_PWORD]=ble/syntax:bash/ctx-pword
   2691 _ble_syntax_context_proc[CTX_PWORDR]=ble/syntax:bash/ctx-pword
   2692 _ble_syntax_context_proc[CTX_PWORDE]=ble/syntax:bash/ctx-pword-error
   2693 function ble/syntax:bash/ctx-param {
   2694   # パラメータ展開 - パラメータの直後
   2695   if [[ $tail == '}'* ]]; then
   2696     ((_ble_syntax_attr[i]=_ble_syntax_attr[inest]))
   2697     ((i+=1))
   2698     ble/syntax/parse/nest-pop
   2699     return 0
   2700   fi
   2701 
   2702   local rex='##?|%%?|:?[-?=+]|:|/[/#%]?'
   2703   ((_ble_bash>=40000)) && rex=$rex'|,,?|\^\^?|~~?'
   2704   if ((_ble_bash>=50200)); then
   2705     rex=$rex'|@[QEPAaUuLKk]?'
   2706   elif ((_ble_bash>=50100)); then
   2707     rex=$rex'|@[QEPAaUuLK]?'
   2708   elif ((_ble_bash>=40400)); then
   2709     rex=$rex'|@[QEPAa]?'
   2710   fi
   2711   rex='^('$rex')'
   2712   if [[ $tail =~ $rex ]]; then
   2713     ((_ble_syntax_attr[i]=CTX_PARAM,
   2714       i+=${#BASH_REMATCH}))
   2715     if [[ $BASH_REMATCH == '/'* ]]; then
   2716       ((ctx=CTX_PWORDR))
   2717     elif [[ $BASH_REMATCH == : ]]; then
   2718       ((ctx=CTX_EXPR,_ble_syntax_attr[i-1]=CTX_EXPR))
   2719     elif [[ $BASH_REMATCH == @* ]]; then
   2720       ((ctx=CTX_PWORDE))
   2721     else
   2722       ((ctx=CTX_PWORD))
   2723       [[ $BASH_REMATCH == [':-+=?#%']* ]] &&
   2724         tail=${text:i} ble/syntax:bash/check-tilde-expansion pword
   2725     fi
   2726     return 0
   2727   else
   2728     local i0=$i
   2729     ((ctx=CTX_PWORD))
   2730     ble/syntax:bash/ctx-pword || return 1
   2731 
   2732     # 一文字だけエラー着色
   2733     if ((i0+2<=i)); then
   2734       ((_ble_syntax_attr[i0+1])) ||
   2735         ((_ble_syntax_attr[i0+1]=_ble_syntax_attr[i0]))
   2736     fi
   2737     ((_ble_syntax_attr[i0]=ATTR_ERR))
   2738     return 0
   2739   fi
   2740 }
   2741 function ble/syntax:bash/ctx-pword {
   2742   # パラメータ展開 - word 部
   2743   if ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[ctx]}]+"; then
   2744     return 0
   2745   elif ((ctx==CTX_PWORDR)) && [[ $tail == '/'* ]]; then
   2746     ((_ble_syntax_attr[i++]=CTX_PARAM,ctx=CTX_PWORD))
   2747     return 0
   2748   elif [[ $tail == '}'* ]]; then
   2749     ((_ble_syntax_attr[i]=_ble_syntax_attr[inest]))
   2750     ((i+=1))
   2751     ble/syntax/parse/nest-pop
   2752     return 0
   2753   elif ble/syntax:bash/check-quotes; then
   2754     return 0
   2755   elif ble/syntax:bash/check-dollar; then
   2756     return 0
   2757   elif ble/syntax:bash/check-glob; then
   2758     return 0
   2759   elif ble/syntax:bash/starts-with-histchars; then
   2760     ble/syntax:bash/check-history-expansion ||
   2761       ((_ble_syntax_attr[i]=ctx,i++))
   2762     return 0
   2763   fi
   2764 
   2765   return 1
   2766 }
   2767 function ble/syntax:bash/ctx-pword-error {
   2768   local i0=$i
   2769   if ble/syntax:bash/ctx-pword; then
   2770     [[ $tail == '}'* ]] ||
   2771       ((_ble_syntax_attr[i0]=ATTR_ERR))
   2772     return 0
   2773   else
   2774     return 1
   2775   fi
   2776 }
   2777 
   2778 ## @const CTX_EXPR
   2779 ##   算術式の文脈値
   2780 ##
   2781 ##   対応する nest types (ntype) の一覧
   2782 ##
   2783 ##   NTYPE             NEST-PUSH LOCATION       QUOTE  DESC
   2784 ##   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   2785 ##   '$(('           @ check-dollar                 x  算術式展開 $(()) の中身
   2786 ##   '$['            @ check-dollar                 x  算術式展開 $[] の中身
   2787 ##   '(('            @ .check-delimiter-or-redirect o  算術式評価コマンド (()) の中身
   2788 ##   'a['            @ check-variable-assignment    o  a[...]= の中身
   2789 ##   'd['            @ ctx-values                   o  a=([...]=) の中身
   2790 ##   'v['            @ check-dollar                 o  ${a[...]} の中身
   2791 ##   '${'            @ check-dollar                 o  ${v:...} の中身
   2792 ##   '"${'           @ check-dollar                 x  "${v:...}" の中身
   2793 ##   'expr-paren'    @ .count-paren                 o  () によるネスト (quote 除去有効)
   2794 ##   'expr-paren-ax' @ .count-paren                 +  () によるネスト / $(( $[ の内部
   2795 ##   'expr-paren-ai' @ .count-paren                 +  () によるネスト / a[ v[ の内部          (unused)
   2796 ##   'expr-paren-di' @ .count-paren                 o  () によるネスト / d[ の内部             (unused)
   2797 ##   'expr-brack'    @ .count-bracket               o  [] によるネスト (quote 除去常時有効)    (unused)
   2798 ##   'expr-brack-ai' @ .count-bracket               +  [] によるネスト / $(( $[ a[ v[ [ の内部
   2799 ##   'expr-brack-di' @ .count-bracket               o  [] によるネスト / d[ の内部
   2800 ##
   2801 ##   '$('            @ check-dollar                 o  $(command)
   2802 ##   'cmdsub_nofork' @ check-dollar                 o  ${ command; }
   2803 ##   'cmd_brace'     @ ctx-command/check-word-end   o  { command; }
   2804 ##
   2805 ##   QUOTE = o ... 内部で quote 除去が有効
   2806 ##   QUOTE = x ... 内部で quote 除去は無効
   2807 ##
   2808 _ble_syntax_context_proc[CTX_EXPR]=ble/syntax:bash/ctx-expr
   2809 ## @fn ble/syntax:bash/ctx-expr/.count-paren
   2810 ##   算術式中の括弧の数 () を数えます。
   2811 ##   @var ntype 現在の算術式の入れ子の種類を指定します。
   2812 ##   @var char  括弧文字を指定します。
   2813 function ble/syntax:bash/ctx-expr/.count-paren {
   2814   if [[ $char == ')' ]]; then
   2815     if [[ $ntype == '((' || $ntype == '$((' ]]; then
   2816       if [[ $tail == '))'* ]]; then
   2817         ((_ble_syntax_attr[i]=_ble_syntax_attr[inest]))
   2818         ((i+=2))
   2819         ble/syntax/parse/nest-pop
   2820       else
   2821         # ((echo) > /dev/null) や $((echo) > /dev/null) などの
   2822         # 紛らわしいサブシェル・コマンド置換だったとみなす。
   2823         # それまでに算術式と思っていた部分については仕方がないのでそのまま。
   2824         ((ctx=CTX_ARGX0,
   2825           _ble_syntax_attr[i++]=_ble_syntax_attr[inest]))
   2826       fi
   2827       return 0
   2828     elif [[ $ntype == expr-paren* ]]; then
   2829       ((_ble_syntax_attr[i++]=ctx))
   2830       ble/syntax/parse/nest-pop
   2831       return 0
   2832     fi
   2833   elif [[ $char == '(' ]]; then
   2834     # determine nested ntype
   2835     local ntype2=
   2836     case $ntype in
   2837     ('((')
   2838       ntype2=expr-paren ;;
   2839     ('$((')
   2840       ntype2=expr-paren-ax ;;
   2841     (expr-paren|expr-paren-ax|expr-paren-ai|expr-paren-di)
   2842       ntype2=$ntype ;;
   2843     ('$['|'a['|'v['|'d['|expr-brack|expr-brack-ai|expr-brack-di|'${'|'"${'|*)
   2844       ble/util/assert 'false' "unexpected ntype='$ntype' here" ;;
   2845     esac
   2846 
   2847     ble/syntax/parse/nest-push "$CTX_EXPR" "$ntype2"
   2848     ((_ble_syntax_attr[i++]=ctx))
   2849     return 0
   2850   fi
   2851 
   2852   return 1
   2853 }
   2854 ## @fn ble/syntax:bash/ctx-expr/.count-bracket
   2855 ##   算術式中の括弧の数 [] を数えます。
   2856 ##   @var ntype 現在の算術式の入れ子の種類を指定します。
   2857 ##   @var char  括弧文字を指定します。
   2858 function ble/syntax:bash/ctx-expr/.count-bracket {
   2859   if [[ $char == ']' ]]; then
   2860     if [[ $ntype == expr-brack* || $ntype == '$[' ]]; then
   2861       # 算術式展開 $[...] や入れ子 ((a[...]=123)) などの場合。
   2862       ((_ble_syntax_attr[i]=_ble_syntax_attr[inest]))
   2863       ((i++))
   2864       ble/syntax/parse/nest-pop
   2865       return 0
   2866     elif [[ $ntype == [ad]'[' ]]; then
   2867       ((_ble_syntax_attr[i++]=CTX_EXPR))
   2868       ble/syntax/parse/nest-pop
   2869       if [[ $tail == ']='* ]]; then
   2870         # a[...]=, a=([...]=) の場合
   2871         ((i++))
   2872         tail=${text:i} ble/syntax:bash/check-tilde-expansion rhs
   2873       elif ((_ble_bash>=30100)) && [[ $tail == ']+'* ]]; then
   2874         ble/syntax/parse/set-lookahead 2
   2875         if [[ $tail == ']+='* ]]; then
   2876           # a[...]+=, a+=([...]+=) の場合
   2877           ((i+=2))
   2878           tail=${text:i} ble/syntax:bash/check-tilde-expansion rhs
   2879         fi
   2880       else
   2881         if [[ $ntype == 'a[' ]]; then
   2882           # a[...]... という唯のコマンドの場合。
   2883           if ((ctx==CTX_VRHS)); then
   2884             # 例: arr[123]aaa
   2885             ((ctx=CTX_CMDI,wtype=CTX_CMDI))
   2886           elif ((ctx==CTX_ARGVR)); then
   2887             # 例: declare arr[123]aaa
   2888             ((ctx=CTX_ARGVI,wtype=CTX_ARGVI))
   2889           elif ((ctx==CTX_ARGER)); then
   2890             # 例: eval arr[123]aaa
   2891             ((ctx=CTX_ARGEI,wtype=CTX_ARGEI))
   2892           fi
   2893         else # ntype == 'd['
   2894           # '[...]...' という唯の値の場合。
   2895           ((ctx=CTX_VALI,wtype=CTX_VALI))
   2896         fi
   2897       fi
   2898       return 0
   2899     elif [[ $ntype == 'v[' ]]; then
   2900       # ${v[]...} などの場合。
   2901       ((_ble_syntax_attr[i++]=CTX_EXPR))
   2902       ble/syntax/parse/nest-pop
   2903       return 0
   2904     fi
   2905   elif [[ $char == '[' ]]; then
   2906     local ntype2=
   2907     case $ntype in
   2908     ('$['|'a['|'v[')
   2909       ntype2=expr-brack-ai ;;
   2910     ('d[')
   2911       ntype2=expr-brack-di ;;
   2912     (expr-brack|expr-brack-ai|expr-brack-di)
   2913       ntype2=$ntype ;;
   2914     ('(('|'$(('|expr-paren|expr-paren-ax|expr-paren-ai|expr-paren-di|'${'|'"${'|*)
   2915       ble/util/assert 'false' "unexpected ntype='$ntype' here" ;;
   2916     esac
   2917     ble/syntax/parse/nest-push "$CTX_EXPR" "$ntype2"
   2918     ((_ble_syntax_attr[i++]=ctx))
   2919     return 0
   2920   fi
   2921 
   2922   return 1
   2923 }
   2924 ## @fn ble/syntax:bash/ctx-expr/.count-brace
   2925 ##   算術式中に閉じ波括弧 '}' が来たら算術式を抜けます。
   2926 ##   @var ntype 現在の算術式の入れ子の種類を指定します。
   2927 ##   @var char  括弧文字を指定します。
   2928 function ble/syntax:bash/ctx-expr/.count-brace {
   2929   if [[ $char == '}' ]]; then
   2930     ((_ble_syntax_attr[i]=_ble_syntax_attr[inest]))
   2931     ((i++))
   2932     ble/syntax/parse/nest-pop
   2933     return 0
   2934   fi
   2935 
   2936   return 1
   2937 }
   2938 ## @fn ble/syntax:bash/ctx-expr/.check-plain-with-escape rex is_quote
   2939 ##   @var[in] ntype
   2940 function ble/syntax:bash/ctx-expr/.check-plain-with-escape {
   2941   local i0=$i
   2942   ble/syntax:bash/check-plain-with-escape "$@" || return 1
   2943 
   2944   if [[ $tail == '\'* ]]; then
   2945     case $ntype in
   2946     ('$(('|'$['|expr-paren-ax|'${'|'"${')
   2947       _ble_syntax_attr[i0]=$ATTR_ERR ;;
   2948     ('(('|expr-paren|expr-brack)
   2949       if ((_ble_bash>=50100)); then
   2950         _ble_syntax_attr[i0]=$ATTR_ERR
   2951       fi ;;
   2952     ('a['|'v['|expr-paren-ai|expr-brack-ai)
   2953       if ((_ble_bash>=40400)); then
   2954         _ble_syntax_attr[i0]=$ATTR_ERR
   2955       fi ;;
   2956     # ('d['|expr-paren-di|expr-brack-di) ;; # d[ (designated init) 内部では常に \ は OK
   2957     esac
   2958   fi
   2959 
   2960   return 0
   2961 }
   2962 
   2963 function ble/syntax:bash/ctx-expr {
   2964   # 式の中身
   2965   local rex
   2966   if rex='^[_a-zA-Z][_a-zA-Z0-9]*'; [[ $tail =~ $rex ]]; then
   2967     local rematch=$BASH_REMATCH
   2968     local ret; ble/syntax/highlight/vartype "$BASH_REMATCH" readvar:expr:global
   2969     ((_ble_syntax_attr[i]=ret,i+=${#rematch}))
   2970     return 0
   2971   elif rex='^0[xX][0-9a-fA-F]*|^[0-9]+(#[_a-zA-Z0-9@]*)?'; [[ $tail =~ $rex ]]; then
   2972     ((_ble_syntax_attr[i]=ATTR_VAR_NUMBER,i+=${#BASH_REMATCH}))
   2973     return 0
   2974   fi
   2975 
   2976   local ntype
   2977   ble/syntax/parse/nest-type
   2978   if ble/syntax:bash/ctx-expr/.check-plain-with-escape "[^${_ble_syntax_bash_chars[ctx]}_a-zA-Z0-9]+" 1; then
   2979     return 0
   2980   elif [[ $tail == ['][()}']* ]]; then
   2981     local char=${tail::1}
   2982     if [[ $ntype == *'(' || $ntype == expr-paren* ]]; then
   2983       # ntype = '(('            # ((...))
   2984       #       = '$(('           # $((...))
   2985       #       = 'expr-paren'    # 式中の (..)
   2986       #       = 'expr-paren-ax' # $(( $[ 中の (..)
   2987       #       = 'expr-paren-ai' # a[ v[  中の (..)
   2988       #       = 'expr-paren-di' # d[     中の (..)
   2989       ble/syntax:bash/ctx-expr/.count-paren && return 0
   2990     elif [[ $ntype == *'[' || $ntype == expr-brack* ]]; then
   2991       # ntype = 'a['             # a[...]=
   2992       #       = 'v['             # ${a[...]}
   2993       #       = 'd['             # a=([...]=)
   2994       #       = '$['             # $[...]
   2995       #       = 'expr-brack'     # 式中の [...]
   2996       #       = 'expr-brack-ai'  # $(( $[ a[ v[ 中の [...]
   2997       #       = 'expr-brack-di'  # d[           中の [...]
   2998       ble/syntax:bash/ctx-expr/.count-bracket && return 0
   2999     elif [[ $ntype == '${' || $ntype == '"${' ]]; then
   3000       # ntype = '${'  # ${var:offset:length}
   3001       #       = '"${' # "${var:offset:length}"
   3002       ble/syntax:bash/ctx-expr/.count-brace && return 0
   3003     else
   3004       ble/util/assert 'false' "unexpected ntype=$ntype for arithmetic expression"
   3005     fi
   3006 
   3007     # 入れ子処理されなかった文字は通常文字として処理
   3008     ((_ble_syntax_attr[i++]=ctx))
   3009     return 0
   3010   elif ble/syntax:bash/check-quotes; then
   3011     return 0
   3012   elif ble/syntax:bash/check-dollar; then
   3013     return 0
   3014   elif ble/syntax:bash/starts-with-histchars; then
   3015     # 恐ろしい事に数式中でも履歴展開が有効…。
   3016     ble/syntax:bash/check-history-expansion ||
   3017       ((_ble_syntax_attr[i]=ctx,i++))
   3018     return 0
   3019   fi
   3020 
   3021   return 1
   3022 }
   3023 
   3024 #------------------------------------------------------------------------------
   3025 # ブレース展開
   3026 
   3027 ## CTX_CONDI 及び CTX_RDRS の時は不活性化したブレース展開として振る舞う。
   3028 ## CTX_RDRF 及び CTX_RDRD, CTX_RDRD2 の時は複数語に展開されるブレース展開はエラーなので、
   3029 ## nest-push して解析だけ行いブレース展開であるということが確定した時点でエラーを設定する。
   3030 
   3031 function ble/syntax:bash/check-brace-expansion {
   3032   [[ $tail == '{'* ]] || return 1
   3033 
   3034   local rex='^\{[-+a-zA-Z0-9.]*(\}?)'
   3035   [[ $tail =~ $rex ]]
   3036   local str=$BASH_REMATCH
   3037 
   3038   local force_attr= inactive=
   3039 
   3040   # 特定の文脈では完全に不活性
   3041   # Note: {fd}> リダイレクトの先読みに合わせて、
   3042   #   不活性であっても一気に読み取る必要がある。
   3043   #   cf ble/syntax:bash/starts-with-delimiter-or-redirect
   3044   if [[ $- != *B* ]]; then
   3045     inactive=1
   3046   elif ((ctx==CTX_CONDI||ctx==CTX_CONDQ||ctx==CTX_RDRS||ctx==CTX_VRHS)); then
   3047     inactive=1
   3048   elif ((_ble_bash>=50300&&ctx==CTX_VALR)); then
   3049     # bash-5.3 以降では arr=([9]={1..10}) 等のブレース展開は不活性
   3050     inactive=1
   3051   elif ((ctx==CTX_PATN||ctx==CTX_BRAX)); then
   3052     local ntype; ble/syntax/parse/nest-type
   3053     if [[ $ntype == glob_attr=* ]]; then
   3054       force_attr=${ntype#*=}
   3055       (((force_attr==CTX_RDRS||force_attr==CTX_VRHS||force_attr==CTX_ARGVR||force_attr==CTX_ARGER||force_attr==CTX_VALR)&&(inactive=1)))
   3056     elif ((ctx==CTX_BRAX)); then
   3057       local nctx; ble/syntax/parse/nest-ctx
   3058       (((nctx==CTX_CONDI||octx==CTX_CONDQ)&&(inactive=1)))
   3059     fi
   3060   elif ((ctx==CTX_BRACE1||ctx==CTX_BRACE2)); then
   3061     local ntype; ble/syntax/parse/nest-type
   3062     if [[ $ntype == glob_attr=* ]]; then
   3063       force_attr=${ntype#*=}
   3064     fi
   3065   fi
   3066 
   3067   if [[ $inactive ]]; then
   3068     ((_ble_syntax_attr[i]=${force_attr:-ctx},i+=${#str}))
   3069     return 0
   3070   fi
   3071 
   3072   # ブレース展開がある時チルダ展開は無効化される
   3073   # Note: CTX_VRHS 等のときは inactive なので此処には来ないので OK
   3074   [[ ${_ble_syntax_bash_command_IsAssign[ctx]} ]] &&
   3075     ctx=${_ble_syntax_bash_command_IsAssign[ctx]}
   3076 
   3077   # {a..b..c} の形式のブレース展開
   3078   if rex='^\{(([-+]?[0-9]+)\.\.[-+]?[0-9]+|[a-zA-Z]\.\.[a-zA-Z])(\.\.[-+]?[0-9]+)?\}$'; [[ $str =~ $rex ]]; then
   3079     if [[ $force_attr ]]; then
   3080       ((_ble_syntax_attr[i]=force_attr,i+=${#str}))
   3081     else
   3082       local rematch1=${BASH_REMATCH[1]}
   3083       local rematch2=${BASH_REMATCH[2]}
   3084       local rematch3=${BASH_REMATCH[3]}
   3085       local len2=${#rematch2}; ((len2||(len2=1)))
   3086       local attr=$ATTR_BRACE
   3087       if ((ctx==CTX_RDRF||ctx==CTX_RDRD||ctx==CTX_RDRD2)); then
   3088         # リダイレクトで複数語に展開される時はエラー
   3089         local lhs=${rematch1::len2} rhs=${rematch1:len2+2}
   3090         if [[ $rematch2 ]]; then
   3091           local lhs1=$((10#0${lhs#[-+]})); [[ $lhs == -* ]] && ((lhs1=-lhs1))
   3092           local rhs1=$((10#0${rhs#[-+]})); [[ $rhs == -* ]] && ((rhs1=-rhs1))
   3093           lhs=$lhs1 rhs=$rhs1
   3094         fi
   3095         [[ $lhs != "$rhs" ]] && ((attr=ATTR_ERR))
   3096       fi
   3097 
   3098       ((_ble_syntax_attr[i++]=attr))
   3099       ((_ble_syntax_attr[i]=ctx,i+=len2,
   3100         _ble_syntax_attr[i]=ATTR_BRACE,i+=2,
   3101         _ble_syntax_attr[i]=ctx,i+=${#rematch1}-len2-2))
   3102       if [[ $rematch3 ]]; then
   3103         ((_ble_syntax_attr[i]=ATTR_BRACE,i+=2,
   3104           _ble_syntax_attr[i]=ctx,i+=${#rematch3}-2))
   3105       fi
   3106       ((_ble_syntax_attr[i++]=attr))
   3107     fi
   3108 
   3109     return 0
   3110   fi
   3111 
   3112   # それ以外
   3113   # Note: {aa},bb} は {"aa}","bb"} と解釈されるので、
   3114   #   ここでは終端の "}" の有無に拘らず nest-push する。
   3115   local ntype=
   3116   ((ctx==CTX_RDRF||ctx==CTX_RDRD||ctx==CTX_RDRD2)) && force_attr=$ctx
   3117   [[ $force_attr ]] && ntype="glob_attr=$force_attr"
   3118   ble/syntax/parse/nest-push "$CTX_BRACE1" "$ntype"
   3119   local len=$((${#str}-1))
   3120   ((_ble_syntax_attr[i++]=${force_attr:-ATTR_BRACE},
   3121     len&&(_ble_syntax_attr[i]=${force_attr:-ctx},i+=len)))
   3122 
   3123   return 0
   3124 }
   3125 
   3126 # 文脈 CTX_BRAX (brace expansion)
   3127 _ble_syntax_context_proc[CTX_BRACE1]=ble/syntax:bash/ctx-brace-expansion
   3128 _ble_syntax_context_proc[CTX_BRACE2]=ble/syntax:bash/ctx-brace-expansion
   3129 _ble_syntax_context_end[CTX_BRACE1]=ble/syntax:bash/ctx-brace-expansion.end
   3130 _ble_syntax_context_end[CTX_BRACE2]=ble/syntax:bash/ctx-brace-expansion.end
   3131 function ble/syntax:bash/ctx-brace-expansion {
   3132   if [[ $tail == '}'* ]] && ((ctx==CTX_BRACE2)); then
   3133     local force_attr=
   3134     local ntype; ble/syntax/parse/nest-type
   3135     [[ $ntype == glob_attr=* ]] && force_attr=$ATTR_ERR # ※${ntype#*=} ではなくエラー
   3136 
   3137     ((_ble_syntax_attr[i++]=${force_attr:-ATTR_BRACE}))
   3138     ble/syntax/parse/nest-pop
   3139     return 0
   3140   elif [[ $tail == ','* ]]; then
   3141     local force_attr=
   3142     local ntype; ble/syntax/parse/nest-type
   3143     [[ $ntype == glob_attr=* ]] && force_attr=${ntype#*=}
   3144 
   3145     ((_ble_syntax_attr[i++]=${force_attr:-ATTR_BRACE}))
   3146     ((ctx=CTX_BRACE2))
   3147     return 0
   3148   fi
   3149 
   3150   local chars=",${_ble_syntax_bash_chars[CTX_ARGI]//'~:'}"
   3151   ((ctx==CTX_BRACE2)) && chars="}$chars"
   3152   ble/syntax:bash/cclass/update/reorder chars
   3153   if ble/syntax:bash/check-plain-with-escape "[^$chars]+"; then
   3154     return 0
   3155   elif ble/syntax:bash/check-process-subst; then
   3156     return 0
   3157   elif ble/syntax:bash/check-quotes; then
   3158     return 0
   3159   elif ble/syntax:bash/check-dollar; then
   3160     return 0
   3161   elif ble/syntax:bash/check-glob; then
   3162     return 0
   3163   elif ble/syntax:bash/check-brace-expansion; then
   3164     return 0
   3165   elif ble/syntax:bash/starts-with-histchars; then
   3166     ble/syntax:bash/check-history-expansion ||
   3167       ((_ble_syntax_attr[i++]=ctx))
   3168     return 0
   3169   fi
   3170 
   3171   return 1
   3172 }
   3173 function ble/syntax:bash/ctx-brace-expansion.end {
   3174   if ((i==${#text})) || ble/syntax:bash/check-word-end/is-delimiter; then
   3175     ble/syntax/parse/nest-pop
   3176     ble/syntax/parse/check-end
   3177     return "$?"
   3178   fi
   3179 
   3180   return 0
   3181 }
   3182 
   3183 #------------------------------------------------------------------------------
   3184 # チルダ展開
   3185 
   3186 # ${_ble_syntax_bash_chars[CTX_ARGI]} により読み取りを行っている
   3187 # ctx-command ctx-values ctx-conditions ctx-redirect から呼び出される事を想定している。
   3188 
   3189 ## @fn ble/syntax:bash/check-tilde-expansion
   3190 ##   チルダ展開を検出して処理します。
   3191 ##   単語の始めの ~、または変数代入形式の単語の途中の :~ または
   3192 ##   パラメータ展開中の word の始めの ~ の処理を行います。
   3193 function ble/syntax:bash/check-tilde-expansion {
   3194   [[ $tail == ['~:']* ]] || return 1
   3195 
   3196   # @var rhs_enabled
   3197   #   変数代入形式の文脈の右辺でチルダ展開が有効かどうか。set -o posix では限ら
   3198   #   れた文脈のみで有効になる。
   3199   local rhs_enabled=
   3200   { ((ctx==CTX_VRHS||ctx==CTX_ARGVR||ctx==CTX_VALR||ctx==CTX_ARGER)) ||
   3201       ! ble/base/is-POSIXLY_CORRECT; } && rhs_enabled=1
   3202 
   3203   local tilde_enabled=$((i==wbegin||ctx==CTX_PWORD))
   3204   [[ $1 == rhs && $rhs_enabled ]] && tilde_enabled=1 # = の直後
   3205 
   3206   if [[ $tail == ':'* ]]; then
   3207     _ble_syntax_attr[i++]=$ctx
   3208 
   3209     # 変数代入の右辺、または、その一つ下の角括弧式のときチルダ展開が有効。
   3210     if [[ $rhs_enabled ]]; then
   3211       if ! ((tilde_enabled=_ble_syntax_bash_command_IsAssign[ctx])); then
   3212         if ((ctx==CTX_BRAX)); then
   3213           local nctx; ble/syntax/parse/nest-ctx
   3214           ((tilde_enabled=_ble_syntax_bash_command_IsAssign[nctx]))
   3215         fi
   3216       fi
   3217     fi
   3218 
   3219     local tail=${text:i}
   3220     [[ $tail == '~'* ]] || return 0
   3221   fi
   3222 
   3223   if ((tilde_enabled)); then
   3224     local chars="${_ble_syntax_bash_chars[CTX_ARGI]}/:"
   3225     # Note: pword の時は delimiters も除外したいので
   3226     #   _ble_syntax_bash_chars[CTX_PWORD] ではなく
   3227     #   _ble_syntax_bash_chars[CTX_ARGI] を修正して使う。
   3228     ((ctx==CTX_PWORD)) && chars=${chars/'{'/'{}'}
   3229 
   3230     ble/syntax:bash/cclass/update/reorder chars
   3231     local delimiters="$_ble_term_IFS;|&)<>"
   3232     local rex='^(~\+|~[^'$chars']*)([^'$delimiters'/:]?)'; [[ $tail =~ $rex ]]
   3233     local str=${BASH_REMATCH[1]}
   3234 
   3235     local path attr=$ctx
   3236     builtin eval "path=$str"
   3237     if [[ ! ${BASH_REMATCH[2]} && $path != "$str" ]]; then
   3238       ((attr=ATTR_TILDE))
   3239 
   3240       if ((ctx==CTX_BRAX)); then
   3241         # CTX_BRAX は単語先頭には来ないので、
   3242         # ここに来るのは [[ $tail == ':~'* ]] だった時のみのはず。
   3243         # このとき、各括弧式は : の直後でキャンセルする。
   3244         ble/util/assert 'ble/util/unlocal tail; [[ $tail == ":~"* ]]'
   3245         ble/syntax/parse/nest-pop
   3246       fi
   3247     else
   3248       # ~+ で始まってかつ有効なチルダ展開ではない時 ~ まで後退 (#D1424)
   3249       if [[ $str == '~+' ]]; then
   3250         ble/syntax/parse/set-lookahead 3
   3251         str='~'
   3252       fi
   3253     fi
   3254     ((_ble_syntax_attr[i]=attr,i+=${#str}))
   3255   else
   3256     ((_ble_syntax_attr[i]=ctx,i++)) # skip tilde
   3257     local chars=${_ble_syntax_bash_chars[CTX_ARGI]}
   3258     ble/syntax:bash/check-plain-with-escape "[^$chars]+" # 追加(失敗してもOK)
   3259   fi
   3260 
   3261   return 0
   3262 }
   3263 
   3264 #------------------------------------------------------------------------------
   3265 # 変数代入の形式の単語
   3266 #
   3267 #   実は通常の引数であっても変数代入の形式をしているものは微妙に扱いが異なる。
   3268 #   変数代入の形式の引数の右辺ではチルダ展開が有効である。
   3269 #
   3270 
   3271 # 変数代入形式の時に文脈を切り替える文脈値。実際に変数代入でなくても変数代入形
   3272 # 式によるチルダ展開が有効である時には区別する必要がある。
   3273 _ble_syntax_bash_command_CtxAssign[CTX_CMDI]=$CTX_VRHS
   3274 _ble_syntax_bash_command_CtxAssign[CTX_COARGI]=$CTX_VRHS
   3275 _ble_syntax_bash_command_CtxAssign[CTX_ARGVI]=$CTX_ARGVR
   3276 _ble_syntax_bash_command_CtxAssign[CTX_ARGEI]=$CTX_ARGER
   3277 _ble_syntax_bash_command_CtxAssign[CTX_ARGI]=$CTX_ARGQ
   3278 _ble_syntax_bash_command_CtxAssign[CTX_FARGI3]=$CTX_FARGQ3
   3279 _ble_syntax_bash_command_CtxAssign[CTX_CARGI1]=$CTX_CARGQ1
   3280 _ble_syntax_bash_command_CtxAssign[CTX_CPATI]=$CTX_CPATQ
   3281 _ble_syntax_bash_command_CtxAssign[CTX_VALI]=$CTX_VALQ
   3282 _ble_syntax_bash_command_CtxAssign[CTX_CONDI]=$CTX_CONDQ
   3283 
   3284 # 以下の配列はチルダ展開が有効な文脈を無効な文脈に切り替えるのに使っている。
   3285 _ble_syntax_bash_command_IsAssign[CTX_VRHS]=$CTX_CMDI
   3286 _ble_syntax_bash_command_IsAssign[CTX_ARGVR]=$CTX_ARGVI
   3287 _ble_syntax_bash_command_IsAssign[CTX_ARGER]=$CTX_ARGEI
   3288 _ble_syntax_bash_command_IsAssign[CTX_ARGQ]=$CTX_ARGI
   3289 _ble_syntax_bash_command_IsAssign[CTX_FARGQ3]=$CTX_FARGI3
   3290 _ble_syntax_bash_command_IsAssign[CTX_CARGQ1]=$CTX_CARGI1
   3291 _ble_syntax_bash_command_IsAssign[CTX_CPATQ]=$CTX_CPATI
   3292 _ble_syntax_bash_command_IsAssign[CTX_VALR]=$CTX_VALI
   3293 _ble_syntax_bash_command_IsAssign[CTX_VALQ]=$CTX_VALI
   3294 _ble_syntax_bash_command_IsAssign[CTX_CONDQ]=$CTX_CONDI
   3295 
   3296 ## @fn ble/syntax:bash/check-variable-assignment
   3297 ## @var[in] tail
   3298 function ble/syntax:bash/check-variable-assignment {
   3299   ((wbegin==i)) || return 1
   3300 
   3301   # 値リストにおける [0]=value の形式の単語は特別に扱う。
   3302   if ((ctx==CTX_VALI)) && [[ $tail == '['* ]]; then
   3303     ((ctx=CTX_VALR))
   3304     ble/syntax/parse/nest-push "$CTX_EXPR" 'd['
   3305     # → ble/syntax:bash/ctx-expr/.count-bracket で抜ける
   3306     ((_ble_syntax_attr[i++]=ctx))
   3307     return 0
   3308   fi
   3309 
   3310   [[ ${_ble_syntax_bash_command_CtxAssign[ctx]} ]] || return 1
   3311 
   3312   # パターン一致 (var= var+= arr[ のどれか)
   3313   local suffix='[=[]'
   3314   ((_ble_bash>=30100)) && suffix=$suffix'|\+=?'
   3315   local rex_assign="^([_a-zA-Z][_a-zA-Z0-9]*)($suffix)"
   3316   [[ $tail =~ $rex_assign ]] || return 1
   3317   local rematch=$BASH_REMATCH
   3318   local rematch1=${BASH_REMATCH[1]} # for bash-3.1 ${#arr[n]} bug
   3319   local rematch2=${BASH_REMATCH[2]} # for bash-3.1 ${#arr[n]} bug
   3320   if [[ $rematch2 == '+' ]]; then
   3321     # var+... 曖昧状態
   3322 
   3323     # Note: + の次の文字が = でない時に此処に来るので、
   3324     # + の次の文字まで先読みしたことになる。
   3325     ble/syntax/parse/set-lookahead "$((${#rematch}+1))"
   3326 
   3327     return 1
   3328   fi
   3329 
   3330   local variable_assign=
   3331   if ((ctx==CTX_CMDI||ctx==CTX_ARGVI||ctx==CTX_ARGEI&&${#rematch2})); then
   3332     # 変数代入のときは ctx は先に CTX_VRHS, CTX_ARGVR に変換する
   3333     local ret; ble/syntax/highlight/vartype "$rematch1" global
   3334     ((wtype=ATTR_VAR,
   3335       _ble_syntax_attr[i]=ret,
   3336       i+=${#rematch},
   3337       ${#rematch2}&&(_ble_syntax_attr[i-${#rematch2}]=CTX_EXPR),
   3338       variable_assign=1,
   3339       ctx=_ble_syntax_bash_command_CtxAssign[ctx]))
   3340   else
   3341     # 変数代入以外のときは = が現れて初めて CTX_ARGQ などに変換する
   3342     ((_ble_syntax_attr[i]=ctx,
   3343       i+=${#rematch}))
   3344   fi
   3345 
   3346   if [[ $rematch2 == '[' ]]; then
   3347     # arr[
   3348     if [[ $variable_assign ]]; then
   3349       i=$((i-1)) ble/syntax/parse/nest-push "$CTX_EXPR" 'a['
   3350       # → ble/syntax:bash/ctx-expr/.count-bracket で抜ける
   3351     else
   3352       ((i--))
   3353       tail=${text:i} ble/syntax:bash/check-glob assign
   3354       # → ble/syntax:bash/check-glob 内で nest-push "$CTX_BRAX" 'a[' し、
   3355       # → ble/syntax:bash/ctx-bracket-expression で抜けた後で = があれば文脈値設定
   3356     fi
   3357   elif [[ $rematch2 == *'=' ]]; then
   3358     if [[ $variable_assign && ${text:i} == '('* ]]; then
   3359       # var=( var+=(
   3360       # * nest-pop した直後は未だ CTX_VRHS, CTX_ARGVR の続きになっている。
   3361       #   例: a=(1 2)b=1 は a='(1 2)b=1' と解釈される。
   3362       #   従って ctx (nest-pop 時の文脈) はそのまま (CTX_VRHS, CTX_ARGVR) にする。
   3363 
   3364       ble/syntax:bash/ctx-values/enter
   3365       ((_ble_syntax_attr[i++]=ATTR_DEL))
   3366     else
   3367       # var=... var+=...
   3368       [[ $variable_assign ]] || ((ctx=_ble_syntax_bash_command_CtxAssign[ctx]))
   3369       if local tail=${text:i}; [[ $tail == '~'* ]]; then
   3370         ble/syntax:bash/check-tilde-expansion rhs
   3371       fi
   3372     fi
   3373   fi
   3374 
   3375   return 0
   3376 }
   3377 
   3378 #------------------------------------------------------------------------------
   3379 # 文脈: コマンドライン
   3380 
   3381 _ble_syntax_context_proc[CTX_ARGX]=ble/syntax:bash/ctx-command
   3382 _ble_syntax_context_proc[CTX_ARGX0]=ble/syntax:bash/ctx-command
   3383 _ble_syntax_context_proc[CTX_CMDX]=ble/syntax:bash/ctx-command
   3384 _ble_syntax_context_proc[CTX_CMDX0]=ble/syntax:bash/ctx-command
   3385 _ble_syntax_context_proc[CTX_CMDX1]=ble/syntax:bash/ctx-command
   3386 _ble_syntax_context_proc[CTX_CMDXT]=ble/syntax:bash/ctx-command
   3387 _ble_syntax_context_proc[CTX_CMDXC]=ble/syntax:bash/ctx-command
   3388 _ble_syntax_context_proc[CTX_CMDXE]=ble/syntax:bash/ctx-command
   3389 _ble_syntax_context_proc[CTX_CMDXD]=ble/syntax:bash/ctx-command
   3390 _ble_syntax_context_proc[CTX_CMDXD0]=ble/syntax:bash/ctx-command
   3391 _ble_syntax_context_proc[CTX_CMDXV]=ble/syntax:bash/ctx-command
   3392 _ble_syntax_context_proc[CTX_ARGI]=ble/syntax:bash/ctx-command
   3393 _ble_syntax_context_proc[CTX_ARGQ]=ble/syntax:bash/ctx-command
   3394 _ble_syntax_context_proc[CTX_CMDI]=ble/syntax:bash/ctx-command
   3395 _ble_syntax_context_proc[CTX_VRHS]=ble/syntax:bash/ctx-command
   3396 _ble_syntax_context_proc[CTX_ARGVR]=ble/syntax:bash/ctx-command
   3397 _ble_syntax_context_proc[CTX_ARGER]=ble/syntax:bash/ctx-command
   3398 _ble_syntax_context_end[CTX_CMDI]=ble/syntax:bash/ctx-command/check-word-end
   3399 _ble_syntax_context_end[CTX_ARGI]=ble/syntax:bash/ctx-command/check-word-end
   3400 _ble_syntax_context_end[CTX_ARGQ]=ble/syntax:bash/ctx-command/check-word-end
   3401 _ble_syntax_context_end[CTX_VRHS]=ble/syntax:bash/ctx-command/check-word-end
   3402 _ble_syntax_context_end[CTX_ARGVR]=ble/syntax:bash/ctx-command/check-word-end
   3403 _ble_syntax_context_end[CTX_ARGER]=ble/syntax:bash/ctx-command/check-word-end
   3404 
   3405 # declare var=value / eval var=value
   3406 _ble_syntax_context_proc[CTX_ARGVX]=ble/syntax:bash/ctx-command
   3407 _ble_syntax_context_proc[CTX_ARGVI]=ble/syntax:bash/ctx-command
   3408 _ble_syntax_context_end[CTX_ARGVI]=ble/syntax:bash/ctx-command/check-word-end
   3409 _ble_syntax_context_proc[CTX_ARGEX]=ble/syntax:bash/ctx-command
   3410 _ble_syntax_context_proc[CTX_ARGEI]=ble/syntax:bash/ctx-command
   3411 _ble_syntax_context_end[CTX_ARGEI]=ble/syntax:bash/ctx-command/check-word-end
   3412 
   3413 # for var in ... / case arg in
   3414 _ble_syntax_context_proc[CTX_SARGX1]=ble/syntax:bash/ctx-command-compound-expect
   3415 _ble_syntax_context_proc[CTX_FARGX1]=ble/syntax:bash/ctx-command-compound-expect
   3416 _ble_syntax_context_proc[CTX_FARGX2]=ble/syntax:bash/ctx-command-compound-expect
   3417 _ble_syntax_context_proc[CTX_FARGX3]=ble/syntax:bash/ctx-command
   3418 _ble_syntax_context_proc[CTX_FARGI1]=ble/syntax:bash/ctx-command
   3419 _ble_syntax_context_proc[CTX_FARGI2]=ble/syntax:bash/ctx-command
   3420 _ble_syntax_context_proc[CTX_FARGI3]=ble/syntax:bash/ctx-command
   3421 _ble_syntax_context_proc[CTX_FARGQ3]=ble/syntax:bash/ctx-command
   3422 _ble_syntax_context_end[CTX_FARGI1]=ble/syntax:bash/ctx-command/check-word-end
   3423 _ble_syntax_context_end[CTX_FARGI2]=ble/syntax:bash/ctx-command/check-word-end
   3424 _ble_syntax_context_end[CTX_FARGI3]=ble/syntax:bash/ctx-command/check-word-end
   3425 _ble_syntax_context_end[CTX_FARGQ3]=ble/syntax:bash/ctx-command/check-word-end
   3426 _ble_syntax_context_proc[CTX_CARGX1]=ble/syntax:bash/ctx-command-compound-expect
   3427 _ble_syntax_context_proc[CTX_CARGX2]=ble/syntax:bash/ctx-command-compound-expect
   3428 _ble_syntax_context_proc[CTX_CPATX]=ble/syntax:bash/ctx-command-case-pattern-expect
   3429 _ble_syntax_context_proc[CTX_CPATX0]=ble/syntax:bash/ctx-command-case-pattern-expect
   3430 _ble_syntax_context_proc[CTX_CARGI1]=ble/syntax:bash/ctx-command
   3431 _ble_syntax_context_proc[CTX_CARGQ1]=ble/syntax:bash/ctx-command
   3432 _ble_syntax_context_proc[CTX_CARGI2]=ble/syntax:bash/ctx-command
   3433 _ble_syntax_context_proc[CTX_CPATI]=ble/syntax:bash/ctx-command
   3434 _ble_syntax_context_proc[CTX_CPATQ]=ble/syntax:bash/ctx-command
   3435 _ble_syntax_context_end[CTX_CARGI1]=ble/syntax:bash/ctx-command/check-word-end
   3436 _ble_syntax_context_end[CTX_CARGQ1]=ble/syntax:bash/ctx-command/check-word-end
   3437 _ble_syntax_context_end[CTX_CARGI2]=ble/syntax:bash/ctx-command/check-word-end
   3438 _ble_syntax_context_end[CTX_CPATI]=ble/syntax:bash/ctx-command/check-word-end
   3439 _ble_syntax_context_end[CTX_CPATQ]=ble/syntax:bash/ctx-command/check-word-end
   3440 _ble_syntax_context_proc[CTX_TARGX1]=ble/syntax:bash/ctx-command-time-expect
   3441 _ble_syntax_context_proc[CTX_TARGX2]=ble/syntax:bash/ctx-command-time-expect
   3442 _ble_syntax_context_proc[CTX_TARGI1]=ble/syntax:bash/ctx-command
   3443 _ble_syntax_context_proc[CTX_TARGI2]=ble/syntax:bash/ctx-command
   3444 _ble_syntax_context_end[CTX_TARGI1]=ble/syntax:bash/ctx-command/check-word-end
   3445 _ble_syntax_context_end[CTX_TARGI2]=ble/syntax:bash/ctx-command/check-word-end
   3446 _ble_syntax_context_proc[CTX_COARGX]=ble/syntax:bash/ctx-command-compound-expect
   3447 _ble_syntax_context_end[CTX_COARGI]=ble/syntax:bash/ctx-coproc/check-word-end
   3448 
   3449 ## @fn ble/syntax:bash/starts-with-delimiter-or-redirect
   3450 ##
   3451 ##   空白類、コマンド区切り文字、またはリダイレクトかどうかを判定する。
   3452 ##   単語開始における 1>2 や {fd}>2 もリダイレクトと判定する。
   3453 ##
   3454 ##   Note: ここで "1>2" や "{fd}>" に一致しなかったとしても、通常の文脈で
   3455 ##   "{fd}" や "1" 等の列が一気に読み取られる限り先読みの問題は発生しないはず。
   3456 ##   ブレース展開の解析は "{fd}" が一気に読み取られる様に注意深く実装する。
   3457 ##
   3458 function ble/syntax:bash/starts-with-delimiter-or-redirect {
   3459   local delimiters=$_ble_syntax_bash_RexDelimiter
   3460   local redirect=$_ble_syntax_bash_RexRedirect
   3461   [[ ( $tail =~ ^$delimiters || $wbegin -lt 0 && $tail =~ ^$redirect || $wbegin -lt 0 && $tail == $'\\\n'* ) && $tail != ['<>']'('* ]]
   3462 }
   3463 function ble/syntax:bash/starts-with-delimiter {
   3464   [[ $tail == ["$_ble_term_IFS;|&<>()"]* && $tail != ['<>']'('* ]]
   3465 }
   3466 function ble/syntax:bash/check-word-end/is-delimiter {
   3467   local tail=${text:i}
   3468   if [[ $tail == [!"$_ble_term_IFS;|&<>()"]* ]]; then
   3469     return 1
   3470   elif [[ $tail == ['<>']* ]]; then
   3471     ble/syntax/parse/set-lookahead 2
   3472     [[ $tail == ['<>']'('* ]] && return 1
   3473   fi
   3474   return 0
   3475 }
   3476 
   3477 ## @fn ble/syntax:bash/check-here-document-from spaces
   3478 ##   @param[in] spaces
   3479 function ble/syntax:bash/check-here-document-from {
   3480   local spaces=$1
   3481   [[ $nparam && $spaces == *$'\n'* ]] || return 1
   3482   local rex="$_ble_term_FS@([RI][QH][^$_ble_term_FS]*)(.*$)" && [[ $nparam =~ $rex ]] || return 1
   3483 
   3484   # ヒアドキュメントの開始
   3485   local rematch1=${BASH_REMATCH[1]}
   3486   local rematch2=${BASH_REMATCH[2]}
   3487   local padding=${spaces%%$'\n'*}
   3488   ((_ble_syntax_attr[i]=ctx,i+=${#padding}))
   3489   nparam=${nparam::${#nparam}-${#BASH_REMATCH}}${nparam:${#nparam}-${#rematch2}}
   3490   ble/syntax/parse/nest-push "$CTX_HERE0"
   3491   ((i++))
   3492   nparam=$rematch1
   3493   return 0
   3494 }
   3495 
   3496 function ble/syntax:bash/ctx-coproc/.is-next-compound {
   3497   # @var ahead
   3498   #   現在位置 p の次の文字を参照したかどうか
   3499   local p=$i ahead=1 tail=${text:i}
   3500 
   3501   # 空白類は無視
   3502   if local rex=$'^[ \t]+'; [[ $tail =~ $rex ]]; then
   3503     ((p+=${#BASH_REMATCH}))
   3504     ahead=1 tail=${text:p}
   3505   fi
   3506 
   3507   local is_compound=
   3508   if [[ $tail == '('* ]]; then
   3509     is_compound=1
   3510   elif rex='^[a-z]+|^\[\[?|^[{}!]'; [[ $tail =~ $rex ]]; then
   3511     local rematch=$BASH_REMATCH
   3512 
   3513     ((p+=${#rematch}))
   3514     [[ $rematch == ['{}!'] || $rematch == '[[' ]]; ahead=$?
   3515 
   3516     rex='^(\[\[|for|select|case|if|while|until|fi|done|esac|then|elif|else|do|[{}!]|coproc|function)$'
   3517     if  [[ $rematch =~ $rex ]]; then
   3518       if rex='^[;|&()'$_ble_term_IFS']|^$|^[<>]\(?' ahead=1; [[ ${text:p} =~ $rex ]]; then
   3519         local rematch=$BASH_REMATCH
   3520         ((p+=${#rematch}))
   3521         [[ $rematch && $rematch != ['<>'] ]]; ahead=$?
   3522         [[ $rematch != ['<>']'(' ]] && is_compound=1
   3523       fi
   3524     fi
   3525   fi
   3526 
   3527   # 先読みの設定
   3528   ble/syntax/parse/set-lookahead "$((p+ahead-i))"
   3529   [[ $is_compound ]]
   3530 }
   3531 function ble/syntax:bash/ctx-coproc/check-word-end {
   3532   ble/util/assert '((ctx==CTX_COARGI))'
   3533 
   3534   # 単語の中にいない時は抜ける
   3535   ((wbegin<0)) && return 1
   3536 
   3537   # 未だ続きがある場合は抜ける
   3538   ble/syntax:bash/check-word-end/is-delimiter || return 1
   3539 
   3540   local wbeg=$wbegin wlen=$((i-wbegin)) wend=$i
   3541   local word=${text:wbegin:wlen}
   3542   local wt=$wtype
   3543 
   3544   if local rex='^[_a-zA-Z][_a-zA-Z0-9]*$'; [[ $word =~ $rex ]]; then
   3545     if ble/syntax:bash/ctx-coproc/.is-next-compound; then
   3546       # 構文: 変数名 複合コマンド
   3547       local attr=$ATTR_VAR
   3548 
   3549       # alias だった場合は解釈が変わり得る。alias が厳密に変数名に展開された時
   3550       # にのみ変数名と判定する。複合コマンド開始に展開された場合は通常処理にフォー
   3551       # ルバック。それ以外はエラー。
   3552       if ble/alias#active "$word"; then
   3553         attr=
   3554         local ret; ble/alias#expand "$word"
   3555         case $word in
   3556         # 通常処理にフォールバックする
   3557         ('if'|'while'|'until'|'for'|'select'|'case'|'{'|'[[') ;;
   3558         # 通常処理(構文エラー)
   3559         ('fi'|'done'|'esac'|'then'|'elif'|'else'|'do'|'}'|'!'|'coproc'|'function'|'in') ;;
   3560         (*)
   3561           if ble/string#match "$word" '^[_a-zA-Z][_a-zA-Z0-9]*$'; then
   3562             # 変数名に展開される場合はOK
   3563             attr=$ATTR_CMD_ALIAS
   3564           else
   3565             attr=$ATTR_ERR
   3566           fi
   3567         esac
   3568       fi
   3569 
   3570       if [[ $attr ]]; then
   3571         # Note: [_a-zA-Z0-9]+ は一回の読み取りの筈なので、
   3572         #   此処で遡って代入しても問題ない筈。
   3573         _ble_syntax_attr[wbegin]=$attr
   3574         ((ctx=CTX_CMDXC,wtype=CTX_ARGVI))
   3575         ble/syntax/parse/word-pop
   3576         return 0
   3577       fi
   3578     fi
   3579   fi
   3580 
   3581   ((ctx=CTX_CMDI,wtype=CTX_CMDX))
   3582   ble/syntax:bash/ctx-command/check-word-end
   3583 }
   3584 
   3585 ## @arr _ble_syntax_bash_command_EndCtx
   3586 ##   単語が終了した後の次の文脈値を設定する。
   3587 ##   check-word-end で用いる。
   3588 ##
   3589 ##   Note #1: time -p -- cmd は bash-4.2 以降
   3590 ##     bash-4.2 未満では -p の直後にすぐコマンドが来なければならない。
   3591 ##
   3592 _ble_syntax_bash_command_EndCtx=()
   3593 _ble_syntax_bash_command_EndCtx[CTX_ARGI]=$CTX_ARGX
   3594 _ble_syntax_bash_command_EndCtx[CTX_ARGQ]=$CTX_ARGX
   3595 _ble_syntax_bash_command_EndCtx[CTX_ARGVI]=$CTX_ARGVX
   3596 _ble_syntax_bash_command_EndCtx[CTX_ARGVR]=$CTX_ARGVX
   3597 _ble_syntax_bash_command_EndCtx[CTX_ARGEI]=$CTX_ARGEX
   3598 _ble_syntax_bash_command_EndCtx[CTX_ARGER]=$CTX_ARGEX
   3599 _ble_syntax_bash_command_EndCtx[CTX_VRHS]=$CTX_CMDXV
   3600 _ble_syntax_bash_command_EndCtx[CTX_FARGI1]=$CTX_FARGX2
   3601 _ble_syntax_bash_command_EndCtx[CTX_FARGI2]=$CTX_FARGX3
   3602 _ble_syntax_bash_command_EndCtx[CTX_FARGI3]=$CTX_FARGX3
   3603 _ble_syntax_bash_command_EndCtx[CTX_FARGQ3]=$CTX_FARGX3
   3604 _ble_syntax_bash_command_EndCtx[CTX_CARGI1]=$CTX_CARGX2
   3605 _ble_syntax_bash_command_EndCtx[CTX_CARGQ1]=$CTX_CARGX2
   3606 _ble_syntax_bash_command_EndCtx[CTX_CARGI2]=$CTX_CASE
   3607 _ble_syntax_bash_command_EndCtx[CTX_CPATI]=$CTX_CPATX0
   3608 _ble_syntax_bash_command_EndCtx[CTX_CPATQ]=$CTX_CPATX0
   3609 _ble_syntax_bash_command_EndCtx[CTX_TARGI1]=$((_ble_bash>=40200?CTX_TARGX2:CTX_CMDXT)) #1
   3610 _ble_syntax_bash_command_EndCtx[CTX_TARGI2]=$CTX_CMDXT
   3611 
   3612 ## @arr _ble_syntax_bash_command_EndWtype[wtype]
   3613 ##   実際に tree 登録する wtype を指定します。
   3614 ##   ※解析中の wtype には解析開始時の wtype が入っていることに注意する。
   3615 _ble_syntax_bash_command_EndWtype[CTX_ARGX]=$CTX_ARGI
   3616 _ble_syntax_bash_command_EndWtype[CTX_ARGX0]=$CTX_ARGI
   3617 _ble_syntax_bash_command_EndWtype[CTX_ARGVX]=$CTX_ARGVI
   3618 _ble_syntax_bash_command_EndWtype[CTX_ARGEX]=$CTX_ARGEI
   3619 _ble_syntax_bash_command_EndWtype[CTX_CMDX]=$CTX_CMDI
   3620 _ble_syntax_bash_command_EndWtype[CTX_CMDX0]=$CTX_CMDX0
   3621 _ble_syntax_bash_command_EndWtype[CTX_CMDX1]=$CTX_CMDI
   3622 _ble_syntax_bash_command_EndWtype[CTX_CMDXT]=$CTX_CMDI
   3623 _ble_syntax_bash_command_EndWtype[CTX_CMDXC]=$CTX_CMDI
   3624 _ble_syntax_bash_command_EndWtype[CTX_CMDXE]=$CTX_CMDI
   3625 _ble_syntax_bash_command_EndWtype[CTX_CMDXD]=$CTX_CMDI
   3626 _ble_syntax_bash_command_EndWtype[CTX_CMDXD0]=$CTX_CMDI
   3627 _ble_syntax_bash_command_EndWtype[CTX_CMDXV]=$CTX_CMDI
   3628 _ble_syntax_bash_command_EndWtype[CTX_FARGX1]=$CTX_FARGI1 # 変数名
   3629 _ble_syntax_bash_command_EndWtype[CTX_SARGX1]=$CTX_ARGI
   3630 _ble_syntax_bash_command_EndWtype[CTX_FARGX2]=$CTX_FARGI2 # in
   3631 _ble_syntax_bash_command_EndWtype[CTX_FARGX3]=$CTX_ARGI # in
   3632 _ble_syntax_bash_command_EndWtype[CTX_CARGX1]=$CTX_ARGI
   3633 _ble_syntax_bash_command_EndWtype[CTX_CARGX2]=$CTX_CARGI2 # in
   3634 _ble_syntax_bash_command_EndWtype[CTX_CPATX]=$CTX_CPATI
   3635 _ble_syntax_bash_command_EndWtype[CTX_CPATX0]=$CTX_CPATI
   3636 _ble_syntax_bash_command_EndWtype[CTX_TARGX1]=$CTX_ARGI # -p
   3637 _ble_syntax_bash_command_EndWtype[CTX_TARGX2]=$CTX_ARGI # --
   3638 
   3639 ## @arr _ble_syntax_bash_command_Expect
   3640 ##
   3641 ##   許容するコマンドの種類を表す正規表現を設定する。
   3642 ##   check-word-end で用いる。
   3643 ##   配列 _ble_syntax_bash_command_bwtype の設定と対応している必要がある。
   3644 ##
   3645 ##   * 前提: 予約語のみに一致する
   3646 ##     この配列が設定されている文脈値については、
   3647 ##     既定でコマンドの属性は ATTR_ERR にする。
   3648 ##     許容するコマンドは何れも予約語なので、
   3649 ##     許容された暁には自動的に ATTR_KEYWORD で上書きされるのでOK
   3650 ##
   3651 ##     予約語以外に一致する時には、
   3652 ##     明示的に属性値 ATTR_ERR をキャンセルする必要がある。
   3653 ##
   3654 _ble_syntax_bash_command_Expect=()
   3655 _ble_syntax_bash_command_Expect[CTX_CMDXC]='^(\(|\{|\(\(|\[\[|for|select|case|if|while|until)$'
   3656 _ble_syntax_bash_command_Expect[CTX_CMDXE]='^(\}|fi|done|esac|then|elif|else|do)$'
   3657 _ble_syntax_bash_command_Expect[CTX_CMDXD]='^(\{|do)$'
   3658 _ble_syntax_bash_command_Expect[CTX_CMDXD0]='^(\{|do)$'
   3659 
   3660 ## @fn ble/syntax:bash/ctx-command/check-word-end
   3661 ##   @var[in,out] ctx
   3662 ##   @var[in,out] wbegin
   3663 ##   @var[in,out] 他
   3664 function ble/syntax:bash/ctx-command/check-word-end {
   3665   # 単語の中にいない時は抜ける
   3666   ((wbegin<0)) && return 1
   3667 
   3668   # 未だ続きがある場合は抜ける
   3669   ble/syntax:bash/check-word-end/is-delimiter || return 1
   3670 
   3671   local wbeg=$wbegin wlen=$((i-wbegin)) wend=$i
   3672   local word=${text:wbegin:wlen}
   3673   local stat_wt=$wtype # 単語解析中の wtype
   3674 
   3675   [[ ${_ble_syntax_bash_command_EndWtype[stat_wt]} ]] &&
   3676     wtype=${_ble_syntax_bash_command_EndWtype[stat_wt]}
   3677   local rex_expect_command=${_ble_syntax_bash_command_Expect[stat_wt]}
   3678   if [[ $rex_expect_command ]]; then
   3679     # 特定のコマンドのみを受け付ける文脈
   3680     [[ $word =~ $rex_expect_command ]] || ((wtype=CTX_CMDX0))
   3681   elif ((stat_wt==CTX_ARGX0||stat_wt==CTX_CPATX0)); then
   3682     ((wtype=ATTR_ERR))
   3683   elif ((stat_wt==CTX_CMDX1)); then
   3684     local rex='^(then|elif|else|do|\}|done|fi|esac)$'
   3685     [[ $word =~ $rex ]] && ((wtype=CTX_CMDX0))
   3686   fi
   3687   local tree_wt=$wtype # 実際に単語として登録された wtype
   3688   ble/syntax/parse/word-pop
   3689 
   3690   if ((ctx==CTX_CMDI)); then
   3691     local ret
   3692     ble/alias#expand "$word"; local word_expanded=$ret
   3693 
   3694     # キーワードの処理
   3695     if ((tree_wt==CTX_CMDX0)); then
   3696       ((_ble_syntax_attr[wbeg]=ATTR_ERR,ctx=CTX_ARGX))
   3697       return 0
   3698     elif ((stat_wt!=CTX_CMDXV)); then # Note: 変数代入の直後はキーワードは処理しない
   3699       local processed=
   3700       case $word_expanded in
   3701       ('[[')
   3702         # 条件コマンド開始
   3703         ble/syntax/parse/touch-updated-attr "$wbeg"
   3704         ((_ble_syntax_attr[wbeg]=ATTR_DEL,
   3705           ctx=_ble_bash>=50200?CTX_CMDXE:CTX_ARGX0))
   3706 
   3707         ble/syntax/parse/word-cancel # 単語 "[[" (とその内部のノード全て) を削除
   3708         if [[ $word == '[[' ]]; then
   3709           # "[[" は一度角括弧式として読み取られるので、その情報を削除する。
   3710           _ble_syntax_attr[wbeg+1]= # 角括弧式として着色されているのを消去
   3711         fi
   3712 
   3713         i=$wbeg ble/syntax/parse/nest-push "$CTX_CONDX"
   3714 
   3715         # workaround: word "[[" を nest 内部に設置し直す
   3716         i=$wbeg ble/syntax/parse/word-push "$CTX_CMDI" "$wbeg"
   3717         ble/syntax/parse/word-pop
   3718         return 0 ;;
   3719       ('time')               ((ctx=CTX_TARGX1)); processed=keyword ;;
   3720       ('!')                  ((ctx=CTX_CMDXT)) ; processed=keyword ;;
   3721       ('if'|'while'|'until') ((ctx=CTX_CMDX1)) ; processed=begin ;;
   3722       ('for')                ((ctx=CTX_FARGX1)); processed=begin ;;
   3723       ('select')             ((ctx=CTX_SARGX1)); processed=begin ;;
   3724       ('case')               ((ctx=CTX_CARGX1)); processed=begin ;;
   3725       ('{')
   3726         # 単語属性の再設定
   3727         ble/syntax/parse/touch-updated-attr "$wbeg"
   3728         if ((stat_wt==CTX_CMDXD||stat_wt==CTX_CMDXD0)); then
   3729           attr=$ATTR_KEYWORD_MID # "for ...; {" などの時
   3730         else
   3731           attr=$ATTR_KEYWORD_BEGIN
   3732         fi
   3733         ((_ble_syntax_attr[wbeg]=attr))
   3734 
   3735         # 単語削除&入れ子&単語再設置
   3736         ble/syntax/parse/word-cancel
   3737         ((ctx=CTX_CMDXE))
   3738         i=$wbeg ble/syntax/parse/nest-push "$CTX_CMDX1" 'cmd_brace'
   3739         i=$wbeg ble/syntax/parse/word-push "$CTX_CMDI" "$wbeg"
   3740         ble/syntax/parse/word-pop
   3741         return 0 ;;
   3742       ('then'|'elif'|'else'|'do') ((ctx=CTX_CMDX1)); processed=middle ;;
   3743       ('done'|'fi'|'esac')        ((ctx=CTX_CMDXE)); processed=end ;;
   3744       ('}')
   3745         if local ntype; ble/syntax/parse/nest-type; [[ $ntype == 'cmd_brace' ]]; then
   3746           ble/syntax/parse/touch-updated-attr "$wbeg"
   3747           ((_ble_syntax_attr[wbeg]=ATTR_KEYWORD_END))
   3748           ble/syntax/parse/nest-pop
   3749         else
   3750           ble/syntax/parse/touch-updated-attr "$wbeg"
   3751           ((_ble_syntax_attr[wbeg]=ATTR_ERR))
   3752           ((ctx=CTX_CMDXE))
   3753         fi
   3754         return 0 ;;
   3755       ('coproc')
   3756         if ((_ble_bash>=40000)); then
   3757           if ble/syntax:bash/ctx-coproc/.is-next-compound; then
   3758             ((ctx=CTX_CMDXC))
   3759           else
   3760             ((ctx=CTX_COARGX))
   3761           fi
   3762           processed=keyword
   3763         fi ;;
   3764       ('function')
   3765         ((ctx=CTX_ARGX))
   3766         local isfuncsymx=$'\t\n'' "$&'\''();<>\`|' rex_space=$'[ \t]' rex
   3767         if rex="^$rex_space+" && [[ ${text:i} =~ $rex ]]; then
   3768           ((_ble_syntax_attr[i]=CTX_ARGX,i+=${#BASH_REMATCH},ctx=CTX_ARGX))
   3769           if rex="^([^#$isfuncsymx][^$isfuncsymx]*)($rex_space*)(\(\(|\($rex_space*\)?)?" && [[ ${text:i} =~ $rex ]]; then
   3770             local rematch1=${BASH_REMATCH[1]}
   3771             local rematch2=${BASH_REMATCH[2]}
   3772             local rematch3=${BASH_REMATCH[3]}
   3773             ((_ble_syntax_attr[i]=ATTR_FUNCDEF,i+=${#rematch1},
   3774               ${#rematch2}&&(_ble_syntax_attr[i]=CTX_CMDX1,i+=${#rematch2})))
   3775 
   3776             if [[ $rematch3 == '('*')' ]]; then
   3777               ((_ble_syntax_attr[i]=ATTR_DEL,i+=${#rematch3},ctx=CTX_CMDXC))
   3778             elif ((_ble_bash>=40200)) && [[ $rematch3 == '((' ]]; then
   3779               ble/syntax/parse/set-lookahead 2
   3780               ((ctx=CTX_CMDXC))
   3781             elif [[ $rematch3 == '('* ]]; then
   3782               ((_ble_syntax_attr[i]=ATTR_ERR,ctx=CTX_ARGX0))
   3783               ble/syntax/parse/nest-push "$CTX_CMDX1" '('
   3784               ((${#rematch3}>=2&&(_ble_syntax_attr[i+1]=CTX_CMDX1),i+=${#rematch3}))
   3785             else
   3786               ((ctx=CTX_CMDXC))
   3787             fi
   3788             processed=keyword
   3789           fi
   3790         fi
   3791         [[ $processed ]] || ((_ble_syntax_attr[i-1]=ATTR_ERR)) ;;
   3792       esac
   3793 
   3794       if [[ $processed ]]; then
   3795         local attr=
   3796         case $processed in
   3797         (keyword) attr=$ATTR_KEYWORD ;;
   3798         (begin)   attr=$ATTR_KEYWORD_BEGIN ;;
   3799         (end)     attr=$ATTR_KEYWORD_END ;;
   3800         (middle)  attr=$ATTR_KEYWORD_MID ;;
   3801         esac
   3802         if [[ $attr ]]; then
   3803           ble/syntax/parse/touch-updated-attr "$wbeg"
   3804           ((_ble_syntax_attr[wbeg]=attr))
   3805         fi
   3806 
   3807         return 0
   3808       fi
   3809     fi
   3810 
   3811     # 関数定義である可能性を考え stat を置かず読み取る
   3812     ((ctx=CTX_ARGX))
   3813     if local rex='^([ 	]*)(\([ 	]*\)?)?'; [[ ${text:i} =~ $rex && $BASH_REMATCH ]]; then
   3814 
   3815       # for bash-3.1 ${#arr[n]} bug
   3816       local rematch1=${BASH_REMATCH[1]}
   3817       local rematch2=${BASH_REMATCH[2]}
   3818 
   3819       if [[ $rematch2 == '('*')' ]]; then
   3820         # case: /hoge ( *)/ 関数定義 (単語の種類 wtype を変更)
   3821         #   上方の ble/syntax/parse/word-pop で設定した値を書き換え。
   3822         # Note: 単語の種類が CTX_CMDX0 の時はそのままにする。
   3823         ((tree_wt==CTX_CMDX0)) ||
   3824           _ble_syntax_tree[i-1]="$ATTR_FUNCDEF ${_ble_syntax_tree[i-1]#* }"
   3825 
   3826         ((_ble_syntax_attr[i]=CTX_CMDX1,i+=${#rematch1},
   3827           _ble_syntax_attr[i]=ATTR_DEL,i+=${#rematch2},
   3828           ctx=CTX_CMDXC))
   3829       elif [[ $rematch2 == '('* ]]; then
   3830         # case: /hoge \( */ 括弧が閉じていない場合:
   3831         #   仕方がないので extglob 括弧と思って取り敢えず解析する
   3832         ((_ble_syntax_attr[i]=CTX_ARGX0,i+=${#rematch1},
   3833           _ble_syntax_attr[i]=ATTR_ERR,
   3834           ctx=CTX_ARGX0))
   3835         ble/syntax/parse/nest-push "$CTX_PATN"
   3836         ((${#rematch2}>=2&&(_ble_syntax_attr[i+1]=CTX_CMDXC),
   3837           i+=${#rematch2}))
   3838       else
   3839         # case: /hoge */ 恐らくコマンド
   3840         ((_ble_syntax_attr[i]=CTX_ARGX,i+=${#rematch1}))
   3841       fi
   3842     fi
   3843 
   3844     # 引数の取り扱いが特別な builtin
   3845     case $word_expanded in
   3846     ('declare'|'readonly'|'typeset'|'local'|'export'|'alias')
   3847       ((ctx=CTX_ARGVX)) ;;
   3848     ('eval')
   3849       ((ctx=CTX_ARGEX)) ;;
   3850     esac
   3851 
   3852     return 0
   3853   fi
   3854 
   3855   if ((ctx==CTX_FARGI2)); then
   3856     # for name do ...; done
   3857     if [[ $word == do ]]; then
   3858       ((ctx=CTX_CMDX1))
   3859       return 0
   3860     fi
   3861   fi
   3862 
   3863   if ((ctx==CTX_FARGI2||ctx==CTX_CARGI2)); then
   3864     # for name in ... / case value in
   3865     if [[ $word != in ]];  then
   3866       ble/syntax/parse/touch-updated-attr "$wbeg"
   3867       ((_ble_syntax_attr[wbeg]=ATTR_ERR))
   3868     fi
   3869   fi
   3870 
   3871   if ((_ble_syntax_bash_command_EndCtx[ctx])); then
   3872     ((ctx=_ble_syntax_bash_command_EndCtx[ctx]))
   3873   fi
   3874 
   3875   return 0
   3876 }
   3877 
   3878 ## @arr _ble_syntax_bash_command_Opt
   3879 ##   その場でコマンドが終わっても良いかどうかを設定する。
   3880 ##   .check-delimiter-or-redirect で用いる。
   3881 _ble_syntax_bash_command_Opt=()
   3882 _ble_syntax_bash_command_Opt[CTX_ARGX]=1
   3883 _ble_syntax_bash_command_Opt[CTX_ARGX0]=1
   3884 _ble_syntax_bash_command_Opt[CTX_ARGVX]=1
   3885 _ble_syntax_bash_command_Opt[CTX_ARGEX]=1
   3886 _ble_syntax_bash_command_Opt[CTX_CMDX0]=1
   3887 _ble_syntax_bash_command_Opt[CTX_CMDXV]=1
   3888 _ble_syntax_bash_command_Opt[CTX_CMDXE]=1
   3889 _ble_syntax_bash_command_Opt[CTX_CMDXD0]=1
   3890 
   3891 _ble_syntax_bash_is_command_form_for=
   3892 
   3893 function ble/syntax:bash/ctx-command/.check-delimiter-or-redirect {
   3894   if [[ $tail =~ ^$_ble_syntax_bash_RexIFSs || $wbegin -lt 0 && $tail == $'\\\n'* ]]; then
   3895     # 空白 or \ + 改行
   3896 
   3897     local spaces=$BASH_REMATCH
   3898     if [[ $tail == $'\\\n'* ]]; then
   3899       # \ + 改行は単純に無視
   3900       spaces=$'\\\n'
   3901     elif [[ $spaces == *$'\n'* ]]; then
   3902       # 改行がある場合: ヒアドキュメントの確認 / 改行による文脈更新
   3903       ble/syntax:bash/check-here-document-from "$spaces" && return 0
   3904       if ((ctx==CTX_ARGX||ctx==CTX_ARGX0||ctx==CTX_ARGVX||ctx==CTX_ARGEX||ctx==CTX_CMDX0||ctx==CTX_CMDXV||ctx==CTX_CMDXT||ctx==CTX_CMDXE)); then
   3905         ((ctx=CTX_CMDX))
   3906       elif ((ctx==CTX_FARGX2||ctx==CTX_FARGX3||ctx==CTX_CMDXD0)); then
   3907         ((ctx=CTX_CMDXD))
   3908       fi
   3909     fi
   3910 
   3911     # ctx はそのままで素通り
   3912     ((_ble_syntax_attr[i]=ctx,i+=${#spaces}))
   3913     return 0
   3914 
   3915   elif [[ $tail =~ ^$_ble_syntax_bash_RexRedirect ]]; then
   3916     # リダイレクト (& 単体の解釈より優先する)
   3917 
   3918     # for bash-3.1 ${#arr[n]} bug ... 一旦 rematch1 に入れてから ${#rematch1} で文字数を得る。
   3919     local len=${#BASH_REMATCH}
   3920     local rematch1=${BASH_REMATCH[1]}
   3921     local rematch3=${BASH_REMATCH[3]}
   3922     ((_ble_syntax_attr[i]=ATTR_DEL,
   3923       ${#rematch1}<len&&(_ble_syntax_attr[i+${#rematch1}]=CTX_ARGX)))
   3924     if ((ctx==CTX_CMDX||ctx==CTX_CMDX1||ctx==CTX_CMDXT)); then
   3925       ((ctx=CTX_CMDXV))
   3926     elif ((ctx==CTX_CMDXC||ctx==CTX_CMDXD||ctx==CTX_CMDXD0)); then
   3927       ((ctx=CTX_CMDXV,
   3928         _ble_syntax_attr[i]=ATTR_ERR))
   3929     elif ((ctx==CTX_CMDXE)); then
   3930       ((ctx=CTX_CMDX0))
   3931     elif ((ctx==CTX_FARGX3)); then
   3932       ((_ble_syntax_attr[i]=ATTR_ERR))
   3933     fi
   3934 
   3935     if [[ ${text:i+len} != [!$'\n|&()']* ]]; then
   3936       # リダイレクトがその場で終わるときはそもそも nest-push せずエラー。
   3937       # Note: 上の判定の文字集合は _ble_syntax_bash_RexDelimiter の部分集合。
   3938       #   但し、空白類および <> はリダイレクトに含まれ得るので許容する。
   3939       ((_ble_syntax_attr[i+len-1]=ATTR_ERR))
   3940     else
   3941       if [[ $rematch3 == '>&' ]]; then
   3942         ble/syntax/parse/nest-push "$CTX_RDRD2" "$rematch3"
   3943       elif [[ $rematch1 == *'&' ]]; then
   3944         ble/syntax/parse/nest-push "$CTX_RDRD" "$rematch3"
   3945       elif [[ $rematch1 == *'<<<' ]]; then
   3946         ble/syntax/parse/nest-push "$CTX_RDRS" "$rematch3"
   3947       elif [[ $rematch1 == *\<\< ]]; then
   3948         # Note: emacs bug workaround
   3949         #   '<<' と書くと何故か Emacs がヒアドキュメントと
   3950         #   勘違いする様になったので仕方なく \<\< とする。
   3951         ble/syntax/parse/nest-push "$CTX_RDRH" "$rematch3"
   3952       elif [[ $rematch1 == *\<\<- ]]; then
   3953         ble/syntax/parse/nest-push "$CTX_RDRI" "$rematch3"
   3954       else
   3955         ble/syntax/parse/nest-push "$CTX_RDRF" "$rematch3"
   3956       fi
   3957     fi
   3958     ((i+=len))
   3959     return 0
   3960   elif local rex='^(&&|\|[|&]?)|^;(;&?|&)|^[;&]'
   3961        ((_ble_bash<40000)) && rex='^(&&|\|\|?)|^;(;)|^[;&]'
   3962        [[ $tail =~ $rex ]]
   3963   then
   3964     # 制御演算子 && || | & ; |& ;; ;;& ;&
   3965 
   3966     if [[ $BASH_REMATCH == ';' ]]; then
   3967       if ((ctx==CTX_FARGX2||ctx==CTX_FARGX3||ctx==CTX_CMDXD0)); then
   3968         ((_ble_syntax_attr[i++]=ATTR_DEL,ctx=CTX_CMDXD))
   3969         return 0
   3970       elif ((ctx==CTX_CMDXT)); then
   3971         # Note #D0592: time ; 及び ! ; に限っては、エラーにならずに直後に CTX_CMDXE になる
   3972         # Note #D1477: Bash 4.4 で振る舞いが変わる。
   3973         ((_ble_syntax_attr[i++]=ATTR_DEL,ctx=_ble_bash>=40400?CTX_CMDX:CTX_CMDXE))
   3974         return 0
   3975       fi
   3976     fi
   3977 
   3978     # for bash-3.1 ${#arr[n]} bug
   3979     local rematch1=${BASH_REMATCH[1]} rematch2=${BASH_REMATCH[2]}
   3980     ((_ble_syntax_attr[i]=ATTR_DEL,
   3981       (_ble_syntax_bash_command_Opt[ctx]||ctx==CTX_CMDX&&${#rematch2})||
   3982         (_ble_syntax_attr[i]=ATTR_ERR)))
   3983 
   3984     ((ctx=${#rematch1}?CTX_CMDX1:(
   3985          ${#rematch2}?CTX_CASE:
   3986          CTX_CMDX)))
   3987     ((i+=${#BASH_REMATCH}))
   3988     return 0
   3989   elif local rex='^\(\(?' && [[ $tail =~ $rex ]]; then
   3990     # サブシェル (, 算術コマンド ((
   3991     local m=${BASH_REMATCH[0]}
   3992     if ((ctx==CTX_CMDX||ctx==CTX_CMDX1||ctx==CTX_CMDXT||ctx==CTX_CMDXC)); then
   3993       ((_ble_syntax_attr[i]=ATTR_DEL))
   3994       ((ctx=_ble_bash>=50200||${#m}==1?CTX_CMDXE:CTX_ARGX0))
   3995       [[ $_ble_syntax_bash_is_command_form_for && $tail == '(('* ]] && ((ctx=CTX_CMDXD0))
   3996       ble/syntax/parse/nest-push "$((${#m}==1?CTX_CMDX1:CTX_EXPR))" "$m"
   3997       ((i+=${#m}))
   3998     else
   3999       ble/syntax/parse/nest-push "$CTX_PATN"
   4000       ((_ble_syntax_attr[i++]=ATTR_ERR))
   4001     fi
   4002     return 0
   4003   elif [[ $tail == ')'* ]]; then
   4004     local ntype
   4005     ble/syntax/parse/nest-type
   4006     local attr=
   4007     if [[ $ntype == '(' || $ntype == '$(' || $ntype == '((' || $ntype == '$((' ]]; then
   4008       # 1 $ntype == '('
   4009       #   ( sub shell )
   4010       #   <( process substitution )
   4011       #   func ( invalid )
   4012       # 2 $ntype== '$('
   4013       #   $(command substitution)
   4014       # 3 $ntype == '((', '$(('
   4015       #   ((echo) >/dev/null) / $((echo) >/dev/null)
   4016       #   ※これは当初は算術式だと思っていたら実はサブシェルだったというパターン
   4017       ((attr=_ble_syntax_attr[inest]))
   4018     fi
   4019 
   4020     if [[ $attr ]]; then
   4021       ((_ble_syntax_attr[i]=(ctx==CTX_CMDX||ctx==CTX_CMDX0||ctx==CTX_CMDXV||ctx==CTX_CMDXE||ctx==CTX_ARGX||ctx==CTX_ARGX0||ctx==CTX_ARGVX||ctx==CTX_ARGEX)?attr:ATTR_ERR,
   4022         i+=1))
   4023       ble/syntax/parse/nest-pop
   4024       return 0
   4025     fi
   4026   fi
   4027 
   4028   return 1
   4029 }
   4030 
   4031 ## @fn ble/syntax:bash/ctx-command/.check-word-begin
   4032 ##   単語が未開始の場合に開始します。
   4033 ##   @var[in,out] i,ctx,wtype,wbegin
   4034 ##   @return 引数が来てはならない所に引数が来た時に 1 を返します。
   4035 _ble_syntax_bash_command_BeginCtx=()
   4036 _ble_syntax_bash_command_BeginCtx[CTX_ARGX]=$CTX_ARGI
   4037 _ble_syntax_bash_command_BeginCtx[CTX_ARGX0]=$CTX_ARGI
   4038 _ble_syntax_bash_command_BeginCtx[CTX_ARGVX]=$CTX_ARGVI
   4039 _ble_syntax_bash_command_BeginCtx[CTX_ARGEX]=$CTX_ARGEI
   4040 _ble_syntax_bash_command_BeginCtx[CTX_CMDX]=$CTX_CMDI
   4041 _ble_syntax_bash_command_BeginCtx[CTX_CMDX0]=$CTX_CMDI
   4042 _ble_syntax_bash_command_BeginCtx[CTX_CMDX1]=$CTX_CMDI
   4043 _ble_syntax_bash_command_BeginCtx[CTX_CMDXT]=$CTX_CMDI
   4044 _ble_syntax_bash_command_BeginCtx[CTX_CMDXC]=$CTX_CMDI
   4045 _ble_syntax_bash_command_BeginCtx[CTX_CMDXE]=$CTX_CMDI
   4046 _ble_syntax_bash_command_BeginCtx[CTX_CMDXD]=$CTX_CMDI
   4047 _ble_syntax_bash_command_BeginCtx[CTX_CMDXD0]=$CTX_CMDI
   4048 _ble_syntax_bash_command_BeginCtx[CTX_CMDXV]=$CTX_CMDI
   4049 _ble_syntax_bash_command_BeginCtx[CTX_FARGX1]=$CTX_FARGI1
   4050 _ble_syntax_bash_command_BeginCtx[CTX_SARGX1]=$CTX_FARGI1
   4051 _ble_syntax_bash_command_BeginCtx[CTX_FARGX2]=$CTX_FARGI2
   4052 _ble_syntax_bash_command_BeginCtx[CTX_FARGX3]=$CTX_FARGI3
   4053 _ble_syntax_bash_command_BeginCtx[CTX_CARGX1]=$CTX_CARGI1
   4054 _ble_syntax_bash_command_BeginCtx[CTX_CARGX2]=$CTX_CARGI2
   4055 _ble_syntax_bash_command_BeginCtx[CTX_CPATX]=$CTX_CPATI
   4056 _ble_syntax_bash_command_BeginCtx[CTX_CPATX0]=$CTX_CPATI
   4057 _ble_syntax_bash_command_BeginCtx[CTX_TARGX1]=$CTX_TARGI1
   4058 _ble_syntax_bash_command_BeginCtx[CTX_TARGX2]=$CTX_TARGI2
   4059 _ble_syntax_bash_command_BeginCtx[CTX_COARGX]=$CTX_COARGI
   4060 
   4061 #%if !release
   4062 ## @arr _ble_syntax_bash_command_isARGI[ctx]
   4063 ##
   4064 ##   assert 用の配列。シェル単語の解析中に現れても良い文脈値を管理する。この配
   4065 ##   列要素が非空文字列のとき、その文脈はシェル単語の解析中に現れても良い。
   4066 ##
   4067 _ble_syntax_bash_command_isARGI[CTX_CMDI]=1
   4068 _ble_syntax_bash_command_isARGI[CTX_VRHS]=1
   4069 _ble_syntax_bash_command_isARGI[CTX_ARGI]=1
   4070 _ble_syntax_bash_command_isARGI[CTX_ARGQ]=1
   4071 _ble_syntax_bash_command_isARGI[CTX_ARGVI]=1
   4072 _ble_syntax_bash_command_isARGI[CTX_ARGVR]=1
   4073 _ble_syntax_bash_command_isARGI[CTX_ARGEI]=1
   4074 _ble_syntax_bash_command_isARGI[CTX_ARGER]=1
   4075 _ble_syntax_bash_command_isARGI[CTX_FARGI1]=1 # var
   4076 _ble_syntax_bash_command_isARGI[CTX_FARGI2]=1 # in
   4077 _ble_syntax_bash_command_isARGI[CTX_FARGI3]=1 # args...
   4078 _ble_syntax_bash_command_isARGI[CTX_FARGQ3]=1 # args... (= の後)
   4079 _ble_syntax_bash_command_isARGI[CTX_CARGI1]=1 # value
   4080 _ble_syntax_bash_command_isARGI[CTX_CARGQ1]=1 # value (= の後)
   4081 _ble_syntax_bash_command_isARGI[CTX_CARGI2]=1 # in
   4082 _ble_syntax_bash_command_isARGI[CTX_CPATI]=1  # pattern
   4083 _ble_syntax_bash_command_isARGI[CTX_CPATQ]=1  # pattern
   4084 _ble_syntax_bash_command_isARGI[CTX_TARGI1]=1 # -p
   4085 _ble_syntax_bash_command_isARGI[CTX_TARGI2]=1 # --
   4086 _ble_syntax_bash_command_isARGI[CTX_COARGI]=1 # var (coproc の後)
   4087 #%end
   4088 # Detect the end of ${ list; }
   4089 function ble/syntax:bash/ctx-command/.check-funsub-end {
   4090   ((_ble_bash>=50300)) || return 1
   4091 
   4092   # 新しいコマンド名が始まる文脈
   4093   ((wbegin<0&&_ble_syntax_bash_command_BeginCtx[ctx]==CTX_CMDI)) || return 1
   4094 
   4095   [[ $tail == '}'* ]] || return 1
   4096 
   4097   local ntype
   4098   ble/syntax/parse/nest-type
   4099   [[ $ntype == 'cmdsub_nofork' ]] || return 1
   4100 
   4101   ((_ble_syntax_attr[i++]=_ble_syntax_attr[inest]))
   4102   ble/syntax/parse/nest-pop
   4103   return 0
   4104 }
   4105 function ble/syntax:bash/ctx-command/.check-word-begin {
   4106   if ((wbegin<0)); then
   4107     local octx
   4108     ((octx=ctx,
   4109       wtype=octx,
   4110       ctx=_ble_syntax_bash_command_BeginCtx[ctx]))
   4111 #%if !release
   4112     ble/util/assert '((ctx!=0))' "invalid ctx=$octx at the beginning of words" ||
   4113       ((ctx=wtype=CTX_ARGI))
   4114 #%end
   4115 
   4116     # Note: ここで設定される wtype は最終的に ctx-command/check-word-end で
   4117     #   配列 _ble_syntax_bash_command_EndWtype により変換されてから tree に登録される。
   4118     ble/syntax/parse/word-push "$wtype" "$i"
   4119 
   4120     ((octx!=CTX_ARGX0&&octx!=CTX_CPATX0)); return "$?" # return unexpectedWbegin
   4121   fi
   4122 
   4123 #%if !release
   4124   ble/util/assert '((_ble_syntax_bash_command_isARGI[ctx]))' "invalid ctx=$ctx in words"
   4125 #%end
   4126   return 0
   4127 }
   4128 
   4129 # コマンド・引数部分
   4130 function ble/syntax:bash/ctx-command {
   4131 #%if !release
   4132   if ble/syntax:bash/starts-with-delimiter-or-redirect; then
   4133     ble/util/assert '
   4134       ((ctx==CTX_ARGX||ctx==CTX_ARGX0||ctx==CTX_ARGVX||ctx==CTX_ARGEX||
   4135           ctx==CTX_FARGX2||ctx==CTX_FARGX3||ctx==CTX_COARGX||
   4136           ctx==CTX_CMDX||ctx==CTX_CMDX0||ctx==CTX_CMDX1||ctx==CTX_CMDXT||ctx==CTX_CMDXC||
   4137           ctx==CTX_CMDXE||ctx==CTX_CMDXD||ctx==CTX_CMDXD0||ctx==CTX_CMDXV))'  "invalid ctx=$ctx @ i=$i"
   4138     ble/util/assert '((wbegin<0&&wtype<0))' "invalid word-context (wtype=$wtype wbegin=$wbegin) on non-word char."
   4139     ble/syntax:bash/ctx-command/.check-delimiter-or-redirect; return "$?"
   4140   fi
   4141 #%else
   4142   ble/syntax:bash/ctx-command/.check-delimiter-or-redirect && return 0
   4143 #%end
   4144 
   4145   ble/syntax:bash/check-comment && return 0
   4146 
   4147   ble/syntax:bash/ctx-command/.check-funsub-end && return 0
   4148 
   4149   local unexpectedWbegin=-1
   4150   ble/syntax:bash/ctx-command/.check-word-begin || ((unexpectedWbegin=i))
   4151 
   4152   local wtype0=$wtype i0=$i
   4153 
   4154   local flagConsume=0
   4155   if ble/syntax:bash/check-variable-assignment; then
   4156     flagConsume=1
   4157   elif local rex='^([^'${_ble_syntax_bash_chars[CTX_ARGI]}']+|\\.)'; [[ $tail =~ $rex ]]; then
   4158     local rematch=$BASH_REMATCH
   4159     local attr=$ctx
   4160     [[ $BASH_REMATCH == '\'? ]] && attr=$ATTR_QESC
   4161     ((_ble_syntax_attr[i]=attr,i+=${#rematch}))
   4162     flagConsume=1
   4163   elif ble/syntax:bash/check-process-subst; then
   4164     flagConsume=1
   4165   elif ble/syntax:bash/check-quotes; then
   4166     flagConsume=1
   4167   elif ble/syntax:bash/check-dollar; then
   4168     flagConsume=1
   4169   elif ble/syntax:bash/check-glob; then
   4170     flagConsume=1
   4171   elif ble/syntax:bash/check-brace-expansion; then
   4172     flagConsume=1
   4173   elif ble/syntax:bash/check-tilde-expansion; then
   4174     flagConsume=1
   4175   elif ble/syntax:bash/starts-with-histchars; then
   4176     ble/syntax:bash/check-history-expansion ||
   4177       ((_ble_syntax_attr[i]=ctx,i++))
   4178     flagConsume=1
   4179   fi
   4180 
   4181   if ((flagConsume)); then
   4182     ble/util/assert '((wtype0>=0))'
   4183 
   4184     if ((ctx==CTX_FARGI1)); then
   4185       # for var in ... の var の部分は変数名をチェックして着色
   4186       local rex='^[_a-zA-Z][_a-zA-Z0-9]*$' attr=$ATTR_ERR
   4187       if ((i0==wbegin)) && [[ ${text:i0:i-i0} =~ $rex ]]; then
   4188         local ret; ble/syntax/highlight/vartype "$BASH_REMATCH" global; attr=$ret
   4189       fi
   4190       ((_ble_syntax_attr[i0]=attr))
   4191     fi
   4192 
   4193     [[ ${_ble_syntax_bash_command_Expect[wtype0]} ]] &&
   4194       ((_ble_syntax_attr[i0]=ATTR_ERR))
   4195     if ((unexpectedWbegin>=0)); then
   4196       ble/syntax/parse/touch-updated-attr "$unexpectedWbegin"
   4197       ((_ble_syntax_attr[unexpectedWbegin]=ATTR_ERR))
   4198     fi
   4199     return 0
   4200   else
   4201     return 1
   4202   fi
   4203 }
   4204 
   4205 function ble/syntax:bash/ctx-command-compound-expect {
   4206   ble/util/assert '((ctx==CTX_FARGX1||ctx==CTX_SARGX1||ctx==CTX_CARGX1||ctx==CTX_FARGX2||ctx==CTX_CARGX2||ctx==CTX_COARGX))'
   4207   local _ble_syntax_bash_is_command_form_for=
   4208   if ble/syntax:bash/starts-with-delimiter-or-redirect; then
   4209     # "for var in ... / case arg in" を処理している途中で delimiter が来た場合。
   4210     if ((ctx==CTX_FARGX2)) && [[ $tail == [$';\n']* ]]; then
   4211       # for var in ... の in 以降が省略された形である。
   4212       # ble/syntax:bash/ctx-command で FARGX3 と同様に処理する。
   4213       ble/syntax:bash/ctx-command
   4214       return "$?"
   4215     elif ((ctx==CTX_FARGX1)) && [[ $tail == '(('* ]]; then
   4216       # for ((...)) の場合
   4217       # ここで return せずに以降の CTX_CMDX1 用の処理に任せる
   4218       ((ctx=CTX_CMDX1,_ble_syntax_bash_is_command_form_for=1))
   4219     elif [[ $tail == $'\n'* ]]; then
   4220       if ((ctx==CTX_CARGX2)); then
   4221         ((_ble_syntax_attr[i++]=CTX_ARGX))
   4222       else
   4223         ((_ble_syntax_attr[i++]=ATTR_ERR,ctx=CTX_ARGX))
   4224       fi
   4225       return 0
   4226     elif [[ $tail =~ ^$_ble_syntax_bash_RexSpaces ]]; then
   4227       ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   4228       return 0
   4229     elif ((ctx!=CTX_COARGX)); then
   4230       local i0=$i
   4231       ((ctx=CTX_ARGX))
   4232       ble/syntax:bash/ctx-command/.check-delimiter-or-redirect || ((i++))
   4233       ((_ble_syntax_attr[i0]=ATTR_ERR))
   4234       return 0
   4235     fi
   4236   fi
   4237 
   4238   # コメント禁止
   4239   local i0=$i
   4240   if ble/syntax:bash/check-comment; then
   4241     if ((ctx==CTX_FARGX1||ctx==CTX_SARGX1||ctx==CTX_CARGX1||ctx==CTX_COARGX)); then
   4242       # "for var / select var / case arg / coproc" を処理している途中でコメントが来た場合
   4243       ((_ble_syntax_attr[i0]=ATTR_ERR))
   4244     fi
   4245     return 0
   4246   fi
   4247 
   4248   # 他は同じ
   4249   ble/syntax:bash/ctx-command
   4250 }
   4251 
   4252 ## @fn ble/syntax:bash/ctx-command-expect/.match-word word
   4253 ##   現在位置から始まる単語が指定した単語に一致するかどうかを検査します。
   4254 ##   @param[in] word
   4255 ##   @var[in] tail i
   4256 function ble/syntax:bash/ctx-command-expect/.match-word {
   4257   local word=$1 len=${#1}
   4258   if [[ $tail == "$word"* ]]; then
   4259     ble/syntax/parse/set-lookahead "$((len+1))"
   4260     if ((${#tail}==len)) || i=$((i+len)) ble/syntax:bash/check-word-end/is-delimiter; then
   4261       return 0
   4262     fi
   4263   fi
   4264   return 1
   4265 }
   4266 
   4267 function ble/syntax:bash/ctx-command-time-expect {
   4268   ble/util/assert '((ctx==CTX_TARGX1||ctx==CTX_TARGX2))'
   4269 
   4270   if ble/syntax:bash/starts-with-delimiter-or-redirect; then
   4271     ble/util/assert '((wbegin<0&&wtype<0))'
   4272     if [[ $tail =~ ^$_ble_syntax_bash_RexSpaces ]]; then
   4273       ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   4274       return 0
   4275     else
   4276       ((ctx=CTX_CMDXT))
   4277       ble/syntax:bash/ctx-command/.check-delimiter-or-redirect; return "$?"
   4278     fi
   4279   fi
   4280 
   4281   # 期待する単語でない時は CTX_CMDXT に decay
   4282   if ((ctx==CTX_TARGX1)); then
   4283     # Note: bash-5.1 では "--" が来てもOKなので
   4284     #   ctx=CTX_TARGX2 として次の if で処理させる。
   4285     ble/syntax:bash/ctx-command-expect/.match-word '-p' ||
   4286       ((ctx=_ble_bash>=50100?CTX_TARGX2:CTX_CMDXT))
   4287   fi
   4288   if ((ctx==CTX_TARGX2)); then
   4289     ble/syntax:bash/ctx-command-expect/.match-word '--' ||
   4290       ((ctx=CTX_CMDXT))
   4291   fi
   4292 
   4293   # 他は同じ
   4294   ble/syntax:bash/ctx-command
   4295 }
   4296 
   4297 function ble/syntax:bash/ctx-command-case-pattern-expect {
   4298   ble/util/assert '((ctx==CTX_CPATX||ctx==CTX_CPATX0))'
   4299 
   4300   if ble/syntax:bash/starts-with-delimiter-or-redirect; then
   4301     local delimiter=$BASH_REMATCH
   4302     if [[ $tail =~ ^$_ble_syntax_bash_RexSpaces ]]; then
   4303       ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   4304     elif [[ $tail == '|'* ]]; then
   4305       ((_ble_syntax_attr[i++]=ctx==CTX_CPATX?ATTR_ERR:ATTR_GLOB,ctx=CTX_CPATX))
   4306     elif [[ $tail == ')'* ]]; then
   4307       ((_ble_syntax_attr[i++]=ctx==CTX_CPATX?ATTR_ERR:ATTR_GLOB,ctx=CTX_CMDX))
   4308     elif [[ $tail == '('* ]]; then
   4309       # ctx-command と同様にエラーにして @() を始める。
   4310       ble/syntax:bash/ctx-command/.check-delimiter-or-redirect
   4311     else
   4312       # 改行、リダイレクト、; & はエラー
   4313       ((_ble_syntax_attr[i]=ATTR_ERR,i+=${#delimiter}))
   4314     fi
   4315     return "$?"
   4316   fi
   4317 
   4318   # コメント禁止
   4319   local i0=$i
   4320   if ble/syntax:bash/check-comment; then
   4321     ((_ble_syntax_attr[i0]=ATTR_ERR))
   4322     return 0
   4323   fi
   4324 
   4325   # 他は同じ
   4326   ble/syntax:bash/ctx-command
   4327 }
   4328 
   4329 #------------------------------------------------------------------------------
   4330 # 文脈: 配列値リスト
   4331 #
   4332 
   4333 _ble_syntax_context_proc[CTX_VALX]=ble/syntax:bash/ctx-values
   4334 _ble_syntax_context_proc[CTX_VALI]=ble/syntax:bash/ctx-values
   4335 _ble_syntax_context_end[CTX_VALI]=ble/syntax:bash/ctx-values/check-word-end
   4336 _ble_syntax_context_proc[CTX_VALR]=ble/syntax:bash/ctx-values
   4337 _ble_syntax_context_end[CTX_VALR]=ble/syntax:bash/ctx-values/check-word-end
   4338 _ble_syntax_context_proc[CTX_VALQ]=ble/syntax:bash/ctx-values
   4339 _ble_syntax_context_end[CTX_VALQ]=ble/syntax:bash/ctx-values/check-word-end
   4340 
   4341 ## 文脈値 ctx-values
   4342 ##
   4343 ##   arr=() arr+=() から抜けた時にまた元の文脈値に復帰する必要があるので
   4344 ##   nest-push, nest-pop で入れ子構造を一段作成する事にする。
   4345 ##
   4346 ##   但し、外側で設定されたヒアドキュメントを処理する為に工夫が必要である。
   4347 ##   外側の nparam をそのまま利用しまた抜ける場合には外側の nparam に変更結果を適用する。
   4348 ##   ble/syntax:bash/ctx-values/enter, leave はこの nparam の持ち越しに使用する。
   4349 ##
   4350 
   4351 ## @fn ble/syntax:bash/ctx-values/enter
   4352 ##   @remarks この関数は ble/syntax:bash/check-variable-assignment から呼ばれる。
   4353 function ble/syntax:bash/ctx-values/enter {
   4354   local outer_nparam=$nparam
   4355   ble/syntax/parse/nest-push "$CTX_VALX"
   4356   nparam=$outer_nparam
   4357 }
   4358 ## @fn ble/syntax:bash/ctx-values/leave
   4359 function ble/syntax:bash/ctx-values/leave {
   4360   local inner_nparam=$nparam
   4361   ble/syntax/parse/nest-pop
   4362   nparam=$inner_nparam
   4363 }
   4364 
   4365 ## @fn ble/syntax:bash/ctx-values/check-word-end
   4366 function ble/syntax:bash/ctx-values/check-word-end {
   4367   # 単語の中にいない時は抜ける
   4368   ((wbegin<0)) && return 1
   4369 
   4370   # 未だ続きがある場合は抜ける
   4371   [[ ${text:i:1} == [!"$_ble_term_IFS;|&<>()"] ]] && return 1
   4372 
   4373   local wbeg=$wbegin wlen=$((i-wbegin)) wend=$i
   4374   local word=${text:wbegin:wlen}
   4375 
   4376   ble/syntax/parse/word-pop
   4377 
   4378   ble/util/assert '((ctx==CTX_VALI||ctx==CTX_VALR||ctx==CTX_VALQ))' 'invalid context'
   4379   ((ctx=CTX_VALX))
   4380 
   4381   return 0
   4382 }
   4383 
   4384 function ble/syntax:bash/ctx-values {
   4385   # コマンド・引数部分
   4386   if ble/syntax:bash/starts-with-delimiter; then
   4387 #%if !release
   4388     ble/util/assert '((ctx==CTX_VALX))' "invalid ctx=$ctx @ i=$i"
   4389     ble/util/assert '((wbegin<0&&wtype<0))' "invalid word-context (wtype=$wtype wbegin=$wbegin) on non-word char."
   4390 #%end
   4391 
   4392     if [[ $tail =~ ^$_ble_syntax_bash_RexIFSs ]]; then
   4393       local spaces=$BASH_REMATCH
   4394       ble/syntax:bash/check-here-document-from "$spaces" && return 0
   4395 
   4396       # 空白 (ctx はそのままで素通り)
   4397       ((_ble_syntax_attr[i]=ctx,i+=${#spaces}))
   4398       return 0
   4399     elif [[ $tail == ')'* ]]; then
   4400       # 配列定義の終了
   4401       ((_ble_syntax_attr[i++]=ATTR_DEL))
   4402       ble/syntax:bash/ctx-values/leave
   4403       return 0
   4404     elif [[ $type == ';'* ]]; then
   4405       ((_ble_syntax_attr[i++]=ATTR_ERR))
   4406       return 0
   4407     else
   4408       ((_ble_syntax_attr[i++]=ATTR_ERR))
   4409       return 0
   4410     fi
   4411   fi
   4412 
   4413   if ble/syntax:bash/check-comment; then
   4414     return 0
   4415   fi
   4416 
   4417   if ((wbegin<0)); then
   4418     ((ctx=CTX_VALI))
   4419     ble/syntax/parse/word-push "$ctx" "$i"
   4420   fi
   4421 
   4422 #%if !release
   4423   ble/util/assert '((ctx==CTX_VALI||ctx==CTX_VALR||ctx==CTX_VALQ))' "invalid context ctx=$ctx"
   4424 #%end
   4425 
   4426   if ble/syntax:bash/check-variable-assignment; then
   4427     return 0
   4428   elif ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[CTX_ARGI]}]+"; then
   4429     return 0
   4430   elif ble/syntax:bash/check-process-subst; then
   4431     return 0
   4432   elif ble/syntax:bash/check-quotes; then
   4433     return 0
   4434   elif ble/syntax:bash/check-dollar; then
   4435     return 0
   4436   elif ble/syntax:bash/check-glob; then
   4437     return 0
   4438   elif ble/syntax:bash/check-brace-expansion; then
   4439     return 0
   4440   elif ble/syntax:bash/check-tilde-expansion; then
   4441     return 0
   4442   elif ble/syntax:bash/starts-with-histchars; then
   4443     ble/syntax:bash/check-history-expansion ||
   4444       ((_ble_syntax_attr[i]=ctx,i++))
   4445     return 0
   4446   fi
   4447 
   4448   return 1
   4449 }
   4450 
   4451 #------------------------------------------------------------------------------
   4452 # 文脈: [[ 条件式 ]]
   4453 
   4454 _ble_syntax_context_proc[CTX_CONDX]=ble/syntax:bash/ctx-conditions
   4455 _ble_syntax_context_proc[CTX_CONDI]=ble/syntax:bash/ctx-conditions
   4456 _ble_syntax_context_end[CTX_CONDI]=ble/syntax:bash/ctx-conditions/check-word-end
   4457 _ble_syntax_context_proc[CTX_CONDQ]=ble/syntax:bash/ctx-conditions
   4458 _ble_syntax_context_end[CTX_CONDQ]=ble/syntax:bash/ctx-conditions/check-word-end
   4459 
   4460 ## @fn ble/syntax:bash/ctx-conditions/check-word-end
   4461 function ble/syntax:bash/ctx-conditions/check-word-end {
   4462   # 単語の中にいない時は抜ける
   4463   ((wbegin<0)) && return 1
   4464 
   4465   # 未だ続きがある場合は抜ける
   4466   [[ ${text:i:1} == [!"$_ble_term_IFS;|&<>()"] ]] && return 1
   4467 
   4468   local wbeg=$wbegin wlen=$((i-wbegin)) wend=$i
   4469   local word=${text:wbegin:wlen}
   4470 
   4471   ble/syntax/parse/word-pop
   4472 
   4473   ble/util/assert '((ctx==CTX_CONDI||ctx==CTX_CONDQ))' 'invalid context'
   4474   if [[ $word == ']]' ]]; then
   4475     ble/syntax/parse/touch-updated-attr "$wbeg"
   4476     ((_ble_syntax_attr[wbeg]=ATTR_DEL))
   4477     ble/syntax/parse/nest-pop
   4478   else
   4479     ((ctx=CTX_CONDX))
   4480   fi
   4481   return 0
   4482 }
   4483 
   4484 function ble/syntax:bash/ctx-conditions {
   4485   # コマンド・引数部分
   4486   if ble/syntax:bash/starts-with-delimiter; then
   4487 #%if !release
   4488     ble/util/assert '((ctx==CTX_CONDX))' "invalid ctx=$ctx @ i=$i"
   4489     ble/util/assert '((wbegin<0&&wtype<0))' "invalid word-context (wtype=$wtype wbegin=$wbegin) on non-word char."
   4490 #%end
   4491 
   4492     if [[ $tail =~ ^$_ble_syntax_bash_RexIFSs ]]; then
   4493       ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   4494       return 0
   4495     else
   4496       # [(<>;|&] など
   4497       ((_ble_syntax_attr[i++]=CTX_CONDI))
   4498       return 0
   4499     fi
   4500   fi
   4501 
   4502   ble/syntax:bash/check-comment && return 0
   4503 
   4504   if ((wbegin<0)); then
   4505     ((ctx=CTX_CONDI))
   4506     ble/syntax/parse/word-push "$ctx" "$i"
   4507   fi
   4508 
   4509 #%if !release
   4510   ble/util/assert '((ctx==CTX_CONDI||ctx==CTX_CONDQ))' "invalid context ctx=$ctx"
   4511 #%end
   4512 
   4513   if ble/syntax:bash/check-variable-assignment; then
   4514     return 0
   4515   elif ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[CTX_ARGI]}]+"; then
   4516     return 0
   4517   elif ble/syntax:bash/check-process-subst; then
   4518     return 0
   4519   elif ble/syntax:bash/check-quotes; then
   4520     return 0
   4521   elif ble/syntax:bash/check-dollar; then
   4522     return 0
   4523   elif ble/syntax:bash/check-glob; then
   4524     return 0
   4525   elif ble/syntax:bash/check-brace-expansion; then
   4526     return 0
   4527   elif ble/syntax:bash/check-tilde-expansion; then
   4528     return 0
   4529   elif ble/syntax:bash/starts-with-histchars; then
   4530     ble/syntax:bash/check-history-expansion ||
   4531       ((_ble_syntax_attr[i++]=ctx))
   4532     return 0
   4533   else
   4534     # 条件コマンドの時は $ や ) 等を許す。。
   4535     ((_ble_syntax_attr[i++]=ctx))
   4536     return 0
   4537   fi
   4538 
   4539   return 1
   4540 }
   4541 
   4542 
   4543 #------------------------------------------------------------------------------
   4544 # 文脈: リダイレクト
   4545 
   4546 _ble_syntax_context_proc[CTX_RDRF]=ble/syntax:bash/ctx-redirect
   4547 _ble_syntax_context_proc[CTX_RDRD]=ble/syntax:bash/ctx-redirect
   4548 _ble_syntax_context_proc[CTX_RDRD2]=ble/syntax:bash/ctx-redirect
   4549 _ble_syntax_context_proc[CTX_RDRS]=ble/syntax:bash/ctx-redirect
   4550 _ble_syntax_context_end[CTX_RDRF]=ble/syntax:bash/ctx-redirect/check-word-end
   4551 _ble_syntax_context_end[CTX_RDRD]=ble/syntax:bash/ctx-redirect/check-word-end
   4552 _ble_syntax_context_end[CTX_RDRD2]=ble/syntax:bash/ctx-redirect/check-word-end
   4553 _ble_syntax_context_end[CTX_RDRS]=ble/syntax:bash/ctx-redirect/check-word-end
   4554 function ble/syntax:bash/ctx-redirect/check-word-begin {
   4555   if ((wbegin<0)); then
   4556     # ※解析の段階では CTX_RDRF/CTX_RDRD/CTX_RDRD2/CTX_RDRS の間に区別はない。
   4557     #   但し、↓の行で解析に用いられた ctx が保存される。
   4558     #   この情報は後で補完候補を生成するのに用いられる。
   4559     ble/syntax/parse/word-push "$ctx" "$i"
   4560     ble/syntax/parse/touch-updated-word "$i" #■これは不要では?
   4561   fi
   4562 }
   4563 function ble/syntax:bash/ctx-redirect/check-word-end {
   4564   # 単語の中にいない時は抜ける
   4565   ((wbegin<0)) && return 1
   4566 
   4567   # 未だ続きがある場合は抜ける
   4568   ble/syntax:bash/check-word-end/is-delimiter || return 1
   4569 
   4570   # 単語の登録
   4571   ble/syntax/parse/word-pop
   4572 
   4573   # pop
   4574   ble/syntax/parse/nest-pop
   4575 #%if !release
   4576   # ここで終端の必要のある ctx (CMDI や ARGI などの単語中の文脈) になる事は無い。
   4577   # 何故なら push した時は CMDX か ARGX の文脈にいたはずだから。
   4578   ble/util/assert '((!_ble_syntax_bash_command_isARGI[ctx]))' "invalid ctx=$ctx in words"
   4579 #%end
   4580   return 0
   4581 }
   4582 function ble/syntax:bash/ctx-redirect {
   4583   # redirect の直後にコマンド終了や別の redirect があってはならない
   4584   if ble/syntax:bash/starts-with-delimiter-or-redirect; then
   4585     ((_ble_syntax_attr[i++]=ATTR_ERR))
   4586     [[ ${tail:1} =~ ^$_ble_syntax_bash_RexSpaces ]] &&
   4587       ((_ble_syntax_attr[i]=ctx,i+=${#BASH_REMATCH}))
   4588     return 0
   4589   fi
   4590 
   4591   if local i0=$i; ble/syntax:bash/check-comment; then
   4592     ((_ble_syntax_attr[i0]=ATTR_ERR))
   4593     return 0
   4594   fi
   4595 
   4596   # 単語開始の設置
   4597   ble/syntax:bash/ctx-redirect/check-word-begin
   4598 
   4599   if ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[CTX_ARGI]}]+"; then
   4600     return 0
   4601   elif ble/syntax:bash/check-process-subst; then
   4602     return 0
   4603   elif ble/syntax:bash/check-quotes; then
   4604     return 0
   4605   elif ble/syntax:bash/check-dollar; then
   4606     return 0
   4607   elif ble/syntax:bash/check-glob; then
   4608     return 0
   4609   elif ble/syntax:bash/check-brace-expansion; then
   4610     return 0
   4611   elif ble/syntax:bash/check-tilde-expansion; then
   4612     return 0
   4613   elif ble/syntax:bash/starts-with-histchars; then
   4614     ble/syntax:bash/check-history-expansion ||
   4615       ((_ble_syntax_attr[i]=ctx,i++))
   4616     return 0
   4617   fi
   4618 
   4619   return 1
   4620 }
   4621 
   4622 #------------------------------------------------------------------------------
   4623 # 文脈: ヒアドキュメント
   4624 #
   4625 # | <<[-] word
   4626 # | contents
   4627 # | delimiter
   4628 #
   4629 # ctx-heredoc-word (word の解析) は ctx-redirect を参考にして作成する。
   4630 #
   4631 
   4632 _ble_syntax_bash_heredoc_EscSP='\040'
   4633 _ble_syntax_bash_heredoc_EscHT='\011'
   4634 _ble_syntax_bash_heredoc_EscLF='\012'
   4635 _ble_syntax_bash_heredoc_EscFS='\034'
   4636 function ble/syntax:bash/ctx-heredoc-word/initialize {
   4637   local ret
   4638   ble/util/s2c ' '
   4639   ble/util/sprintf _ble_syntax_bash_heredoc_EscSP '\\%03o' "$ret"
   4640   ble/util/s2c $'\t'
   4641   ble/util/sprintf _ble_syntax_bash_heredoc_EscHT '\\%03o' "$ret"
   4642   ble/util/s2c $'\n'
   4643   ble/util/sprintf _ble_syntax_bash_heredoc_EscLF '\\%03o' "$ret"
   4644   ble/util/s2c "$_ble_term_FS"
   4645   ble/util/sprintf _ble_syntax_bash_heredoc_EscFS '\\%03o' "$ret"
   4646 }
   4647 ble/syntax:bash/ctx-heredoc-word/initialize
   4648 
   4649 ## @fn ble/syntax:bash/ctx-heredoc-word/remove-quotes word
   4650 ##   @var[out] delimiter
   4651 function ble/syntax:bash/ctx-heredoc-word/remove-quotes {
   4652   local text=$1 result=
   4653 
   4654   local rex='^[^\$"'\'']+|^\$?["'\'']|^\\.?|^.'
   4655   while [[ $text && $text =~ $rex ]]; do
   4656     local rematch=$BASH_REMATCH
   4657     if [[ $rematch == \" || $rematch == \$\" ]]; then
   4658       if rex='^\$?"(([^\"]|\\.)*)(\\?$|")'; [[ $text =~ $rex ]]; then
   4659         local str=${BASH_REMATCH[1]}
   4660         local a b
   4661         b='\`' a='`'; str=${str//"$b"/"$a"}
   4662         b='\"' a='"'; str=${str//"$b"/"$a"} # WA #D1751 checked
   4663         b='\$' a='$'; str=${str//"$b"/"$a"}
   4664         b='\\' a='\'; str=${str//"$b"/"$a"}
   4665         result=$result$str
   4666         text=${text:${#BASH_REMATCH}}
   4667         continue
   4668       fi
   4669     elif [[ $rematch == \' ]]; then
   4670       if rex="^('[^']*)'?"; [[ $text =~ $rex ]]; then
   4671         builtin eval "result=\$result${BASH_REMATCH[1]}'"
   4672         text=${text:${#BASH_REMATCH}}
   4673         continue
   4674       fi
   4675     elif [[ $rematch == \$\' ]]; then
   4676       if rex='^(\$'\''([^\'\'']|\\.)*)('\''|\\?$)'; [[ $text =~ $rex ]]; then
   4677         builtin eval "result=\$result${BASH_REMATCH[1]}'"
   4678         text=${text:${#BASH_REMATCH}}
   4679         continue
   4680       fi
   4681     elif [[ $rematch == \\* ]]; then
   4682       result=$result${rematch:1}
   4683       text=${text:${#rematch}}
   4684       continue
   4685     fi
   4686 
   4687     result=$result$rematch
   4688     text=${text:${#rematch}}
   4689   done
   4690 
   4691   delimiter=$result$text
   4692 }
   4693 
   4694 ## @fn ble/syntax:bash/ctx-heredoc-word/remove-quotes delimiter
   4695 ##   @var[out] escaped
   4696 function ble/syntax:bash/ctx-heredoc-word/escape-delimiter {
   4697   local ret=$1
   4698   if [[ $ret == *[\\\'$_ble_term_IFS$_ble_term_FS]* ]]; then
   4699     local a b fs=$_ble_term_FS
   4700     a=\\   ; b='\'$a; ret=${ret//"$a"/"$b"}
   4701     a=\'   ; b='\'$a; ret=${ret//"$a"/"$b"}
   4702     a=' '  ; b=$_ble_syntax_bash_heredoc_EscSP; ret=${ret//"$a"/"$b"}
   4703     a=$'\t'; b=$_ble_syntax_bash_heredoc_EscHT; ret=${ret//"$a"/"$b"}
   4704     a=$'\n'; b=$_ble_syntax_bash_heredoc_EscLF; ret=${ret//"$a"/"$b"}
   4705     a=$fs  ; b=$_ble_syntax_bash_heredoc_EscFS; ret=${ret//"$a"/"$b"}
   4706   fi
   4707   escaped=$ret
   4708 }
   4709 function ble/syntax:bash/ctx-heredoc-word/unescape-delimiter {
   4710   builtin eval "delimiter=\$'$1'"
   4711 }
   4712 
   4713 ## 文脈値 CTX_RDRH
   4714 ##
   4715 ##   @remarks
   4716 ##     redirect と同様に nest-push と同時にこの文脈に入る事を想定する。
   4717 ##
   4718 _ble_syntax_context_proc[CTX_RDRH]=ble/syntax:bash/ctx-heredoc-word
   4719 _ble_syntax_context_end[CTX_RDRH]=ble/syntax:bash/ctx-heredoc-word/check-word-end
   4720 _ble_syntax_context_proc[CTX_RDRI]=ble/syntax:bash/ctx-heredoc-word
   4721 _ble_syntax_context_end[CTX_RDRI]=ble/syntax:bash/ctx-heredoc-word/check-word-end
   4722 function ble/syntax:bash/ctx-heredoc-word/check-word-end {
   4723   ((wbegin<0)) && return 1
   4724 
   4725   # 未だ続きがある場合は抜ける
   4726   ble/syntax:bash/check-word-end/is-delimiter || return 1
   4727 
   4728   # word = "EOF" 等の終端文字列
   4729   local octx=$ctx word=${text:wbegin:i-wbegin}
   4730 
   4731   # 終了処理
   4732   ble/syntax/parse/word-pop
   4733   ble/syntax/parse/nest-pop
   4734 
   4735   local I
   4736   if ((octx==CTX_RDRI)); then I=I; else I=R; fi
   4737 
   4738   local Q delimiter
   4739   if [[ $word == *[\'\"\\]* ]]; then
   4740     Q=Q; ble/syntax:bash/ctx-heredoc-word/remove-quotes "$word"
   4741   else
   4742     Q=H; delimiter=$word
   4743   fi
   4744 
   4745   local escaped; ble/syntax:bash/ctx-heredoc-word/escape-delimiter "$delimiter"
   4746   nparam=$nparam$_ble_term_FS@$I$Q$escaped
   4747   return 0
   4748 }
   4749 function ble/syntax:bash/ctx-heredoc-word {
   4750   ble/syntax:bash/ctx-redirect
   4751 }
   4752 
   4753 ## 文脈値 CTX_HERE0, CTX_HERE1
   4754 ##
   4755 ##   nest-push された環境で評価される。
   4756 ##   nparam =~ [RI][QH]delimiter の形式を持つ。
   4757 ##
   4758 _ble_syntax_context_proc[CTX_HERE0]=ble/syntax:bash/ctx-heredoc-content
   4759 _ble_syntax_context_proc[CTX_HERE1]=ble/syntax:bash/ctx-heredoc-content
   4760 function ble/syntax:bash/ctx-heredoc-content {
   4761   local indented= quoted= delimiter=
   4762   ble/syntax:bash/ctx-heredoc-word/unescape-delimiter "${nparam:2}"
   4763   [[ ${nparam::1} == I ]] && indented=1
   4764   [[ ${nparam:1:1} == Q ]] && quoted=1
   4765 
   4766   local rex ht=$'\t' lf=$'\n'
   4767   if ((ctx==CTX_HERE0)); then
   4768     rex="^${indented:+$ht*}"$'([^\n]+\n?|\n)'
   4769     [[ $tail =~ $rex ]] || return 1
   4770 
   4771     # ヒアドキュメント終了判定
   4772     # ※前後の空白も含めて行が delimiter と一致していなければならない。
   4773     local line=${BASH_REMATCH%"$lf"}
   4774     local rematch1=${BASH_REMATCH[1]}
   4775     if [[ ${rematch1%"$lf"} == "$delimiter" ]]; then
   4776       local indent
   4777       ((indent=${#BASH_REMATCH}-${#rematch1},
   4778         _ble_syntax_attr[i]=CTX_HERE0,
   4779         _ble_syntax_attr[i+indent]=CTX_RDRH,
   4780         i+=${#line}))
   4781       ble/syntax/parse/nest-pop
   4782       return 0
   4783     fi
   4784   fi
   4785 
   4786   if [[ $quoted ]]; then
   4787     ble/util/assert '((ctx==CTX_HERE0))'
   4788     ((_ble_syntax_attr[i]=CTX_HERE0,i+=${#BASH_REMATCH}))
   4789     return 0
   4790   else
   4791     ((ctx=CTX_HERE1))
   4792 
   4793     # \? 及び $? ${} $(()) $[] $() ``
   4794     if rex='^(\\[\$`'$lf'])|^([^'${_ble_syntax_bash_chars[CTX_HERE1]}']|\\[^\$`'$lf'])+'$lf'?|^'$lf && [[ $tail =~ $rex ]]; then
   4795       if [[ ${BASH_REMATCH[1]} ]]; then
   4796         ((_ble_syntax_attr[i]=ATTR_QESC))
   4797       else
   4798         ((_ble_syntax_attr[i]=CTX_HERE0))
   4799         [[ $BASH_REMATCH == *"$lf" ]] && ((ctx=CTX_HERE0))
   4800       fi
   4801       ((i+=${#BASH_REMATCH}))
   4802       return 0
   4803     fi
   4804 
   4805     if ble/syntax:bash/check-dollar; then
   4806       return 0
   4807     elif [[ $tail == '`'* ]] && ble/syntax:bash/check-quotes; then
   4808       return 0
   4809     elif ble/syntax:bash/starts-with-histchars; then
   4810       ble/syntax:bash/check-history-expansion ||
   4811         ((_ble_syntax_attr[i]=CTX_HERE0,i++))
   4812       return 0
   4813     else
   4814       # 単独の $ や終端の \ など?
   4815       ((_ble_syntax_attr[i]=CTX_HERE0,i++))
   4816       return 0
   4817     fi
   4818   fi
   4819 }
   4820 
   4821 #------------------------------------------------------------------------------
   4822 # Utilities based on syntactic strutures
   4823 
   4824 function ble/syntax:bash/is-complete {
   4825   local iN=${#_ble_syntax_text}
   4826 
   4827   # (1) 最後の点にエラーが設定されていた時
   4828   # - 閉じていない single quotation などは此処。
   4829   # - 入れ子が閉じていない時もここで引っかかる。
   4830   # - 実はヒアドキュメントが閉じていない時もここでかかる。
   4831   ((iN>0)) && ((_ble_syntax_attr[iN-1]==ATTR_ERR)) && return 1
   4832 
   4833   local stat=${_ble_syntax_stat[iN]}
   4834   if [[ $stat ]]; then
   4835     ble/string#split-words stat "$stat"
   4836 
   4837     # (2) 入れ子が閉じていない時
   4838     local nlen=${stat[3]}; ((nlen>=0)) && return 1
   4839 
   4840     # (3) ヒアドキュメントの待ちがある時
   4841     local nparam=${stat[6]}; [[ $nparam == none ]] && nparam=
   4842     local rex="$_ble_term_FS@([RI][QH][^$_ble_term_FS]*)(.*$)"
   4843     [[ $nparam =~ $rex ]] && return 1
   4844 
   4845     # (4) 完結している文脈値の時以外
   4846     local ctx=${stat[0]}
   4847     ((ctx==CTX_ARGX||ctx==CTX_ARGX0||ctx==CTX_ARGVX||ctx==CTX_ARGEX||
   4848         ctx==CTX_CMDX||ctx==CTX_CMDX0||ctx==CTX_CMDXT||ctx==CTX_CMDXE||ctx==CTX_CMDXV||
   4849         ctx==CTX_TARGX1||ctx==CTX_TARGX2)) || return 1
   4850   fi
   4851 
   4852   # 構文 if..fi, etc が閉じているか?
   4853   local attrs ret
   4854   IFS= builtin eval 'attrs="::${_ble_syntax_attr[*]/%/::}"' # WA #D1570 checked
   4855   ble/string#count-string "$attrs" ":$ATTR_KEYWORD_BEGIN:"; local nbeg=$ret
   4856   ble/string#count-string "$attrs" ":$ATTR_KEYWORD_END:"; local nend=$ret
   4857   ((nbeg>nend)) && return 1
   4858 
   4859   return 0
   4860 }
   4861 
   4862 ## @fn ble/syntax:bash/find-end-of-array-index beg end
   4863 ##   "配列添字の綴じ括弧 ] の直前の位置" を求めます。
   4864 ##   @param[in] beg
   4865 ##     配列要素の添字指定の開始位置 ("[" の位置) を指定します。
   4866 ##   @param[in] end
   4867 ##     探索の終端位置を指定します。
   4868 ##   @var[out] ret
   4869 ##     beg に対応する "]" の位置を返します。
   4870 ##     対応する終端がない場合は空文字列を返します。
   4871 function ble/syntax:bash/find-end-of-array-index {
   4872   local beg=$1 end=$2
   4873   ret=
   4874 
   4875   local inest0=$beg nest0
   4876   [[ ${_ble_syntax_nest[inest0]} ]] || return 1
   4877 
   4878   local q stat1 nlen1 inest1 r=
   4879   for ((q=inest0+1;q<end;q++)); do
   4880     local stat1=${_ble_syntax_stat[q]}
   4881     [[ $stat1 ]] || continue
   4882     ble/string#split-words stat1 "$stat1"
   4883     ((nlen1=stat1[3])) # (workaround Bash-4.2 segfault)
   4884     ((inest1=nlen1<0?nlen1:q-nlen1))
   4885     ((inest1<inest0)) && break
   4886     ((r=q))
   4887   done
   4888 
   4889   [[ ${_ble_syntax_text:r:end-r} == ']'* ]] && ret=$r
   4890   [[ $ret ]]
   4891 }
   4892 
   4893 ## ble/syntax:bash/find-rhs wtype wbeg wlen opts
   4894 ##   変数代入の形式の右辺の開始位置を取得します。
   4895 ##   @param[in] wtype wbeg wlen
   4896 ##   @param[in] opts
   4897 ##     element-assignment
   4898 ##       配列要素の場合にも変数代入の形式を許します。
   4899 ##     long-option
   4900 ##       --long-option= の形式にも強制的に対応します。
   4901 ##   @var[out] ret
   4902 ##     右辺の開始位置を返します。
   4903 ##     変数代入の形式でない時には単語の開始位置を返します。
   4904 ##   @exit
   4905 ##     単語が変数代入の形式を持つ時に成功します。
   4906 ##     それ以外の場合に失敗します。
   4907 function ble/syntax:bash/find-rhs {
   4908   local wtype=$1 wbeg=$2 wlen=$3 opts=$4
   4909 
   4910   local text=$_ble_syntax_text
   4911   local word=${text:wbeg:wlen} wend=$((wbeg+wlen))
   4912 
   4913   local rex=
   4914   if ((wtype==ATTR_VAR)); then
   4915     rex='^[_a-zA-Z0-9]+(\+?=|\[)'
   4916   elif ((wtype==CTX_VALI)); then
   4917     if [[ :$opts: == *:element-assignment:* ]]; then
   4918       # 配列要素に対しても変数代入の形式を許す
   4919       rex='^[_a-zA-Z0-9]+(\+?=|\[)|^(\[)'
   4920     else
   4921       rex='^(\[)'
   4922     fi
   4923   fi
   4924   if [[ :$opts: == *:long-option:* ]]; then
   4925     rex=${rex:+$rex'|'}'^--[-_a-zA-Z0-9]+='
   4926   fi
   4927 
   4928   if [[ $rex && $word =~ $rex ]]; then
   4929     local last_char=${BASH_REMATCH:${#BASH_REMATCH}-1}
   4930     if [[ $last_char == '[' ]]; then
   4931       # wtype==ATTR_VAR: arr[0]=x@ arr[1]+=x@
   4932       # wtype==ATTR_VAR: declare arr[0]=x@ arr[1]+=x@
   4933       # wtype==CTX_VALI: arr=([0]=x@ [1]+=x@)
   4934       local p1=$((wbeg+${#BASH_REMATCH}-1))
   4935       if ble/syntax:bash/find-end-of-array-index "$p1" "$wend"; then
   4936         local p2=$ret
   4937         case ${text:p2:wend-p2} in
   4938         (']='*)  ((ret=p2+2)); return 0 ;;
   4939         (']+='*) ((ret=p2+3)); return 0 ;;
   4940         esac
   4941       fi
   4942     else
   4943       # wtype==ATTR_VAR: var=x@ var+=x@
   4944       # wtype==ATTR_VAR: declare var=x@ var+=x@
   4945       ((ret=wbeg+${#BASH_REMATCH}))
   4946       return 0
   4947     fi
   4948   fi
   4949 
   4950   ret=$wbeg
   4951   return 1
   4952 }
   4953 
   4954 #==============================================================================
   4955 # 解析部
   4956 
   4957 _ble_syntax_vanishing_word_umin=-1
   4958 _ble_syntax_vanishing_word_umax=-1
   4959 function ble/syntax/vanishing-word/register {
   4960   local tree_array=$1 tofs=$2
   4961   local beg=$3 end=$4 lbeg=$5 lend=$6
   4962   (((beg<=0)&&(beg=1)))
   4963 
   4964   local node i nofs
   4965   for ((i=end;i>=beg;i--)); do
   4966     builtin eval "node=(\${$tree_array[tofs+i-1]})"
   4967     ((${#node[@]})) || continue
   4968     for ((nofs=0;nofs<${#node[@]};nofs+=_ble_syntax_TREE_WIDTH)); do
   4969       local wtype=${node[nofs]} wlen=${node[nofs+1]}
   4970       local wbeg=$((wlen<0?wlen:i-wlen)) wend=$i
   4971 
   4972       ((wbeg<lbeg&&(wbeg=lbeg),
   4973         wend>lend&&(wend=lend)))
   4974       ble/syntax/urange#update _ble_syntax_vanishing_word_ "$wbeg" "$wend"
   4975     done
   4976   done
   4977 }
   4978 
   4979 #----------------------------------------------------------
   4980 # shift
   4981 
   4982 ## @var[in] shift2_j
   4983 ## @var[in] beg,end,end0,shift
   4984 function ble/syntax/parse/shift.stat {
   4985   if [[ ${_ble_syntax_stat[shift2_j]} ]]; then
   4986     local -a stat; ble/string#split-words stat "${_ble_syntax_stat[shift2_j]}"
   4987 
   4988     local k klen kbeg
   4989     for k in 1 3 4 5; do # wlen nlen tclen tplen
   4990       (((klen=stat[k])<0)) && continue
   4991       ((kbeg=shift2_j-klen))
   4992       if ((kbeg<beg)); then
   4993         ((stat[k]+=shift))
   4994       elif ((kbeg<end0)); then
   4995         ((stat[k]-=end0-kbeg))
   4996       fi
   4997     done
   4998 
   4999     _ble_syntax_stat[shift2_j]="${stat[*]}"
   5000   fi
   5001 }
   5002 
   5003 ## @var[in] node,shift2_j,nofs
   5004 ## @var[in] beg,end,end0,shift
   5005 function ble/syntax/parse/shift.tree/1 {
   5006   local k klen kbeg
   5007   for k in 1 2 3; do # wlen/nlen tclen tplen
   5008     ((klen=node[nofs+k]))
   5009     ((klen<0||(kbeg=shift2_j-klen)>end0)) && continue
   5010     # 長さが変化した時 (k==1)、または構文木の距離変化があった時 (k==2, k==3) にここへ来る。
   5011 
   5012     # (1) 単語の中身が変化した事を記録
   5013     #   node の中身が書き換わった時 (wbegin < end0 の時):
   5014     #   dirty 拡大の代わりに _ble_syntax_word_umax に登録するに留める。
   5015     if [[ $k == 1 && ${node[nofs]} =~ ^[0-9]$ ]]; then
   5016       ble/syntax/parse/touch-updated-word "$shift2_j"
   5017 
   5018       # 着色情報を clear
   5019       node[nofs+4]='-'
   5020     fi
   5021 
   5022     # (1) 長さ・相対位置の補正
   5023     if ((kbeg<beg)); then
   5024       ((node[nofs+k]+=shift))
   5025     elif ((kbeg<end0)); then
   5026       ((node[nofs+k]-=end0-kbeg))
   5027     fi
   5028   done
   5029 }
   5030 
   5031 ## @var[in] shift2_j
   5032 ## @var[in] beg,end,end0,shift
   5033 function ble/syntax/parse/shift.tree {
   5034   [[ ${_ble_syntax_tree[shift2_j-1]} ]] || return 1
   5035   local -a node
   5036   ble/string#split-words node "${_ble_syntax_tree[shift2_j-1]}"
   5037 
   5038   local nofs
   5039   if [[ $1 ]]; then
   5040     nofs=$1 ble/syntax/parse/shift.tree/1
   5041   else
   5042     for ((nofs=0;nofs<${#node[@]};nofs+=_ble_syntax_TREE_WIDTH)); do
   5043       ble/syntax/parse/shift.tree/1
   5044     done
   5045   fi
   5046 
   5047   _ble_syntax_tree[shift2_j-1]="${node[*]}"
   5048 }
   5049 
   5050 ## @var[in] shift2_j
   5051 ## @var[in] beg,end,end0,shift
   5052 function ble/syntax/parse/shift.nest {
   5053   # stat の先頭以外でも nest-push している
   5054   #   @ ctx-command/check-word-begin の "関数名 ( " にて。
   5055   if [[ ${_ble_syntax_nest[shift2_j]} ]]; then
   5056     local -a nest
   5057     ble/string#split-words nest "${_ble_syntax_nest[shift2_j]}"
   5058 
   5059     local k klen kbeg
   5060     for k in 1 3 4 5; do
   5061       (((klen=nest[k])))
   5062       ((klen<0||(kbeg=shift2_j-klen)<0)) && continue
   5063       if ((kbeg<beg)); then
   5064         ((nest[k]+=shift))
   5065       elif ((kbeg<end0)); then
   5066         ((nest[k]-=end0-kbeg))
   5067       fi
   5068     done
   5069 
   5070     _ble_syntax_nest[shift2_j]="${nest[*]}"
   5071   fi
   5072 }
   5073 
   5074 function ble/syntax/parse/shift.impl2/.shift-until {
   5075   local limit=$1
   5076   while ((shift2_j>=limit)); do
   5077 #%if !release
   5078     [[ $bleopt_syntax_debug ]] && _ble_syntax_stat_shift[shift2_j+shift]=1
   5079 #%end
   5080     ble/syntax/parse/shift.stat
   5081     ble/syntax/parse/shift.nest
   5082     ((shift2_j--))
   5083   done
   5084 }
   5085 
   5086 ## @fn ble/syntax/parse/shift.impl2/.proc1
   5087 ##
   5088 ## @var[in] TE_i
   5089 ##   tree-enumerate によって設定される変数です。
   5090 ##   現在処理している単語の終端境界を表します。
   5091 ##   単語の情報は _ble_syntax_tree[TE_i-1] に格納されています。
   5092 ##
   5093 ## @var[in,out] shift2_j  何処まで処理したかを格納します。
   5094 ##
   5095 ## @var[in]     i1,i2,j2,iN
   5096 ## @var[in]     beg,end,end0,shift
   5097 ##   これらの変数は更に子関数で使用されます。
   5098 ##
   5099 function ble/syntax/parse/shift.impl2/.proc1 {
   5100   if ((TE_i<j2)); then
   5101     ((tprev=-1)) # 中断
   5102     return 0
   5103   fi
   5104 
   5105   ble/syntax/parse/shift.impl2/.shift-until "$((TE_i+1))"
   5106   ble/syntax/parse/shift.tree "$TE_nofs"
   5107 
   5108   if ((tprev>end0&&wbegin>end0)) && [[ ${wtype//[0-9]} ]]; then
   5109     # skip 可能
   5110     #   単語 (wtype=整数) の時は、nlen が外部に参照を持つ可能性がある。
   5111     #   tprev<=end0 の場合、stat の中の tplen が shift 対象の可能性がある事に注意する。
   5112 #%if !release
   5113     [[ $bleopt_syntax_debug ]] && _ble_syntax_stat_shift[shift2_j+shift]=1
   5114 #%end
   5115     ble/syntax/parse/shift.stat
   5116     ble/syntax/parse/shift.nest
   5117     ((shift2_j=wbegin)) # skip
   5118   elif ((tchild>=0)); then
   5119     ble/syntax/tree-enumerate-children ble/syntax/parse/shift.impl2/.proc1
   5120   fi
   5121 }
   5122 
   5123 function ble/syntax/parse/shift.method1 {
   5124   # shift (shift は毎回やり切る。途中状態で抜けたりはしない)
   5125   local i j
   5126   for ((i=i2,j=j2;i<=iN;i++,j++)); do
   5127     # 注意: データの範囲
   5128     #   stat[i]   は i in [0,iN]
   5129     #   attr[i]   は i in [0,iN)
   5130     #   tree[i-1] は i in (0,iN]
   5131     local shift2_j=$j
   5132     ble/syntax/parse/shift.stat
   5133     ((j>0))  && ble/syntax/parse/shift.tree
   5134     ((i<iN)) && ble/syntax/parse/shift.nest
   5135   done
   5136 }
   5137 
   5138 function ble/syntax/parse/shift.method2 {
   5139 #%if !release
   5140   [[ $bleopt_syntax_debug ]] && _ble_syntax_stat_shift=()
   5141 #%end
   5142 
   5143   local iN=${#_ble_syntax_text} # tree-enumerate 起点は (古い text の長さ) である
   5144   local shift2_j=$iN # proc1 に渡す変数
   5145   ble/syntax/tree-enumerate ble/syntax/parse/shift.impl2/.proc1
   5146   ble/syntax/parse/shift.impl2/.shift-until "$j2" # 未処理部分
   5147 }
   5148 
   5149 ## @var[in] i1,i2,j2,iN
   5150 ## @var[in] beg,end,end0,shift
   5151 function ble/syntax/parse/shift {
   5152   # ※shift==0 でも更新で消滅した部分を縮める必要があるので
   5153   #   shift 実行する必要がある。
   5154 
   5155   # ble/syntax/parse/shift.method1 # 直接探索
   5156   ble/syntax/parse/shift.method2 # tree-enumerate による skip
   5157 
   5158   if ((shift!=0)); then
   5159     # 更新範囲の shift
   5160     ble/syntax/urange#shift _ble_syntax_attr_
   5161     ble/syntax/wrange#shift _ble_syntax_word_
   5162     ble/syntax/wrange#shift _ble_syntax_word_defer_
   5163     ble/syntax/urange#shift _ble_syntax_vanishing_word_
   5164   fi
   5165 }
   5166 
   5167 #----------------------------------------------------------
   5168 # parse
   5169 
   5170 _ble_syntax_dbeg=-1 _ble_syntax_dend=-1
   5171 
   5172 ## @fn ble/syntax/parse/determine-parse-range
   5173 ##
   5174 ##   @var[out] i1 i2 j2
   5175 ##
   5176 ##   @var[in] beg end end0
   5177 ##   @var[in] _ble_syntax_dbeg
   5178 ##   @var[in] _ble_syntax_dend
   5179 ##     文字列の変更範囲と、前回の解析でやり残した範囲を指定します。
   5180 ##
   5181 function ble/syntax/parse/determine-parse-range {
   5182   local flagSeekStat=0
   5183   ((i1=_ble_syntax_dbeg,i1>=end0&&(i1+=shift),
   5184     i2=_ble_syntax_dend,i2>=end0&&(i2+=shift),
   5185     (i1<0||beg<i1)&&(i1=beg,flagSeekStat=1),
   5186     (i2<0||i2<end)&&(i2=end),
   5187     (i2>iN)&&(i2=iN),
   5188     j2=i2-shift))
   5189 
   5190   if ((flagSeekStat)); then
   5191     # beg より前の最後の stat の位置まで戻る
   5192     local lookahead='stat[7]'
   5193     local -a stat
   5194     while ((i1>0)); do
   5195       if [[ ${_ble_syntax_stat[--i1]} ]]; then
   5196         ble/string#split-words stat "${_ble_syntax_stat[i1]}"
   5197         ((i1+lookahead<=beg)) && break
   5198       fi
   5199     done
   5200   fi
   5201 
   5202 #%if !release
   5203   ble/util/assert '((0<=i1&&i1<=beg&&end<=i2&&i2<=iN))' "X2 0 <= $i1 <= $beg <= $end <= $i2 <= $iN"
   5204 #%end
   5205 }
   5206 
   5207 function ble/syntax/parse/check-end {
   5208   [[ ${_ble_syntax_context_end[ctx]} ]] && "${_ble_syntax_context_end[ctx]}"
   5209 }
   5210 
   5211 ## @fn ble/syntax/parse text opts [beg end end0]
   5212 ##
   5213 ##   @param[in]     text
   5214 ##     解析対象の文字列を指定します。
   5215 ##
   5216 ##   @param[in]     opts
   5217 ##     細かい動作を制御するオプションを指定します。
   5218 ##
   5219 ##   @param[in]     beg                text変更範囲 開始点 (既定値 = text先頭)
   5220 ##   @param[in]     end                text変更範囲 終了点 (既定値 = text末端)
   5221 ##   @param[in]     end0               長さが変わった時用 (既定値 = end)
   5222 ##     これらの引数はtextに変更があった場合にその範囲を伝達するのに用います。
   5223 ##
   5224 ##   @var  [in,out] _ble_syntax_dbeg   解析予定範囲 開始点 (初期値 -1 = 解析予定無し)
   5225 ##   @var  [in,out] _ble_syntax_dend   解析予定範囲 終了点 (初期値 -1 = 解析予定無し)
   5226 ##     これらの変数はどの部分を解析する必要があるかを記録します。
   5227 ##     beg end beg2 end2 を用いてtextの変更範囲を指定しても、
   5228 ##     その変更範囲に対する解析を即座に完了させる訳ではなく逐次更新します。
   5229 ##     ここには前回の parse 呼出でやり残した解析範囲の情報が格納されます。
   5230 ##
   5231 ##   @var  [in,out] _ble_syntax_stat[] (内部使用) 解析途中状態を記録
   5232 ##   @var  [in,out] _ble_syntax_nest[] (内部使用) 入れ子の構造を記録
   5233 ##   @var  [in,out] _ble_syntax_attr[] 各文字の属性
   5234 ##   @var  [in,out] _ble_syntax_tree[] シェル単語の情報を記録
   5235 ##     これらの変数には解析結果が格納されます。
   5236 ##
   5237 ##   @var  [in,out] _ble_syntax_attr_umin
   5238 ##   @var  [in,out] _ble_syntax_attr_umax
   5239 ##   @var  [in,out] _ble_syntax_word_umin
   5240 ##   @var  [in,out] _ble_syntax_word_umax
   5241 ##     今回の呼出によって文法的な解釈の変更が行われた範囲を更新します。
   5242 ##
   5243 function ble/syntax/parse {
   5244   local text=$1 iN=${#1}
   5245   local opts=$2
   5246   local beg=${3:-0} end=${4:-$iN} end0=${5:-0}
   5247   ((end==beg&&end0==beg&&_ble_syntax_dbeg<0)) && return 0
   5248 
   5249   local IFS=$_ble_term_IFS
   5250 
   5251   local shift=$((end-end0))
   5252 #%if !release
   5253   ble/util/assert \
   5254     '((0<=beg&&beg<=end&&end<=iN&&beg<=end0))' \
   5255     "X1 0 <= beg:$beg <= end:$end <= iN:$iN, beg:$beg <= end0:$end0 (shift=$shift text=$text)" ||
   5256     ((beg=0,end=iN))
   5257 #%else
   5258   ((0<=beg&&beg<=end&&end<=iN&&beg<=end0)) || ((beg=0,end=iN))
   5259 #%end
   5260 
   5261   # 解析予定範囲の更新
   5262   #   @var i1 解析範囲開始
   5263   #   @var i2 解析必要範囲終端 (此処以降で文脈が一致した時に解析終了)
   5264   #   @var j2 シフト前の解析終端
   5265   local i1 i2 j2
   5266   ble/syntax/parse/determine-parse-range
   5267 
   5268   ble/syntax/vanishing-word/register _ble_syntax_tree 0 "$i1" "$j2" 0 "$i2"
   5269 
   5270   ble/syntax/parse/shift
   5271 
   5272   # 解析途中状態の復元
   5273   local ctx wbegin wtype inest tchild tprev nparam ilook
   5274   if ((i1>0)) && [[ ${_ble_syntax_stat[i1]} ]]; then
   5275     local -a stat
   5276     ble/string#split-words stat "${_ble_syntax_stat[i1]}"
   5277     local wlen=${stat[1]} nlen=${stat[3]} tclen=${stat[4]} tplen=${stat[5]}
   5278     ctx=${stat[0]}
   5279     wbegin=$((wlen<0?wlen:i1-wlen))
   5280     wtype=${stat[2]}
   5281     inest=$((nlen<0?nlen:i1-nlen))
   5282     tchild=$((tclen<0?tclen:i1-tclen))
   5283     tprev=$((tplen<0?tplen:i1-tplen))
   5284     nparam=${stat[6]}; [[ $nparam == none ]] && nparam=
   5285     ilook=$((i1+${stat[7]:-1}))
   5286   else
   5287     # 初期値
   5288     ctx=$CTX_UNSPECIFIED ##!< 現在の解析の文脈
   5289     ble/syntax:"$_ble_syntax_lang"/initialize-ctx # ctx 初期化
   5290     wbegin=-1       ##!< シェル単語内にいる時、シェル単語の開始位置
   5291     wtype=-1        ##!< シェル単語内にいる時、シェル単語の種類
   5292     inest=-1        ##!< 入れ子の時、親の開始位置
   5293     tchild=-1
   5294     tprev=-1
   5295     nparam=
   5296     ilook=1
   5297   fi
   5298 
   5299   # 前回までに解析が終わっている部分 [0,i1), [i2,iN)
   5300   local -a tail_syntax_stat tail_syntax_tree tail_syntax_nest tail_syntax_attr
   5301   tail_syntax_stat=("${_ble_syntax_stat[@]:j2:iN-i2+1}")
   5302   tail_syntax_tree=("${_ble_syntax_tree[@]:j2:iN-i2}")
   5303   tail_syntax_nest=("${_ble_syntax_nest[@]:j2:iN-i2}")
   5304   tail_syntax_attr=("${_ble_syntax_attr[@]:j2:iN-i2}")
   5305   ble/array#reserve-prototype "$iN"
   5306   _ble_syntax_stat=("${_ble_syntax_stat[@]::i1}" "${_ble_array_prototype[@]:i1:iN-i1}") # 再開用データ
   5307   _ble_syntax_tree=("${_ble_syntax_tree[@]::i1}" "${_ble_array_prototype[@]:i1:iN-i1}") # 単語
   5308   _ble_syntax_nest=("${_ble_syntax_nest[@]::i1}" "${_ble_array_prototype[@]:i1:iN-i1}") # 入れ子の親
   5309   _ble_syntax_attr=("${_ble_syntax_attr[@]::i1}" "${_ble_array_prototype[@]:i1:iN-i1}") # 文脈・色とか
   5310 
   5311   ble/syntax:"$_ble_syntax_lang"/initialize-vars
   5312 
   5313   # 解析
   5314   _ble_syntax_text=$text
   5315   local i sstat tail
   5316 #%if !release
   5317   local debug_p1
   5318 #%end
   5319   for ((i=i1;i<iN;)); do
   5320     ble/syntax/parse/serialize-stat
   5321     if ((i>=i2)) && [[ ${tail_syntax_stat[i-i2]} == "$sstat" ]]; then
   5322       if ble/syntax/parse/nest-equals "$inest"; then
   5323         # 前回の解析と同じ状態になった時 → 残りは前回の結果と同じ
   5324         _ble_syntax_stat=("${_ble_syntax_stat[@]::i}" "${tail_syntax_stat[@]:i-i2}")
   5325         _ble_syntax_tree=("${_ble_syntax_tree[@]::i}" "${tail_syntax_tree[@]:i-i2}")
   5326         _ble_syntax_nest=("${_ble_syntax_nest[@]::i}" "${tail_syntax_nest[@]:i-i2}")
   5327         _ble_syntax_attr=("${_ble_syntax_attr[@]::i}" "${tail_syntax_attr[@]:i-i2}")
   5328         break
   5329       fi
   5330     fi
   5331     _ble_syntax_stat[i]=$sstat
   5332 
   5333     tail=${text:i}
   5334 #%if !release
   5335     debug_p1=$i
   5336 #%end
   5337     # 処理
   5338     "${_ble_syntax_context_proc[ctx]}" || ((_ble_syntax_attr[i]=ATTR_ERR,i++))
   5339 
   5340     # nest-pop で CMDI/ARGI になる事もあるし、
   5341     # また単語終端な文字でも FCTX が失敗する事もある (unrecognized な場合) ので、
   5342     # (FCTX の中や直後ではなく) ここで単語終端をチェック
   5343     ble/syntax/parse/check-end
   5344   done
   5345 #%if !release
   5346   builtin unset -v debug_p1
   5347 #%end
   5348 
   5349   ble/syntax/vanishing-word/register tail_syntax_tree "$((-i2))" "$((i2+1))" "$i" 0 "$i"
   5350 
   5351   ble/syntax/urange#update _ble_syntax_attr_ "$i1" "$i"
   5352 
   5353   (((i>=i2)?(
   5354       _ble_syntax_dbeg=_ble_syntax_dend=-1
   5355     ):(
   5356       _ble_syntax_dbeg=i,_ble_syntax_dend=i2)))
   5357 
   5358   # 終端の状態の記録
   5359   if ((i>=iN)); then
   5360     ((i=iN))
   5361     ble/syntax/parse/serialize-stat
   5362     _ble_syntax_stat[i]=$sstat
   5363 
   5364     # ネスト開始点のエラー表示は +syntax 内で。
   5365     # ここで設定すると部分更新の際に取り消しできないから。
   5366     if ((inest>0)); then
   5367       ((_ble_syntax_attr[iN-1]=ATTR_ERR))
   5368       while ((inest>=0)); do
   5369         ((i=inest))
   5370         ble/syntax/parse/nest-pop
   5371         ((inest>=i&&(inest=i-1)))
   5372       done
   5373     fi
   5374   fi
   5375 
   5376 #%if !release
   5377   ble/util/assert \
   5378     '((${#_ble_syntax_stat[@]}==iN+1))' \
   5379     "unexpected array length #arr=${#_ble_syntax_stat[@]} (expected to be $iN), #proto=${#_ble_array_prototype[@]} should be >= $iN"
   5380 #%end
   5381 }
   5382 
   5383 ## @fn ble/syntax/highlight text [lang]
   5384 ##   @param[in] text
   5385 ##   @param[in] lang
   5386 ##   @var[out] ret
   5387 function ble/syntax/highlight {
   5388   local text=$1 lang=${2:-bash} cache_prefix=$3
   5389 
   5390   local -a _ble_highlight_layer_list=(plain syntax)
   5391   local -a vars=()
   5392   ble/array#push vars "${_ble_syntax_VARNAMES[@]}"
   5393   ble/array#push vars "${_ble_highlight_layer_plain_VARNAMES[@]}"
   5394   ble/array#push vars "${_ble_highlight_layer_syntax_VARNAMES[@]}"
   5395 
   5396   local "${vars[@]/%/=}" # WA #D1570 checked
   5397   if [[ $cache_prefix ]] && ((${cache_prefix}_INITIALIZED++)); then
   5398     ble/util/restore-vars "$cache_prefix" "${vars[@]}"
   5399 
   5400     ble/string#common-prefix "$_ble_syntax_text" "$text"
   5401     local beg=${#ret}
   5402     ble/string#common-suffix "${_ble_syntax_text:beg}" "${text:beg}"
   5403     local end=$((${#text}-${#ret})) end0=$((${#_ble_syntax_text}-${#ret}))
   5404   else
   5405     ble/syntax/initialize-vars
   5406     ble/highlight/layer:plain/initialize-vars
   5407     ble/highlight/layer:syntax/initialize-vars
   5408     _ble_syntax_lang=$lang
   5409     local beg=0 end=${#text} end0=0
   5410   fi
   5411 
   5412   ble/syntax/parse "$text" '' "$beg" "$end" "$end0"
   5413 
   5414   local HIGHLIGHT_BUFF HIGHLIGHT_UMIN HIGHLIGHT_UMAX
   5415   ble/highlight/layer/update "$text" '' "$beg" "$end" "$end0"
   5416   IFS= builtin eval "ret=\"\${$HIGHLIGHT_BUFF[*]}\""
   5417 
   5418   [[ $cache_prefix ]] &&
   5419     ble/util/save-vars "$cache_prefix" "${vars[@]}"
   5420   return 0
   5421 }
   5422 
   5423 #==============================================================================
   5424 #
   5425 # syntax-complete
   5426 #
   5427 #==============================================================================
   5428 
   5429 # ## @fn ble/syntax/getattr index
   5430 # function ble/syntax/getattr {
   5431 #   local i
   5432 #   attr=
   5433 #   for ((i=$1;i>=0;i--)); do
   5434 #     if [[ ${_ble_syntax_attr[i]} ]]; then
   5435 #       ((attr=_ble_syntax_attr[i]))
   5436 #       return 0
   5437 #     fi
   5438 #   done
   5439 #   return 1
   5440 # }
   5441 
   5442 # ## @fn ble/syntax/getstat index
   5443 # function ble/syntax/getstat {
   5444 #   local i
   5445 #   for ((i=$1;i>=0;i--)); do
   5446 #     if [[ ${_ble_syntax_stat[i]} ]]; then
   5447 #       ble/string#split-words stat "${_ble_syntax_stat[i]}"
   5448 #       return 0
   5449 #     fi
   5450 #   done
   5451 #   return 1
   5452 # }
   5453 
   5454 function ble/syntax/completion-context/.add {
   5455   local source=$1
   5456   local comp1=$2
   5457   ble/util/assert '[[ $source && comp1 -ge 0 ]]'
   5458   sources[${#sources[*]}]="$source $comp1"
   5459 }
   5460 
   5461 ## @fn ble/syntax/completion-context/.check/parameter-expansion
   5462 ##   @var[in] text istat index ctx
   5463 function ble/syntax/completion-context/.check/parameter-expansion {
   5464   local rex_paramx='^(\$(\{[!#]?)?)([_a-zA-Z][_a-zA-Z0-9]*)?$'
   5465   if [[ ${text:istat:index-istat} =~ $rex_paramx ]]; then
   5466     local rematch1=${BASH_REMATCH[1]}
   5467     local source=variable
   5468     if [[ $rematch1 == '${'* ]]; then
   5469       source=variable:b # suffix }
   5470     elif ((ctx==CTX_BRACE1||ctx==CTX_BRACE2)); then
   5471       source=variable:n # no suffix
   5472     fi
   5473     ble/syntax/completion-context/.add "$source" "$((istat+${#rematch1}))"
   5474   fi
   5475 }
   5476 
   5477 ## @fn ble/syntax/completion-context/.check-prefix/ctx:*
   5478 ##
   5479 ##   @var[in] text index
   5480 ##     補完対象のコマンドラインと現在のカーソルの位置を指定します。
   5481 ##
   5482 ##   @var[in] istat stat
   5483 ##     直前の解析再開点の位置と記録されている情報を指定します。
   5484 ##
   5485 ##   @var[in] ctx wbeg wlen
   5486 ##     直前の解析再開点の文脈・単語開始点・
   5487 ##     直前の解析再開点に於ける単語の長さを指定します。
   5488 ##
   5489 ##   @var[in] rex_param
   5490 ##
   5491 
   5492 ## @fn ble/syntax/completion-context/.check-prefix/ctx:inside-command
   5493 ##   CMDI 系統 (コマンドの続き) の文脈に対する補完文脈の生成
   5494 _ble_syntax_bash_complete_check_prefix[CTX_CMDI]=inside-command
   5495 function ble/syntax/completion-context/.check-prefix/ctx:inside-command {
   5496   if ((wlen>=0)); then
   5497     ble/syntax/completion-context/.add command "$wbeg"
   5498     if [[ ${text:wbeg:index-wbeg} =~ $rex_param ]]; then
   5499       ble/syntax/completion-context/.add variable:= "$wbeg"
   5500     fi
   5501   fi
   5502   ble/syntax/completion-context/.check/parameter-expansion
   5503 }
   5504 ## @fn ble/syntax/completion-context/.check-prefix/ctx:inside-argument source
   5505 ##   ARGI 系統 (引数の続き) の文脈に対する補完文脈の生成
   5506 ##   @param[in] source
   5507 _ble_syntax_bash_complete_check_prefix[CTX_ARGI]='inside-argument argument'
   5508 _ble_syntax_bash_complete_check_prefix[CTX_ARGQ]='inside-argument argument'
   5509 _ble_syntax_bash_complete_check_prefix[CTX_FARGI1]='inside-argument variable:w'
   5510 _ble_syntax_bash_complete_check_prefix[CTX_FARGI3]='inside-argument argument'
   5511 _ble_syntax_bash_complete_check_prefix[CTX_FARGQ3]='inside-argument argument'
   5512 _ble_syntax_bash_complete_check_prefix[CTX_CARGI1]='inside-argument argument'
   5513 _ble_syntax_bash_complete_check_prefix[CTX_CARGQ1]='inside-argument argument'
   5514 _ble_syntax_bash_complete_check_prefix[CTX_CPATI]='inside-argument argument'
   5515 _ble_syntax_bash_complete_check_prefix[CTX_CPATQ]='inside-argument argument'
   5516 _ble_syntax_bash_complete_check_prefix[CTX_COARGI]='inside-argument variable command'
   5517 _ble_syntax_bash_complete_check_prefix[CTX_VALI]='inside-argument sabbrev file'
   5518 _ble_syntax_bash_complete_check_prefix[CTX_VALQ]='inside-argument sabbrev file'
   5519 _ble_syntax_bash_complete_check_prefix[CTX_CONDI]='inside-argument sabbrev file option'
   5520 _ble_syntax_bash_complete_check_prefix[CTX_CONDQ]='inside-argument sabbrev file'
   5521 _ble_syntax_bash_complete_check_prefix[CTX_ARGVI]='inside-argument sabbrev variable:='
   5522 _ble_syntax_bash_complete_check_prefix[CTX_ARGEI]='inside-argument command:D variable:= file'
   5523 function ble/syntax/completion-context/.check-prefix/ctx:inside-argument {
   5524   if ((wlen>=0)); then
   5525     local source
   5526     for source; do
   5527       ble/syntax/completion-context/.add "$source" "$wbeg"
   5528       if [[ $source != argument ]]; then
   5529         local sub=${text:wbeg:index-wbeg}
   5530         if [[ $sub == *[=:]* ]]; then
   5531           sub=${sub##*[=:]}
   5532           ble/syntax/completion-context/.add "$source" "$((index-${#sub}))"
   5533         fi
   5534       fi
   5535     done
   5536   fi
   5537   ble/syntax/completion-context/.check/parameter-expansion
   5538 }
   5539 
   5540 ## @fn ble/syntax/completion-context/.check-prefix/ctx:next-command
   5541 ##   CMDX 系統の文脈に対する補完文脈の生成
   5542 _ble_syntax_bash_complete_check_prefix[CTX_CMDX]=next-command
   5543 _ble_syntax_bash_complete_check_prefix[CTX_CMDX1]=next-command
   5544 _ble_syntax_bash_complete_check_prefix[CTX_CMDXT]=next-command
   5545 _ble_syntax_bash_complete_check_prefix[CTX_CMDXV]=next-command
   5546 function ble/syntax/completion-context/.check-prefix/.test-redirection {
   5547   ##   @var[in] index
   5548   local word=$1
   5549   [[ $word =~ ^$_ble_syntax_bash_RexRedirect$ ]] || return 1
   5550   # 文法的に元々リダイレクトは許されない
   5551   ((ctx==CTX_CMDXC||ctx==CTX_CMDXD||ctx==CTX_CMDXD0||ctx==CTX_FARGX3)) && return 0
   5552 
   5553   local rematch3=${BASH_REMATCH[3]}
   5554   case $rematch3 in
   5555   ('>&')
   5556     ble/syntax/completion-context/.add fd "$index"
   5557     ble/syntax/completion-context/.add file:no-fd "$index" ;;
   5558   (*'&')
   5559     ble/syntax/completion-context/.add fd "$index" ;;
   5560   ('<<'|'<<-')
   5561     ble/syntax/completion-context/.add wordlist:EOF:END:HERE "$index" ;;
   5562   ('<<<'|*)
   5563     ble/syntax/completion-context/.add file "$index" ;;
   5564   esac
   5565   return 0
   5566 }
   5567 function ble/syntax/completion-context/.check-prefix/ctx:next-command {
   5568   # 直前の再開点が CMDX だった場合、
   5569   # 現在地との間にコマンド名があればそれはコマンドである。
   5570   # スペースや ;&| 等のコマンド以外の物がある可能性もある事に注意する。
   5571   local word=${text:istat:index-istat}
   5572 
   5573   # コマンドのチェック
   5574   if ble/syntax:bash/simple-word/is-simple-or-open-simple "$word"; then
   5575     # 単語が istat から開始している場合
   5576     ble/syntax/completion-context/.add command "$istat"
   5577 
   5578     # 変数・代入のチェック
   5579     if local rex='^[_a-zA-Z][_a-zA-Z0-9]*(\+?=)?$' && [[ $word =~ $rex ]]; then
   5580       if [[ $word == *= ]]; then
   5581         if ((_ble_bash>=30100)) || [[ $word != *+= ]]; then
   5582           # VAR=<argument>: 現在位置から argument 候補を生成する
   5583           ble/syntax/completion-context/.add argument "$index"
   5584         fi
   5585       else
   5586         # VAR<+variable>: 単語を変数名の一部と思って変数名を生成する
   5587         ble/syntax/completion-context/.add variable:= "$istat"
   5588       fi
   5589     fi
   5590   elif ble/syntax/completion-context/.check-prefix/.test-redirection; then
   5591     true
   5592   elif [[ $word =~ ^$_ble_syntax_bash_RexSpaces$ ]]; then
   5593     # 単語が未だ開始していない時 (空白)
   5594     shopt -q no_empty_cmd_completion ||
   5595       ble/syntax/completion-context/.add command "$index"
   5596   fi
   5597 
   5598   ble/syntax/completion-context/.check/parameter-expansion
   5599 }
   5600 ## @fn ble/syntax/completion-context/.check-prefix/ctx:next-argument
   5601 ##   ARGX 系統の文脈に対する補完文脈の生成
   5602 _ble_syntax_bash_complete_check_prefix[CTX_ARGX]=next-argument
   5603 _ble_syntax_bash_complete_check_prefix[CTX_CARGX1]=next-argument
   5604 _ble_syntax_bash_complete_check_prefix[CTX_CPATX]=next-argument
   5605 _ble_syntax_bash_complete_check_prefix[CTX_FARGX3]=next-argument
   5606 _ble_syntax_bash_complete_check_prefix[CTX_COARGX]=next-argument
   5607 _ble_syntax_bash_complete_check_prefix[CTX_ARGVX]=next-argument
   5608 _ble_syntax_bash_complete_check_prefix[CTX_ARGEX]=next-argument
   5609 _ble_syntax_bash_complete_check_prefix[CTX_VALX]=next-argument
   5610 _ble_syntax_bash_complete_check_prefix[CTX_CONDX]=next-argument
   5611 _ble_syntax_bash_complete_check_prefix[CTX_RDRS]=next-argument
   5612 function ble/syntax/completion-context/.check-prefix/ctx:next-argument {
   5613   local source
   5614   if ((ctx==CTX_ARGX||ctx==CTX_CARGX1||ctx==CTX_FARGX3)); then
   5615     source=(argument)
   5616   elif ((ctx==CTX_COARGX)); then
   5617     # Note: variable:w でも variable:= でもなく variable にしているのは、
   5618     #   coproc の後は変数名が来ても "変数代入 " か "coproc 配列名"
   5619     #   か分からないので、取り敢えず何も挿入しない様にする為。
   5620     source=(command variable)
   5621   elif ((ctx==CTX_ARGVX)); then
   5622     source=(sabbrev variable:= option)
   5623   elif ((ctx==CTX_ARGEX)); then
   5624     source=(command:D variable:= file)
   5625   elif ((ctx==CTX_CONDX)); then
   5626     source=(sabbrev file option)
   5627   else
   5628     source=(sabbrev file)
   5629   fi
   5630 
   5631   local word=${text:istat:index-istat}
   5632   if ble/syntax:bash/simple-word/is-simple-or-open-simple "$word"; then
   5633     # 単語が istat から開始している場合
   5634     local src
   5635     for src in "${source[@]}"; do
   5636       ble/syntax/completion-context/.add "$src" "$istat"
   5637     done
   5638 
   5639     if [[ ${source[0]} != argument ]]; then
   5640       # 引数の途中に unquoted '=' がある場合
   5641       local rex="^([^='\"\$\\{}]|\\.)*="
   5642       if [[ $word =~ $rex ]]; then
   5643         word=${word:${#BASH_REMATCH}}
   5644         ble/syntax/completion-context/.add rhs "$((index-${#word}))"
   5645       fi
   5646     fi
   5647   elif ble/syntax/completion-context/.check-prefix/.test-redirection "$word"; then
   5648     true
   5649   elif [[ $word =~ ^$_ble_syntax_bash_RexSpaces$ ]]; then
   5650     # 単語が未だ開始していない時 (空白)
   5651     local src
   5652     for src in "${source[@]}"; do
   5653       ble/syntax/completion-context/.add "$src" "$index"
   5654     done
   5655   fi
   5656   ble/syntax/completion-context/.check/parameter-expansion
   5657 }
   5658 ## @fn ble/syntax/completion-context/.check-prefix/ctx:next-compound
   5659 ##   複合コマンドを補完します。
   5660 _ble_syntax_bash_complete_check_prefix[CTX_CMDXC]=next-compound
   5661 function ble/syntax/completion-context/.check-prefix/ctx:next-compound {
   5662   local rex word=${text:istat:index-istat}
   5663   if [[ ${text:istat:index-istat} =~ $rex_param ]]; then
   5664     ble/syntax/completion-context/.add wordlist:-r:'for:select:case:if:while:until' "$istat"
   5665   elif rex='^[[({]+$'; [[ $word =~ $rex ]]; then
   5666     ble/syntax/completion-context/.add wordlist:-r:'(:{:((:[[' "$istat"
   5667   fi
   5668 }
   5669 ## @fn ble/syntax/completion-context/.check-prefix/ctx:next-identifier source
   5670 ##   エスケープやクォートのない単純な単語に補完する文脈。
   5671 ##   @param[in] source
   5672 _ble_syntax_bash_complete_check_prefix[CTX_FARGX1]="next-identifier variable:w" # CTX_FARGX1 → (( でなければ 変数名
   5673 _ble_syntax_bash_complete_check_prefix[CTX_SARGX1]="next-identifier variable:w"
   5674 function ble/syntax/completion-context/.check-prefix/ctx:next-identifier {
   5675   local source=$1 word=${text:istat:index-istat}
   5676   if [[ $word =~ $rex_param ]]; then
   5677     ble/syntax/completion-context/.add "$source" "$istat"
   5678   elif [[ $word =~ ^$_ble_syntax_bash_RexSpaces$ ]]; then
   5679     # 単語が未だ開始していない時は現在位置から補完開始
   5680     ble/syntax/completion-context/.add "$source" "$index"
   5681   else
   5682     ble/syntax/completion-context/.add none "$istat"
   5683   fi
   5684 }
   5685 _ble_syntax_bash_complete_check_prefix[CTX_ARGX0]="next-word sabbrev"
   5686 _ble_syntax_bash_complete_check_prefix[CTX_CMDX0]="next-word sabbrev"
   5687 _ble_syntax_bash_complete_check_prefix[CTX_CPATX0]="next-word sabbrev"
   5688 _ble_syntax_bash_complete_check_prefix[CTX_CMDXD0]="next-word wordlist:-rs:';:{:do'"
   5689 _ble_syntax_bash_complete_check_prefix[CTX_CMDXD]="next-word wordlist:-rs:'{:do'"
   5690 _ble_syntax_bash_complete_check_prefix[CTX_CMDXE]="next-word wordlist:-rs:'}:fi:done:esac:then:elif:else:do'"
   5691 _ble_syntax_bash_complete_check_prefix[CTX_CARGX2]="next-word wordlist:-rs:'in'"
   5692 _ble_syntax_bash_complete_check_prefix[CTX_CARGI2]="next-word wordlist:-rs:'in'"
   5693 _ble_syntax_bash_complete_check_prefix[CTX_FARGX2]="next-word wordlist:-rs:'in:do'"
   5694 _ble_syntax_bash_complete_check_prefix[CTX_FARGI2]="next-word wordlist:-rs:'in:do'"
   5695 function ble/syntax/completion-context/.check-prefix/ctx:next-word {
   5696   local source=$1 word=${text:istat:index-istat} rex=$'^[^ \t]*$'
   5697   if [[ $word =~ ^$_ble_syntax_bash_RexSpaces$ ]]; then
   5698     ble/syntax/completion-context/.add "$source" "$index"
   5699   else
   5700     ble/syntax/completion-context/.add "$source" "$istat"
   5701   fi
   5702 }
   5703 ## @fn ble/syntax/completion-context/.check-prefix/ctx:time-argument {
   5704 _ble_syntax_bash_complete_check_prefix[CTX_TARGX1]=time-argument
   5705 _ble_syntax_bash_complete_check_prefix[CTX_TARGI1]=time-argument
   5706 _ble_syntax_bash_complete_check_prefix[CTX_TARGX2]=time-argument
   5707 _ble_syntax_bash_complete_check_prefix[CTX_TARGI2]=time-argument
   5708 function ble/syntax/completion-context/.check-prefix/ctx:time-argument {
   5709   ble/syntax/completion-context/.check/parameter-expansion
   5710   ble/syntax/completion-context/.add command "$istat"
   5711   if ((ctx==CTX_TARGX1)); then
   5712     local rex='^-p?$' words='-p'
   5713     ((_ble_bash>=50100)) &&
   5714       rex='^-[-p]?$' words='-p':'--'
   5715     [[ ${text:istat:index-istat} =~ $rex ]] &&
   5716       ble/syntax/completion-context/.add wordlist:--:"$words" "$istat"
   5717   elif ((ctx==CTX_TARGX2)); then
   5718     local rex='^--?$'
   5719     [[ ${text:istat:index-istat} =~ $rex ]] &&
   5720       ble/syntax/completion-context/.add wordlist:--:'--' "$istat"
   5721   fi
   5722 }
   5723 ## @fn ble/syntax/completion-context/.check-prefix/ctx:quote
   5724 _ble_syntax_bash_complete_check_prefix[CTX_QUOT]=quote
   5725 function ble/syntax/completion-context/.check-prefix/ctx:quote {
   5726   ble/syntax/completion-context/.check/parameter-expansion
   5727   ble/syntax/completion-context/.check-prefix/ctx:quote/.check-container-word
   5728 }
   5729 function ble/syntax/completion-context/.check-prefix/ctx:quote/.check-container-word {
   5730   # Note: CTX_QUOTE は nest の中にあるので、一旦外側に出て単語を探す。
   5731 
   5732   local nlen=${stat[3]}; ((nlen>=0)) || return 1
   5733   local inest=$((nlen<0?nlen:istat-nlen))
   5734 
   5735   local nest; ble/string#split-words nest "${_ble_syntax_nest[inest]}"
   5736   [[ ${nest[0]} ]] || return 1
   5737 
   5738   local wlen2=${nest[1]}; ((wlen2>=0)) || return 1
   5739   local wbeg2=$((wlen2<0?wlen2:inest-wlen2))
   5740   if ble/syntax:bash/simple-word/is-simple-or-open-simple "${text:wbeg2:index-wbeg2}"; then
   5741     local wt=${nest[2]}
   5742     [[ ${_ble_syntax_bash_command_EndWtype[wt]} ]] &&
   5743       wt=${_ble_syntax_bash_command_EndWtype[wt]}
   5744     if ((wt==CTX_CMDI)); then
   5745       ble/syntax/completion-context/.add command "$wbeg2"
   5746     elif ((wt==CTX_ARGI||wt==CTX_ARGVI||wt==CTX_ARGEI||wt==CTX_FARGI2||wt==CTX_CARGI2)); then
   5747       ble/syntax/completion-context/.add argument "$wbeg2"
   5748     elif ((wt==CTX_CPATI)); then # case pattern の内部
   5749       #ble/syntax/completion-context/.add file "$wbeg2"
   5750       return 0
   5751     fi
   5752   fi
   5753 }
   5754 
   5755 ## @fn ble/syntax/completion-context/.check-prefix/ctx:redirection
   5756 ##   redirect の filename 部分を補完する文脈
   5757 _ble_syntax_bash_complete_check_prefix[CTX_RDRF]=redirection
   5758 _ble_syntax_bash_complete_check_prefix[CTX_RDRD2]=redirection
   5759 _ble_syntax_bash_complete_check_prefix[CTX_RDRD]=redirection
   5760 function ble/syntax/completion-context/.check-prefix/ctx:redirection {
   5761   ble/syntax/completion-context/.check/parameter-expansion
   5762   local p=$((wlen>=0?wbeg:istat))
   5763   if ble/syntax:bash/simple-word/is-simple-or-open-simple "${text:p:index-p}"; then
   5764     if ((ctx==CTX_RDRF)); then
   5765       ble/syntax/completion-context/.add file "$p"
   5766     elif ((ctx==CTX_RDRD)); then
   5767       ble/syntax/completion-context/.add fd "$p"
   5768     elif ((ctx==CTX_RDRD2)); then
   5769       ble/syntax/completion-context/.add fd "$p"
   5770       ble/syntax/completion-context/.add file:no-fd "$p"
   5771     fi
   5772   fi
   5773 }
   5774 _ble_syntax_bash_complete_check_prefix[CTX_RDRH]=here
   5775 _ble_syntax_bash_complete_check_prefix[CTX_RDRI]=here
   5776 function ble/syntax/completion-context/.check-prefix/ctx:here {
   5777   local p=$((wlen>=0?wbeg:istat))
   5778   ble/syntax/completion-context/.add wordlist:EOF:END:HERE "$p"
   5779 }
   5780 
   5781 
   5782 ## @fn ble/syntax/completion-context/.check-prefix/ctx:rhs
   5783 ##   VAR=value の value 部分を補完する文脈
   5784 _ble_syntax_bash_complete_check_prefix[CTX_VRHS]=rhs
   5785 _ble_syntax_bash_complete_check_prefix[CTX_ARGVR]=rhs
   5786 _ble_syntax_bash_complete_check_prefix[CTX_ARGER]=rhs
   5787 _ble_syntax_bash_complete_check_prefix[CTX_VALR]=rhs
   5788 function ble/syntax/completion-context/.check-prefix/ctx:rhs {
   5789   ble/syntax/completion-context/.check/parameter-expansion
   5790   if ((wlen>=0)); then
   5791     local p=$wbeg
   5792     local rex='^[_a-zA-Z0-9]+(\+?=|\[)'
   5793     ((ctx==CTX_VALR)) && rex='^(\[)'
   5794     if [[ ${text:p:index-p} =~ $rex ]]; then
   5795       if [[ ${BASH_REMATCH[1]} == '[' ]]; then
   5796         # CTX_VRHS:  arr[0]=x@ arr[1]+=x@
   5797         # CTX_ARGVR: declare arr[0]=x@ arr[1]+=x@
   5798         # CTX_VALR:  arr=([0]=x@ [1]+=x@)
   5799         local p1=$((wbeg+${#BASH_REMATCH}-1))
   5800         if local ret; ble/syntax:bash/find-end-of-array-index "$p1" "$index"; then
   5801           local p2=$ret
   5802           case ${_ble_syntax_text:p2:index-p2} in
   5803           (']='*)  ((p=p2+2)) ;;
   5804           (']+='*) ((p=p2+3)) ;;
   5805           (']+')
   5806             ble/syntax/completion-context/.add wordlist:-rW:'+=' "$((p2+1))"
   5807             p= ;;
   5808           esac
   5809         fi
   5810       else
   5811         # CTX_VRHS:  var=x@ var+=x@
   5812         # CTX_ARGVR: declare var=x@ var+=x@
   5813         ((p+=${#BASH_REMATCH}))
   5814       fi
   5815     fi
   5816   else
   5817     local p=$istat
   5818   fi
   5819 
   5820   if [[ $p ]] && ble/syntax:bash/simple-word/is-simple-or-open-simple "${text:p:index-p}"; then
   5821     ble/syntax/completion-context/.add rhs "$p"
   5822   fi
   5823 }
   5824 
   5825 _ble_syntax_bash_complete_check_prefix[CTX_PARAM]=param
   5826 function ble/syntax/completion-context/.check-prefix/ctx:param {
   5827   local tail=${text:istat:index-istat}
   5828   if [[ $tail == : ]]; then
   5829     return 0
   5830   elif [[ $tail == '}'* ]]; then
   5831     local nlen=${stat[3]}
   5832     local inest=$((nlen<0?nlen:istat-nlen))
   5833     ((0<=inest&&inest<istat)) &&
   5834       ble/syntax/completion-context/.check-prefix "$inest"
   5835     return "$?"
   5836   else
   5837     return 1
   5838   fi
   5839 }
   5840 
   5841 ## @fn ble/syntax/completion-context/.check-prefix/ctx:expr
   5842 ##   数式中の変数名を補完する文脈
   5843 _ble_syntax_bash_complete_check_prefix[CTX_EXPR]=expr
   5844 function ble/syntax/completion-context/.check-prefix/ctx:expr {
   5845   local tail=${text:istat:index-istat} rex='[_a-zA-Z]+$'
   5846   if [[ $tail =~ $rex ]]; then
   5847     local p=$((index-${#BASH_REMATCH}))
   5848     ble/syntax/completion-context/.add variable:a "$p"
   5849     return 0
   5850   elif [[ $tail == ']'* ]]; then
   5851     local inest=... ntype
   5852     local nlen=${stat[3]}; ((nlen>=0)) || return 1
   5853     local inest=$((istat-nlen))
   5854     ble/syntax/parse/nest-type # ([in] inest; [out] ntype)
   5855 
   5856     if [[ $ntype == [ad]'[' ]]; then
   5857       # arr[...]=@ or arr=([...]=@)
   5858       if [[ $tail == ']' ]]; then
   5859         ble/syntax/completion-context/.add wordlist:-rW:'=' "$((istat+1))"
   5860       elif ((_ble_bash>=30100)) && [[ $tail == ']+' ]]; then
   5861         ble/syntax/completion-context/.add wordlist:-rW:'+=' "$((istat+1))"
   5862       elif [[ $tail == ']=' || _ble_bash -ge 30100 && $tail == ']+=' ]]; then
   5863         ble/syntax/completion-context/.add rhs "$index"
   5864       fi
   5865     fi
   5866   fi
   5867 }
   5868 
   5869 ## @fn ble/syntax/completion-context/.check-prefix/ctx:expr
   5870 ##   ブレース展開の中での補完
   5871 _ble_syntax_bash_complete_check_prefix[CTX_BRACE1]=brace
   5872 _ble_syntax_bash_complete_check_prefix[CTX_BRACE2]=brace
   5873 function ble/syntax/completion-context/.check-prefix/ctx:brace {
   5874   # (1) CTX_BRACE{1,2} 以外になるまで nest を出る
   5875   local ctx1=$ctx istat1=$istat nlen1=${stat[3]}
   5876   ((nlen1>=0)) || return 1
   5877   local inest1=$((istat1-nlen1))
   5878   while :; do
   5879     local nest=${_ble_syntax_nest[inest1]}
   5880     [[ $nest ]] || return 1
   5881     ble/string#split-words nest "$nest"
   5882     ctx1=${nest[0]}
   5883     ((ctx1==CTX_BRACE1||ctx1==CTX_BRACE2)) || break
   5884     inest1=${nest[3]}
   5885     ((inest1>=0)) || return 1
   5886   done
   5887 
   5888   # (2) 直前の stat
   5889   for ((istat1=inest1;1;istat1--)); do
   5890     ((istat1>=0)) || return 1
   5891     [[ ${_ble_syntax_stat[istat1]} ]] && break
   5892   done
   5893 
   5894   # (3) 単語の開始点
   5895   local stat1
   5896   ble/string#split-words stat1 "${_ble_syntax_stat[istat1]}"
   5897   local wlen=${stat1[1]}
   5898   local wbeg=$((wlen>=0?istat1-wlen:istat1))
   5899 
   5900   ble/syntax/completion-context/.check/parameter-expansion
   5901   ble/syntax/completion-context/.add argument "$wbeg"
   5902 }
   5903 
   5904 
   5905 ## @fn ble/syntax/completion-context/.search-last-istat index
   5906 ##   @param[in] index
   5907 ##   @var[out] ret
   5908 function ble/syntax/completion-context/.search-last-istat {
   5909   local index=$1 istat
   5910   for ((istat=index;istat>=0;istat--)); do
   5911     if [[ ${_ble_syntax_stat[istat]} ]]; then
   5912       ret=$istat
   5913       return 0
   5914     fi
   5915   done
   5916   ret=
   5917   return 1
   5918 }
   5919 
   5920 ## @fn ble/syntax/completion-context/.check-prefix from
   5921 ##   @param[in,opt] from
   5922 ##   @var[in] text
   5923 ##   @var[in] index
   5924 ##   @var[out] sources
   5925 function ble/syntax/completion-context/.check-prefix {
   5926   local rex_param='^[_a-zA-Z][_a-zA-Z0-9]*$'
   5927   local from=${1:-$((index-1))}
   5928 
   5929   local ret
   5930   ble/syntax/completion-context/.search-last-istat "$from" || return 1
   5931   local istat=$ret stat
   5932   ble/string#split-words stat "${_ble_syntax_stat[istat]}"
   5933   [[ ${stat[0]} ]] || return 1
   5934 
   5935   local ctx=${stat[0]} wlen=${stat[1]}
   5936   local wbeg=$((wlen<0?wlen:istat-wlen))
   5937   local name=${_ble_syntax_bash_complete_check_prefix[ctx]}
   5938   if [[ $name ]]; then
   5939     builtin eval "ble/syntax/completion-context/.check-prefix/ctx:$name"
   5940   fi
   5941 }
   5942 
   5943 ## @fn ble/syntax/completion-context/.check-here
   5944 ##   現在地点を開始点とする補完の可能性を列挙します
   5945 ##   @var[in]  text
   5946 ##   @var[in]  index
   5947 ##   @var[out] sources
   5948 function ble/syntax/completion-context/.check-here {
   5949   ((${#sources[*]})) && return 0
   5950   local -a stat
   5951   ble/string#split-words stat "${_ble_syntax_stat[index]}"
   5952   if [[ ${stat[0]} ]]; then
   5953     # Note: ここで CTX_CMDI や CTX_ARGI は処理しない。既に check-prefix で引っ
   5954     #   かかっている筈だから。
   5955     #
   5956     # Note (#D1690): 文句が出たので 当初引数類の補完はその場で開始しない事にし
   5957     #   たが、すると空文字列から補完を開始できなくなってしまった。やはり、ここ
   5958     #   で引数の補完を開始しなければならない。
   5959     #
   5960     #   そもそも .check-prefix で補完文脈を生成できる時は、入力済みの内容に拘ら
   5961     #   ず補完文脈を生成するべきである。それにより、.check-here に到達するのは
   5962     #   補完文脈が他に生成しようがない状況に限定される。この時、実は
   5963     #   .check-here の側は修正は必要なかった。
   5964     local ctx=${stat[0]}
   5965     if ((ctx==CTX_CMDX||ctx==CTX_CMDXV||ctx==CTX_CMDX1||ctx==CTX_CMDXT)); then
   5966       if ! shopt -q no_empty_cmd_completion; then
   5967         ble/syntax/completion-context/.add command "$index"
   5968         ble/syntax/completion-context/.add variable:= "$index"
   5969       fi
   5970     elif ((ctx==CTX_CMDXC)); then
   5971       ble/syntax/completion-context/.add wordlist:-rs:'(:{:((:[[:for:select:case:if:while:until' "$index"
   5972     elif ((ctx==CTX_CMDXE)); then
   5973       ble/syntax/completion-context/.add wordlist:-rs:'}:fi:done:esac:then:elif:else:do' "$index"
   5974     elif ((ctx==CTX_CMDXD0)); then
   5975       ble/syntax/completion-context/.add wordlist:-rs:';:{:do' "$index"
   5976     elif ((ctx==CTX_CMDXD)); then
   5977       ble/syntax/completion-context/.add wordlist:-rs:'{:do' "$index"
   5978     elif ((ctx==CTX_ARGX0||ctx==CTX_CPATX0||ctx==CTX_CMDX0)); then
   5979       ble/syntax/completion-context/.add sabbrev "$index"
   5980     elif ((ctx==CTX_ARGX||ctx==CTX_CARGX1||ctx==CTX_FARGX3)); then
   5981       ble/syntax/completion-context/.add argument "$index"
   5982     elif ((ctx==CTX_FARGX1||ctx==CTX_SARGX1)); then
   5983       ble/syntax/completion-context/.add variable:w "$index"
   5984       ble/syntax/completion-context/.add sabbrev "$index"
   5985     elif ((ctx==CTX_ARGVX)); then
   5986       # declare @
   5987       ble/syntax/completion-context/.add variable:= "$index"
   5988       ble/syntax/completion-context/.add option "$index"
   5989       ble/syntax/completion-context/.add sabbrev "$index"
   5990     elif ((ctx==CTX_ARGEX)); then
   5991       # eval @, eval echo @
   5992       ble/syntax/completion-context/.add variable:= "$index"
   5993       ble/syntax/completion-context/.add command:D "$index"
   5994       ble/syntax/completion-context/.add file "$index"
   5995     elif ((ctx==CTX_CARGX2)); then
   5996       # case a @
   5997       ble/syntax/completion-context/.add wordlist:-rs:'in' "$index"
   5998     elif ((ctx==CTX_FARGX2)); then
   5999       # for a @
   6000       ble/syntax/completion-context/.add wordlist:-rs:'in:do' "$index"
   6001     elif ((ctx==CTX_TARGX1)); then
   6002       # time @
   6003       local words='-p'
   6004       ((_ble_bash>=50100)) && words='-p':'--'
   6005       ble/syntax/completion-context/.add command "$index"
   6006       ble/syntax/completion-context/.add wordlist:--:"$words" "$index"
   6007     elif ((ctx==CTX_TARGX2)); then
   6008       # time -p @
   6009       ble/syntax/completion-context/.add command "$index"
   6010       ble/syntax/completion-context/.add wordlist:--:'--' "$index"
   6011     elif ((ctx==CTX_COARGX)); then
   6012       # coproc @
   6013       ble/syntax/completion-context/.add variable:w "$index"
   6014       ble/syntax/completion-context/.add command "$index"
   6015     elif ((ctx==CTX_CONDX)); then
   6016       # [[ @
   6017       ble/syntax/completion-context/.add sabbrev "$index"
   6018       ble/syntax/completion-context/.add option "$index"
   6019       ble/syntax/completion-context/.add file "$index"
   6020     elif ((ctx==CTX_CPATI||ctx==CTX_RDRF||ctx==CTX_RDRS)); then
   6021       ble/syntax/completion-context/.add file "$index"
   6022     elif ((ctx==CTX_RDRD)); then
   6023       ble/syntax/completion-context/.add fd "$index"
   6024     elif ((ctx==CTX_RDRD2)); then
   6025       ble/syntax/completion-context/.add fd "$index"
   6026       ble/syntax/completion-context/.add file:no-fd "$index"
   6027     elif ((ctx==CTX_RDRH||ctx==CTX_RDRI)); then
   6028       ble/syntax/completion-context/.add wordlist:EOF:END:HERE "$index"
   6029     elif ((ctx==CTX_VRHS||ctx==CTX_ARGVR||ctx==CTX_ARGER||ctx==CTX_VALR)); then
   6030       ble/syntax/completion-context/.add rhs "$index"
   6031     fi
   6032   fi
   6033 }
   6034 
   6035 ## @fn ble/syntax/completion-context/generate
   6036 ##   @var[out] sources[]
   6037 function ble/syntax/completion-context/generate {
   6038   local text=$1 index=$2
   6039   sources=()
   6040   ((index<0&&(index=0)))
   6041 
   6042   ble/syntax/completion-context/.check-prefix
   6043   ble/syntax/completion-context/.check-here
   6044 }
   6045 
   6046 #------------------------------------------------------------------------------
   6047 # extract-command
   6048 
   6049 ## @fn ble/syntax:bash/extract-command/.register-word
   6050 ## @var[in,out] comp_words, comp_line, comp_point, comp_cword
   6051 ## @var[in]     TE_i TE_nofs wbegin wlen
   6052 function ble/syntax:bash/extract-command/.register-word {
   6053   local wtxt=${_ble_syntax_text:wbegin:wlen}
   6054   if [[ ! $comp_cword ]] && ((wbegin<=EC_pos)); then
   6055     if ((EC_pos<=wbegin+wlen)); then
   6056       comp_cword=${#comp_words[@]}
   6057       comp_point=$((${#comp_line}+wbegin+wlen-EC_pos))
   6058       comp_line="$wtxt$comp_line"
   6059       ble/array#push comp_words "$wtxt"
   6060     else
   6061       comp_cword=${#comp_words[@]}
   6062       comp_point=${#comp_line}
   6063       comp_line="$wtxt $comp_line"
   6064       ble/array#push comp_words "" "$wtxt"
   6065     fi
   6066   else
   6067     comp_line="$wtxt$comp_line"
   6068     ble/array#push comp_words "$wtxt"
   6069   fi
   6070   [[ $EC_opts == *:treeinfo:* ]] &&
   6071     ble/array#push tree_words "$TE_i:$TE_nofs"
   6072 }
   6073 
   6074 function ble/syntax:bash/extract-command/.construct-proc {
   6075   if [[ $wtype =~ ^[0-9]+$ ]]; then
   6076     if ((wtype==CTX_CMDI||wtype==CTX_CMDX0)); then
   6077       if ((EC_pos<wbegin)); then
   6078         comp_line= comp_point= comp_cword= comp_words=()
   6079       else
   6080         ble/syntax:bash/extract-command/.register-word
   6081         ble/syntax/tree-enumerate-break
   6082         EC_found=1
   6083         return 0
   6084       fi
   6085     elif ((wtype==CTX_ARGI||wtype==CTX_ARGVI||wtype==CTX_ARGEI||wtype==ATTR_VAR)); then
   6086       ble/syntax:bash/extract-command/.register-word
   6087       comp_line=" $comp_line"
   6088     fi
   6089   fi
   6090 }
   6091 
   6092 function ble/syntax:bash/extract-command/.construct {
   6093   comp_line= comp_point= comp_cword= comp_words=()
   6094 
   6095   if [[ $1 == nested ]]; then
   6096     ble/syntax/tree-enumerate-children \
   6097       ble/syntax:bash/extract-command/.construct-proc
   6098   else
   6099     ble/syntax/tree-enumerate \
   6100       ble/syntax:bash/extract-command/.construct-proc
   6101   fi
   6102 
   6103   ble/array#reverse comp_words
   6104   ((comp_cword=${#comp_words[@]}-1-comp_cword,
   6105     comp_point=${#comp_line}-comp_point))
   6106   [[ $EC_opts == *:treeinfo:* ]] &&
   6107     ble/array#reverse tree_words
   6108 }
   6109 
   6110 ## (tree-enumerate-proc) ble/syntax:bash/extract-command/.scan
   6111 function ble/syntax:bash/extract-command/.scan {
   6112   ((EC_pos<wbegin)) && return 0
   6113 
   6114   if ((wbegin+wlen<EC_pos)); then
   6115     ble/syntax/tree-enumerate-break
   6116   else
   6117     local EC_has_word=
   6118     ble/syntax/tree-enumerate-children \
   6119       ble/syntax:bash/extract-command/.scan
   6120     local has_word=$EC_has_word
   6121     ble/util/unlocal EC_has_word
   6122 
   6123     if [[ $has_word && ! $EC_found ]]; then
   6124       ble/syntax:bash/extract-command/.construct nested
   6125       ble/syntax/tree-enumerate-break
   6126     fi
   6127   fi
   6128 
   6129   if [[ $wtype =~ ^[0-9]+$ && ! $EC_has_word ]]; then
   6130     EC_has_word=$wtype
   6131     return 0
   6132   fi
   6133 }
   6134 
   6135 ## @fn ble/syntax:bash/extract-command index [opts]
   6136 ##   @param[in] index
   6137 ##   @param[in] opts
   6138 ##     treeinfo
   6139 ##       コマンドを構成する各単語の構文木の情報を取得します。
   6140 ##       以下の配列に結果を格納します。
   6141 ##       @arr[out] tree_words
   6142 ##
   6143 ##   @var[out] comp_cword comp_words comp_line comp_point
   6144 function ble/syntax:bash/extract-command {
   6145   local EC_pos=$1 EC_opts=:$2:
   6146   local EC_found=
   6147 
   6148   local EC_has_word=
   6149   ble/syntax/tree-enumerate \
   6150     ble/syntax:bash/extract-command/.scan
   6151   if [[ ! $EC_found && $EC_has_word ]]; then
   6152     ble/syntax:bash/extract-command/.construct
   6153   fi
   6154   [[ $EC_found ]]
   6155 }
   6156 
   6157 #------------------------------------------------------------------------------
   6158 # extract-command-by-noderef
   6159 
   6160 ## @fn ble/syntax/tree#previous-sibling i[:nofs] [opts]
   6161 ## @fn ble/syntax/tree#next-sibling     i[:nofs] [opts]
   6162 ##   指定した位置の単語について、前または次の兄弟ノードを取得します。
   6163 ##
   6164 ##   @param[in] i:nofs
   6165 ##
   6166 ##   @param[in] opts
   6167 ##     コロン区切りのオプションです。
   6168 ##
   6169 ##     wvars が指定されている時、以下の変数に見つかった単語の情報を格納します。
   6170 ##     @var[out] wtype wlen wbeg wend wattr
   6171 ##
   6172 ##   @var[out] ret=i:nofs
   6173 ##
   6174 function ble/syntax/tree#previous-sibling {
   6175   local i0=${1%%:*} nofs0=0 opts=:$2:
   6176   [[ $1 == *:* ]] && nofs0=${1#*:}
   6177 
   6178   local node
   6179   ble/string#split-words node "${_ble_syntax_tree[i0-1]}"
   6180   ble-assert '((${#node[@]}>nofs0))' "Broken AST: tree-node info missing at $((i0-1))[$nofs0]" || return 1
   6181   local tplen=${node[nofs0+3]}
   6182   ((tplen>=0)) || return 1
   6183 
   6184   local i=$((i0-tplen)) nofs=0
   6185   ret=$i:$nofs
   6186   if [[ $opts == *:wvars:* ]]; then
   6187     ble/string#split-words node "${_ble_syntax_tree[i-1]}"
   6188     ble-assert '((${#node[@]}>nofs))' "Broken AST: tree-node info missing at $((i-1))[$nofs]" || return 1
   6189     wtype=${node[nofs]}
   6190     wlen=${node[nofs+1]}
   6191     ((wbeg=i-wlen,wend=i))
   6192     wattr=${node[nofs+4]}
   6193   fi
   6194   return 0
   6195 }
   6196 function ble/syntax/tree#next-sibling {
   6197   local i0=${1%%:*} nofs0=0 opts=:$2:
   6198   [[ $1 == *:* ]] && nofs0=${1#*:}
   6199 
   6200   # 自分が末尾にいるので弟はない
   6201   ((nofs0)) && return 1
   6202 
   6203   local iN=${#_ble_syntax_text} i nofs node
   6204   for ((i=i0+1;i<=iN;i++)); do
   6205     [[ ${_ble_syntax_tree[i-1]} ]] || continue
   6206     ble/string#split-words node "${_ble_syntax_tree[i-1]}"
   6207     nofs=${#node[@]}
   6208     while (((nofs-=_ble_syntax_TREE_WIDTH)>=0)); do
   6209       # node = (wtype wlen tclen tplen wattr)+
   6210       if ((i0==i-node[nofs+2])); then
   6211         # 親が見つかったので弟はいない
   6212         return 1
   6213       elif ((i0==i-node[nofs+3])); then
   6214         # これが弟
   6215         ret=$i:$nofs
   6216         if [[ $opts == *:wvars:* ]]; then
   6217           wtype=${node[nofs]}
   6218           wlen=${node[nofs+1]}
   6219           ((wbeg=i-wlen,wend=i))
   6220           wattr=${node[nofs+4]}
   6221         fi
   6222         return 0
   6223       fi
   6224     done
   6225   done
   6226   return 1
   6227 }
   6228 
   6229 ## @fn ble/syntax:bash/extract-command-by-noderef i[:nofs] [opts]
   6230 ##   @param[in] i:nofs
   6231 ##     単語を指定します。
   6232 ##
   6233 ##   @param[in] opts
   6234 ##     コロン区切りのオプションです。
   6235 ##
   6236 ##     treeinfo が指定された時は以下の配列に各単語の位置を
   6237 ##     "i:nofs" の形式で格納します。
   6238 ##     @var[out] tree_words
   6239 ##
   6240 ##   @var[out] comp_words comp_line comp_cword comp_point
   6241 ##     再構築したコマンド情報を格納します。
   6242 ##     カーソル位置は指定した単語の末尾にあると仮定します。
   6243 ##
   6244 function ble/syntax:bash/extract-command-by-noderef {
   6245   local i=${1%%:*} nofs=0 opts=:$2:
   6246   [[ $1 == *:* ]] && nofs=${1#*:}
   6247 
   6248   # initialize output
   6249   comp_words=()
   6250   tree_words=()
   6251   comp_line=
   6252   comp_cword=0
   6253   comp_point=0
   6254 
   6255   local ExprIsArgument='wtype==CTX_ARGI||wtype==CTX_ARGVI||wtype==CTX_ARGEI||wtype==ATTR_VAR'
   6256 
   6257   # 自ノードの追加
   6258   local ret node wtype wlen wbeg wend wattr
   6259   ble/string#split-words node "${_ble_syntax_tree[i-1]}"
   6260   wtype=${node[nofs]} wlen=${node[nofs+1]}
   6261   [[ ! ${wtype//[0-9]} ]] && ((wtype==CTX_CMDI||ExprIsArgument)) || return 1
   6262   ble/array#push comp_words "${_ble_syntax_text:i-wlen:wlen}"
   6263   [[ $opts == *:treeinfo:* ]] &&
   6264     ble/array#push tree_words "$i:$nofs"
   6265 
   6266   # 兄ノードの追加
   6267   ret=$i:$nofs
   6268   while
   6269     { [[ ${wtype//[0-9]} ]] || ((wtype!=CTX_CMDI)); } &&
   6270       ble/syntax/tree#previous-sibling "$ret" wvars
   6271   do
   6272     [[ ! ${wtype//[0-9]} ]] || continue
   6273     if ((wtype==CTX_CMDI||ExprIsArgument)); then
   6274       ble/array#push comp_words "${_ble_syntax_text:wbeg:wlen}"
   6275       [[ $opts == *:treeinfo:* ]] &&
   6276         ble/array#push tree_words "$ret"
   6277     fi
   6278   done
   6279   ble/array#reverse comp_words
   6280   [[ $opts == *:treeinfo:* ]] &&
   6281     ble/array#reverse tree_words
   6282 
   6283   # 現在位置 (comp_cword, comp_point)
   6284   ((comp_cword=${#comp_words[@]}-1))
   6285 
   6286   # 弟ノードの探索
   6287   ret=$i:$nofs
   6288   while ble/syntax/tree#next-sibling "$ret" wvars; do
   6289     [[ ! ${wtype//[0-9]} ]] || continue
   6290     ((wtype==CTX_CMDI)) && break
   6291     if ((ExprIsArgument)); then
   6292       ble/array#push comp_words "${_ble_syntax_text:wbeg:wlen}"
   6293       [[ $opts == *:treeinfo:* ]] &&
   6294         ble/array#push tree_words "$ret"
   6295     fi
   6296   done
   6297 
   6298   local IFS=$_ble_term_IFS
   6299   comp_line="${comp_words[*]}"
   6300   local tmp="${comp_words[*]::comp_cword+1}"
   6301   comp_point=${#tmp}
   6302 }
   6303 
   6304 #==============================================================================
   6305 #
   6306 # syntax-highlight
   6307 #
   6308 #==============================================================================
   6309 
   6310 # 遅延初期化対象
   6311 _ble_syntax_attr2iface=()
   6312 
   6313 # 遅延初期化子
   6314 function ble/syntax/attr2iface/color_defface.onload {
   6315   function ble/syntax/attr2iface/.define {
   6316     ((_ble_syntax_attr2iface[$1]=_ble_faces__$2))
   6317   }
   6318 
   6319   ble/syntax/attr2iface/.define CTX_ARGX     syntax_default
   6320   ble/syntax/attr2iface/.define CTX_ARGX0    syntax_default
   6321   ble/syntax/attr2iface/.define CTX_ARGI     syntax_default
   6322   ble/syntax/attr2iface/.define CTX_ARGQ     syntax_default
   6323   ble/syntax/attr2iface/.define CTX_ARGVX    syntax_default
   6324   ble/syntax/attr2iface/.define CTX_ARGVI    syntax_default
   6325   ble/syntax/attr2iface/.define CTX_ARGVR    syntax_default
   6326   ble/syntax/attr2iface/.define CTX_ARGEX    syntax_default
   6327   ble/syntax/attr2iface/.define CTX_ARGEI    syntax_default
   6328   ble/syntax/attr2iface/.define CTX_ARGER    syntax_default
   6329   ble/syntax/attr2iface/.define CTX_CMDX     syntax_default
   6330   ble/syntax/attr2iface/.define CTX_CMDX0    syntax_default
   6331   ble/syntax/attr2iface/.define CTX_CMDX1    syntax_default
   6332   ble/syntax/attr2iface/.define CTX_CMDXT    syntax_default
   6333   ble/syntax/attr2iface/.define CTX_CMDXC    syntax_default
   6334   ble/syntax/attr2iface/.define CTX_CMDXE    syntax_default
   6335   ble/syntax/attr2iface/.define CTX_CMDXD    syntax_default
   6336   ble/syntax/attr2iface/.define CTX_CMDXD0   syntax_default
   6337   ble/syntax/attr2iface/.define CTX_CMDXV    syntax_default
   6338   ble/syntax/attr2iface/.define CTX_CMDI     syntax_command
   6339   ble/syntax/attr2iface/.define CTX_VRHS     syntax_default
   6340   ble/syntax/attr2iface/.define CTX_QUOT     syntax_quoted
   6341   ble/syntax/attr2iface/.define CTX_EXPR     syntax_expr
   6342   ble/syntax/attr2iface/.define ATTR_ERR     syntax_error
   6343   ble/syntax/attr2iface/.define ATTR_VAR     syntax_varname
   6344   ble/syntax/attr2iface/.define ATTR_QDEL    syntax_quotation
   6345   ble/syntax/attr2iface/.define ATTR_QESC    syntax_escape
   6346   ble/syntax/attr2iface/.define ATTR_DEF     syntax_default
   6347   ble/syntax/attr2iface/.define ATTR_DEL     syntax_delimiter
   6348   ble/syntax/attr2iface/.define CTX_PARAM    syntax_param_expansion
   6349   ble/syntax/attr2iface/.define CTX_PWORD    syntax_default
   6350   ble/syntax/attr2iface/.define CTX_PWORDE   syntax_error
   6351   ble/syntax/attr2iface/.define CTX_PWORDR   syntax_default
   6352   ble/syntax/attr2iface/.define ATTR_HISTX   syntax_history_expansion
   6353   ble/syntax/attr2iface/.define ATTR_FUNCDEF syntax_function_name
   6354   ble/syntax/attr2iface/.define CTX_VALX     syntax_default
   6355   ble/syntax/attr2iface/.define CTX_VALI     syntax_default
   6356   ble/syntax/attr2iface/.define CTX_VALR     syntax_default
   6357   ble/syntax/attr2iface/.define CTX_VALQ     syntax_default
   6358   ble/syntax/attr2iface/.define CTX_CONDX    syntax_default
   6359   ble/syntax/attr2iface/.define CTX_CONDI    syntax_default
   6360   ble/syntax/attr2iface/.define CTX_CONDQ    syntax_default
   6361   ble/syntax/attr2iface/.define ATTR_COMMENT syntax_comment
   6362   ble/syntax/attr2iface/.define CTX_CASE     syntax_default
   6363   ble/syntax/attr2iface/.define CTX_PATN     syntax_default
   6364   ble/syntax/attr2iface/.define ATTR_GLOB    syntax_glob
   6365   ble/syntax/attr2iface/.define CTX_BRAX     syntax_default
   6366   ble/syntax/attr2iface/.define ATTR_BRACE   syntax_brace
   6367   ble/syntax/attr2iface/.define CTX_BRACE1   syntax_default
   6368   ble/syntax/attr2iface/.define CTX_BRACE2   syntax_default
   6369   ble/syntax/attr2iface/.define ATTR_TILDE   syntax_tilde
   6370 
   6371   # for var in ... / case arg in / time -p --
   6372   ble/syntax/attr2iface/.define CTX_SARGX1   syntax_default
   6373   ble/syntax/attr2iface/.define CTX_FARGX1   syntax_default
   6374   ble/syntax/attr2iface/.define CTX_FARGX2   syntax_default
   6375   ble/syntax/attr2iface/.define CTX_FARGX3   syntax_default
   6376   ble/syntax/attr2iface/.define CTX_FARGI1   syntax_varname
   6377   ble/syntax/attr2iface/.define CTX_FARGI2   command_keyword
   6378   ble/syntax/attr2iface/.define CTX_FARGI3   syntax_default
   6379   ble/syntax/attr2iface/.define CTX_FARGQ3   syntax_default
   6380 
   6381   ble/syntax/attr2iface/.define CTX_CARGX1   syntax_default
   6382   ble/syntax/attr2iface/.define CTX_CARGX2   syntax_default
   6383   ble/syntax/attr2iface/.define CTX_CARGI1   syntax_default
   6384   ble/syntax/attr2iface/.define CTX_CARGQ1   syntax_default
   6385   ble/syntax/attr2iface/.define CTX_CARGI2   command_keyword
   6386   ble/syntax/attr2iface/.define CTX_CPATX    syntax_default
   6387   ble/syntax/attr2iface/.define CTX_CPATI    syntax_default
   6388   ble/syntax/attr2iface/.define CTX_CPATQ    syntax_default
   6389   ble/syntax/attr2iface/.define CTX_CPATX0   syntax_default
   6390 
   6391   ble/syntax/attr2iface/.define CTX_TARGX1   syntax_default
   6392   ble/syntax/attr2iface/.define CTX_TARGX2   syntax_default
   6393   ble/syntax/attr2iface/.define CTX_TARGI1   syntax_default
   6394   ble/syntax/attr2iface/.define CTX_TARGI2   syntax_default
   6395 
   6396   ble/syntax/attr2iface/.define CTX_COARGX   syntax_default
   6397   ble/syntax/attr2iface/.define CTX_COARGI   syntax_command
   6398 
   6399   # redirection words
   6400   ble/syntax/attr2iface/.define CTX_RDRF    syntax_default
   6401   ble/syntax/attr2iface/.define CTX_RDRD    syntax_default
   6402   ble/syntax/attr2iface/.define CTX_RDRD2   syntax_default
   6403   ble/syntax/attr2iface/.define CTX_RDRS    syntax_default
   6404 
   6405   # here documents
   6406   ble/syntax/attr2iface/.define CTX_RDRH    syntax_document_begin
   6407   ble/syntax/attr2iface/.define CTX_RDRI    syntax_document_begin
   6408   ble/syntax/attr2iface/.define CTX_HERE0   syntax_document
   6409   ble/syntax/attr2iface/.define CTX_HERE1   syntax_document
   6410 
   6411   ble/syntax/attr2iface/.define ATTR_CMD_BOLD      command_builtin_dot
   6412   ble/syntax/attr2iface/.define ATTR_CMD_BUILTIN   command_builtin
   6413   ble/syntax/attr2iface/.define ATTR_CMD_ALIAS     command_alias
   6414   ble/syntax/attr2iface/.define ATTR_CMD_FUNCTION  command_function
   6415   ble/syntax/attr2iface/.define ATTR_CMD_FILE      command_file
   6416   ble/syntax/attr2iface/.define ATTR_CMD_JOBS      command_jobs
   6417   ble/syntax/attr2iface/.define ATTR_CMD_DIR       command_directory
   6418   ble/syntax/attr2iface/.define ATTR_KEYWORD       command_keyword
   6419   ble/syntax/attr2iface/.define ATTR_KEYWORD_BEGIN command_keyword
   6420   ble/syntax/attr2iface/.define ATTR_KEYWORD_END   command_keyword
   6421   ble/syntax/attr2iface/.define ATTR_KEYWORD_MID   command_keyword
   6422   ble/syntax/attr2iface/.define ATTR_FILE_DIR      filename_directory
   6423   ble/syntax/attr2iface/.define ATTR_FILE_STICKY   filename_directory_sticky
   6424   ble/syntax/attr2iface/.define ATTR_FILE_LINK     filename_link
   6425   ble/syntax/attr2iface/.define ATTR_FILE_ORPHAN   filename_orphan
   6426   ble/syntax/attr2iface/.define ATTR_FILE_FILE     filename_other
   6427   ble/syntax/attr2iface/.define ATTR_FILE_SETUID   filename_setuid
   6428   ble/syntax/attr2iface/.define ATTR_FILE_SETGID   filename_setgid
   6429   ble/syntax/attr2iface/.define ATTR_FILE_EXEC     filename_executable
   6430   ble/syntax/attr2iface/.define ATTR_FILE_WARN     filename_warning
   6431   ble/syntax/attr2iface/.define ATTR_FILE_FIFO     filename_pipe
   6432   ble/syntax/attr2iface/.define ATTR_FILE_SOCK     filename_socket
   6433   ble/syntax/attr2iface/.define ATTR_FILE_BLK      filename_block
   6434   ble/syntax/attr2iface/.define ATTR_FILE_CHR      filename_character
   6435   ble/syntax/attr2iface/.define ATTR_FILE_URL      filename_url
   6436   ble/syntax/attr2iface/.define ATTR_VAR_UNSET     varname_unset
   6437   ble/syntax/attr2iface/.define ATTR_VAR_EMPTY     varname_empty
   6438   ble/syntax/attr2iface/.define ATTR_VAR_NUMBER    varname_number
   6439   ble/syntax/attr2iface/.define ATTR_VAR_EXPR      varname_expr
   6440   ble/syntax/attr2iface/.define ATTR_VAR_ARRAY     varname_array
   6441   ble/syntax/attr2iface/.define ATTR_VAR_HASH      varname_hash
   6442   ble/syntax/attr2iface/.define ATTR_VAR_READONLY  varname_readonly
   6443   ble/syntax/attr2iface/.define ATTR_VAR_TRANSFORM varname_transform
   6444   ble/syntax/attr2iface/.define ATTR_VAR_EXPORT    varname_export
   6445 }
   6446 blehook/eval-after-load color_defface ble/syntax/attr2iface/color_defface.onload
   6447 
   6448 #------------------------------------------------------------------------------
   6449 # ble/syntax/highlight/cmdtype
   6450 
   6451 
   6452 ## @fn ble/syntax/highlight/cmdtype1 command_type command
   6453 ##   指定したコマンドに対する属性を決定します。
   6454 ##   @param[in] command_type
   6455 ##     builtin type -t command の結果を指定します。
   6456 ##   @param[in] command
   6457 ##     コマンド名です。
   6458 ##   @var[out] type
   6459 function ble/syntax/highlight/cmdtype1 {
   6460   type=$1
   6461   local cmd=$2
   6462   case $type:$cmd in
   6463   (builtin::|builtin:.)
   6464     # 見にくいので太字にする
   6465     ((type=ATTR_CMD_BOLD)) ;;
   6466   (builtin:*)
   6467     ((type=ATTR_CMD_BUILTIN)) ;;
   6468   (alias:*)
   6469     ((type=ATTR_CMD_ALIAS)) ;;
   6470   (function:*)
   6471     ((type=ATTR_CMD_FUNCTION)) ;;
   6472   (file:*)
   6473     ((type=ATTR_CMD_FILE)) ;;
   6474   (keyword:*)
   6475     ((type=ATTR_KEYWORD)) ;;
   6476   (*:%*)
   6477     # jobs
   6478     ble/util/joblist.check
   6479     if jobs -- "$cmd" &>/dev/null; then
   6480       ((type=ATTR_CMD_JOBS))
   6481     else
   6482       ((type=ATTR_ERR))
   6483     fi ;;
   6484   (*)
   6485     if [[ -d $cmd ]] && shopt -q autocd &>/dev/null; then
   6486       ((type=ATTR_CMD_DIR))
   6487     else
   6488       ((type=ATTR_ERR))
   6489     fi ;;
   6490   esac
   6491 }
   6492 
   6493 # #D1341 #D1355 #D1440 locale 対策
   6494 function ble/syntax/highlight/cmdtype/.jobs { local LC_ALL=C; jobs; }
   6495 ble/function#suppress-stderr ble/syntax/highlight/cmdtype/.jobs
   6496 function ble/syntax/highlight/cmdtype/.is-job-name {
   6497   ble/util/joblist.check
   6498 
   6499   local value=$1 word=$2
   6500   if [[ $value == '%'* ]] && jobs -- "$value" &>/dev/null; then
   6501     return 0
   6502   fi
   6503 
   6504   local quote=\'\"\\\`
   6505   if [[ ${auto_resume+set} && $word != *["$quote"]* ]]; then
   6506     if [[ $auto_resume == exact ]]; then
   6507       local jobs job ret
   6508       ble/util/assign-array jobs 'ble/syntax/highlight/cmdtype/.jobs'
   6509       for job in "${jobs[@]}"; do
   6510         ble/string#trim "${job#*' '}"
   6511         ble/string#trim "${ret#*' '}"
   6512         [[ $value == "$ret" ]] && return 0
   6513       done
   6514       return 1
   6515     elif [[ $auto_resume == substring ]]; then
   6516       jobs -- "%?$value" &>/dev/null; return "$?"
   6517     else
   6518       jobs -- "%$value" &>/dev/null; return "$?"
   6519     fi
   6520   fi
   6521 
   6522   return 1
   6523 }
   6524 function ble/syntax/highlight/cmdtype/.impl {
   6525   local cmd=$1 word=$2
   6526   local cmd_type; ble/util/type cmd_type "$cmd"
   6527   ble/syntax/highlight/cmdtype1 "$cmd_type" "$cmd"
   6528 
   6529   if [[ $type == "$ATTR_CMD_ALIAS" && $cmd != "$word" ]]; then
   6530     # alias を \ で無効化している場合は
   6531     # unalias して再度 check (2fork)
   6532     type=$(
   6533       builtin unalias "$cmd"
   6534       ble/util/type cmd_type "$cmd"
   6535       ble/syntax/highlight/cmdtype1 "$cmd_type" "$cmd"
   6536       printf %s "$type")
   6537   elif ble/syntax/highlight/cmdtype/.is-job-name "$cmd" "$word"; then
   6538     # %() { :; } として 関数を定義できるが jobs の方が優先される。
   6539     # (% という名の関数を呼び出す方法はない?)
   6540     # でも % で始まる物が keyword になる事はそもそも無いような。
   6541     ((type=ATTR_CMD_JOBS))
   6542   elif [[ $type == "$ATTR_KEYWORD" ]]; then
   6543     # Note: 予約語 (keyword) の時は構文解析の時点で着色しているのでコマンドとしての着色は行わない。
   6544     #   関数 ble/syntax/highlight/cmdtype が呼び出されたとすれば、コマンドとしての文脈である。
   6545     #   予約語がコマンドとして取り扱われるのは、クォートされていたか変数代入やリダイレクトの後だった時。
   6546     #   この時 type -a -t の二番目の候補を用いて種類を決定する #D1406
   6547     ble/syntax/highlight/cmdtype1 "${cmd_type[1]}" "$cmd"
   6548   fi
   6549 }
   6550 
   6551 ## @fn ble/syntax/highlight/cmdtype cmd word
   6552 ##   @param[in] cmd
   6553 ##     シェル展開・クォート除去を実行した後の文字列を指定します。
   6554 ##   @param[in] word
   6555 ##     シェル展開・クォート除去を実行する前の文字列を指定します。
   6556 ##   @var[out] type
   6557 
   6558 # Note: 連想配列 _ble_syntax_highlight_filetype は core-syntax-def.sh で先に定義される。
   6559 _ble_syntax_highlight_filetype_version=-1
   6560 function ble/syntax/highlight/cmdtype {
   6561   local cmd=$1 word=$2
   6562 
   6563   # check cache
   6564   if ((_ble_syntax_highlight_filetype_version!=_ble_edit_LINENO)); then
   6565     ble/gdict#clear _ble_syntax_highlight_filetype
   6566     ((_ble_syntax_highlight_filetype_version=_ble_edit_LINENO))
   6567   fi
   6568 
   6569   if local ret; ble/gdict#get _ble_syntax_highlight_filetype "$word"; then
   6570     type=$ret
   6571     return 0
   6572   fi
   6573 
   6574   ble/syntax/highlight/cmdtype/.impl "$cmd" "$word"
   6575   ble/gdict#set _ble_syntax_highlight_filetype "$word" "$type"
   6576 }
   6577 
   6578 #------------------------------------------------------------------------------
   6579 # ble/syntax/highlight/filetype
   6580 
   6581 ## @fn ble/syntax/highlight/filetype filename
   6582 ##   @var[out] type
   6583 function ble/syntax/highlight/filetype {
   6584   type=
   6585   local file=$1
   6586 
   6587   # Note: #D1168 Cygwin では // で始まるパスはとても遅い
   6588   if [[ ( $OSTYPE == cygwin || $OSTYPE == msys ) && $file == //* ]]; then
   6589     [[ $file == // ]] && ((type=ATTR_FILE_DIR))
   6590     [[ $type ]]; return "$?"
   6591   fi
   6592 
   6593   if [[ -h $file ]]; then
   6594     if [[ -e $file ]]; then
   6595       ((type=ATTR_FILE_LINK))
   6596     else
   6597       ((type=ATTR_FILE_ORPHAN))
   6598     fi
   6599   elif [[ -e $file ]]; then
   6600     if [[ -d $file ]]; then
   6601       if [[ -k $file ]]; then
   6602         ((type=ATTR_FILE_STICKY))
   6603       elif [[ -h ${file%/} ]]; then
   6604         ((type=ATTR_FILE_LINK))
   6605       else
   6606         ((type=ATTR_FILE_DIR))
   6607       fi
   6608     elif [[ -f $file ]]; then
   6609       if [[ -u $file ]]; then
   6610         ((type=ATTR_FILE_SETUID))
   6611       elif [[ -g $file ]]; then
   6612         ((type=ATTR_FILE_SETGID))
   6613       elif [[ -x $file ]]; then
   6614         ((type=ATTR_FILE_EXEC))
   6615       else
   6616         ((type=ATTR_FILE_FILE))
   6617       fi
   6618     elif [[ -c $file ]]; then
   6619       ((type=ATTR_FILE_CHR))
   6620     elif [[ -p $file ]]; then
   6621       ((type=ATTR_FILE_FIFO))
   6622     elif [[ -S $file ]]; then
   6623       ((type=ATTR_FILE_SOCK))
   6624     elif [[ -b $file ]]; then
   6625       ((type=ATTR_FILE_BLK))
   6626     fi
   6627   elif local rex='^https?://[^ ^`"<>\{|}]+$'; [[ $file =~ $rex ]]; then
   6628     ((type=ATTR_FILE_URL))
   6629   fi
   6630   [[ $type ]]
   6631 }
   6632 
   6633 #------------------------------------------------------------------------------
   6634 # ble/syntax/highlight/ls_colors
   6635 
   6636 function ble/syntax/highlight/ls_colors/.clear {
   6637   _ble_syntax_highlight_lscolors=()
   6638   ble/gdict#clear _ble_syntax_highlight_lscolors_ext
   6639 }
   6640 
   6641 ## @fn ble/syntax/highlight/ls_colors/.register-extension key value
   6642 ##   @param[in] key value
   6643 ## @fn ble/syntax/highlight/ls_colors/.read-extension key
   6644 ##   @param[in] key
   6645 ##   @var[out] ret
   6646 
   6647 # Note: _ble_syntax_highlight_lscolors_ext は core-syntax-def.sh で先に定義
   6648 function ble/syntax/highlight/ls_colors/.register-extension {
   6649   local key=$1 value=$2
   6650   ble/gdict#set _ble_syntax_highlight_lscolors_ext "$key" "$value"
   6651 }
   6652 function ble/syntax/highlight/ls_colors/.read-extension {
   6653   ble/gdict#get _ble_syntax_highlight_lscolors_ext "$1"
   6654 }
   6655 
   6656 function ble/syntax/highlight/ls_colors/.parse {
   6657   ble/syntax/highlight/ls_colors/.clear
   6658 
   6659   local fields field
   6660   ble/string#split fields : "$1"
   6661   for field in "${fields[@]}"; do
   6662     [[ $field == *=* ]] || continue
   6663     local lhs=${field%%=*}
   6664     local ret; ble/color/sgrspec2g "${field#*=}"; local rhs=$ret
   6665     case $lhs in
   6666     ('di') _ble_syntax_highlight_lscolors[ATTR_FILE_DIR]=$rhs  ;;
   6667     ('st') _ble_syntax_highlight_lscolors[ATTR_FILE_STICKY]=$rhs  ;;
   6668     ('ln') _ble_syntax_highlight_lscolors[ATTR_FILE_LINK]=$rhs ;;
   6669     ('or') _ble_syntax_highlight_lscolors[ATTR_FILE_ORPHAN]=$rhs ;;
   6670     ('fi') _ble_syntax_highlight_lscolors[ATTR_FILE_FILE]=$rhs ;;
   6671     ('su') _ble_syntax_highlight_lscolors[ATTR_FILE_SETUID]=$rhs ;;
   6672     ('sg') _ble_syntax_highlight_lscolors[ATTR_FILE_SETGID]=$rhs ;;
   6673     ('ex') _ble_syntax_highlight_lscolors[ATTR_FILE_EXEC]=$rhs ;;
   6674     ('cd') _ble_syntax_highlight_lscolors[ATTR_FILE_CHR]=$rhs  ;;
   6675     ('pi') _ble_syntax_highlight_lscolors[ATTR_FILE_FIFO]=$rhs ;;
   6676     ('so') _ble_syntax_highlight_lscolors[ATTR_FILE_SOCK]=$rhs ;;
   6677     ('bd') _ble_syntax_highlight_lscolors[ATTR_FILE_BLK]=$rhs  ;;
   6678     (\*.*)
   6679       ble/syntax/highlight/ls_colors/.register-extension "${lhs:2}" "$rhs" ;;
   6680     esac
   6681   done
   6682 }
   6683 
   6684 ## @fn ble/syntax/highlight/ls_colors filename
   6685 ##   対応する LS_COLORS 設定が見つかった時に g を上書きし成功します。
   6686 ##   それ以外の場合は g は変更せず失敗します。
   6687 ##   @param[in] filename
   6688 ##   @var[in] type
   6689 ##   @var[in,out] g
   6690 function ble/syntax/highlight/ls_colors {
   6691   local file=$1
   6692   if ((type==ATTR_FILE_FILE)); then
   6693     local ext=${file##*/} ret=
   6694     while [[ $ext == *.* ]]; do
   6695       ext=${ext#*.}
   6696       [[ $ext ]] || break
   6697       if ble/syntax/highlight/ls_colors/.read-extension "$ext"; then
   6698         local g1=$ret
   6699         ble/color/face2g filename_ls_colors; g=$ret
   6700         ble/color/g#append g "$g1"
   6701         return 0
   6702       fi
   6703     done
   6704   fi
   6705 
   6706   local g1=${_ble_syntax_highlight_lscolors[type]}
   6707   if [[ $g1 ]]; then
   6708     ble/color/face2g filename_ls_colors; g=$ret
   6709     ble/color/g#append g "$g1"
   6710     return 0
   6711   fi
   6712 
   6713   return 1
   6714 }
   6715 
   6716 function ble/syntax/highlight/getg-from-filename {
   6717   local filename=$1 type=
   6718   ble/syntax/highlight/filetype "$filename"
   6719   if [[ $bleopt_filename_ls_colors ]]; then
   6720     if ble/syntax/highlight/ls_colors "$filename"; then
   6721       return 0
   6722     fi
   6723   fi
   6724 
   6725   if [[ $type ]]; then
   6726     ble/syntax/attr2g "$type"
   6727   else
   6728     g=
   6729   fi
   6730 }
   6731 
   6732 function bleopt/check:filename_ls_colors {
   6733   ble/syntax/highlight/ls_colors/.parse "$value"
   6734 }
   6735 bleopt -I filename_ls_colors
   6736 
   6737 #------------------------------------------------------------------------------
   6738 # ble/progcolor
   6739 
   6740 _ble_syntax_progcolor_vars=(
   6741   node TE_i TE_nofs wtype wlen wbeg wend wattr)
   6742 _ble_syntax_progcolor_wattr_vars=(
   6743   wattr_buff wattr_pos wattr_g)
   6744 
   6745 ## @fn ble/progcolor/load-word-data i:nofs
   6746 ##   @var[out] TE_i TE_nofs node
   6747 ##   @var[out] wtype wlen wbeg wend wattr
   6748 function ble/progcolor/load-word-data {
   6749   # TE_i TE_nofs
   6750   TE_i=${1%%:*} TE_nofs=${1#*:}
   6751   [[ $1 != *:* ]] && TE_nofs=0
   6752 
   6753   # node
   6754   ble/string#split-words node "${_ble_syntax_tree[TE_i-1]}"
   6755 
   6756   # wvars
   6757   wtype=${node[TE_nofs]}
   6758   wlen=${node[TE_nofs+1]}
   6759   wattr=${node[TE_nofs+4]}
   6760   wbeg=$((TE_i-wlen))
   6761   wend=$TE_i
   6762 }
   6763 
   6764 ## @fn ble/progcolor/set-wattr value
   6765 ##   @var[in] TE_i TE_nofs node
   6766 function ble/progcolor/set-wattr {
   6767   ble/syntax/urange#update color_ "$wbeg" "$wend"
   6768   ble/syntax/wrange#update _ble_syntax_word_ "$TE_i"
   6769   node[TE_nofs+4]=$1
   6770   local IFS=$_ble_term_IFS
   6771   _ble_syntax_tree[TE_i-1]="${node[*]}"
   6772 }
   6773 
   6774 ## @fn ble/progcolor/eval-word [iword] [opts]
   6775 ##   現在のコマンドの iword 番目の単語を評価した値を返します。
   6776 ##   iword を省略した場合には現在着色中の単語が使われます。
   6777 ##   単語が評価できない場合にはコマンドは失敗します。
   6778 ##
   6779 ##   @param[in,opt] iword
   6780 ##   @param[in,opt] opts
   6781 ##     ble/syntax:bash/simple-word/eval に対する opts を指定します。
   6782 ##
   6783 ##   @var[in] progcolor_iword
   6784 ##     現在処理中の単語の番号を指定します。
   6785 ##     iword を省略した時に使われます。
   6786 ##   @var[in,out] progcolor_wvals
   6787 ##   @var[in,out] progcolor_stats
   6788 ##     単語の評価値のキャッシュです。
   6789 ##   @var[out] ret
   6790 ##
   6791 function ble/progcolor/eval-word {
   6792   local iword=${1:-progcolor_iword} opts=$2
   6793   if [[ ${progcolor_stats[iword]+set} ]]; then
   6794     ret=${progcolor_wvals[iword]}
   6795     return "${progcolor_stats[iword]}"
   6796   fi
   6797 
   6798   local wtxt=${comp_words[iword]}
   6799   local simple_flags simple_ibrace
   6800   if ! ble/syntax:bash/simple-word/reconstruct-incomplete-word "$wtxt"; then
   6801     # not simple word
   6802     ret=
   6803     progcolor_stats[iword]=2
   6804     progcolor_wvals[iword]=
   6805     return 2
   6806   fi
   6807 
   6808   ble/syntax:bash/simple-word/eval "$ret" "$opts"; local ext=$?
   6809   ((ext==148)) && return 148
   6810   if ((ext!=0)); then
   6811     # fail glob
   6812     ret=
   6813     progcolor_stats[iword]=1
   6814     progcolor_wvals[iword]=
   6815     return 1
   6816   fi
   6817 
   6818   progcolor_stats[iword]=0
   6819   progcolor_wvals[iword]=$ret
   6820   return 0
   6821 }
   6822 
   6823 ## @fn ble/progcolor/load-cmdspec-opts
   6824 ##   @var[out] cmdspec_opts
   6825 function ble/progcolor/load-cmdspec-opts {
   6826   if [[ $progcolor_cmdspec_opts ]]; then
   6827     cmdspec_opts=$progcolor_cmdspec_opts
   6828   else
   6829     ble/cmdspec/opts#load "${comp_words[0]}"
   6830     progcolor_cmdspec_opts=${cmdspec_opts:-:}
   6831   fi
   6832 }
   6833 
   6834 ## @fn ble/progcolor/stop-option#init cmdspec_opts
   6835 ##   @var[out] rexrej rexreq stopat
   6836 function ble/progcolor/stop-option#init {
   6837   rexrej='^--$' rexreq= stopat=
   6838   local cmdspec_opts=$1
   6839   if [[ $cmdspec_opts ]]; then
   6840     # copied from ble/complete/source:option/.stops-option
   6841     if [[ :$cmdspec_opts: == *:no-options:* ]]; then
   6842       stopat=0
   6843       return 1
   6844     elif ble/opts#extract-first-optarg "$cmdspec_opts" stop-options-at && [[ $ret ]]; then
   6845       ((stopat=ret))
   6846       return 1
   6847     fi
   6848 
   6849     local ret
   6850     if ble/opts#extract-first-optarg "$cmdspec_opts" stop-options-on && [[ $ret ]]; then
   6851       rexrej=$ret
   6852     elif [[ :$cmdspec_opts: == *:disable-double-hyphen:* ]]; then
   6853       rexrej=
   6854     fi
   6855     if ble/opts#extract-first-optarg "$cmdspec_opts" stop-options-unless && [[ $ret ]]; then
   6856       rexreq=$ret
   6857     elif [[ :$cmdspec_opts: == *:stop-options-postarg:* ]]; then
   6858       rexreq='^-.+'
   6859       ble/opts#has "$cmdspec_opts" plus-options && rexreq='^[-+].+'
   6860     fi
   6861   fi
   6862 }
   6863 ## @fn ble/progcolor/stop-option#test value
   6864 ##   @var[in] rexrej rexreq
   6865 function ble/progcolor/stop-option#test {
   6866   [[ $rexrej && $1 =~ $rexrej || $rexreq && ! ( $1 =~ $rexreq ) ]]
   6867 }
   6868 
   6869 ## @fn ble/progcolor/is-option-context
   6870 ##   現在の単語位置がオプションが解釈される文脈かどうかを判定します。
   6871 ##
   6872 ##   @var progcolor_iword
   6873 ##     現在の単語の位置。
   6874 ##
   6875 ##   @var progcolor_optctx[0]
   6876 ##     空の時には未だオプションを初期化していない事を示します。
   6877 ##     現在のコマンドについて何処までオプション停止条件を検査済みかを記録します。
   6878 ##
   6879 ##   @var progcolor_optctx[1]
   6880 ##     負の時は常にオプションが有効である事を示す。
   6881 ##     0の時は常にオプションが無効である事を示す。
   6882 ##     正の時はその位置以前の引数でオプションが有効である事を示す。
   6883 ##
   6884 ##   @var progcolor_optctx[2]
   6885 ##   @var progcolor_optctx[3]
   6886 ##   @var progcolor_optctx[4]
   6887 ##     それぞれ抽出済みの rexrej rexreq stopat の値を記録します。
   6888 ##     ble/progcolor/stop-option#init によって初期化された値のキャッシュです。
   6889 ##
   6890 function ble/progcolor/is-option-context {
   6891   # 既にオプション停止位置が計算済みの場合
   6892   if [[ ${progcolor_optctx[1]} ]]; then
   6893     # Note: 等号は停止を引き起こした引数 -- 自体の時 (オプションとして有効)
   6894     ((progcolor_optctx[1]<0?1:(progcolor_iword<=progcolor_optctx[1])))
   6895     return "$?"
   6896   fi
   6897 
   6898   local rexrej rexreq stopat
   6899   if [[ ! ${progcolor_optctx[0]} ]]; then
   6900     progcolor_optctx[0]=1
   6901     local cmdspec_opts
   6902     ble/progcolor/load-cmdspec-opts
   6903     ble/progcolor/stop-option#init "$cmdspec_opts"
   6904     if [[ ! $rexrej$rexreq ]]; then
   6905       progcolor_optctx[1]=${stopat:--1}
   6906       ((progcolor_optctx[1]<0?1:(progcolor_iword<=progcolor_optctx[1])))
   6907       return "$?"
   6908     fi
   6909     progcolor_optctx[2]=$rexrej
   6910     progcolor_optctx[3]=$rexreq
   6911     progcolor_optctx[4]=$stopat
   6912   else
   6913     rexrej=${progcolor_optctx[2]}
   6914     rexreq=${progcolor_optctx[3]}
   6915     stopat=${progcolor_optctx[4]}
   6916   fi
   6917   [[ $stopat ]] && ((progcolor_iword>stopat)) && return 1
   6918 
   6919   local iword
   6920   for ((iword=progcolor_optctx[0];iword<progcolor_iword;iword++)); do
   6921     ble/progcolor/eval-word "$iword" "$highlight_eval_opts"
   6922     if ble/progcolor/stop-option#test "$ret"; then
   6923       progcolor_optctx[1]=$iword
   6924       return 1
   6925     fi
   6926   done
   6927   progcolor_optctx[0]=$iword
   6928   return 0
   6929 }
   6930 
   6931 ## @fn ble/progcolor/wattr#initialize
   6932 ##   @var[out] wattr_buff
   6933 ##   @var[out] wattr_pos
   6934 ##   @var[out] wattr_g
   6935 function ble/progcolor/wattr#initialize {
   6936   wattr_buff=()
   6937   wattr_pos=$wbeg
   6938   wattr_g=d
   6939 }
   6940 ## @fn ble/progcolor/wattr#setg pos g
   6941 ##   @param[in] pos
   6942 ##   @param[in] g
   6943 ##   @var[in,out] wattr_buff
   6944 ##   @var[in,out] wattr_pos
   6945 ##   @var[in,out] wattr_g
   6946 function ble/progcolor/wattr#setg {
   6947   local pos=$1 g=$2
   6948   local len=$((pos-wattr_pos))
   6949   ((len>0)) && ble/array#push wattr_buff "$len:$wattr_g"
   6950   wattr_pos=$pos
   6951   wattr_g=$g
   6952 }
   6953 function ble/progcolor/wattr#setattr {
   6954   local pos=$1 attr=$2 g
   6955   ble/syntax/attr2g "$attr"
   6956   ble/progcolor/wattr#setg "$pos" "$g"
   6957 }
   6958 ## @fn ble/progcolor/wattr#finalize
   6959 ##   @var[in,out] wattr_buff
   6960 ##   @var[in,out] wattr_pos
   6961 ##   @var[in,out] wattr_g
   6962 function ble/progcolor/wattr#finalize {
   6963   local wattr
   6964   if ((${#wattr_buff[@]})); then
   6965     local len=$((wend-wattr_pos))
   6966     ((len>0)) && ble/array#push wattr_buff \$:"$wattr_g"
   6967     wattr_pos=$wend
   6968     wattr_g=d
   6969     IFS=, builtin eval 'wattr="m${wattr_buff[*]}"'
   6970   else
   6971     wattr=$wattr_g
   6972   fi
   6973   ble/progcolor/set-wattr "$wattr"
   6974 }
   6975 
   6976 
   6977 ## @fn ble/progcolor/highlight-filename/.detect-separated-path word
   6978 ##   @param[in] word
   6979 ##   @var[in] wtype p0
   6980 ##   @var[in] _ble_syntax_attr
   6981 ##   @var[out] ret
   6982 ##     有効な区切り文字の集合を返します。
   6983 function ble/progcolor/highlight-filename/.detect-separated-path {
   6984   local word=$1
   6985   ((wtype==CTX_ARGI||wtype==CTX_ARGEI||wtype==CTX_VALI||wtype==ATTR_VAR||wtype==CTX_RDRS)) || return 1
   6986 
   6987   local detect_opts=url:$highlight_eval_opts
   6988   ((wtype==CTX_RDRS)) && detect_opts=$detect_opts:noglob
   6989   [[ $word == '~'* ]] && ((_ble_syntax_attr[p0]!=ATTR_TILDE)) && detect_opts=$detect_opts:notilde
   6990   ble/syntax:bash/simple-word/detect-separated-path "$word" :, "$detect_opts"
   6991 }
   6992 
   6993 ## @fn ble/progcolor/highlight-filename/.pathspec.wattr g [opts]
   6994 ##   @param[in] g
   6995 ##   @param[in,opt] opts
   6996 ##   @var[in] wtype p0 p1
   6997 ##   @var[in] path spec
   6998 function ble/progcolor/highlight-filename/.pathspec.wattr {
   6999   local p=$p0 opts=$2
   7000 
   7001   if [[ :$opts: != *:no-path-color:* ]]; then
   7002     local ipath npath=${#path[@]}
   7003     for ((ipath=0;ipath<npath-1;ipath++)); do
   7004       local epath=${path[ipath]} espec=${spec[ipath]}
   7005       local g=d
   7006 
   7007       if ble/syntax/util/is-directory "$epath"; then
   7008         local type
   7009         if ble/syntax/highlight/filetype "$epath"; then
   7010           ble/syntax/highlight/ls_colors ||
   7011             ble/syntax/attr2g "$type"
   7012         fi
   7013       elif ((wtype==CTX_CMDI)); then
   7014         # コマンド名の時はディレクトリが存在する必要 #D1419
   7015         ble/syntax/attr2g "$ATTR_ERR"
   7016       fi
   7017 
   7018       # コマンド名の時は下線は引かない様にする
   7019       ((wtype==CTX_CMDI&&(g&=~_ble_color_gflags_Underline)))
   7020 
   7021       ble/progcolor/wattr#setg "$p" "$g"
   7022       ((p=p0+${#espec}))
   7023     done
   7024   fi
   7025 
   7026   ble/progcolor/wattr#setg "$p" "$1"
   7027   [[ $1 != d ]] &&
   7028     ble/progcolor/wattr#setg "$p1" d
   7029   return 0
   7030 }
   7031 
   7032 ## @fn ble/progcolor/highlight-filename/.pathspec-with-attr.wattr attr
   7033 ##   @param[in] attr
   7034 ##   @var[in] wtype p0 p1
   7035 ##   @var[in] path spec
   7036 function ble/progcolor/highlight-filename/.pathspec-with-attr.wattr {
   7037   local g; ble/syntax/attr2g "$1"
   7038   ble/progcolor/highlight-filename/.pathspec.wattr "$g"
   7039   return 0
   7040 }
   7041 ## @fn ble/progcolor/highlight-filename/.pathspec-by-name.wattr value
   7042 ##   @param[in] value
   7043 ##   @var[in] wtype p0 p1
   7044 ##   @var[in] path spec
   7045 function ble/progcolor/highlight-filename/.pathspec-by-name.wattr {
   7046   local value=$1
   7047 
   7048   local highlight_opts=
   7049   local type=; ble/syntax/highlight/filetype "$value"
   7050   ((type==ATTR_FILE_URL)) && highlight_opts=no-path-color
   7051 
   7052   # check values
   7053   if ((wtype==CTX_RDRF||wtype==CTX_RDRD2)); then
   7054     if ((type==ATTR_FILE_DIR)); then
   7055       # ディレクトリにリダイレクトはできない
   7056       type=$ATTR_ERR
   7057     elif ((_ble_syntax_TREE_WIDTH<=TE_nofs)); then
   7058       # noclobber の時は既存ファイルを > または <> で上書きできない
   7059       #
   7060       # 仮定: _ble_syntax_word に於いてリダイレクトとファイルは同じ位置で終了すると想定する。
   7061       #   この時、リダイレクトの情報の次にファイル名の情報が格納されている筈で、
   7062       #   リダイレクトの情報は node[TE_nofs-_ble_syntax_TREE_WIDTH] に入っていると考えられる。
   7063       #
   7064       local redirect_ntype=${node[TE_nofs-_ble_syntax_TREE_WIDTH]:1}
   7065       if [[ ( $redirect_ntype == *'>' || $redirect_ntype == '>'[\|\&] ) ]]; then
   7066         if [[ -e $value || -h $value ]]; then
   7067           if [[ -d $value || ! -w $value ]]; then
   7068             # ディレクトリまたは書き込み権限がない
   7069             type=$ATTR_ERR
   7070           elif [[ ( $redirect_ntype == [\<\&]'>' || $redirect_ntype == '>' || $redirect_ntype == '>&' ) && -f $value ]]; then
   7071             if [[ -o noclobber ]]; then
   7072               # 上書き禁止
   7073               type=$ATTR_ERR
   7074             else
   7075               # 上書き注意
   7076               type=$ATTR_FILE_WARN
   7077             fi
   7078           fi
   7079         elif [[ $value == */* && ! -w ${value%/*}/ || $value != */* && ! -w ./ ]]; then
   7080           # ディレクトリに書き込み権限がない
   7081           type=$ATTR_ERR
   7082         fi
   7083       elif [[ $redirect_ntype == '<' && ! -r $value ]]; then
   7084         # ファイルがないまたは読み取り権限がない
   7085         type=$ATTR_ERR
   7086       fi
   7087     fi
   7088   fi
   7089 
   7090   local g=
   7091   if [[ $bleopt_filename_ls_colors ]]; then
   7092     ble/syntax/highlight/ls_colors "$value"
   7093   fi
   7094   [[ $type && ! $g ]] && ble/syntax/attr2g "$type"
   7095 
   7096   ble/progcolor/highlight-filename/.pathspec.wattr "${g:-d}" "$highlight_opts"
   7097   return 0
   7098 }
   7099 
   7100 ## @fn ble/progcolor/highlight-filename/.single.wattr p0:p1
   7101 ##   @param[in] p0 p1
   7102 ##     ファイル名の (コマンドライン内部における) 範囲を指定します。
   7103 ##   @param[in] wtype
   7104 function ble/progcolor/highlight-filename/.single.wattr {
   7105   local p0=${1%%:*} p1=${1#*:}
   7106   local wtxt=${text:p0:p1-p0}
   7107 
   7108   # alias はシェル展開等を行う前に判定するべき
   7109   if ((wtype==CTX_CMDI)) && ble/alias#active "$wtxt"; then
   7110     # Note: [*?] 等の文字が含まれている alias 名の場合 failglob するかもしれ
   7111     # ないが、実際は展開されるので問題ない。
   7112     ble/progcolor/wattr#setattr "$p0" "$ATTR_CMD_ALIAS"
   7113     return 0
   7114   fi
   7115 
   7116   local path_opts=after-sep:$highlight_eval_opts
   7117   # チルダ展開の文脈でない時には抑制
   7118   [[ $wtxt == '~'* ]] && ((_ble_syntax_attr[p0]!=ATTR_TILDE)) && path_opts=$path_opts:notilde
   7119   ((wtype==CTX_RDRS||wtype==ATTR_VAR||wtype==CTX_VALI&&wbeg<p0)) && path_opts=$path_opts:noglob
   7120 
   7121   local ret path spec ext value count
   7122   ble/syntax:bash/simple-word/evaluate-path-spec "$wtxt" / "count:$path_opts"; ext=$? value=("${ret[@]}")
   7123   ((ext==148)) && return 148
   7124   if ((ext==142)); then
   7125     if [[ $ble_textarea_render_defer_running ]]; then
   7126       # background で timeout した時はこのファイル名の着色は諦める
   7127       ble/progcolor/wattr#setg "$p0" d
   7128     else
   7129       # foreground で timeout した時は後で background で着色する為に取り敢えず抜ける
   7130       return 148
   7131     fi
   7132   elif ((ext&&(wtype==CTX_CMDI||wtype==CTX_ARGI||wtype==CTX_ARGEI||wtype==CTX_RDRF||wtype==CTX_RDRS||wtype==CTX_RDRD||wtype==CTX_RDRD2||wtype==CTX_VALI))); then
   7133     # failglob 等の理由で展開に失敗した場合
   7134     ble/progcolor/highlight-filename/.pathspec-with-attr.wattr "$ATTR_ERR"
   7135   elif (((wtype==CTX_RDRF||wtype==CTX_RDRD||wtype==CTX_RDRD2)&&count>=2)); then
   7136     # 複数語に展開されたら駄目
   7137     ble/progcolor/wattr#setattr "$p0" "$ATTR_ERR"
   7138   elif ((wtype==CTX_CMDI)); then
   7139     local attr=${_ble_syntax_attr[wbeg]}
   7140     if ((attr!=ATTR_KEYWORD&&attr!=ATTR_KEYWORD_BEGIN&&attr!=ATTR_KEYWORD_END&&attr!=ATTR_KEYWORD_MID&&attr!=ATTR_DEL)); then
   7141       local type=; ble/syntax/highlight/cmdtype "$value" "$wtxt"
   7142       if ((type==ATTR_CMD_FILE||type==ATTR_CMD_FILE||type==ATTR_ERR)); then
   7143         ble/progcolor/highlight-filename/.pathspec-with-attr.wattr "$type"
   7144       elif [[ $type ]]; then
   7145         ble/progcolor/wattr#setattr "$p0" "$type"
   7146       fi
   7147     fi
   7148   elif ((wtype==CTX_RDRD||wtype==CTX_RDRD2)); then
   7149     if local rex='^[0-9]+-?$|^-$'; [[ $value =~ $rex ]]; then
   7150       ble/progcolor/wattr#setattr "$p0" "$ATTR_DEL"
   7151     elif ((wtype==CTX_RDRD2)); then
   7152       ble/progcolor/highlight-filename/.pathspec-by-name.wattr "$value"
   7153     else
   7154       ble/progcolor/wattr#setattr "$p0" "$ATTR_ERR"
   7155     fi
   7156   elif ((wtype==CTX_ARGI||wtype==CTX_ARGEI||wtype==CTX_VALI||wtype==ATTR_VAR||wtype==CTX_RDRS||wtype==CTX_RDRF)); then
   7157     ble/progcolor/highlight-filename/.pathspec-by-name.wattr "$value"
   7158   fi
   7159 }
   7160 
   7161 function ble/progcolor/highlight-filename.wattr {
   7162   local p0=$1 p1=$2
   7163   if ((p0<p1)) && [[ $bleopt_highlight_filename ]]; then
   7164     local wtxt=${text:p0:p1-p0}
   7165     local ret; ble/progcolor/highlight-filename/.detect-separated-path "$wtxt"; local ext=$?
   7166     ((ext==148)) && return 148
   7167     if ((ext==0)); then
   7168       local sep=$ret ranges i
   7169       ble/syntax:bash/simple-word/locate-filename "$wtxt" "$sep" "url:$highlight_eval_opts"
   7170       (($?==148)) && return 148; ranges=("${ret[@]}")
   7171       for ((i=0;i<${#ranges[@]};i+=2)); do
   7172         ble/progcolor/highlight-filename/.single.wattr "$((p0+ranges[i])):$((p0+ranges[i+1]))"
   7173         (($?==148)) && return 148
   7174       done
   7175     elif ble/syntax:bash/simple-word/is-simple "$wtxt"; then
   7176       ble/progcolor/highlight-filename/.single.wattr "$p0":"$p1"
   7177       (($?==148)) && return 148
   7178     fi
   7179   fi
   7180 }
   7181 
   7182 function ble/progcolor/@wattr {
   7183   [[ $wtype =~ ^[0-9]+$ ]] || return 1
   7184   [[ $wattr == - ]] || return 1
   7185   local "${_ble_syntax_progcolor_wattr_vars[@]/%/=}" # WA #D1570 checked
   7186   ble/progcolor/wattr#initialize
   7187 
   7188   "$@"; local ext=$?
   7189 
   7190   if ((ext==148)); then
   7191     _ble_textarea_render_defer=1
   7192     ble/syntax/wrange#update _ble_syntax_word_defer_ "$wend"
   7193   else
   7194     ble/progcolor/wattr#finalize
   7195   fi
   7196   return "$ext"
   7197 }
   7198 
   7199 ## @fn ble/progcolor/word:default/.is-option wtxt
   7200 ##   @var[in] wtype
   7201 ##   @var[in] progcolor_*
   7202 function ble/progcolor/word:default/.is-option {
   7203   # Note: オプションとして許される文字に関しては_ble_complete_option_chars と同期を取る。
   7204   ((wtype==CTX_ARGI||wtype==CTX_ARGEI||wtype==CTX_ARGVI)) &&
   7205     ble/string#match "$1" '^(-[-_a-zA-Z0-9!#$%&:;.,^~|\?/*@]*)=?' && # 高速な判定を先に済ませる
   7206     ble/progcolor/is-option-context &&
   7207     ble/string#match "$1" '^(-[-_a-zA-Z0-9!#$%&:;.,^~|\?/*@]*)=?' # 再実行 for BASH_REMATCH
   7208 }
   7209 
   7210 ## @fn ble/progcolor/word:default
   7211 ##   @var[in] node TE_i TE_nofs
   7212 ##   @var[in] wtype wlen wbeg wend wattr
   7213 ##   @var[in] ${_ble_syntax_progcolor_wattr_vars[@]}
   7214 function ble/progcolor/word:default/impl.wattr {
   7215   if ((wtype==CTX_RDRH||wtype==CTX_RDRI||wtype==ATTR_FUNCDEF||wtype==ATTR_ERR)); then
   7216     # ヒアドキュメントのキーワード指定部分は、
   7217     # 展開・コマンド置換などに従った解析が行われるが、
   7218     # 実行は一切起こらないので一色で塗りつぶす。
   7219     ble/progcolor/wattr#setattr "$wbeg" "$wtype"
   7220 
   7221   else
   7222     # @var p0 p1
   7223     #   文字列を切り出す範囲。
   7224     local p0=$wbeg p1=$wend wtxt=${text:wbeg:wlen}
   7225 
   7226     # 変数代入
   7227     if ((wtype==ATTR_VAR||wtype==CTX_VALI)); then
   7228       # 変数代入の場合は右辺だけ切り出す。
   7229       #   Note: arr=(a=a*b a[1]=a*b) などはパス名展開の対象ではない。
   7230       #     これは変数代入の形式として認識されているからである。
   7231       #     以下では element-assignment を指定することで、
   7232       #     配列要素についても変数代入の形式を抽出する様にしている。
   7233       local ret
   7234       ble/syntax:bash/find-rhs "$wtype" "$wbeg" "$wlen" element-assignment && p0=$ret
   7235     elif ((wtype==CTX_ARGI||wtype==CTX_ARGEI||wtype==CTX_VALI)) && { local rex='^[_a-zA-Z][_a-zA-Z0-9]*='; [[ $wtxt =~ $rex ]]; }; then
   7236       # 変数代入形式の通常引数
   7237       ((p0+=${#BASH_REMATCH}))
   7238     elif ble/progcolor/word:default/.is-option "$wtxt"; then
   7239       # --prefix= 等のオプション引数
   7240       local rematch=$BASH_REMATCH rematch1=${BASH_REMATCH[1]}
   7241       local ret; ble/color/face2g argument_option
   7242       ble/progcolor/wattr#setg "$p0" "$ret"
   7243       ble/progcolor/wattr#setg "$((p0+${#rematch1}))" d
   7244       ((p0+=${#rematch}))
   7245     fi
   7246 
   7247     ble/progcolor/highlight-filename.wattr "$p0" "$p1"
   7248     (($?==148)) && return 148
   7249   fi
   7250 
   7251   return 0
   7252 }
   7253 
   7254 function ble/progcolor/word:default {
   7255   ble/progcolor/@wattr ble/progcolor/word:default/impl.wattr
   7256 }
   7257 
   7258 ## @fn ble/progcolor/default
   7259 ##   @var[in] comp_words comp_cword comp_line comp_point
   7260 ##   @var[in] tree_words
   7261 ##   @var[in,out] color_umin color_umax
   7262 function ble/progcolor/default {
   7263   local i "${_ble_syntax_progcolor_vars[@]/%/=}" # WA #D1570 checked
   7264   for ((i=1;i<${#comp_words[@]};i++)); do
   7265     local ref=${tree_words[i]}
   7266     [[ $ref ]] || continue
   7267     local progcolor_iword=$i
   7268     ble/progcolor/load-word-data "$ref"
   7269     ble/progcolor/word:default
   7270   done
   7271 }
   7272 
   7273 ## @fn ble/progcolor/.compline-rewrite-command command [args...]
   7274 ##   @var[in,out] comp_words comp_cword comp_line comp_point
   7275 function ble/progcolor/.compline-rewrite-command {
   7276   local ocmd=${comp_words[0]}
   7277   [[ $1 != "$ocmd" ]] || (($#>=2)) || return 1
   7278   local IFS=$_ble_term_IFS
   7279   local ins="$*"
   7280   comp_line=$ins${comp_line:${#ocmd}}
   7281   ((comp_point-=${#ocmd},comp_point<0&&(comp_point=0),comp_point+=${#ins}))
   7282   comp_words=("$@" "${comp_words[@]:1}")
   7283   ((comp_cword&&(comp_cword+=$#-1)))
   7284 
   7285   # update tree_words
   7286   ble/array#reserve-prototype $#
   7287   tree_words=("${tree_words[0]}" "${_ble_array_prototype[@]::$#-1}" "${tree_words[@]:1}")
   7288 }
   7289 ## @fn ble/progcolor cmd opts
   7290 ##   @var[in] comp_words comp_cword comp_line comp_point
   7291 ##   @var[in] tree_words
   7292 ##   @var[in,out] color_umin color_umax
   7293 function ble/progcolor {
   7294   local cmd=$1 opts=$2
   7295 
   7296   # cache used by "eval-word"
   7297   local -a progcolor_stats=()
   7298   local -a progcolor_wvals=()
   7299 
   7300   # cache used by "load-cmdspec-opts"
   7301   local progcolor_cmdspec_opts=
   7302 
   7303   # cache used by "is-option-context"
   7304   local -a progcolor_optctx=()
   7305 
   7306   local -a alias_args=()
   7307   local checked=" " processed=
   7308   while :; do
   7309     if ble/is-function ble/cmdinfo/cmd:"$cmd"/chroma; then
   7310       ble/progcolor/.compline-rewrite-command "$cmd" "${alias_args[@]}"
   7311       ble/cmdinfo/cmd:"$cmd"/chroma "$opts"
   7312       processed=1
   7313       break
   7314     elif [[ $cmd == */?* ]] && ble/is-function ble/cmdinfo/cmd:"${cmd##*/}"/chroma; then
   7315       ble/progcolor/.compline-rewrite-command "${cmd##*/}" "${alias_args[@]}"
   7316       ble/cmdinfo/cmd:"${cmd##*/}"/chroma "$opts"
   7317       processed=1
   7318       break
   7319     fi
   7320     checked="$checked$cmd "
   7321 
   7322     local ret
   7323     ble/alias#expand "$cmd"
   7324     ble/string#split-words ret "$ret"
   7325     [[ $checked == *" $ret "* ]] && break
   7326     cmd=$ret
   7327     ((${#ret[@]}>=2)) &&
   7328       alias_args=("${ret[@]:1}" "${alias_args[@]}")
   7329   done
   7330   [[ $processed ]] ||
   7331     ble/progcolor/default
   7332 
   7333   # コマンド名に対しては既定の着色を実行
   7334   if [[ ${tree_words[0]} ]]; then
   7335     local "${_ble_syntax_progcolor_vars[@]/%/=}" # WA #D1570 checked
   7336     ble/progcolor/load-word-data "${tree_words[0]}"
   7337     [[ $wattr == - ]] && ble/progcolor/word:default
   7338   fi
   7339 }
   7340 
   7341 #------------------------------------------------------------------------------
   7342 # ble/highlight/layer:syntax
   7343 
   7344 # adapter に頼らず直接実装したい
   7345 function ble/highlight/layer:syntax/touch-range {
   7346   ble/syntax/urange#update '' "$@"
   7347 }
   7348 function ble/highlight/layer:syntax/fill {
   7349   local _ble_local_script='
   7350     local iARR=0 i1ARR=$2 i2ARR=$3
   7351     for ((iARR=i1ARR;iARR<i2ARR;iARR++)); do
   7352       ARR[iARR]=$4
   7353     done
   7354   '; builtin eval -- "${_ble_local_script//ARR/$1}"
   7355 }
   7356 
   7357 _ble_highlight_layer_syntax_VARNAMES=(
   7358   _ble_highlight_layer_syntax_buff
   7359   _ble_highlight_layer_syntax_active
   7360   _ble_highlight_layer_syntax1_table
   7361   _ble_highlight_layer_syntax2_table
   7362   _ble_highlight_layer_syntax3_list
   7363   _ble_highlight_layer_syntax3_table)
   7364 function ble/highlight/layer:syntax/initialize-vars {
   7365   # Note (#D2000): core-syntax は遅延ロードされるので layter/update/shift 対象
   7366   # の配列は全て必要な要素数を備えている必要がある。
   7367   local prev_iN=${#_ble_highlight_layer_plain_buff[*]}
   7368   ble/array#reserve-prototype "$prev_iN"
   7369   _ble_highlight_layer_syntax_buff=("${_ble_array_prototype[@]::prev_iN}")
   7370   _ble_highlight_layer_syntax1_table=("${_ble_array_prototype[@]::prev_iN}")
   7371   _ble_highlight_layer_syntax2_table=("${_ble_array_prototype[@]::prev_iN}")
   7372   _ble_highlight_layer_syntax3_table=("${_ble_array_prototype[@]::prev_iN}") # errors
   7373   _ble_highlight_layer_syntax3_list=()
   7374 }
   7375 ble/highlight/layer:syntax/initialize-vars
   7376 
   7377 function ble/highlight/layer:syntax/update-attribute-table {
   7378   ble/highlight/layer/update/shift _ble_highlight_layer_syntax1_table
   7379   if ((_ble_syntax_attr_umin>=0)); then
   7380     ble/highlight/layer:syntax/touch-range _ble_syntax_attr_umin _ble_syntax_attr_umax
   7381 
   7382     local i g=0
   7383     ((_ble_syntax_attr_umin>0)) &&
   7384       ((g=_ble_highlight_layer_syntax1_table[_ble_syntax_attr_umin-1]))
   7385 
   7386     for ((i=_ble_syntax_attr_umin;i<_ble_syntax_attr_umax;i++)); do
   7387       if ((${_ble_syntax_attr[i]})); then
   7388         ble/syntax/attr2g "${_ble_syntax_attr[i]}"
   7389       fi
   7390       _ble_highlight_layer_syntax1_table[i]=$g
   7391     done
   7392 
   7393     _ble_syntax_attr_umin=-1 _ble_syntax_attr_umax=-1
   7394   fi
   7395 }
   7396 
   7397 function ble/highlight/layer:syntax/word/.update-attributes/.proc {
   7398   [[ $wtype =~ ^[0-9]+$ ]] || return 1
   7399   [[ ${node[TE_nofs+4]} == - ]] || return 1
   7400 
   7401   if ((wtype==CTX_CMDI||wtype==CTX_ARGI||wtype==CTX_ARGVI||wtype==CTX_ARGEI)); then
   7402     local comp_line comp_point comp_words comp_cword tree_words
   7403     if ble/syntax:bash/extract-command-by-noderef "$TE_i:$TE_nofs" treeinfo; then
   7404       local cmd=${comp_words[0]}
   7405       ble/progcolor "$cmd"
   7406       return 0
   7407     fi
   7408   fi
   7409 
   7410   # コマンドラインを復元できなければ単一単語の着色
   7411   ble/progcolor/word:default
   7412 }
   7413 
   7414 ## @fn ble/highlight/layer:syntax/word/.update-attributes
   7415 ## @var[in] _ble_syntax_word_umin,_ble_syntax_word_umax
   7416 ## @var[in,out] color_umin color_umax
   7417 function ble/highlight/layer:syntax/word/.update-attributes {
   7418   ((_ble_syntax_word_umin>=0)) || return 1
   7419 
   7420   # Timeout setting for simple-word/eval
   7421   if [[ ! $ble_textarea_render_defer_running ]]; then
   7422     local _ble_syntax_bash_simple_eval_timeout=$bleopt_highlight_timeout_sync
   7423     local _ble_syntax_bash_simple_eval_timeout_carry=
   7424     local highlight_eval_opts=cached:single:stopcheck:timeout-carry
   7425   else
   7426     local _ble_syntax_bash_simple_eval_timeout=$bleopt_highlight_timeout_async
   7427     local highlight_eval_opts=cached:single:stopcheck
   7428   fi
   7429 
   7430   ble/syntax/tree-enumerate-in-range "$_ble_syntax_word_umin" "$_ble_syntax_word_umax" \
   7431     ble/highlight/layer:syntax/word/.update-attributes/.proc
   7432 }
   7433 
   7434 ## @fn ble/highlight/layer:syntax/word/.apply-attribute wbeg wend wattr
   7435 ##   @param[in] wbeg wend wattr
   7436 function ble/highlight/layer:syntax/word/.apply-attribute {
   7437   local wbeg=$1 wend=$2 wattr=$3
   7438   ((wbeg<color_umin&&(wbeg=color_umin),
   7439     wend>color_umax&&(wend=color_umax),
   7440     wbeg<wend)) || return 1
   7441 
   7442   if [[ $wattr =~ ^[0-9]+$ ]]; then
   7443     ble/array#fill-range _ble_highlight_layer_syntax2_table "$wbeg" "$wend" "$wattr"
   7444   elif [[ $wattr == m* ]]; then
   7445     local ranges; ble/string#split ranges , "${wattr:1}"
   7446     local i=$wbeg j range
   7447     for range in "${ranges[@]}"; do
   7448       local len=${range%%:*} sub_wattr=${range#*:}
   7449       if [[ $len == '$' ]]; then
   7450         j=$wend
   7451       else
   7452         ((j=i+len,j>wend&&(j=wend)))
   7453       fi
   7454       ble/highlight/layer:syntax/word/.apply-attribute "$i" "$j" "$sub_wattr"
   7455       (((i=j)<wend)) || break
   7456     done
   7457   elif [[ $wattr == d ]]; then
   7458     ble/array#fill-range _ble_highlight_layer_syntax2_table "$wbeg" "$wend" ''
   7459   fi
   7460 }
   7461 
   7462 function ble/highlight/layer:syntax/word/.proc-childnode {
   7463   if [[ $wtype =~ ^[0-9]+$ ]]; then
   7464     local wbeg=$wbegin wend=$TE_i
   7465     ble/highlight/layer:syntax/word/.apply-attribute "$wbeg" "$wend" "$attr"
   7466   fi
   7467 
   7468   ((tchild>=0)) && ble/syntax/tree-enumerate-children "$proc_children"
   7469 }
   7470 
   7471 ## @var[in,out] _ble_syntax_word_umin _ble_syntax_word_umax
   7472 function ble/highlight/layer:syntax/update-word-table {
   7473   # update table2 (単語の削除に関しては後で考える)
   7474   # (1) 単語色の計算
   7475   local color_umin=-1 color_umax=-1 iN=${#_ble_syntax_text}
   7476   ble/highlight/layer:syntax/word/.update-attributes
   7477 
   7478   # (2) 色配列 shift
   7479   ble/highlight/layer/update/shift _ble_highlight_layer_syntax2_table
   7480 
   7481   # 2015-08-16 暫定 (本当は入れ子構造を考慮に入れたい)
   7482   ble/syntax/wrange#update _ble_syntax_word_ "$_ble_syntax_vanishing_word_umin" "$_ble_syntax_vanishing_word_umax"
   7483   ble/syntax/wrange#update color_ "$_ble_syntax_vanishing_word_umin" "$_ble_syntax_vanishing_word_umax"
   7484   _ble_syntax_vanishing_word_umin=-1 _ble_syntax_vanishing_word_umax=-1
   7485 
   7486   # (3) 色配列に登録
   7487   ble/highlight/layer:syntax/word/.apply-attribute 0 "$iN" d # clear word color
   7488   local TE_i
   7489   for ((TE_i=_ble_syntax_word_umax;TE_i>=_ble_syntax_word_umin;)); do
   7490     if ((TE_i>0)) && [[ ${_ble_syntax_tree[TE_i-1]} ]]; then
   7491       local -a node
   7492       ble/string#split-words node "${_ble_syntax_tree[TE_i-1]}"
   7493 
   7494       local wlen=${node[1]}
   7495       local wbeg=$((TE_i-wlen)) wend=$TE_i
   7496 
   7497       if [[ ${node[0]} =~ ^[0-9]+$ ]]; then
   7498         local attr=${node[4]}
   7499         ble/highlight/layer:syntax/word/.apply-attribute "$wbeg" "$wend" "$attr"
   7500       fi
   7501 
   7502       local tclen=${node[2]}
   7503       if ((tclen>=0)); then
   7504         local tchild=$((TE_i-tclen))
   7505         local tree= TE_nofs=0 proc_children=ble/highlight/layer:syntax/word/.proc-childnode
   7506         ble/syntax/tree-enumerate-children "$proc_children"
   7507       fi
   7508 
   7509       ((TE_i=wbeg))
   7510     else
   7511       ((TE_i--))
   7512     fi
   7513   done
   7514   ((color_umin>=0)) && ble/highlight/layer:syntax/touch-range "$color_umin" "$color_umax"
   7515 
   7516   _ble_syntax_word_umin=-1 _ble_syntax_word_umax=-1
   7517 }
   7518 
   7519 function ble/highlight/layer:syntax/update-error-table/set {
   7520   local i1=$1 i2=$2 g=$3
   7521   if ((i1<i2)); then
   7522     ble/highlight/layer:syntax/touch-range "$i1" "$i2"
   7523     ble/highlight/layer:syntax/fill _ble_highlight_layer_syntax3_table "$i1" "$i2" "$g"
   7524     _ble_highlight_layer_syntax3_list[${#_ble_highlight_layer_syntax3_list[@]}]="$i1 $i2"
   7525   fi
   7526 }
   7527 function ble/highlight/layer:syntax/update-error-table {
   7528   ble/highlight/layer/update/shift _ble_highlight_layer_syntax3_table
   7529 
   7530   # clear old errors
   7531   #   shift の前の方が簡単に更新できるが、
   7532   #   umin umax を更新する為に shift の後で処理する。
   7533   local j=0 jN=${#_ble_highlight_layer_syntax3_list[*]}
   7534   if ((jN)); then
   7535     for ((j=0;j<jN;j++)); do
   7536       local -a range
   7537       ble/string#split-words range "${_ble_highlight_layer_syntax3_list[j]}"
   7538 
   7539       local a=${range[0]} b=${range[1]}
   7540       ((a>=DMAX0?(a+=DMAX-DMAX0):(a>=DMIN&&(a=DMIN)),
   7541         b>=DMAX0?(b+=DMAX-DMAX0):(b>=DMIN&&(b=DMIN))))
   7542       if ((a<b)); then
   7543         ble/highlight/layer:syntax/fill _ble_highlight_layer_syntax3_table "$a" "$b" ''
   7544         ble/highlight/layer:syntax/touch-range "$a" "$b"
   7545       fi
   7546     done
   7547     _ble_highlight_layer_syntax3_list=()
   7548   fi
   7549 
   7550   # この実装では毎回全てのエラーを設定するので
   7551   # 実は下の様にすれば良いだけ…
   7552   #_ble_highlight_layer_syntax3_table=()
   7553 
   7554   # set errors
   7555   if ((iN>0)) && [[ ${_ble_syntax_stat[iN]} ]]; then
   7556     # iN==0 の時は実行しない。face 遅延初期化のため(最初は iN==0)。
   7557     local ret; ble/color/face2g syntax_error; local g=$ret
   7558 
   7559     # 入れ子が閉じていないエラー
   7560     local -a stat
   7561     ble/string#split-words stat "${_ble_syntax_stat[iN]}"
   7562     local ctx=${stat[0]} nlen=${stat[3]} nparam=${stat[6]}
   7563     [[ $nparam == none ]] && nparam=
   7564     local i inest
   7565     if ((nlen>0)) || [[ $nparam ]]; then
   7566       # 終端点の着色
   7567       ble/highlight/layer:syntax/update-error-table/set "$((iN-1))" "$iN" "$g"
   7568 
   7569       if ((nlen>0)); then
   7570         ((inest=iN-nlen))
   7571         while ((inest>=0)); do
   7572           # 開始字句の着色
   7573           local inest2
   7574           for ((inest2=inest+1;inest2<iN;inest2++)); do
   7575             [[ ${_ble_syntax_attr[inest2]} ]] && break
   7576           done
   7577           ble/highlight/layer:syntax/update-error-table/set "$inest" "$inest2" "$g"
   7578 
   7579           ((i=inest))
   7580           local wtype wbegin tchild tprev
   7581           ble/syntax/parse/nest-pop
   7582           ((inest>=i&&(inest=i-1)))
   7583         done
   7584       fi
   7585     fi
   7586 
   7587     # コマンド欠落・引数の欠落
   7588     if ((ctx==CTX_CMDX1||ctx==CTX_CMDXC||ctx==CTX_FARGX1||ctx==CTX_SARGX1||ctx==CTX_FARGX2||ctx==CTX_CARGX1||ctx==CTX_CARGX2||ctx==CTX_COARGX)); then
   7589       # 終端点の着色
   7590       ble/highlight/layer:syntax/update-error-table/set "$((iN-1))" "$iN" "$g"
   7591     fi
   7592   fi
   7593 }
   7594 
   7595 function ble/highlight/layer:syntax/update {
   7596   local text=$1 player=$2
   7597   local i iN=${#text}
   7598 
   7599   local umin=-1 umax=-1
   7600   # 少なくともこの範囲は文字が変わっているので再描画する必要がある
   7601   ((DMIN>=0)) && umin=$DMIN umax=$DMAX
   7602 
   7603   # 今回 layer:syntax が無効の時
   7604   if [[ ! $bleopt_highlight_syntax ]]; then
   7605     if [[ $_ble_highlight_layer_syntax_active ]]; then
   7606       _ble_highlight_layer_syntax_active=
   7607       PREV_UMIN=0 PREV_UMAX=${#1}
   7608     fi
   7609     return 0
   7610   fi
   7611 
   7612   # 前回 layer:syntax が無効だった時
   7613   if [[ ! $_ble_highlight_layer_syntax_active ]]; then
   7614     # Request full update
   7615     _ble_highlight_layer_syntax_active=1
   7616     umin=0 umax=${#text}
   7617   fi
   7618 
   7619   #--------------------------------------------------------
   7620 
   7621 #%if !release
   7622   if [[ $bleopt_syntax_debug ]]; then
   7623     local debug_attr_umin=$_ble_syntax_attr_umin
   7624     local debug_attr_uend=$_ble_syntax_attr_umax
   7625   fi
   7626 #%end
   7627 
   7628   ble/cmdspec/initialize # load chroma
   7629   ble/highlight/layer:syntax/update-attribute-table
   7630   ble/highlight/layer:syntax/update-word-table
   7631   ble/highlight/layer:syntax/update-error-table
   7632 
   7633   # shift&sgr 設定
   7634   if ((DMIN>=0)); then
   7635     ble/highlight/layer/update/shift _ble_highlight_layer_syntax_buff
   7636     if ((DMAX>0)); then
   7637       local g sgr ch ret
   7638       ble/highlight/layer:syntax/getg "$DMAX"
   7639       ble/color/g2sgr "$g"; sgr=$ret
   7640       ch=${_ble_highlight_layer_plain_buff[DMAX]}
   7641       _ble_highlight_layer_syntax_buff[DMAX]=$sgr$ch
   7642     fi
   7643   fi
   7644 
   7645   local i j g gprev=0
   7646   if ((umin>0)); then
   7647     ble/highlight/layer:syntax/getg "$((umin-1))"
   7648     gprev=$g
   7649   fi
   7650 
   7651   if ((umin>=0)); then
   7652     local ret
   7653     for ((i=umin;i<=umax;i++)); do
   7654       local ch=${_ble_highlight_layer_plain_buff[i]}
   7655       ble/highlight/layer:syntax/getg "$i"
   7656       [[ $g ]] || ble/highlight/layer/update/getg "$i"
   7657       if ((gprev!=g)); then
   7658         ble/color/g2sgr "$g"
   7659         ch=$ret$ch
   7660         ((gprev=g))
   7661       fi
   7662       _ble_highlight_layer_syntax_buff[i]=$ch
   7663     done
   7664   fi
   7665 
   7666   PREV_UMIN=$umin PREV_UMAX=$umax
   7667   PREV_BUFF=_ble_highlight_layer_syntax_buff
   7668 
   7669 #%if !release
   7670   if [[ $bleopt_syntax_debug ]]; then
   7671     local status buff= nl=$'\n'
   7672     _ble_syntax_attr_umin=$debug_attr_umin _ble_syntax_attr_umax=$debug_attr_uend ble/syntax/print-status -v status
   7673 
   7674     local -a DRAW_BUFF=()
   7675     ble/syntax/print-layer-buffer.draw plain
   7676     ble/syntax/print-layer-buffer.draw syntax
   7677     ble/syntax/print-layer-buffer.draw disabled
   7678     ble/syntax/print-layer-buffer.draw region
   7679     ble/syntax/print-layer-buffer.draw overwrite
   7680     local ret; ble/canvas/sflush.draw
   7681     status=$status$ret
   7682     #ble/util/assign buff 'declare -p _ble_textarea_bufferName $_ble_textarea_bufferName | cat -A'; status="$status$buff"
   7683     ble/edit/info/show ansi "$status"
   7684   fi
   7685 #%end
   7686 
   7687   # # 以下は単語の分割のデバグ用
   7688   # local -a words=() word
   7689   # for ((i=1;i<=iN;i++)); do
   7690   #   if [[ ${_ble_syntax_tree[i-1]} ]]; then
   7691   #     ble/string#split-words word "${_ble_syntax_tree[i-1]}"
   7692   #     local wtxt="${text:i-word[1]:word[1]}" value
   7693   #     if ble/syntax:bash/simple-word/is-simple "$wtxt"; then
   7694   #       local ret; ble/syntax:bash/simple-word/eval "$wtxt" noglob; value=$ret
   7695   #     else
   7696   #       value="? ($wtxt)"
   7697   #     fi
   7698   #     ble/array#push words "[$value ${word[*]}]"
   7699   #   fi
   7700   # done
   7701   # ble/edit/info/show text "${words[*]}"
   7702 }
   7703 
   7704 function ble/highlight/layer:syntax/getg {
   7705   [[ $bleopt_highlight_syntax ]] || return 1
   7706   local i=$1
   7707   if [[ ${_ble_highlight_layer_syntax3_table[i]} ]]; then
   7708     g=${_ble_highlight_layer_syntax3_table[i]}
   7709   elif [[ ${_ble_highlight_layer_syntax2_table[i]} ]]; then
   7710     g=${_ble_highlight_layer_syntax2_table[i]}
   7711   elif [[ ${_ble_highlight_layer_syntax1_table[i]} ]]; then
   7712     g=${_ble_highlight_layer_syntax1_table[i]}
   7713   fi
   7714 }
   7715 
   7716 function ble/highlight/layer:syntax/textarea_render_defer.hook {
   7717   ble/syntax/wrange#update _ble_syntax_word_ "$_ble_syntax_word_defer_umin" "$_ble_syntax_word_defer_umax"
   7718   _ble_syntax_word_defer_umin=-1
   7719   _ble_syntax_word_defer_umax=-1
   7720 }
   7721 blehook textarea_render_defer!=ble/highlight/layer:syntax/textarea_render_defer.hook
   7722 
   7723 #%#----------------------------------------------------------------------------
   7724 #%# old test samples
   7725 #%#----------------------------------------------------------------------------
   7726 #%begin
   7727 # mytest 'echo hello world'
   7728 # mytest 'echo "hello world"'
   7729 # mytest 'echo a"hed"a "aa"b b"aa" aa'
   7730 # mytest 'echo a"$"a a"\$\",$*,$var,$12"a $*,$var,$12'
   7731 # mytest 'echo a"---$((1+a[12]*3))---$(echo hello)---"a'
   7732 # mytest 'a=1 b[x[y]]=1234 echo <( world ) >> hello; ( sub shell); ((1+2*3));'
   7733 # mytest 'a=${#hello} b=${world[10]:1:(5+2)*3} c=${arr[*]%%"test"$(cmd).cpp} d+=12'
   7734 # mytest 'for ((i=0;i<10;i++)); do echo hello; done; { : '"'worlds'\\'' record'"'; }'
   7735 # mytest '[[ echo == echo ]]; echo hello'
   7736 
   7737 # 関数名に使える文字?
   7738 #
   7739 # 全く使えない文字 |&;<>()!$\'"`
   7740 #
   7741 # name() の形式だと
   7742 #   { } をコマンドとして定義できない。function の形式なら可能
   7743 #
   7744 # set -H だと
   7745 #   ! を履歴展開の構文で含む関数は定義できない。
   7746 #   set +H にしておけば定義する事ができる。
   7747 #   name() の形式では ^ で始まる関数は定義できない。
   7748 #
   7749 # extglob on だと
   7750 #   ? * @ + ! は name() の形式で定義できない。
   7751 #   一応 name () と間に空白を挟めば定義できる。
   7752 #   function ?() *() などとすると "?()" という名前で関数が作られる。
   7753 #
   7754 #%end
   7755 #%#----------------------------------------------------------------------------
   7756 #%)
   7757 #%m main main.r/\<ATTR_/_ble_attr_/
   7758 #%m main main.r/\<CTX_/_ble_ctx_/
   7759 #%x main
   7760 
   7761 function ble/syntax/import { :; }
   7762 
   7763 blehook/invoke syntax_load
   7764 ble/function#try ble/textarea#invalidate str
   7765 
   7766 return 0