#autoload ## usage: _regex_arguments funcname regex ## configuration key used: # regex_arguments_path # The path to a directory for caching. (default: ~/.zsh/regex_arguments) ## # _regex_arguments compiles `regex' and emit the result of the state # machine into the function `funcname'. `funcname' parses a command line # according to `regex' and evaluate appropriate actions in `regex'. Before # parsing the command line string is genereted by concatinating `words' # (before `PREFIX') and `PREFIX' with a separator NUL ($'\0'). # The `regex' is defined as follows. ## regex word definition: # pattern = "/" ( glob | "[]" ) "/" [ "+" | "-" ] # lookahead = "%" glob "%" # guard = "-" zsh-code-to-eval # caction = ":" zsh-code-to-eval # action = "{" zsh-code-to-eval "}" ## regex word sequence definition: # element = pattern [ lookahead ] [ guard ] [ caction ] # # regex = element # | "(" regex ")" # | regex "#" # | ( regex | action ) # # | regex "|" regex # example: # compdef _tst tst # _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' \) \# # _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' \) \# # _tst complete `aaa' or `bbb'. ## Recursive decent regex parser # return status of parser functions: # 0 : success # 1 : parse error # 2 : fatal parse error _ra_comp () { _ra_actions=("$_ra_actions[@]" "$1") } _regex_arguments () { local regex funcname="$1" shift regex=(${@:/(#b):(*)/":_ra_comp ${(qqqq)match[1]}"}) eval \ "$funcname"' () { local _ra_p1 _ra_p2 _ra_left _ra_right _ra_com local _ra_actions _ra_line="${(pj:\0:)${(@)words[1,CURRENT - 1]:Q}}"$'\''\0'\''"$PREFIX" zregexparse -c _ra_p1 _ra_p2 "$_ra_line" '"${(j: :)${(qqqq)regex[@]}}"' case "$?" in 0|2) _message "no more arguments";; 1) if [[ "$_ra_line[_ra_p1 + 1, -1]" = *$'\''\0'\''* ]]; then _message "parse failed before current word" else _ra_left="$_ra_line[_ra_p1 + 1, _ra_p2]" _ra_right="$_ra_line[_ra_p2 + 1, -1]" compset -p $(( $#PREFIX - $#_ra_line + $_ra_p1 )) for _ra_com in "$_ra_actions[@]"; do eval "$_ra_com" done fi ;; 3) _message "invalid regex";; esac }' } _regex_arguments "$@"