1#autoload
2
3# A simple compiler for _arguments descriptions.  The first argument of
4# _arg_compile is the name of an array parameter in which the parse is
5# returned.  The remaining arguments form a series of `phrases'.  Each
6# `phrase' begins with one of the keywords "argument", "option", or "help"
7# and consists of a series of keywords and/or values.  The syntax is as
8# free-form as possible, but "argument" phrases generally must appear in
9# the same relative position as the corresponding argument on the command
10# line to be completed, and there are some restrictions on ordering of
11# keywords and values within each phrase.
12#
13# Anything appearing before the first phrase or after the last is passed
14# through verbatim.  (See TODO.)  If more detailed mixing of compiled and
15# uncompiled fragments is necessary, use two or more calls, either with
16# different array names or by passing the output of each previous call
17# through the next.
18#
19# In the documentation below, brackets [ ] indicate optional elements and
20# braces { } indicate elements that may be repeated zero or more times.
21# Except as noted, bracketed or braced elements may appear in any order
22# relative to each other, but tokens within each element are ordered.
23#
24#   argument [POS] [means MSG] [action ACT]
25#
26#     POS may be an integer N for the Nth argument or "*" for all, and
27#      must appear first if it appears at all.
28#     MSG is a string to be displayed above the matches in a listing.
29#     ACT is (currently) as described in the compsys manual.
30#
31#   option OPT [follow HOW] [explain STR] {unless XOR} \
32#    {[means MSG] [action ACT]} [through PAT [means MSG] [action ACT]]
33#
34#     OPT is the option, prefixed with "*" if it may appear more than once.
35#     HOW refers to a following argument, and may be one of:
36#       "close"   must appear in the same word (synonyms "join" or "-")
37#       "next"    the argument must appear in the next word (aka "split")
38#       "loose"   the argument may appear in the same or the next word ("+")
39#       "assign"  as loose, but must follow an "=" in the same word ("=")
40#     HOW should be suffixed with a colon if the following argument is
41#      _not_ required to appear.
42#     STR is to be displayed based on style `description'
43#     XOR is another option in combination with which OPT may not appear.
44#      It may be ":" to disable non-option completions when OPT is present.
45#     MSG is a string to be displayed above the matches in a listing.
46#     ACT is (currently) as described in the compsys manual.
47#     PAT is either "*" for "all remaining words on the line" or a pattern
48#      that, if matched, marks the end of the arguments of this option.
49#      The "through PAT ..." description must be the last.
50#     PAT may be suffixed with one colon to narrow the $words array to
51#      the remainder of the command line, or with two colons to narrow
52#      to the words before (not including) the next that matches PAT.
53#
54#   help PAT [means MSG] action ACT
55#
56#     ACT is applied to any option output by --help that matches PAT.
57#      Do not use "help" with commands that do not support --help.
58#     PAT may be suffixed with a colon if the following argument is
59#      _not_ required to appear (this is usually inferred from --help).
60#     MSG is a string to be displayed above the matches in a listing.
61
62# EXAMPLE:
63# This is from _gprof in the standard distribution.  Note that because of
64# the brace expansion trick used in the "function name" case, no attempt
65# is made to use `phrase' form; that part gets passed through unchanged.
66# It could simply be moved to the _arguments call ahead of "$args[@]".
67#
68# _arg_compile args -s -{a,b,c,D,h,i,l,L,s,T,v,w,x,y,z} \
69#              -{A,C,e,E,f,F,J,n,N,O,p,P,q,Q,Z}:'function name:->funcs' \
70#              option -I means directory action _dir_list \
71#              option -d follow close means "debug level" \
72#              option -k means "function names" action '->pair' \
73#              option -m means "minimum execution count" \
74#              argument means executable action '_files -g \*\(-\*\)' \
75#              argument means "profile file" action '_files -g gmon.\*' \
76#              help '*=name*' means "function name" action '->funcs' \
77#              help '*=dirs*' means "directory" action _dir_list
78# _arguments "$args[@]"
79
80# TODO:
81# Verbose forms of various actions, e.g. (but not exactly)
82#   "state foo"                  becomes "->foo"
83#   "completion X explain Y ..." becomes "((X\:Y ...))"
84#   etc.
85# Represent leading "*" in OPT some other way.
86# Represent trailing colons in HOW and PAT some other way.
87# Stricter syntax checking on HOW, sanity checks on XOR.
88# Something less obscure than "unless :" would be nice.
89# Warning or other syntax check for stuff after the last phrase.
90
91emulate -L zsh
92local -h argspec dspec helpspec prelude xor
93local -h -A amap dmap safe
94
95[[ -n "$1" ]] || return 1
96[[ ${(tP)${1}} = *-local ]] && { print -R NAME CONFLICT: $1 1>&2; return 1 }
97safe[reply]="$1"; shift
98
99# First consume and save anything before the argument phrases
100
101helpspec=()
102prelude=()
103
104while (($#))
105do
106  case $1 in
107  (argument|help|option) break;;
108  (*) prelude=("$prelude[@]" "$1"); shift;;
109  esac
110done
111
112# Consume all the argument phrases and build the argspec array
113
114while (($#))
115do
116  amap=()
117  dspec=()
118  case $1 in
119
120  # argument [POS] [means MSG] [action ACT]
121  (argument)
122    shift
123    while (($#))
124    do
125      case $1 in
126      (<1->|\*) amap[position]="$1"; shift;;
127      (means|action) amap[$1]="$2"; shift 2;;
128      (argument|option|help) break;;
129      (*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
130      esac
131    done
132    if (( $#amap ))
133    then
134      argspec=("$argspec[@]" "${amap[position]}:${amap[means]}:${amap[action]}")
135    fi;;
136
137  # option OPT [follow HOW] [explain STR] {unless XOR} \
138  #  {[through PAT] [means MSG] [action ACT]}
139  (option)
140    amap[option]="$2"; shift 2
141    dmap=()
142    xor=()
143    while (( $# ))
144    do
145      (( ${+amap[$1]} || ${+dmap[through]} )) && break;
146      case $1 in
147      (follow)
148	amap[follow]="${2:s/join/-/:s/close/-/:s/next//:s/split//:s/loose/+/:s/assign/=/:s/none//}"
149	shift 2;;
150      (explain) amap[explain]="[$2]" ; shift 2;;
151      (unless) xor=("$xor[@]" "${(@)=2}"); shift 2;;
152      (through|means|action)
153	while (( $# ))
154	do
155	  (( ${+dmap[$1]} )) && break 2
156	  case $1 in
157	  (through|means|action) dmap[$1]=":${2}"; shift 2;;
158	  (argument|option|help|follow|explain|unless) break;;
159	  (*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
160	  esac
161	done;;
162      (argument|option|help) break;;
163      (*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
164      esac
165      if (( $#dmap ))
166      then
167	dspec=("$dspec[@]" "${dmap[through]}${dmap[means]:-:}${dmap[action]:-:}")
168      fi
169    done
170    if (( $#amap ))
171    then
172      argspec=("$argspec[@]" "${xor:+($xor)}${amap[option]}${amap[follow]}${amap[explain]}${dspec}")
173    fi;;
174
175  # help PAT [means MSG] action ACT
176  (help)
177    amap[pattern]="$2"; shift 2
178    while (($#))
179    do
180      (( ${+amap[$1]} )) && break;
181      case $1 in
182      (means|action) amap[$1]="$2"; shift 2;;
183      (argument|option|help) break;;
184      (*) print -R SYNTAX ERROR at "$@" 1>&2; return 1;;
185      esac
186    done
187    if (( $#amap ))
188    then
189      helpspec=("$helpspec[@]" "${amap[pattern]}:${amap[means]}:${amap[action]}")
190    fi;;
191  (*) break;;
192  esac
193done
194
195eval $safe[reply]'=( "${prelude[@]}" "${argspec[@]}" ${helpspec:+"-- ${helpspec[@]}"} "$@" )'
196
197# print -R _arguments "${prelude[@]:q}" "${argspec[@]:q}" ${helpspec:+"-- ${helpspec[@]:q}"} "$@:q"
198
199return 0
200