All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
3 " Last Change: 2012 Dec 19
5 " Author: Andy Wokula <anwoku@yahoo.de>
6 " License: Vim License, see :h license
9 " Implements :ExplainPattern [pattern]
12 " - more testing, completeness check
13 " - detailed collections
15 " ? literal string: also print the unescaped magic items
16 " ? literal string: show leading/trailing spaces
17 " + start of "first" capturing group, start of 2nd ...
18 " + `\|' should get less indent than the branches, do we need to create an
19 " AST? ! no, keep it straight forward
27 let g:explainpat#loaded = 1
30 func! explainpat#ExplainPattern(cmd_arg) "{{{
32 " let pattern_str = nwo#vis#Get()
33 echo "(usage) :ExplainPattern [{register} | {pattern}]"
35 elseif strlen(a:cmd_arg) == 1 && a:cmd_arg =~ '["@0-9a-z\-:.*+/]'
36 echo 'Register:' a:cmd_arg
37 let pattern_str = getreg(a:cmd_arg)
39 let pattern_str = a:cmd_arg
42 echo printf('Pattern: %s', pattern_str)
43 let magicpat = nwo#magic#MakeMagic(pattern_str)
44 if magicpat !=# pattern_str
45 echo printf('Magic Pattern: %s', magicpat)
48 " hmm, we need state for \%[ ... ]
49 let s:in_opt_atoms = 0
50 let s:capture_group_nr = 0
52 let hulit = s:NewHelpPrinter()
53 call hulit.AddIndent(' ')
54 let bull = s:NewTokenBiter(magicpat, '')
56 let item = bull.Bite(s:magic_item_pattern)
58 let Doc = get(s:doc, item, '')
60 call hulit.AddLiteral(item)
61 elseif type(Doc) == s:STRING
62 call hulit.Print(item, Doc)
63 elseif type(Doc) == s:FUNCREF
64 call call(Doc, [bull, hulit, item])
67 echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest())
72 call hulit.FlushLiterals()
76 let s:STRING = type("")
78 let s:FUNCREF = type(function("tr"))
81 let s:magic_item_pattern = '\C^\%(\\\%(@<.\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|{\|@[>=!]\|_[[^$.]\=\|.\)\|.\)'
84 " this is all the help data ...
85 " strings, funcrefs and intermixed s:DocFoo() functions
86 " strongly depends on s:magic_item_pattern
88 func! s:DocOrBranch(bull, hulit, item) "{{{
89 call a:hulit.RemIndent()
90 call a:hulit.Print(a:item, "OR branch")
91 call a:hulit.AddIndent(' ')
94 let s:doc['\|'] = function("s:DocOrBranch")
95 let s:doc['\&'] = "AND branch"
97 let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth')
99 func! s:DocGroupStart(bull, hulit, item) "{{{
101 call a:hulit.Print(a:item, "start of non-capturing group")
102 else " a:item == '\('
103 let s:capture_group_nr += 1
104 call a:hulit.Print(a:item, printf("start of %s capturing group", get(s:ord, s:capture_group_nr, '(invalid)')))
106 call a:hulit.AddIndent('| ', ' ')
108 func! s:DocGroupEnd(bull, hulit, item) "{{{
109 call a:hulit.RemIndent(2)
110 call a:hulit.Print(a:item, "end of group")
113 let s:doc['\('] = function("s:DocGroupStart")
114 let s:doc['\%('] = function("s:DocGroupStart")
115 let s:doc['\)'] = function("s:DocGroupEnd")
117 let s:doc['\z('] = "only in syntax scripts"
118 let s:doc['*'] = "(multi) zero or more of the preceding atom"
119 let s:doc['\+'] = "(multi) one or more of the preceding atom"
120 let s:doc['\='] = "(multi) zero or one of the preceding atom"
121 let s:doc['\?'] = "(multi) zero or one of the preceding atom"
122 " let s:doc['\{'] = "(multi) N to M, greedy"
123 " let s:doc['\{-'] = "(multi) N to M, non-greedy"
125 func! s:DocBraceMulti(bull, hulit, item) "{{{
126 let rest = a:bull.Bite('^-\=\d*\%(,\d*\)\=}')
129 call a:hulit.Print(a:item. rest, "non-greedy version of `*'")
131 call a:hulit.Print(a:item. rest, "(multi) N to M, non-greedy")
133 call a:hulit.Print(a:item. rest, "(multi) N to M, greedy")
136 echoerr printf('ExplainPattern: cannot parse %s', a:item. a:bull.Rest())
140 let s:doc['\{'] = function("s:DocBraceMulti")
142 let s:doc['\@>'] = "(multi) match preceding atom like a full pattern"
143 let s:doc['\@='] = "(assertion) require match for preceding atom"
144 let s:doc['\@!'] = "(assertion) forbid match for preceding atom"
145 let s:doc['\@<='] = "(assertion) require match for preceding atom to the left"
146 let s:doc['\@<!'] = "(assertion) forbid match for preceding atom to the left"
147 let s:doc['^'] = "(assertion) require match at start of line"
148 let s:doc['\_^'] = "(assertion) like `^', allowed anywhere in the pattern"
149 let s:doc['$'] = "(assertion) require match at end of line"
150 let s:doc['\_$'] = "(assertion) like `$', allowed anywhere in the pattern"
151 let s:doc['.'] = "match any character"
152 let s:doc['\_.'] = "match any character or newline"
154 func! s:DocUnderscore(bull, hulit, item) "{{{
155 let cclass = a:bull.Bite('^\a')
157 let cclass_doc = get(s:doc, '\'. cclass, '(invalid character class)')
158 call a:hulit.Print(a:item. cclass, printf('%s or end-of-line', cclass_doc))
160 echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.'))
164 let s:doc['\_'] = function("s:DocUnderscore")
165 let s:doc['\<'] = "(assertion) require match at begin of word, :h word"
166 let s:doc['\>'] = "(assertion) require match at end of word, :h word"
167 let s:doc['\zs'] = "set begin of match here"
168 let s:doc['\ze'] = "set end of match here"
169 let s:doc['\%^'] = "(assertion) match at begin of buffer"
170 let s:doc['\%$'] = "(assertion) match at end of buffer"
171 let s:doc['\%V'] = "(assertion) match within the Visual area"
172 let s:doc['\%#'] = "(assertion) match with cursor position"
175 " \%23l \%<23l \%>23l
176 " \%23c \%<23c \%>23c
177 " \%23v \%<23v \%>23v
178 " backslash percent at/before/after
179 func! s:DocBspercAt(bull, hulit, item) "{{{
180 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
182 call a:hulit.Print(a:item.rest, "(assertion) match with position of mark ". rest[1])
184 let number = rest[:-2]
187 call a:hulit.Print(a:item.rest, "match in line ". number)
189 call a:hulit.Print(a:item.rest, "match in column ". number)
191 call a:hulit.Print(a:item.rest, "match in virtual column ". number)
193 echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
197 func! s:DocBspercBefore(bull, hulit, item) "{{{
198 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
200 call a:hulit.Print(a:item.rest, "(assertion) match before position of mark ". rest[1])
202 let number = rest[:-2]
205 call a:hulit.Print(a:item.rest, printf("match above line %d (towards start of buffer)", number))
207 call a:hulit.Print(a:item.rest, "match before column ". number)
209 call a:hulit.Print(a:item.rest, "match before virtual column ". number)
211 echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
215 func! s:DocBspercAfter(bull, hulit, item) "{{{
216 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
218 call a:hulit.Print(a:item.rest, "(assertion) match after position of mark ". rest[1])
220 let number = rest[:-2]
223 call a:hulit.Print(a:item.rest, printf("match below line %d (towards end of buffer)", number))
225 call a:hulit.Print(a:item.rest, "match after column ". number)
227 call a:hulit.Print(a:item.rest, "match after virtual column ". number)
229 echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
234 let s:doc['\%'] = function("s:DocBspercAt")
235 let s:doc['\%<'] = function("s:DocBspercBefore")
236 let s:doc['\%>'] = function("s:DocBspercAfter")
238 let s:doc['\i'] = "identifier character (see 'isident' option)"
239 let s:doc['\I'] = "like \"\\i\", but excluding digits"
240 let s:doc['\k'] = "keyword character (see 'iskeyword' option)"
241 let s:doc['\K'] = "like \"\\k\", but excluding digits"
242 let s:doc['\f'] = "file name character (see 'isfname' option)"
243 let s:doc['\F'] = "like \"\\f\", but excluding digits"
244 let s:doc['\p'] = "printable character (see 'isprint' option)"
245 let s:doc['\P'] = "like \"\\p\", but excluding digits"
246 let s:doc['\s'] = "whitespace character: <Space> and <Tab>"
247 let s:doc['\S'] = "non-whitespace character; opposite of \\s"
248 let s:doc['\d'] = "digit: [0-9]"
249 let s:doc['\D'] = "non-digit: [^0-9]"
250 let s:doc['\x'] = "hex digit: [0-9A-Fa-f]"
251 let s:doc['\X'] = "non-hex digit: [^0-9A-Fa-f]"
252 let s:doc['\o'] = "octal digit: [0-7]"
253 let s:doc['\O'] = "non-octal digit: [^0-7]"
254 let s:doc['\w'] = "word character: [0-9A-Za-z_]"
255 let s:doc['\W'] = "non-word character: [^0-9A-Za-z_]"
256 let s:doc['\h'] = "head of word character: [A-Za-z_]"
257 let s:doc['\H'] = "non-head of word character: [^A-Za-z_]"
258 let s:doc['\a'] = "alphabetic character: [A-Za-z]"
259 let s:doc['\A'] = "non-alphabetic character: [^A-Za-z]"
260 let s:doc['\l'] = "lowercase character: [a-z]"
261 let s:doc['\L'] = "non-lowercase character: [^a-z]"
262 let s:doc['\u'] = "uppercase character: [A-Z]"
263 let s:doc['\U'] = "non-uppercase character: [^A-Z]"
265 let s:doc['\e'] = "match <Esc>"
266 let s:doc['\t'] = "match <Tab>"
267 let s:doc['\r'] = "match <CR>"
268 let s:doc['\b'] = "match CTRL-H"
269 let s:doc['\n'] = "match a newline"
270 let s:doc['~'] = "match the last given substitute string"
271 let s:doc['\1'] = "match first captured string"
272 let s:doc['\2'] = "match second captured string"
273 let s:doc['\3'] = "match third captured string"
274 let s:doc['\4'] = "match fourth captured string "
275 let s:doc['\5'] = "match fifth captured string"
276 let s:doc['\6'] = "match sixth captured string"
277 let s:doc['\7'] = "match seventh captured string"
278 let s:doc['\8'] = "match eighth captured string"
279 let s:doc['\9'] = "match ninth captured string"
286 " skip the rest of a collection
287 let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]'
289 func! s:DocCollection(bull, hulit, item) "{{{
290 let collstr = a:bull.Bite(s:coll_skip_pat)
291 let inverse = collstr =~ '^\^'
292 let with_nl = a:item == '\_['
293 let descr = inverse ? printf('collection not matching [%s', collstr[1:]) : 'collection'
294 let descr_nl = printf("%s%s", (inverse && with_nl ? ', but' : ''), (with_nl ? ' with end-of-line added' : ''))
295 call a:hulit.Print(a:item. collstr, descr. descr_nl)
298 let s:doc['['] = function("s:DocCollection")
299 let s:doc['\_['] = function("s:DocCollection")
301 func! s:DocOptAtoms(bull, hulit, item) "{{{
303 call a:hulit.Print(a:item, "start a sequence of optionally matched atoms")
304 let s:in_opt_atoms = 1
305 call a:hulit.AddIndent('. ')
308 call a:hulit.RemIndent()
309 call a:hulit.Print(a:item, "end of optionally matched atoms")
310 let s:in_opt_atoms = 0
312 call a:hulit.AddLiteral(a:item)
317 " let s:doc['\%['] = "start a sequence of optionally matched atoms"
318 let s:doc['\%['] = function("s:DocOptAtoms")
319 let s:doc[']'] = function("s:DocOptAtoms")
321 let s:doc['\c'] = "ignore case while matching the pattern"
322 let s:doc['\C'] = "match case while matching the pattern"
323 let s:doc['\Z'] = "ignore composing characters in the pattern"
331 func! s:DocBspercDecimal(bull, hulit, item) "{{{
332 let number = a:bull.Bite('^\d\{,3}')
333 let char = strtrans(nr2char(str2nr(number)))
334 call a:hulit.Print(a:item. number, printf("match character specified by decimal number %s (%s)", number, char))
336 func! s:DocBspercHexTwo(bull, hulit, item) "{{{
337 let number = a:bull.Bite('^\x\{,2}')
338 let char = strtrans(nr2char(str2nr(number,16)))
339 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s (%s)", number, char))
341 func! s:DocBspercOctal(bull, hulit, item) "{{{
342 let number = a:bull.Bite('^\o\{,4}')
343 let char = strtrans(nr2char(str2nr(number,8)))
344 call a:hulit.Print(a:item. number, printf("match character specified with octal number 0%s (%s)", substitute(number, '^0*', '', ''), char))
346 func! s:DocBspercHexFour(bull, hulit, item) "{{{
347 let number = a:bull.Bite('^\x\{,4}')
348 let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : ''
349 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char))
351 func! s:DocBspercHexEight(bull, hulit, item) "{{{
352 let number = a:bull.Bite('^\x\{,8}')
353 let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : ''
354 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char))
357 let s:doc['\%d'] = function("s:DocBspercDecimal") " 123
358 let s:doc['\%x'] = function("s:DocBspercHexTwo") " 2a
359 let s:doc['\%o'] = function("s:DocBspercOctal") " 0377
360 let s:doc['\%u'] = function("s:DocBspercHexFour") " 20AC
361 let s:doc['\%U'] = function("s:DocBspercHexEight") " 1234abcd
369 func! s:NewHelpPrinter() "{{{
371 let obj.literals = ''
373 let obj.len = 0 " can be negative (!)
375 func! obj.Print(str, ...) "{{{
376 call self.FlushLiterals()
377 let indstr = join(self.indents, '')
384 " echo indstr. printf("`%s' %s", a:str, a:1)
386 echon printf("%-10s", a:str)
389 echon printf(" %s", a:1)
394 func! obj.AddLiteral(item) "{{{
395 let self.literals .= a:item
398 func! obj.FlushLiterals() "{{{
399 if self.literals == ''
402 let indstr = join(self.indents, '')
406 if self.literals =~ '^\s\|\s$'
407 echon printf("%-10s", '"'. self.literals. '"')
409 echon printf("%-10s", self.literals)
412 echon " literal string"
413 if exists("*strchars")
414 if self.literals =~ '\\'
415 let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
417 let so = self.literals =~ '[^ ]' ? '' : ', spaces only'
418 echon " (". strchars(self.literals). " atom(s)". so.")"
421 let self.literals = ''
424 func! obj.AddIndent(...) "{{{
425 call self.FlushLiterals()
427 call extend(self.indents, copy(a:000))
428 elseif self.len + a:0 >= 1
429 call extend(self.indents, a:000[-(self.len+a:0):])
434 func! obj.RemIndent(...) "{{{
435 call self.FlushLiterals()
438 call remove(self.indents, -1)
443 call remove(self.indents, -a:1, -1)
445 call remove(self.indents, 0, -1)
454 func! s:NewTokenBiter(str, ...) "{{{
455 " {str} string to eat pieces from
456 " {a:1} pattern to skip separators
457 let whitepat = a:0>=1 ? a:1 : '^\s*'
458 let obj = {'str': a:str, 'whitepat': whitepat}
460 " consume piece from start of input matching {pat}
461 func! obj.Bite(pat) "{{{
462 " {pat} should start with '^'
463 let skip = matchend(self.str, self.whitepat)
464 let bite = matchstr(self.str, a:pat, skip)
465 let self.str = strpart(self.str, matchend(self.str, self.whitepat, skip + strlen(bite)))
469 " get the unparsed rest of input
470 func! obj.Rest() "{{{
474 " check if end of input reached
475 func! obj.AtEnd() "{{{
476 return self.str == ""
483 let &cpo = s:cpo_save
485 " vim:ts=8:fdm=marker: