From 611c1d93a11c6d723c60375776ba1b5cd344bdaa Mon Sep 17 00:00:00 2001 From: Koichi Murase Date: Mon, 26 Jun 2023 10:29:22 +0900 Subject: [PATCH] syntax: support version-dependent arithmetic quotes --- docs/ChangeLog.md | 10 ++-- lib/core-syntax.sh | 142 +++++++++++++++++++++++++++++++++++---------- memo/D2051.sh | 100 +++++++++++++++++++++++++++++++ note.txt | 86 +++++++++++++++++++++++++++ 4 files changed, 302 insertions(+), 36 deletions(-) create mode 100755 memo/D2051.sh diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md index c0fa2bc2..772cbfd5 100644 --- a/docs/ChangeLog.md +++ b/docs/ChangeLog.md @@ -8,8 +8,8 @@ - bgproc: support opts `kill9-timeout=TIMEOUT` `#D2034` 3ab41652 - progcomp(cd): change display name and support mandb desc (requested by EmilySeville7cfg) `#D2039` 74402098 - cmdspec: add completion options for builtins (motivated by EmilySeville7cfg) `#D2040` 9bd24691 -- syntax: support bash-5.3 function subst `${ list; }` `#2045` 0906fd95 xxxxxxxx -- complete: support `bleopt complete_requote_threshold` (requested by rauldipeas) `#2048` xxxxxxxx +- syntax: support bash-5.3 function subst `${ list; }` `#2045` 0906fd95 71272a4b +- complete: support `bleopt complete_requote_threshold` (requested by rauldipeas) `#2048` bb7e118e ## Fixes @@ -20,14 +20,16 @@ - progcomp: make option unique after applying mandb description `#D2042` 308ceeed - util (`ble/util/idle`): fix an infinite loop `#D2043` 5f4c0afd - main: fix `--inputrc=TYPE` not applied on startup `#D2044` 1b15b851 0adce7c9 -- stty: suggest `stty sane` after exiting from bash >= 5.2 to non-ble session `#D2046` xxxxxxxx +- stty: suggest `stty sane` after exiting from bash >= 5.2 to non-ble session `#D2046` b57ab2d6 - util (`ble/builtin/readonly`): adjust bash options (reported by dongxi8) `#D2050` xxxxxxxx ## Compatibility - main: check `nawk` version explicitly `#D2037` 0ff7bca1 - mandb: inject in bash-completion-2.12 interfaces `#D2041` dabc8553 -- complete: determine comp prefix from `COMPS` when `ble/syntax-raw` is specified (reported by teutat3s) `#D2049` xxxxxxxx +- complete: determine comp prefix from `COMPS` when `ble/syntax-raw` is specified (reported by teutat3s) `#D2049` f16c0d80 +- syntax: allow double-quotes in `$(())` in bash-4.4 (requested by mozirilla213) `#D2051` xxxxxxxx +- syntax: support version-dependent arithmetic backslash `#D2051` xxxxxxxx ## Contrib diff --git a/lib/core-syntax.sh b/lib/core-syntax.sh index e2f1dd22..d79b5a98 100644 --- a/lib/core-syntax.sh +++ b/lib/core-syntax.sh @@ -2083,19 +2083,37 @@ function ble/syntax:bash/check-quotes { local rex aqdel=$ATTR_QDEL aquot=$CTX_QUOT # 字句的に解釈されるが除去はされない場合 - if ((ctx==CTX_EXPR)); then + if ((ctx==CTX_EXPR)) && [[ $tail != \`* ]]; then local ntype ble/syntax/parse/nest-type - if [[ $ntype == '${' || $ntype == '$[' || $ntype == '$((' || $ntype == 'NQ(' ]]; then - # $[...] / $((...)) / ${var:...} の中では - # 如何なる quote も除去されない (字句的には解釈される)。 - # 除去されない quote は算術式エラーである。 - ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) - elif [[ $ntype == '"${' ]] && ! { [[ $tail == '$'[\'\"]* ]] && shopt -q extquote; }; then - # "${var:...}" の中では 〈extquote が設定されている時の $'' $""〉 を例外として - # quote は除去されない (字句的には解釈される)。 - ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) - fi + case $ntype in + ('${') + # ${var:...} の中では如何なる quote も除去されない (字句的には解釈される)。 + # 除去されない quote は算術式エラーである。Note: bash >= 5.2 では "..." + # と $"..." は許される。 + if ((_ble_bash<50200)) || [[ $tail == \'* || $tail == \$\'* ]]; then + ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) + fi ;; + ('$['|'$(('|expr-paren-ax) + if [[ $tail == \'* ]] || ((_ble_bash<40400)); then + ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) + fi ;; + ('(('|expr-paren|expr-brack) + if [[ $tail == \'* ]] && ((_ble_bash>=50100)); then + ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) + fi ;; + ('a['|'v['|expr-paren-ai|expr-brack-ai) + if [[ $tail == \'* ]] && ((_ble_bash>=40400)); then + ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) + fi ;; + ('"${') + if ! { [[ $tail == '$'[\'\"]* ]] && shopt -q extquote; }; then + # "${var:...}" の中では 〈extquote が設定されている時の $'' $""〉 を例 + # 外としてquote は除去されない (字句的には解釈される)。 + ((aqdel=ATTR_ERR,aquot=CTX_EXPR)) + fi ;; + # ('d['|expr-paren-di|expr-brack-di) ;; # nop + esac elif ((ctx==CTX_PWORD||ctx==CTX_PWORDE||ctx==CTX_PWORDR)); then # "${var ~}" の中では $'' $"" は ! shopt -q extquote の時除去されない。 if [[ $tail == '$'[\'\"]* ]] && ! shopt -q extquote; then @@ -2772,9 +2790,14 @@ function ble/syntax:bash/ctx-pword-error { ## 'v[' @ check-dollar o ${a[...]} の中身 ## '${' @ check-dollar o ${v:...} の中身 ## '"${' @ check-dollar x "${v:...}" の中身 -## '(' @ .count-paren o () によるネスト (quote 除去有効) -## 'NQ(' @ .count-paren x () によるネスト (quote 除去無効) -## '[' @ .count-bracket o [] によるネスト (quote 除去常時有効) +## 'expr-paren' @ .count-paren o () によるネスト (quote 除去有効) +## 'expr-paren-ax' @ .count-paren + () によるネスト / $(( $[ の内部 +## 'expr-paren-ai' @ .count-paren + () によるネスト / a[ v[ の内部 (unused) +## 'expr-paren-di' @ .count-paren o () によるネスト / d[ の内部 (unused) +## 'expr-brack' @ .count-bracket o [] によるネスト (quote 除去常時有効) (unused) +## 'expr-brack-ai' @ .count-bracket + [] によるネスト / $(( $[ a[ v[ [ の内部 +## 'expr-brack-di' @ .count-bracket o [] によるネスト / d[ の内部 +## ## '$(' @ check-dollar o $(command) ## 'cmdsub_nofork' @ check-dollar o ${ command; } ## 'cmd_brace' @ ctx-command/check-word-end o { command; } @@ -2802,14 +2825,25 @@ function ble/syntax:bash/ctx-expr/.count-paren { _ble_syntax_attr[i++]=_ble_syntax_attr[inest])) fi return 0 - elif [[ $ntype == '(' || $ntype == 'NQ(' ]]; then + elif [[ $ntype == expr-paren* ]]; then ((_ble_syntax_attr[i++]=ctx)) ble/syntax/parse/nest-pop return 0 fi elif [[ $char == '(' ]]; then - local ntype2='(' - [[ $ntype == '$((' || $ntype == 'NQ(' ]] && ntype2='NQ(' + # determine nested ntype + local ntype2= + case $ntype in + ('((') + ntype2=expr-paren ;; + ('$((') + ntype2=expr-paren-ax ;; + (expr-paren|expr-paren-ax|expr-paren-ai|expr-paren-di) + ntype2=$ntype ;; + ('$['|'a['|'v['|'d['|expr-brack|expr-brack-ai|expr-brack-di|'${'|'"${'|*) + ble/util/assert 'false' "unexpected ntype='$ntype' here" ;; + esac + ble/syntax/parse/nest-push "$CTX_EXPR" "$ntype2" ((_ble_syntax_attr[i++]=ctx)) return 0 @@ -2823,7 +2857,7 @@ function ble/syntax:bash/ctx-expr/.count-paren { ## @var char 括弧文字を指定します。 function ble/syntax:bash/ctx-expr/.count-bracket { if [[ $char == ']' ]]; then - if [[ $ntype == '[' || $ntype == '$[' ]]; then + if [[ $ntype == expr-brack* || $ntype == '$[' ]]; then # 算術式展開 $[...] や入れ子 ((a[...]=123)) などの場合。 ((_ble_syntax_attr[i]=_ble_syntax_attr[inest])) ((i++)) @@ -2869,7 +2903,18 @@ function ble/syntax:bash/ctx-expr/.count-bracket { return 0 fi elif [[ $char == '[' ]]; then - ble/syntax/parse/nest-push "$CTX_EXPR" '[' + local ntype2= + case $ntype in + ('$['|'a['|'v[') + ntype2=expr-brack-ai ;; + ('d[') + ntype2=expr-brack-di ;; + (expr-brack|expr-brack-ai|expr-brack-di) + ntype2=$ntype ;; + ('(('|'$(('|expr-paren|expr-paren-ax|expr-paren-ai|expr-paren-di|'${'|'"${'|*) + ble/util/assert 'false' "unexpected ntype='$ntype' here" ;; + esac + ble/syntax/parse/nest-push "$CTX_EXPR" "$ntype2" ((_ble_syntax_attr[i++]=ctx)) return 0 fi @@ -2890,6 +2935,31 @@ function ble/syntax:bash/ctx-expr/.count-brace { return 1 } +## @fn ble/syntax:bash/ctx-expr/.check-plain-with-escape rex is_quote +## @var[in] ntype +function ble/syntax:bash/ctx-expr/.check-plain-with-escape { + local i0=$i + ble/syntax:bash/check-plain-with-escape "$@" || return 1 + + if [[ $tail == '\'* ]]; then + case $ntype in + ('$(('|'$['|expr-paren-ax|'${'|'"${') + _ble_syntax_attr[i0]=$ATTR_ERR ;; + ('(('|expr-paren|expr-brack) + if ((_ble_bash>=50100)); then + _ble_syntax_attr[i0]=$ATTR_ERR + fi ;; + ('a['|'v['|expr-paren-ai|expr-brack-ai) + if ((_ble_bash>=40400)); then + _ble_syntax_attr[i0]=$ATTR_ERR + fi ;; + # ('d['|expr-paren-di|expr-brack-di) ;; # d[ (designated init) 内部では常に \ は OK + esac + fi + + return 0 +} + function ble/syntax:bash/ctx-expr { # 式の中身 local rex @@ -2901,22 +2971,30 @@ function ble/syntax:bash/ctx-expr { elif rex='^0[xX][0-9a-fA-F]*|^[0-9]+(#[_a-zA-Z0-9@]*)?'; [[ $tail =~ $rex ]]; then ((_ble_syntax_attr[i]=ATTR_VAR_NUMBER,i+=${#BASH_REMATCH})) return 0 - elif ble/syntax:bash/check-plain-with-escape "[^${_ble_syntax_bash_chars[ctx]}_a-zA-Z0-9]+" 1; then + fi + + local ntype + ble/syntax/parse/nest-type + if ble/syntax:bash/ctx-expr/.check-plain-with-escape "[^${_ble_syntax_bash_chars[ctx]}_a-zA-Z0-9]+" 1; then return 0 elif [[ $tail == ['][()}']* ]]; then - local char=${tail::1} ntype - ble/syntax/parse/nest-type - if [[ $ntype == *'(' ]]; then - # ntype = '((' # ((...)) - # = '$((' # $((...)) - # = '(' # 式中の (..) + local char=${tail::1} + if [[ $ntype == *'(' || $ntype == expr-paren* ]]; then + # ntype = '((' # ((...)) + # = '$((' # $((...)) + # = 'expr-paren' # 式中の (..) + # = 'expr-paren-ax' # $(( $[ 中の (..) + # = 'expr-paren-ai' # a[ v[ 中の (..) + # = 'expr-paren-di' # d[ 中の (..) ble/syntax:bash/ctx-expr/.count-paren && return 0 - elif [[ $ntype == *'[' ]]; then - # ntype = 'a[' # a[...]= - # = 'v[' # ${a[...]} - # = 'd[' # a=([...]=) - # = '$[' # $[...] - # = '[' # 式中の [...] + elif [[ $ntype == *'[' || $ntype == expr-brack* ]]; then + # ntype = 'a[' # a[...]= + # = 'v[' # ${a[...]} + # = 'd[' # a=([...]=) + # = '$[' # $[...] + # = 'expr-brack' # 式中の [...] + # = 'expr-brack-ai' # $(( $[ a[ v[ 中の [...] + # = 'expr-brack-di' # d[ 中の [...] ble/syntax:bash/ctx-expr/.count-bracket && return 0 elif [[ $ntype == '${' || $ntype == '"${' ]]; then # ntype = '${' # ${var:offset:length} diff --git a/memo/D2051.sh b/memo/D2051.sh new file mode 100755 index 00000000..643063fe --- /dev/null +++ b/memo/D2051.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash + +contexts=('echo $((%s))' 'echo $[%s]' '((%s))' 'b[%s]=' ': ${b[%s]}' 'b=([%s]=)' ': ${v:%s}' ': "${v:%s}"') +quote_types=('\10' "'10'" "\$'10'" '"10"' '$"10"' 'a[\10]' 'a["10"]' '`echo 10`') + +function test-quote-error { + local cmd + printf -v cmd "$1" "$2" + [[ ! $(eval "$cmd" 2>&1 1>/dev/null) ]] +} + +function sub:test-compact { + local v=1 + + local t q cmd vec + for t in "${contexts[@]}"; do + vec= + for q in "${quote_types[@]}"; do + if test-quote-error "$t" "$q"; then + vec+=o + else + vec+=x + fi + done + printf -v cmd "$t" expr + printf "%-20s: %s\n" "$cmd" "$vec" + done +} + +function sub:summarize-compact { + printf '%-20s: 4.3 4.4 5.1 5.2 dev\n' + paste \ + -d ' ' \ + <(bash-4.3 "$0" test-compact) \ + <(bash-4.4 "$0" test-compact | sed 's/.* \([ox]\{1,\}\)$/\1/') \ + <(bash-5.1 "$0" test-compact | sed 's/.* \([ox]\{1,\}\)$/\1/') \ + <(bash-5.2 "$0" test-compact | sed 's/.* \([ox]\{1,\}\)$/\1/') \ + <(bash-dev "$0" test-compact | sed 's/.* \([ox]\{1,\}\)$/\1/') +} + +function sub:perform-test { + local v=1 + + local t q cmd vec + for t in "${contexts[@]}"; do + for q in "${quote_types[@]}"; do + printf -v cmd "$t" "$q" + if test-quote-error "$t" "$q"; then + vec=o + else + vec=x + fi + printf "%-20s: %s\n" "$cmd" "$vec" + done + done +} + +function main { + printf '%-20s: 4.3 4.4 5.1 5.2 dev\n' COMMAND + paste \ + -d ' ' \ + <(bash-4.3 "$0" perform-test | sed 's/.$/ & /') \ + <(bash-4.4 "$0" perform-test | sed 's/.*\([ox]\)$/ \1 /') \ + <(bash-5.1 "$0" perform-test | sed 's/.*\([ox]\)$/ \1 /') \ + <(bash-5.2 "$0" perform-test | sed 's/.*\([ox]\)$/ \1 /') \ + <(bash-dev "$0" perform-test | sed 's/.*\([ox]\)$/ \1/') +} + +function sub:test-markdown { + local v=1 + + local t q cmd vec + for t in "${contexts[@]}"; do + for q in "${quote_types[@]}"; do + printf -v cmd "$t" "$q" + if test-quote-error "$t" "$q"; then + vec='✅' + else + vec='⬜' + fi + printf "| %-20s | %s\n" "$cmd" "$vec" + done + done +} +function sub:summarize-markdown { + printf '| COMMAND | 3.0..4.3 | 4.4..5.0 | 5.1 | 5.2..dev |\n' + printf '|:----------------------|:--------:|:--------:|:--------:|:--------:|\n' + paste \ + -d '|' \ + <(bash-4.3 "$0" test-markdown) \ + <(bash-4.4 "$0" test-markdown | sed 's/.* \([^[:space:]]\{1,\}\)$/ \1 /') \ + <(bash-5.1 "$0" test-markdown | sed 's/.* \([^[:space:]]\{1,\}\)$/ \1 /') \ + <(bash-5.2 "$0" test-markdown | sed 's/.* \([^[:space:]]\{1,\}\)$/ \1 |/') +} + +if declare -f "sub:$1" >/dev/null; then + "sub:$@" +else + main +fi diff --git a/note.txt b/note.txt index fbaf5144..86d5a647 100644 --- a/note.txt +++ b/note.txt @@ -208,6 +208,22 @@ これは分かりにくい動作だが、これを逆に使う人もあるのかもしれない。 実の所、ブレース展開も文法レベルで実施されるべきなのかもしれない。 + - $((a[\10])) $((a["10"])) ${v:a[\10]} (bash <= 4.3) + + Bash では最初の解析で a[\10] の部分が抜き出されて、その後で a[\10] が処理 + される。最初の解析では [] の入れ子は考慮されない。一方で \10 が有効かどう + かを判定する為には [] の入れ子を追跡する必要がある (bash-4.3 以下では \10 + は [] の外では使えないが中では使える)。ble.sh の実装ではこの場合には [] + の入れ子は追跡しないことにする。つまり、bash-4.3 以下では $((a[\10])) が + 許される筈なのにエラー着色になる。 + + Bash-4.4 以上では上記の文脈では [] の内外で振る舞いが一貫しているので実際 + 上の問題は出ない。他にも ((expr)) や ${v:expr} でも同様の入れ子追跡の処理 + の問題があるが、これらの場合には [] 内部と外部の振る舞いは一貫しているの + で実際上の問題にはならない。 + + - ${v:a["10"]} に関しても上と同様の問題がある。これは bash-5.1 以下で問題になる。 + * 2019-02-04 プログラム補完関数の中で標準入力は使えない。 どうしてもユーザからの入力を得たい場合には、 現在の補完が自動補完でない事を確認してから /dev/tty から直接取る事。 @@ -6994,6 +7010,76 @@ bash_tips 2023-06-26 + * syntax: $(()) の中で "" が含められないということ (reported by mozirilla213) [#D2051] + https://github.com/akinomyoga/ble.sh/issues/336 + + 動作を確認してみたら 4.3 では駄目だったが 4.4 以降では動く様だ。(()) ではど + のバージョンでも動く。 + + 更に別の問題についても気づいてしまった。$((\10)) はどのバージョンでも失敗す + るが、ble.sh は常に許容している。一方で、((\10)) は 5.0 までは動いていた。 + 5.1 から動かなくなっている。また色々調べるともっと複雑である。 + + context expr = \10 expr = "10" ASSIGNED NTYPE + ------------ ------------ ------------- -------------- + $((expr)) false bash >= 4.4 expr-paren-ax + $[expr] false bash >= 4.4 expr-paren-ax + $((a[expr])) bash < 4.4 true expr-brack-ai + $[a[expr]] bash < 4.4 true expr-brack-ai + + ((expr)) bash < 5.1 true expr-paren + ((a[expr])) bash < 5.1 true expr-brack + + a[expr] bash < 4.4 true expr-paren-ai + b[a[expr]] bash < 4.4 true expr-brack-ai + ${a[expr]} bash < 4.4 true expr-paren-ai + ${a[b[expr]]} bash < 4.4 true expr-brack-ai + printf -v a[x] bash < 4.4 true expr-paren-ai + printf -v a[b[ bash < 4.4 true expr-brack-ai + + ${v:expr} false bash >= 5.2 expr-paren-br? + "${v:expr}" false bash >= 5.2 expr-paren-br? + ${v:a[expr]} bash < 4.4 true expr-brack-ai + "${v:a[expr]}" bash < 4.4 true expr-brack-ai + + a=([expr]=) true true expr-paren-di + a=([b[x]]=) true true expr-brack-di + + ? $["10"] はどうなのか → 4.4 以降では動く。うーん。全体的に改めてどの様な + 文脈で quote が許されるのか確認する必要がある。 + + * うーん。'NQ(' の導入は割と古くて #D0375 である。取り敢えず新しい文脈を導 + 入してしまう事にする。取り敢えず実装した。思ったがちゃんと反映されていな + い。 + + x fixed: $((\10)) でエラー着色になっていない。と思ったら単に新しく用意した + 関数 ctx-expr/.check-plain-with-escape を呼び出していなかった。呼び出す様 + にしたら期待通りに動作する様になった。 + + * single quote 等の振る舞いは大丈夫なのか? → 調べてみた所 \10 と全く同じ振 + る舞いである。 + + x 気づいてしまったのだが算術式中の a[expr] は更にそれがどの文脈だったかによっ + て振る舞いが変わる。 + + x reject: うーん。面倒な事に気づいた。${} や (()) の中では現在は [] の入れ + 子は追跡していない。しかし実際には quote の取り扱いが変わるのでそこまでちゃ + んと対応しようと思ったらちゃんと追跡する必要が出てくる。しかし、一方で + ${} や (()) の中では [] の入れ子は ${} および (()) 全体の終了に対しては考 + 慮されない。うーん。 + + これはまた two-pass parsing の問題である。((...)) を抽出する時には [ を無 + 視して、次の解析で [ の入れ子を処理している。これはどうしようもないので対 + 応しない事にする。 + + → ページ上部の構文解析を諦めたものに追加した。 + + * うーん。振る舞いが複雑すぎるのでテストを用意するべきの気がする → + memo/D2051.sh に作った。 + + x fixed: Bash では `echo 10` はいつでも有効の様だ。これに対応できていない。 + →対応した。 + * util: ble/builtin/readonly が bash options で壊れる (reported by dongxi8) [#D2050] https://github.com/akinomyoga/ble.sh/issues/335#issuecomment-1598485650