about summary refs log tree commit diff
path: root/Completion
diff options
context:
space:
mode:
Diffstat (limited to 'Completion')
-rw-r--r--Completion/Base/_regex_arguments342
-rw-r--r--Completion/Debian/_apt331
-rw-r--r--Completion/User/_combination28
3 files changed, 320 insertions, 381 deletions
diff --git a/Completion/Base/_regex_arguments b/Completion/Base/_regex_arguments
index 12f4d6d2d..bbf8a2e5b 100644
--- a/Completion/Base/_regex_arguments
+++ b/Completion/Base/_regex_arguments
@@ -1,9 +1,5 @@
 #autoload
 
-## todo
-
-# imprement `guard' to more generic branch selection.
-
 ## usage: _regex_arguments funcname regex
 
 ## configuration key used:
@@ -23,118 +19,33 @@
 
 ## regex word definition:
 
-# elt-pattern = "/" ( pattern | "[]" )	# cutoff
-#	      | "%" pattern		# non-cutoff
-# lookahead = "@" pattern
-# parse-action = "-" zsh-code-to-eval
-# complete-action = "!" zsh-code-to-eval
+# pattern = "/" ( glob | "[]" ) "/" [ "+" | "-" ]
+# lookahead = "%" glob "%"
+# guard = "-" zsh-code-to-eval
+# action = ":" zsh-code-to-eval
 
 ## regex word sequence definition:
 
-# element = elt-pattern [ lookahead ] [ parse-action ] [ complete-action ]
+# element = pattern [ lookahead ] [ guard ] [ action ]
 #
 # regex = element 
 #	| "(" regex ")"
 #	| regex "#"
 #	| regex regex
 #	| regex "|" regex
-#	| void
-#	| null
-#
-# NOTE: void and null has no explicit representation. However null can
-# be represent with empty words such as \( \).
-
-# example: (in zsh quoted form)
-
-# $'[^\0]#\0' \#	: zero or more words
-
-## auxiliary functions definition:
-
-# nullable : regex -> bool
-# first : regex -> list of element
-# match : string * list of element -> element + {bottom}
-# right : string * element -> string
-# left : string * element -> string
-# next : regex * element -> regex + {bottom}
-# trans : string * string * regex -> (string * string * regex) + {bottom}
-
-# nullable(void) = false
-# nullable(null) = true
-# nullable(e) = false
-# nullable(r #) = true
-# nullable(r1 r2) = nullable(r1) and nullable(r2)
-# nullable(r1 | r2) = nullable(r1) or nullable(r2)
-
-# first(void) = {}
-# first(null) = {}
-# first(e) = [ e ]
-# first(r #) = first(r)
-# first(r1 r2) = nullable(r1) ? first(r1) ++ first(r2) : first(r1)
-# first(r1 | r2) = first(r1) ++ first(r2)
-
-# match(s, []) = bottom
-# match(s, [e1, e2, ...]) = e	if [[ $s = $elt-pattern[e]$lookahead[e]* ]]
-#		   	  | match(s, [e2, ...])	otherwise
-
-# right(s, e) = ${s##$elt-pattern[e]}
-# left(s, e) = ${(M)s##$elt-pattern[e]}
-
-### XXX: It can treat lookaheads if zsh provide $1, $2, ... in perl.
-
-# next(void, e) = bottom
-# next(null, e) = bottom
-# next(e1, e0) = e1 eq e0 ? null : bottom	# eq is test operator of identity equality.
-# next(r #, e) = next(r, e) != bottom ? next(r, e) (r #) : bottom
-# next(r1 r2, e) = next(r1, e) != bottom ? next(r1, e) r2 : next(r2, e)
-# next(r1 | r2, e) = next(r1, e) != bottom ? next(r1, e) : next(r2, e)
-
-# trans( (t, s, r) ) = ( (cutoff(e) ? '' : t ++ left(s, e)), right(s, e), next(r, e) )
-#   where e = match(s, first(r))
-
-# NOTE: This `next' definition is slightly different to ordinaly one.
-# This definition uses only one element of first(r) for transition
-# instead of all elements of first(r).
-
-# If _regex_arguments takes the regex r0, the first state of the state
-# machine is r0.  The state of the state machine transit as follows.
-
-# ('', s0, r0) -> trans('', s0, r0) = (t1, s1, r1) -> trans(t1, s1, r1) -> ... 
-
-# If the state is reached to bottom, the state transition is stopped.
-
-# ... -> (tN, sN, rN) -> bottom
-
-# For each transitions (tI, sI, rI) to trans(tI, sI, rI), the state
-# machine evaluate parse-action bound to match(sI, first(rI)).
-
-# In parse-action bound to match(sI, first(rI)) = e, it can refer variables:
-#  _ra_left : tI+1
-#  _ra_match : left(sI, e)
-#  _ra_right : sI+1
-
-# If the state transition is stopped, the state machine evaluate
-# complete-actions bound to first(rN) if tN and sN does not contain NUL.
-# When complete-actions are evaluated, completion focus is restricted to
-# tN ++ sN. (This is reason of tN and sN cannot contain NUL when
-# completion.)
-# Also, if there are last transitions that does not cut off the string
-# (tJ ++ sJ = tJ+1 ++ sJ+1 = ... = tN-1 ++ sN-1 = tN ++ sN),
-# complete-actions bound to them
-# --- match(sJ, first(rJ)), ..., match(sN-1, first(rN-1)) --- are also
-# evaluated before complete-actions bound to first(rN).
 
 # example:
 
 # compdef _tst tst
 
-# _regex_arguments _tst /$'[^\0]#\0' /$'[^\0]#\0' '!compadd aaa'
+# _regex_arguments _tst /$'[^\0]#\0'/ /$'[^\0]#\0'/ :'compadd aaa'
 #  _tst complete `aaa' for first argument.
 #  First $'[^\0]#\0' is required to match with command name.
 
-# _regex_arguments _tst /$'[^\0]#\0' \( /$'[^\0]#\0' '!compadd aaa' /$'[^\0]#\0' !'compadd bbb' \) \#
+# _regex_arguments _tst /$'[^\0]#\0'/ \( /$'[^\0]#\0'/ :'compadd aaa' /$'[^\0]#\0'/ :'compadd bbb' \) \#
 #  _tst complete `aaa' for (2i+1)th argument and `bbb' for (2i)th argument.
 
-# _regex_arguments _tst /$'[^\0]#\0' \( /$'[^\0]#\0' '!compadd aaa' \| /$'[^\0]#\0' !'compadd bbb' \) \#
+# _regex_arguments _tst /$'[^\0]#\0'/ \( /$'[^\0]#\0'/ :'compadd aaa' \| /$'[^\0]#\0'/ :'compadd bbb' \) \#
 #  _tst complete `aaa' or `bbb'.
 
 ## Recursive decent regex parser
@@ -146,37 +57,42 @@
 # 2 : fatal parse error
 
 _ra_parse_elt () {
-  : index=$index "[$regex[$index]]"
-  local state
+  local state act
   if (( $#regex < index )); then
     return 1
   else
     case "$regex[index]" in
-      [/%]*) state=$index
+      /*/([-+]|)) state=$index
           first=($state)
 	  last=($state)
 	  nullable=
-	  case "${regex[index][1]}" in
-	    /) cutoff[$state]=yes ;;
-	    %) cutoff[$state]= ;;
+	  case "$regex[index]" in 
+	    */+) cutoff[$state]=+;;
+	    */) cutoff[$state]=/;;
+	    */-) cutoff[$state]=-;;
 	  esac
-          pattern[$state]="${regex[index++][2,-1]}"
-	  [[ -n "$pattern[$state]" ]] && pattern[$state]="($pattern[$state])"
-	  if [[ $index -le $#regex && $regex[index] = @* ]]; then
-	    lookahead[$state]="${regex[index++][2,-1]}"
-	    [[ -n "$lookahead[$state]" ]] && lookahead[$state]="($lookahead[$state])"
+          pattern[$state]="${${regex[index++]#/}%/([-+]|)}"
+	  if [[ $pattern[$state] != "[]" ]]; then
+	    pattern[$state]="(#b)((#B)$pattern[$state])"
+	  fi
+	  if [[ $index -le $#regex && $regex[index] = %*% ]]; then
+	    lookahead[$state]="(#B)${regex[index++][2,-2]}"
 	  else
 	    lookahead[$state]=""
 	  fi
 	  if [[ $index -le $#regex && $regex[index] = -* ]]; then
-	    parse_action[$state]="${regex[index++][2,-1]}"
+	    guard[$state]="${regex[index++][2,-1]}"
 	  else
-	    parse_action[$state]=""
+	    guard[$state]=""
 	  fi
-	  if [[ $index -le $#regex && $regex[index] = \!* ]]; then
-	    complete_action[$state]="${regex[index++][2,-1]}"
+	  if [[ $index -le $#regex && $regex[index] = :* ]]; then
+	    act="${regex[index++][2,-1]}"
+	    action[$state]="$act"
+	    # `actions[$act]="${actions[$act]} $state"' is not work properly
+	    # because $act on lhs is expanded twice.
+	    : ${actions[$act]::="${actions[$act]} $state"}
 	  else
-	    complete_action[$state]=""
+	    action[$state]=""
 	  fi
 	  ;;
       \() (( index++ ))
@@ -193,7 +109,6 @@ _ra_parse_elt () {
 }
 
 _ra_parse_clo () {
-  : index=$index "[$regex[$index]]"
   _ra_parse_elt || return $?
 
   if (( index <= $#regex )) && [[ "$regex[$index]" = \# ]]; then
@@ -207,7 +122,6 @@ _ra_parse_clo () {
 }
 
 _ra_parse_seq () {
-  : index=$index "[$regex[$index]]"
   local last_seq
   local first_seq nullable_seq
   first_seq=()
@@ -248,7 +162,6 @@ _ra_parse_seq () {
 }
 
 _ra_parse_alt () {
-  : index=$index "[$regex[$index]]"
   local last_alt
   local first_alt nullable_alt 
   first_alt=()
@@ -286,8 +199,9 @@ _ra_parse_alt () {
 
 _ra_gen_func () {
   local old new
-  local state next index
-  local start="${(j/:/)first}"
+  local state index
+  local test tmp
+  local start="0"
 
   old=()
   new=($start)
@@ -295,8 +209,8 @@ _ra_gen_func () {
   print -lr - \
     "$funcname () {" \
       'setopt localoptions extendedglob' \
-      'local _ra_state _ra_left _ra_match _ra_right _ra_actions _ra_tmp' \
-      "_ra_state='$start'" \
+      'local _ra_state _ra_left _ra_right _ra_actions' \
+      "_ra_state=$start" \
       '_ra_left=' \
       '_ra_right="${(pj:\0:)${(@)words[1,CURRENT - 1]:Q}}"$'\''\0'\''"$PREFIX"' \
       '_ra_actions=()' \
@@ -307,99 +221,114 @@ _ra_gen_func () {
     state="$new[1]"
     shift new
     old=("$old[@]" "$state")
-
     print -lr - \
-	"$state)" \
-	  'case "$_ra_right" in'
-
-    for index in ${(s/:/)state}; do
-      if [[ "$pattern[$index]" != "([])" ]]; then
-	next="${(j/:/)${(@)=tbl[$index]}}"
-	print -lr - \
-	      "$pattern[$index]$lookahead[$index]*)"
-	if [[ -n "$pattern[$index]" ]]; then
-	  if [[ -n "$cutoff[$index]" ]]; then
-	    print -lr - \
-		  '_ra_match="${(M)_ra_right##'"$pattern[$index]"'}"' \
-		  '_ra_right="$_ra_right[$#_ra_match + 1, -1]"' \
-		  '_ra_left=' \
-		  'if (( $#_ra_match )); then' \
-		    '_ra_actions=()'
-	    if [[ -n "${complete_action[$index]:q}" ]]; then
-	      print -lr - \
-		  'else' \
-		    '_ra_actions=("$_ra_actions[@]" '"${complete_action[$index]:q}"')'
-	    fi
-	    print -lr - \
-		  'fi'
-	  else
-	    print -lr - \
-		  '_ra_match="${(M)_ra_right##'"$pattern[$index]"'}"' \
-		  '_ra_right="$_ra_right[$#_ra_match + 1, -1]"' \
-		  '_ra_left="$_ra_left$_ra_match"'
-	    if [[ -n "${complete_action[$index]:q}" ]]; then
-	      print -lr - \
-		  '_ra_actions=("$_ra_actions[@]" '"${complete_action[$index]:q}"')'
-	    fi
-	  fi
-	else
-	  print -lr - \
-		'_ra_match=' \
-		'_ra_actions=("$_ra_actions[@]" '"${complete_action[$index]:q}"')'
-	fi
-	print -lr - \
-		"$parse_action[$index]"
-	if [[ -n $next ]]; then
-	  print -lr - \
-		"_ra_state=$next"
-	  (( $old[(I)$next] || $new[(I)$next] )) || new=($next "$new[@]")
-	else
-	  print -lr - \
-		'_message "no arg"' \
-		'break'
-	fi
-	print -lr - \
-		';;'
-      fi
-    done
-
+	"$state)"
+    _ra_gen_parse_state
     print -lr - \
-	    '*)' \
-	      'if [[ "$_ra_left$_ra_right" = *$'\''\0'\''* ]]; then' \
-		'_message "parse failed before current word"' \
-	      'else' \
-		'compset -p $(( $#PREFIX - $#_ra_right - $#_ra_left ))'
+	  ';;'
+  done
 
-    print -lr - \
-		'for _ra_tmp in $_ra_actions; do' \
-		  'eval "$_ra_tmp"' \
-		'done'
-    for index in ${(s/:/)state}; do
-      print -lr - \
-		"$complete_action[$index]"
-    done
+  print -lr - \
+	'esac' \
+      'done' \
+      'while (( $#_ra_actions )); do' \
+	'case "$_ra_actions[1]" in'
 
-    print -lr - \
-	      'fi' \
-	      'break' \
-	      ';;' \
-	  'esac' \
-	  ';;'
+  for tmp in "${(@k)actions}"; do
+    #print -lr - "KEY:{$tmp}" "VAL:{$actions[$tmp]}" >&2
+    print -lr - "${(j:);&:)${=actions[$tmp]}})" $tmp ';;'
   done
 
   print -lr - \
 	'esac' \
+	'shift _ra_actions' \
       'done' \
     '}'
 }
 
+_ra_gen_parse_state () {
+  local actions i
+  test='if'
+  for index in $=tbl[$state]; do
+    if [[ "$pattern[$index]" != "[]" ]]; then
+      if [[ -z "$guard[$index]" ]]; then
+	print -lr - \
+	  "$test [[ \$_ra_right = $pattern[$index]$lookahead[$index]* ]]"
+      else
+	print -lr - \
+	  "$test [[ \$_ra_right = $pattern[$index]$lookahead[$index]* ]] && {" \
+	      "$guard[$index]" \
+	    "}"
+      fi
+      test='elif'
+      (( $old[(I)$index] || $new[(I)$index] )) || new=($index "$new[@]")
+      print -lr - \
+	  "then" \
+	    "_ra_state=$index" \
+	    '_ra_right="${_ra_right[mend[1] + 1, -1]}"'
+      actions=()
+      for i in $=tbl[$index]; do
+	if [[ -n $action[$i] ]]; then
+	  actions=($actions $i)
+	fi
+      done
+      case "$cutoff[$index]" in
+	+) print -lr - \
+	    '_ra_left="$_ra_left$match[1]"'
+	  if (( $#actions )); then
+	    print -lr - \
+	    "_ra_actions=($actions \$_ra_actions)"
+	  fi
+          ;;
+	/) print -lr - \
+	    '_ra_left='
+	  print -lr - \
+	    'if (( mend[1] )); then' \
+	      "_ra_actions=($actions)"
+	  if (( $#actions )); then
+	    print -lr - \
+	    'else' \
+	      "_ra_actions=($actions \$_ra_actions)"
+	  fi
+	  print -lr - \
+	    'fi'
+	  ;;
+	-) print -lr - \
+	    '_ra_left=' \
+	    "_ra_actions=($actions)"
+	  ;;
+      esac
+    fi
+  done
+
+  if [[ $test != 'if' ]]; then
+  # Some branchs are exists. But all of them are failed.
+    print -lr - \
+	  'else' \
+	    'if [[ "$_ra_left$_ra_right" = *$'\''\0'\''* ]]; then' \
+	      '_message "parse failed before current word"' \
+	      '_ra_actions=()' \
+	    'else' \
+	      'compset -p $(( $#PREFIX - $#_ra_right - $#_ra_left ))' \
+	    'fi' \
+	    'break' \
+	  'fi'
+  else
+  # There are no branch.
+    print -lr - \
+	  '_message "no more arguments"' \
+	  '_ra_actions=()' \
+	  'break'
+  fi
+}
+
 _regex_arguments () {
   setopt localoptions extendedglob
 
   local funcname="_regex_arguments_tmp"
   local funcdef
 
-  typeset -A tbl cutoff pattern lookahead parse_action complete_action
+  typeset -A tbl cutoff pattern lookahead guard action actions
   local regex index first last nullable
   local i state next
 
@@ -408,18 +337,16 @@ _regex_arguments () {
   local cache_test
 
   if ! [[ -f "$cache_file" ]] || ! source "$cache_file" "$@"; then
-    cache_test='[[ $# -eq '$#' && "$*" = '"${*:q}"' ]]'
-
     funcname="$1"
-    shift
 
-    regex=("$@")
+    regex=("${(@)argv[2,-1]}")
     index=1
     tbl=()
     pattern=()
     lookahead=()
-    parse_action=()
-    complete_action=()
+    guard=()
+    action=()
+    actions=()
     _ra_parse_alt
 
     if (( $? == 2 || index != $#regex + 1 )); then
@@ -431,18 +358,21 @@ _regex_arguments () {
       return 1
     fi
 
-    funcdef="$(_ra_gen_func)"
+    tbl[0]=" $first"
 
     unfunction "$funcname" 2>/dev/null
-    eval "${(F)funcdef}"
+    funcdef="$(_ra_gen_func)"
 
-    [[ -d "$cache_dir" && -w "$cache_dir" ]] && {
+    if [[ -d "$cache_dir" && -w "$cache_dir" ]]; then
       print -lr - \
-	"if $cache_test; then" \
+	'if [[ $# -eq '$#' && "$*" = '"${*:q}"' ]]; then' \
 	"$funcdef" \
 	'true; else false; fi' > "${cache_file}.$HOST.$$"
+      source "${cache_file}.$HOST.$$" "$@"
       mv "${cache_file}.$HOST.$$" "${cache_file}"
-    }
+    else
+      source =(print -lr - "$funcdef")
+    fi
   fi
 }
 
diff --git a/Completion/Debian/_apt b/Completion/Debian/_apt
index d73b0df85..f4ed69a45 100644
--- a/Completion/Debian/_apt
+++ b/Completion/Debian/_apt
@@ -17,7 +17,7 @@ _apt_arguments () {
   funcname="$1"
   shift
 
-  typeset -A canonicalize options
+  typeset -A canonicalize num_options
   local short_hasarg short_bool short_intlevel short_configfile short_arbitem
   local long_hasarg long_bool long_intlevel long_configfile long_arbitem
   local comp_hasarg=''
@@ -28,11 +28,11 @@ _apt_arguments () {
     type="${1#*:}"
 
     case $type in
-      bool) options[$opts]=1;;
-      intlevel) options[$opts]=-1;;
-      configfile) options[$opts]=1;;
-      arbitem) options[$opts]=-1;;
-      *) options[$opts]=1
+      bool) num_options[$opts]=1;;
+      intlevel) num_options[$opts]=-1;;
+      configfile) num_options[$opts]=1;;
+      arbitem) num_options[$opts]=-1;;
+      *) num_options[$opts]=1
 	 comp_hasarg="${comp_hasarg}$opts) $type;;"$'\n'
 	 type=hasarg;;
     esac
@@ -82,25 +82,25 @@ _apt_arguments () {
   comp_short=\
 'if [[ $PREFIX = -'"$short_seq"' ]]; then
   _apt_consume_short ${PREFIX[2,-1]}
-  tmp1=(${(M)${(s:,:)${(kj:,:)options[(R)*~0]}}:#-?})
+  tmp1=(${(M)${(s:,:)${(kj:,:)num_options[(R)*~0]}}:#-?})
   tmp2=(${PREFIX}${^tmp1#-})
   _describe -o option tmp1 tmp2
 elif [[ -z "$PREFIX" ]]; then
-  tmp1=(${(M)${(s:,:)${(kj:,:)options[(R)*~0]}}:#-?})
+  tmp1=(${(M)${(s:,:)${(kj:,:)num_options[(R)*~0]}}:#-?})
   _describe -o option tmp1
 fi'
 
   comp_long=\
-'tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)options[(R)*~0]}}:#--*}#--}}"
+'tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)num_options[(R)*~0]}}:#--*}#--}}"
 tmp2=(--${(M)^long_bool:#$~tmp1} --${(M)^long_intlevel:#$~tmp1})
 tmp3=(--${(M)^long_hasarg:#$~tmp1} --${(M)^long_configfile:#$~tmp1} --${(M)^long_arbitem:#$~tmp1})
 _describe -o option tmp2 -- tmp3 -S= -- bool_prefix -S ""'
 
   comp_long_prefix=\
-'tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)options[(R)*~0]}}:#--*}#--}}"
+'tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)num_options[(R)*~0]}}:#--*}#--}}"
 tmp2=($_ra_left${(M)^long_bool:#$~tmp1} $_ra_left${(M)^long_intlevel:#$~tmp1})
 tmp3=($_ra_left${(M)^long_hasarg:#$~tmp1} $_ra_left${(M)^long_configfile:#$~tmp1} $_ra_left${(M)^long_arbitem:#$~tmp1})
-tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)options[(R)*~0]}}:#-?}#-}}"
+tmp1="${(j:|:)${(@)${(@M)${(@s:,:)${(@kj:,:)num_options[(R)*~0]}}:#-?}#-}}"
 tmp2=("$tmp2[@]" $_ra_left${(M)^short_bool:#$~tmp1} $_ra_left${(M)^short_intlevel:#$~tmp1})
 tmp3=("$tmp3[@]" $_ra_left${(M)^short_hasarg:#$~tmp1} $_ra_left${(M)^short_configfile:#$~tmp1} $_ra_left${(M)^short_arbitem:#$~tmp1})
 _describe -o option tmp2 -- tmp3 -S='
@@ -113,203 +113,200 @@ _describe -o option tmp2 -- tmp3 -S='
 
   if (( $#short_hasarg )); then
     regex_short=("$regex_short[@]"
-      /"$short_seq(${(j:|:)short_hasarg})(=|)"
-	-'_apt_consume_short ${_ra_match%=}; current_option=${canonicalize[-${${_ra_match%=}[-1]}]}'
-	\( /"$word1" !"$comp_hasarg" \| /"$nul" /"$word" !"$comp_hasarg" \) \|
+      /"$short_seq(${(j:|:)short_hasarg})(=|)"/
+	-'_apt_consume_short ${match[1]%=}; current_option=${canonicalize[-${${match[1]%=}[-1]}]}'
+	\( /"$word1"/ :"$comp_hasarg" \| /"$nul"/ /"$word"/ :"$comp_hasarg" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)short_hasarg})$nul"
-	-'_apt_consume_short ${_ra_match[-2]}; current_option=${canonicalize[-${${_ra_match%$nul}[-1]}]}'
-	/"$word" !"$comp_hasarg" \|
-      /"(${(j:|:)short_hasarg})="
-	-'_apt_consume_short ${_ra_match[-2]}; current_option=${canonicalize[-${${_ra_match%=}[-1]}]}'
-	\( /"$word1" !"$comp_hasarg" \| /"$nul" /"$word" !"$comp_hasarg" \) \|
+      /"(${(j:|:)short_hasarg})$nul"/
+	-'_apt_consume_short ${match[1][-2]}; current_option=${canonicalize[-${${match[1]%$nul}[-1]}]}'
+	/"$word"/ :"$comp_hasarg" \|
+      /"(${(j:|:)short_hasarg})="/
+	-'_apt_consume_short ${match[1][-2]}; current_option=${canonicalize[-${${match[1]%=}[-1]}]}'
+	\( /"$word1"/ :"$comp_hasarg" \| /"$nul"/ /"$word"/ :"$comp_hasarg" \) \|
     )
   fi
 
   if (( $#short_bool )); then
     regex_short=("$regex_short[@]"
-      /"$short_seq(${(j:|:)short_bool})($nul(${(j:|:)bool})|(${(j:|:)bool})|)$nul"
-	-"_apt_consume_short \${_ra_match%%($nul(${(j:|:)bool})|(${(j:|:)bool})|)$nul}" \|
-      /"$short_seq(${(j:|:)short_bool})="
-	-"_apt_consume_short \${_ra_match%=}"
-	\( /"$word1" !"$comp_bool" \| /"$nul" /"$word" !"$comp_bool" \) \|
+      /"$short_seq(${(j:|:)short_bool})($nul(${(j:|:)bool})|(${(j:|:)bool})|)$nul"/
+	-"_apt_consume_short \${match[1]%%($nul(${(j:|:)bool})|(${(j:|:)bool})|)$nul}" \|
+      /"$short_seq(${(j:|:)short_bool})="/
+	-"_apt_consume_short \${match[1]%=}"
+	\( /"$word1"/ :"$comp_bool" \| /"$nul"/ /"$word"/ :"$comp_bool" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)short_bool})="
-	-"_apt_consume_short \${_ra_match[-2]}"
-	\( /"$word1" !"$comp_bool" \| /"$nul" /"$word" !"$comp_bool" \) \|
-      /"(${(j:|:)short_bool})$nul"
-	-"_apt_consume_short \${_ra_match[-2]}"
-	/"((${(j:|:)bool})$nul|)" !"$comp_bool" \|
+      /"(${(j:|:)short_bool})="/
+	-"_apt_consume_short \${match[1][-2]}"
+	\( /"$word1"/ :"$comp_bool" \| /"$nul"/ /"$word"/ :"$comp_bool" \) \|
+      /"(${(j:|:)short_bool})$nul"/
+	-"_apt_consume_short \${match[1][-2]}"
+	/"((${(j:|:)bool})$nul|)"/ :"$comp_bool" \|
     )
   fi
 
   if (( $#short_intlevel )); then
     regex_short=("$regex_short[@]"
-      /"$short_seq(${(j:|:)short_intlevel})($nul$intlevel|$intlevel|)$nul"
-	-"_apt_consume_short \${_ra_match%%($nul$intlevel|$intlevel|)$nul}" \|
-      /"$short_seq(${(j:|:)short_intlevel})="
-	-"_apt_consume_short \${_ra_match%=}"
-	\( /"$word1" !"$comp_intlevel" \| /"$nul" /"$word" !"$comp_intlevel" \) \|
+      /"$short_seq(${(j:|:)short_intlevel})($nul$intlevel|$intlevel|)$nul"/
+	-"_apt_consume_short \${match[1]%%($nul$intlevel|$intlevel|)$nul}" \|
+      /"$short_seq(${(j:|:)short_intlevel})="/
+	-"_apt_consume_short \${match[1]%=}"
+	\( /"$word1"/ :"$comp_intlevel" \| /"$nul"/ /"$word"/ :"$comp_intlevel" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)short_intlevel})="
-	-"_apt_consume_short \${_ra_match[-2]}"
-	\( /"$word1" !"$comp_intlevel" \| /"$nul" /"$word" !"$comp_intlevel" \) \|
-      /"(${(j:|:)short_intlevel})$nul"
-	-"_apt_consume_short \${_ra_match[-2]}"
-	/"($intlevel$nul|)" !"$comp_intlevel" \|
+      /"(${(j:|:)short_intlevel})="/
+	-"_apt_consume_short \${match[1][-2]}"
+	\( /"$word1"/ :"$comp_intlevel" \| /"$nul"/ /"$word"/ :"$comp_intlevel" \) \|
+      /"(${(j:|:)short_intlevel})$nul"/
+	-"_apt_consume_short \${match[1][-2]}"
+	/"($intlevel$nul|)"/ :"$comp_intlevel" \|
     )
   fi
 
   if (( $#short_configfile )); then
     regex_short=("$regex_short[@]"
-      /"$short_seq(${(j:|:)short_configfile})(=|)"
-	-"_apt_consume_short \${_ra_match%=}"
-	\( /"$word1" !"$comp_configfile" \| /"$nul" /"$word" !"$comp_configfile" \) \|
+      /"$short_seq(${(j:|:)short_configfile})(=|)"/
+	-"_apt_consume_short \${match[1]%=}"
+	\( /"$word1"/ :"$comp_configfile" \| /"$nul"/ /"$word"/ :"$comp_configfile" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)short_configfile})$nul"
-	-"_apt_consume_short \${_ra_match[-2]}"
-	/"$word" !"$comp_configfile" \|
-      /"(${(j:|:)short_configfile})="
-	-"_apt_consume_short \${_ra_match[-2]}"
-	\( /"$word1" !"$comp_configfile" \| /"$nul" /"$word" !"$comp_configfile" \) \|
+      /"(${(j:|:)short_configfile})$nul"/
+	-"_apt_consume_short \${match[1][-2]}"
+	/"$word"/ :"$comp_configfile" \|
+      /"(${(j:|:)short_configfile})="/
+	-"_apt_consume_short \${match[1][-2]}"
+	\( /"$word1"/ :"$comp_configfile" \| /"$nul"/ /"$word"/ :"$comp_configfile" \) \|
     )
   fi
 
   if (( $#short_arbitem )); then
     regex_short=("$regex_short[@]"
-      /"$short_seq(${(j:|:)short_arbitem})(=|)"
-	-"_apt_consume_short \${_ra_match%=}"
-	\( /"$word1" !"$comp_arbitem" \| /"$nul" /"$word" !"$comp_arbitem" \) \|
+      /"$short_seq(${(j:|:)short_arbitem})(=|)"/
+	-"_apt_consume_short \${match[1]%=}"
+	\( /"$word1"/ :"$comp_arbitem" \| /"$nul"/ /"$word"/ :"$comp_arbitem" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)short_arbitem})$nul"
-	-"_apt_consume_short \${_ra_match[-2]}"
-	/"$word" !"$comp_arbitem" \|
-      /"(${(j:|:)short_arbitem})="
-	-"_apt_consume_short \${_ra_match[-2]}"
-	\( /"$word1" !"$comp_arbitem" \| /"$nul" /"$word" !"$comp_arbitem" \) \|
+      /"(${(j:|:)short_arbitem})$nul"/
+	-"_apt_consume_short \${match[1][-2]}"
+	/"$word"/ :"$comp_arbitem" \|
+      /"(${(j:|:)short_arbitem})="/
+	-"_apt_consume_short \${match[1][-2]}"
+	\( /"$word1"/ :"$comp_arbitem" \| /"$nul"/ /"$word"/ :"$comp_arbitem" \) \|
     )
   fi
 
   if (( $#long_hasarg )); then
     regex_long=("$regex_long[@]"
-      /"(${(j:|:)long_hasarg})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}; current_option=\${canonicalize[--\${_ra_match%$nul}]}"
-	/"$word" !"$comp_hasarg" \|
-      /"(${(j:|:)long_hasarg})="
-	-"_apt_consume_long \${_ra_match%=}; current_option=\${canonicalize[--\${_ra_match%=}]}"
-	\( /"$word1" !"$comp_hasarg" \| /"$nul" /"$word" !"$comp_hasarg" \) \|
+      /"(${(j:|:)long_hasarg})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}; current_option=\${canonicalize[--\${match[1]%$nul}]}"
+	/"$word"/ :"$comp_hasarg" \|
+      /"(${(j:|:)long_hasarg})="/
+	-"_apt_consume_long \${match[1]%=}; current_option=\${canonicalize[--\${match[1]%=}]}"
+	\( /"$word1"/ :"$comp_hasarg" \| /"$nul"/ /"$word"/ :"$comp_hasarg" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)long_hasarg})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}; current_option=\${canonicalize[--\${_ra_match%$nul}]}"
-	/"$word" !"$comp_hasarg" \|
-      /"(${(j:|:)long_hasarg})="
-	-"_apt_consume_long \${_ra_match%=}; current_option=\${canonicalize[--\${_ra_match%=}]}"
-	\( /"$word1" !"$comp_hasarg" \| /"$nul" /"$word" !"$comp_hasarg" \) \|
+      /"(${(j:|:)long_hasarg})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}; current_option=\${canonicalize[--\${match[1]%$nul}]}"
+	/"$word"/ :"$comp_hasarg" \|
+      /"(${(j:|:)long_hasarg})="/
+	-"_apt_consume_long \${match[1]%=}; current_option=\${canonicalize[--\${match[1]%=}]}"
+	\( /"$word1"/ :"$comp_hasarg" \| /"$nul"/ /"$word"/ :"$comp_hasarg" \) \|
     )
   fi
 
   if (( $#long_bool )); then
     regex_long=("$regex_long[@]"
-      /"(${(j:|:)long_bool})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_bool" \| /"$nul" /"$word" !"$comp_bool" \) \|
-      /"(${(j:|:)long_bool})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"((${(j:|:)bool})$nul|)" !"$comp_bool" \|
+      /"(${(j:|:)long_bool})="/
+	-"_apt_consume_long xxx \${match[1]%=}"
+	\( /"$word1"/ :"$comp_bool" \| /"$nul"/ /"$word"/ :"$comp_bool" \) \|
+      /"(${(j:|:)long_bool})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"((${(j:|:)bool})$nul|)"/ :"$comp_bool" \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)long_bool})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_bool" \| /"$nul" /"$word" !"$comp_bool" \) \|
-      /"(${(j:|:)long_bool})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"((${(j:|:)bool})$nul|)" !"$comp_bool" \|
+      /"(${(j:|:)long_bool})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_bool" \| /"$nul"/ /"$word"/ :"$comp_bool" \) \|
+      /"(${(j:|:)long_bool})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"((${(j:|:)bool})$nul|)"/ :"$comp_bool" \|
     )
   fi
 
   if (( $#long_intlevel )); then
     regex_long=("$regex_long[@]"
-      /"(${(j:|:)long_intlevel})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_intlevel" \| /"$nul" /"$word" !"$comp_intlevel" \) \|
-      /"(${(j:|:)long_intlevel})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"($intlevel$nul|)" !"$comp_intlevel" \|
+      /"(${(j:|:)long_intlevel})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_intlevel" \| /"$nul"/ /"$word"/ :"$comp_intlevel" \) \|
+      /"(${(j:|:)long_intlevel})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"($intlevel$nul|)"/ :"$comp_intlevel" \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)long_intlevel})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"$intlevel" !"$comp_intlevel" /"$nul" \|
-      /"(${(j:|:)long_intlevel})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_intlevel" \| /"$nul" /"$word" !"$comp_intlevel" \) \|
-      /"(${(j:|:)long_intlevel})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"($intlevel$nul|)" !"$comp_intlevel" \|
+      /"(${(j:|:)long_intlevel})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_intlevel" \| /"$nul"/ /"$word"/ :"$comp_intlevel" \) \|
+      /"(${(j:|:)long_intlevel})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"($intlevel$nul|)"/ :"$comp_intlevel" \|
     )
   fi
 
   if (( $#long_configfile )); then
     regex_long=("$regex_long[@]"
-      /"(${(j:|:)long_configfile})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"$word" !"$comp_configfile" \|
-      /"(${(j:|:)long_configfile})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_configfile" \| /"$nul" /"$word" !"$comp_configfile" \) \|
+      /"(${(j:|:)long_configfile})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"$word"/ :"$comp_configfile" \|
+      /"(${(j:|:)long_configfile})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_configfile" \| /"$nul"/ /"$word"/ :"$comp_configfile" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)long_configfile})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"$word" !"$comp_configfile" \|
-      /"(${(j:|:)long_configfile})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_configfile" \| /"$nul" /"$word" !"$comp_configfile" \) \|
+      /"(${(j:|:)long_configfile})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"$word"/ :"$comp_configfile" \|
+      /"(${(j:|:)long_configfile})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_configfile" \| /"$nul"/ /"$word"/ :"$comp_configfile" \) \|
     )
   fi
 
   if (( $#long_arbitem )); then
     regex_long=("$regex_long[@]"
-      /"(${(j:|:)long_arbitem})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"$word" !"$comp_arbitem" \|
-      /"(${(j:|:)long_arbitem})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_arbitem" \| /"$nul" /"$word" !"$comp_arbitem" \) \|
+      /"(${(j:|:)long_arbitem})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"$word"/ :"$comp_arbitem" \|
+      /"(${(j:|:)long_arbitem})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_arbitem" \| /"$nul"/ /"$word"/ :"$comp_arbitem" \) \|
     )
     regex_long_prefix=("$regex_long_prefix[@]"
-      /"(${(j:|:)long_arbitem})$nul"
-	-"_apt_consume_long \${_ra_match%$nul}"
-	/"$word" !"$comp_arbitem" \|
-      /"(${(j:|:)long_arbitem})="
-	-"_apt_consume_long \${_ra_match%=}"
-	\( /"$word1" !"$comp_arbitem" \| /"$nul" /"$word" !"$comp_arbitem" \) \|
+      /"(${(j:|:)long_arbitem})$nul"/
+	-"_apt_consume_long \${match[1]%$nul}"
+	/"$word"/ :"$comp_arbitem"/ \|
+      /"(${(j:|:)long_arbitem})="/
+	-"_apt_consume_long \${match[1]%=}"
+	\( /"$word1"/ :"$comp_arbitem" \| /"$nul"/ /"$word"/ :"$comp_arbitem" \) \|
     )
   fi
 
   regex_all=(
-    /"$word"
-    \( %-- \( "$regex_long[@]"
-	      %"(${(j:|:)bool})-"
-	      \( "$regex_long_prefix[@]" /"[]" !"$comp_long_prefix" \) \|
-	      /"[]" !"$comp_long" \) \|
-       %- \( "$regex_short[@]" /"[]" !"$comp_short; $comp_long" \) \|
-       /"[]" !"$comp_opt" \) \#
+    /"$word"/
+    \( /--/+ \( "$regex_long[@]"
+		/"(${(j:|:)bool})-"/+
+		  \( "$regex_long_prefix[@]"
+		     /"[]"/ :"$comp_long_prefix" \) \) \|
+       /-/+ \( "$regex_short[@]" /"[]"/ \) \|
+       /"[]"/ :"$comp_opt" \) \#
     "$regex_all[@]"
   )
 
   _regex_arguments "${funcname}_sm" "$regex_all[@]"
 
   eval "$funcname () {
-    typeset -A canonicalize options
+    typeset -A canonicalize num_options
     canonicalize=(${(kv)canonicalize})
-    options=(${(kv)options})
+    num_options=(${(kv)num_options})
 
     local short_hasarg short_bool short_intlevel short_configfile short_arbitem
     local long_hasarg long_bool long_intlevel long_configfile long_arbitem
@@ -341,14 +338,16 @@ _apt_consume_short () {
   local short opt
   for short in ${(s::)1}; do
     opt="$canonicalize[-$short]"
-    (( 0 < options[$opt] && options[$opt]-- ))
+    (( 0 < num_options[$opt] && num_options[$opt]-- ))
   done
+  return 0
 }
 
 _apt_consume_long () {
   local long opt
   opt="$canonicalize[--$1]"
-  (( 0 < options[$opt] && options[$opt]-- ))
+  (( 0 < num_options[$opt] && num_options[$opt]-- ))
+  return 0
 }
 
 _apt-get () {
@@ -373,18 +372,18 @@ _apt-get () {
     -c,--config-file:configfile \
     -o,--option:arbitem \
     -- \
-    /$'update\0' \| \
-    /$'upgrade\0' \| \
-    /$'install\0' /$'[^\0]#\0' !'_deb_packages uninstalled "$expl_packages[@]" || _deb_packages installed "$expl_packages[@]" ' \# \| \
-    /$'remove\0' /$'[^\0]#\0' !'_deb_packages installed "$expl_packages[@]"' \# \| \
-    /$'dist-upgrade\0' \| \
-    /$'dselect-upgrade\0' \| \
-    /$'clean\0' \| \
-    /$'autoclean\0' \| \
-    /$'check\0' \| \
-    /$'source\0' /$'[^\0]#\0' !'_deb_packages avail "$expl_packages[@]"' \# \| \
-    /$'help\0' \| \
-    /"[]"	!'compadd "$expl_action[@]" update upgrade install remove dist-upgrade dselect-upgrade clean autoclean check source help'
+    /$'update\0'/ \| \
+    /$'upgrade\0'/ \| \
+    /$'install\0'/ /$'[^\0]#\0'/ :'_deb_packages uninstalled "$expl_packages[@]" || _deb_packages installed "$expl_packages[@]" ' \# \| \
+    /$'remove\0'/ /$'[^\0]#\0'/ :'_deb_packages installed "$expl_packages[@]"' \# \| \
+    /$'dist-upgrade\0'/ \| \
+    /$'dselect-upgrade\0'/ \| \
+    /$'clean\0'/ \| \
+    /$'autoclean\0'/ \| \
+    /$'check\0'/ \| \
+    /$'source\0'/ /$'[^\0]#\0'/ :'_deb_packages avail "$expl_packages[@]"' \# \| \
+    /$'help\0/' \| \
+    /"[]"/	:'compadd "$expl_action[@]" update upgrade install remove dist-upgrade dselect-upgrade clean autoclean check source help'
 
   _apt-get () {
     local expl_action expl_packages
@@ -410,19 +409,19 @@ _apt-cache () {
     -c,--config-file:configfile \
     -o,--option:arbitem \
     -- \
-    /$'help\0' \| \
-    /$'add\0' /$'[^\0]#\0' !'_files' \# \| \
-    /$'gencaches\0' \| \
-    /$'showpkg\0' /$'[^\0]#\0' !'_deb_packages avail "$expl_packages[@]"' \# \| \
-    /$'stats\0' \| \
-    /$'dump\0' \| \
-    /$'dumpavail\0' \| \
-    /$'unmet\0' \| \
-    /$'check\0' \| \
-    /$'search\0' \| \
-    /$'show\0' \| \
-    /$'depends\0' \| \
-    /"[]"	!'compadd "$expl_action[@]" help add gencaches showpkg stats dump dumpavail unmet check search show depends'
+    /$'help\0'/ \| \
+    /$'add\0'/ /$'[^\0]#\0'/ :'_files' \# \| \
+    /$'gencaches\0'/ \| \
+    /$'showpkg\0'/ /$'[^\0]#\0'/ :'_deb_packages avail "$expl_packages[@]"' \# \| \
+    /$'stats\0'=$status[4]/ \| \
+    /$'dump\0'/ \| \
+    /$'dumpavail\0'/ \| \
+    /$'unmet\0'/ \| \
+    /$'check\0'/ \| \
+    /$'search\0'/ \| \
+    /$'show\0'/ \| \
+    /$'depends\0'/ \| \
+    /"[]"/ :'compadd "$expl_action[@]" help add gencaches showpkg stats dump dumpavail unmet check search show depends'
 
   _apt-cache () {
     local expl_action expl_packages expl_pkg_cache expl_src_cache
@@ -450,8 +449,8 @@ _apt-cdrom () {
     -c,--config-file:configfile \
     -o,--option:arbitem \
     -- \
-    /$'add\0' \| \
-    /"[]"	!'compadd "$expl_action[@]" add'
+    /$'add\0'/ \| \
+    /"[]"/	:'compadd "$expl_action[@]" add'
 
   _apt-cdrom () {
     local expl_action expl_mount_point
@@ -471,13 +470,13 @@ _apt-config () {
     -c,--config-file:configfile \
     -o,--option:arbitem \
     -- \
-    /$'shell\0' \
+    /$'shell\0'/ \
       \( \
-	/$'[^\0]#\0' !'compgen "$expl_shell_var[@]" -v' \
-	/$'[^\0]#\0' !'compadd "$expl_config_key[@]" - ${${(f)"$(apt-config dump 2>&1)"}% *}' \
+	/$'[^\0]#\0'/ :'compgen "$expl_shell_var[@]" -v' \
+	/$'[^\0]#\0'/ :'compadd "$expl_config_key[@]" - ${${(f)"$(apt-config dump 2>&1)"}% *}' \
       \) \# \| \
-    /$'dump\0' \| \
-    /"[]"	!'compadd "$expl_action[@]" shell dump'
+    /$'dump\0'/ \| \
+    /"[]"/	:'compadd "$expl_action[@]" shell dump'
 
   _apt-config () {
     local expl_action expl_shell_var expl_config_key
diff --git a/Completion/User/_combination b/Completion/User/_combination
index a122bd86f..631547311 100644
--- a/Completion/User/_combination
+++ b/Completion/User/_combination
@@ -1,9 +1,9 @@
 #autoload
 
 # Usage:
-#   _combination [-s SEP] VARIABLE KEYi=PATi KEYj=PATj ... KEYm=PATm KEY EXPL...
+#   _combination [-s S] V[:K1:...] Ki1[:Ni1]=Pi1 Ki2[:Ni2]=Pi2 ... Kim[:Nim]=Pim Kj[:Nj] EXPL...
 #
-#  VARIABLE must be formd as PREFIX_KEY1_..._KEYn.
+#  It is assumed that V is formed as PRE_K1_..._Kn if `:K1:...' is not specified.
 #
 # Example: telnet
 #
@@ -48,7 +48,7 @@
 #  the port argument if they are exist. And if it is failed, `_users' is
 #  called.
 
-local sep var keys pats key tmp
+local sep var keys pats key num tmp
 
 if [[ "$1" = -s ]]; then
   sep="$2"
@@ -60,21 +60,31 @@ fi
 var=$1
 shift
 
-keys=( "${(@s:_:)${var#*_}}" )
+if [[ $var = *:* ]]; then
+  keys=( ${(s/:/)var} )
+  shift keys
+  var="${var%%:*}"
+else
+  keys=( "${(@s:_:)${var#*_}}" )
+fi
 pats=( "${(@)keys/*/*}" )
 
 while [[ "$1" = *=* ]]; do
-  pats[$keys[(i)${1%%\=*}]]="${1#*\=}"
+  tmp="${1%%\=*}"
+  key="${tmp%:*}"
+  num="${${tmp##*:}:-1}"
+  pats[$keys[(in:num:)$key]]="${1#*\=}"
   shift
 done
 
-key="$1"
+key="${1%:*}"
+num="${${1##*:}:-1}"
 shift
 
 if (( ${(P)+${var}} )); then
-  eval "tmp=( \"\${(@M)${var}:#\${(j!$sep!)~pats}}\" )"
-  if (( keys[(i)$key] != 1 )); then
-    eval "tmp=( \${tmp#\${(j!${sep}!)~\${(@)\${(@)keys[2,(r)\$key]}/*/*}}$sep} )"
+  eval "tmp=( \"\${(@M)${var}:#\${(j($sep))~pats}}\" )"
+  if (( keys[(in:num:)$key] != 1 )); then
+    eval "tmp=( \${tmp#\${(j(${sep}))~\${(@)\${(@)keys[2,(rn:num:)\$key]}/*/*}}$sep} )"
   fi
   tmp=( ${tmp%%$sep*} )