syntax(sh): Improve the recognition of bracket expressions

- Define a general non-"contained" "shBracketExpr" group,
  and replace with it the "contained" bracket variant of
  "shOperator", adjusting the patterns for the competing
  conditional commands "[" and "[[".
- Accommodate some unbalanced brackets (e.g. "[!][!]").
- Make the leading "!" (or "^") stand out in NON-matching
  bracket expressions.
- Support literal newlines in parametric patterns (along
  with pathname globbings and "case" patterns).
- Also match bracket expressions in:
  * parametric patterns (e.g. "${1#[ab]_}");
  * pathname globbings (e.g. "[ab]*.txt");
  * arguments for the "[[", "echo", and "print" commands.
- Recognise collating symbols (e.g. "[.a.]") and equivalence
  classes (e.g. "[=a=]").
- Recognise end patterns for a pattern substitution form of
  parameter expansion and match bracket expressions in such
  patterns (e.g. "${1/%[.!]/;}").

fixes #15799
closes: #15941

References:
https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap09.html#tag_09_03_05
https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14
https://git.savannah.gnu.org/gitweb/?p=bash.git;a=blob_plain;f=doc/bash.html;hb=37b7e91d64ad10b1a1815d12128c9475636df670
http://www.mirbsd.org/htman/i386/man1/mksh.htm

Signed-off-by: Aliaksei Budavei <0x000c70@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
diff --git a/runtime/syntax/testdir/input/sh_12.sh b/runtime/syntax/testdir/input/sh_12.sh
new file mode 100644
index 0000000..dcfa553
--- /dev/null
+++ b/runtime/syntax/testdir/input/sh_12.sh
@@ -0,0 +1,206 @@
+#!/bin/bash
+# VIM_TEST_SETUP highlight link shArrayValue Identifier
+# VIM_TEST_SETUP highlight link shBracketExprDelim Structure
+# VIM_TEST_SETUP highlight link shCharClass Todo
+# VIM_TEST_SETUP highlight link shRange CursorLine
+
+
+
+
+[[ ( "$1" == ?(-)+([0-9]) && "$1" =~ ^-?[[:digit:]]+$ &&
+'^?(-)+([[:digit:]])$' == "$2" && ! "^-?[0-9]+$" =~ "$2" ) ]] || :
+
+[ \( "$1" != "?(-)+([0-9])" -a "$1" != '?(-)+([[:digit:]])' \) ] &&
+[ \( "?(-)+([[:digit:]])" != "$2" -a '?(-)+([0-9])' != "$2" \) ] || :
+
+# Match "\[0\]\[0\]", "{0}{0}", etc.
+: [[{][0-9]*[]}][[{][[:digit:]]*[]}]
+: [\
+[{][0-9]*[]}][[{][[:digit:]]*[]}]
+: [[\
+{][0-9]*[]}][[{][[:digit:]]*[]}]
+: [[{\
+][0-9]*[]}][[{][[:digit:]]*[]}]
+: [[{]\
+[0-9]*[]}][[{][[:digit:]]*[]}]
+: [[{][\
+0-9]*[]}][[{][[:digit:]]*[]}]
+: [[{][0-9\
+]*[]}][[{][[:digit:]]*[]}]
+: [[{][0-9]\
+*[]}][[{][[:digit:]]*[]}]
+: [[{][0-9]*\
+[]}][[{][[:digit:]]*[]}]
+: [[{][0-9]*[]\
+}][[{][[:digit:]]*[]}]
+: [[{][0-9]*[]}\
+][[{][[:digit:]]*[]}]
+: [[{][0-9]*[]}]\
+[[{][[:digit:]]*[]}]
+: [[{][0-9]*[]}][\
+[{][[:digit:]]*[]}]
+: [[{][0-9]*[]}][[\
+{][[:digit:]]*[]}]
+: [[{][0-9]*[]}][[{\
+][[:digit:]]*[]}]
+: [[{][0-9]*[]}][[{]\
+[[:digit:]]*[]}]
+: [[{][0-9]*[]}][[{][\
+[:digit:]]*[]}]
+: [[{][0-9]*[]}][[{][[:digit:]\
+]*[]}]
+: [[{][0-9]*[]}][[{][[:digit:]]\
+*[]}]
+: [[{][0-9]*[]}][[{][[:digit:]]*\
+[]}]
+: [[{][0-9]*[]}][[{][[:digit:]]*[]\
+}]
+: [[{][0-9]*[]}][[{][[:digit:]]*[]}\
+]
+: [[{][0-9]*[]}][[{][[:digit:]]*[[.].]}]
+: [[{][0-9]*[]}][[{][[:digit:]]*[\
+[.].]}]
+: [[{][0-9]*[]}][[{][[:digit:]]*[[.].]\
+}]
+: [[{][0-9]*[]}][[{][[:digit:]]*[[.].]}\
+]
+
+# Match "\[0\]\[0\]", "{0}{0}", etc.
+case "$1" in
+[[{][0-9]*[]}][[{][[:digit:]]*[]}]*) : [0-9]; : [!0-9]; : [^0-9];;
+[\
+[{][0-9]*[]}][[{][[:digit:]]*[]}]*) : [0123]; : [!0123]; : [^0123];;
+[[\
+{][0-9]*[]}][[{][[:digit:]]*[]}]*) : [1[.0.]]; : [![.0.]]; : [^[.0.]^];;
+[[{\
+][0-9]*[]}][[{][[:digit:]]*[]}]*) : [1[=0=]]; : [![=0=]!]; : [^[=0=]];;
+[[{]\
+[0-9]*[]}][[{][[:digit:]]*[]}]*) ;;
+[[{][\
+0-9]*[]}][[{][[:digit:]]*[]}]*) ;;
+[[{][0-9\
+]*[]}][[{][[:digit:]]*[]}]*) : ?[[:foo:]]?; : [![:foo:]]?; : ?[^[:foo:]];;
+[[{][0-9]\
+*[]}][[{][[:digit:]]*[]}]*) : [[:digit:]]; : [![:digit:]]; : [^[:digit:]];;
+[[{][0-9]*\
+[]}][[{][[:digit:]]*[]}]*) : "${1^[[:lower:]]}"; : "${1^^[a-z]}";;
+[[{][0-9]*[]\
+}][[{][[:digit:]]*[]}]*) : "${1,[[:upper:]]}"; : "${1,,[A-Z]}";;
+[[{][0-9]*[]}\
+][[{][[:digit:]]*[]}]*) ;;
+[[{][0-9]*[]}]\
+[[{][[:digit:]]*[]}]*) ;;
+[[{][0-9]*[]}][\
+[{][[:digit:]]*[]}]*) : "${1#[[:digit:]]}"; : "${1##[0-9]}";;
+[[{][0-9]*[]}][[\
+{][[:digit:]]*[]}]*) : "${1%[[:digit:]]}"; : "${1%%[0-9]}";;
+[[{][0-9]*[]}][[{\
+][[:digit:]]*[]}]*) : "${1/[][:digit:][]/[0-9]}"; : "${1//[]0-9[]/[0-9]}";;
+[[{][0-9]*[]}][[{]\
+[[:digit:]]*[]}]*) : "${1/#[][:digit:][]/[0-9]}"; : "${1/%[]0-9[]/[0-9]}";;
+[[{][0-9]*[]}][[{][\
+[:digit:]]*[]}]*) ;;
+[[{][0-9]*[]}][[{][[:digit:]\
+]*[]}]*) ;;
+[[{][0-9]*[]}][[{][[:digit:]]\
+*[]}]*) : "${1#*[[.[.][.].]]}"; : "${1%[[.].][.[.]]*}"; : "${1#*" "[][]}";;
+[[{][0-9]*[]}][[{][[:digit:]]*\
+[]}]*) : "${1#*[[=[=][=]=]]}"; : "${1%[[=]=][=[=]]*}"; : "${1#*\ [!][]}";;
+[[{][0-9]*[]}][[{][[:digit:]]*[]\
+}]*) : "${1#*[!]]}"; : "${1#*[^]]}"; : "${1%[![]*}"; : "${1%[^[]*}";;
+[[{][0-9]*[]}][[{][[:digit:]]*[]}\
+]*) : "${1#*[!\]]}"; : "${1#*[^\]]}"; : "${1%[!\[]*}"; : "${1%[^\[]*}";;
+[[{][0-9]*[]}][[{][[:digit:]]*[]}]\
+*) ;;
+[[{][0-9]*[]}][[{][[:digit:]]*[[.].]}]\
+*) ;;
+[[{][0-9]*[]}][[{][[:digit:]]*[\
+[.].]}]*) ;;
+[[{][0-9]*[]}][[{][[:digit:]]*[[.].]\
+}]*) ;;
+[[{][0-9]*[]}][[{][[:digit:]]*[[.].]}\
+]*) ;;
+[!][:digit:][:xdigit:]\ [^[:lower:]![:upper:]]*) ;;
+[^][:digit:]0-9a-fA-F\ [![:lower:]^[:upper:]]*) ;;
+[!!] | [!![] | [!]!] | [!^] | [!^[] | [!]^]) ;;
+[^!] | [^![] | [^]!] | [^^] | [^^[] | [^]^]) ;;
+esac
+
+# Match "\[0\]\[0\]", "{0}{0}", etc.
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*\
+[[{][0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[\
+[{][0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[\
+{][0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{\
+][0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{]\
+[0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][\
+0-9]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9\
+]*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]\
+*[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*\
+[]}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]\
+}][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}\
+][[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}]\
+[[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][\
+[{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[\
+{][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{\
+][[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{]\
+[[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{][\
+[:digit:]]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]\
+]*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]\
+*[]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*\
+[]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[]\
+}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[]}\
+]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[]}]\
+}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[[.].]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[\
+[.].]}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[[.].]\
+}]}"
+: "${1#*[[{][0-9]*[]}][[{][[:digit:]]*[[.].]}]\
+}"
+
+: *[x[=[=][=]=]]; : [x[=]=][=[=]]*; : *[x[.[.][.].]]; : [x[.].][.[.]]*
+: *[[=[=]x[=]=]]; : [[=]=]x[=[=]]*; : *[[.[.]x[.].]]; : [[.].]x[.[.]]*
+: *[[=[=][=]=]x]; : [[=]=][=[=]x]*; : *[[.[.][.].]x]; : [[.].][.[.]x]*
+: [!]]; : [^]]; : [![]; : [^[]; : [!\]]; : [^\]]; : [!\[]; : [^\[]
+: [$'\x5b']; : [$'\x5d']; : []]; : [[]; : [\]]; : [\[]
+: [$'\x20'[]; : [" "[]; : [' '[]; : [\ []; : [\ \[]; : "${1#[ 	]}"
+: [[$'\x20']; : [[" "]; : [[' ']; : [[\ ]; : [\[\ ]; : "${1#[	 ]}"
+: [^$'\x20'[]; : [!" "[]; : [^' '[]; : [!\ []; : "${1#[^ 	]}"
+: [![$'\x20']; : [^[" "]; : [![' ']; : [^[\ ]; : "${1#[!	 ]}"
+
+nl='
+'
+echo "${1#${1%%[!"${nl}"]*}}"; echo "${1#${1%%[!'${nl}']*}}"
+echo "${1#${1%%[!\"${nl}]*}}"; echo "${1#${1%%[!\'${nl}]*}}"
+echo "${1#${1%%[!${nl}\"]*}}"; echo "${1#${1%%[!${nl}\']*}}"
+
+bins=(); bins=(0 1); eval bins+=({0..1})
+bins[0]=0; bins[1]=1; eval bins+=(\${bins[{1..0}]}); unset bins[3] bins[2]
+bins=([8#0]=$((2#100)) [$((8#1))]=$((2#10)) [3]=${bins[2#1]} ${bins[0]})
+(echo sum[$((bins[16#3] + bins[8#2] + bins[2#1] + bins[0]))])
+eval unset bins[{0..$((${#bins[*]} - 1))}]
+: