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: 2017 Dec 15
5 " Author: Andy Wokula <anwoku@yahoo.de>
6 " License: Vim License, see :h license
8 " Implements :ExplainPattern [pattern]
11 " 2013 Jun 21 AND/OR text is confusing, removed
16 " - add something like "(empty) ... match everywhere" ... example: '\v(&&|str)'
18 " Magic Pattern: \(\&\&\|str\)
19 " \( start of first capturing group
20 " | (empty) match everywhere
22 " | (empty) match everywhere
24 " | (empty) match everywhere
26 " | str literal string (3 atom(s))
28 " - more testing, completeness check
29 " ? detailed collections
30 " ? literal string: also print the unescaped magic items
31 " ? literal string: show leading/trailing spaces
38 let g:explainpat#loaded = 1
41 func! explainpat#ExplainPattern(cmd_arg, ...) "{{{
42 " {a:1} alternative help printer object (caution, no sanity check)
45 " let pattern_str = nwo#vis#Get()
46 echo "(usage) :ExplainPattern [{register} | {pattern}]"
48 elseif strlen(a:cmd_arg) == 1 && a:cmd_arg =~ '["@0-9a-z\-:.*+/]'
49 echo 'Register:' a:cmd_arg
50 let pattern_str = getreg(a:cmd_arg)
52 let pattern_str = a:cmd_arg
55 echo printf('Pattern: %s', pattern_str)
56 let magicpat = nwo#magic#MakeMagic(pattern_str)
57 if magicpat !=# pattern_str
58 echo printf('Magic Pattern: %s', magicpat)
62 " set flag when in `\%[ ... ]' (optionally matched atoms):
63 let s:in_opt_atoms = 0
65 let s:capture_group_nr = 0
66 " >=1 at pos 0 or after '\|', '\&', '\(', '\%(' or '\n'; else 0 or less:
67 let s:at_begin_of_pat = 1
69 let hulit = a:0>=1 && type(a:1)==s:DICT ? a:1 : explainpat#NewHelpPrinter()
70 call hulit.AddIndent(' ')
71 let bull = s:NewTokenBiter(magicpat)
73 let item = bull.Bite(s:magic_item_pattern)
75 let Doc = get(s:doc, item, '')
77 call hulit.AddLiteral(item)
78 elseif type(Doc) == s:STRING
79 call hulit.Print(item, Doc)
80 elseif type(Doc) == s:FUNCREF
81 call call(Doc, [bull, hulit, item])
82 elseif type(Doc) == s:LIST
83 call call(Doc[0], [bull, hulit, item, Doc[1]])
85 let s:at_begin_of_pat -= 1
87 echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest())
92 call hulit.FlushLiterals()
96 let s:STRING = type("")
98 let s:FUNCREF = type(function("tr"))
102 let s:magic_item_pattern = '\C^\%(\\\%(%#=\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|@[>=!]\=\|_[[^$.]\=\|.\)\|.\)'
105 " this is all the help data ...
106 " strings, funcrefs and intermixed s:DocFoo() functions
107 " strongly depends on s:magic_item_pattern
109 func! s:DocOrBranch(bull, hulit, item) "{{{
110 call a:hulit.RemIndent()
111 call a:hulit.Print(a:item, "OR")
112 call a:hulit.AddIndent(' ')
113 let s:at_begin_of_pat = 2
116 let s:doc['\|'] = function("s:DocOrBranch")
118 func! s:DocBeginOfPat(bull, hulit, item, msg) "{{{
119 call a:hulit.Print(a:item, a:msg)
120 let s:at_begin_of_pat = 2
123 let s:doc['\&'] = [function("s:DocBeginOfPat"), "AND"]
125 let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth')
127 func! s:DocGroupStart(bull, hulit, item) "{{{
129 call a:hulit.Print(a:item, "start of non-capturing group")
130 elseif a:item == '\('
131 let s:capture_group_nr += 1
132 call a:hulit.Print(a:item, printf("start of %s capturing group", get(s:ord, s:capture_group_nr, '(invalid)')))
133 else " a:item == '\z('
134 call a:hulit.Print(a:item, 'start of "external" group (only usable in :syn-region)')
136 call a:hulit.AddIndent('| ', ' ')
137 let s:at_begin_of_pat = 2
139 func! s:DocGroupEnd(bull, hulit, item) "{{{
140 call a:hulit.RemIndent(2)
141 call a:hulit.Print(a:item, "end of group")
144 let s:doc['\('] = function("s:DocGroupStart")
145 let s:doc['\%('] = function("s:DocGroupStart")
146 let s:doc['\)'] = function("s:DocGroupEnd")
147 " let s:doc['\z('] = "only in syntax scripts"
148 let s:doc['\z('] = function("s:DocGroupStart")
150 func! s:DocStar(bull, hulit, item) "{{{
151 if s:at_begin_of_pat >= 1
152 " call a:hulit.Print(a:item, "(at begin of pattern) literal `*'")
153 call a:hulit.AddLiteral(a:item)
155 call a:hulit.Print(a:item, "(multi) zero or more of the preceding atom")
159 " let s:doc['*'] = "(multi) zero or more of the preceding atom"
160 let s:doc['*'] = function("s:DocStar")
162 let s:doc['\+'] = "(multi) one or more of the preceding atom"
163 let s:doc['\='] = "(multi) zero or one of the preceding atom"
164 let s:doc['\?'] = "(multi) zero or one of the preceding atom"
165 " let s:doc['\{'] = "(multi) N to M, greedy"
166 " let s:doc['\{-'] = "(multi) N to M, non-greedy"
168 func! s:DocBraceMulti(bull, hulit, item) "{{{
169 let rest = a:bull.Bite('^-\=\d*\%(,\d*\)\=\\\=}')
172 call a:hulit.Print(a:item. rest, "non-greedy version of `*'")
174 call a:hulit.Print(a:item. rest, "(multi) N to M, non-greedy")
176 call a:hulit.Print(a:item. rest, "(multi) N to M, greedy")
179 call a:hulit.Print(a:item, "(invalid) incomplete `\\{...}' item")
183 let s:doc['\{'] = function("s:DocBraceMulti")
185 let s:doc['\@>'] = "(multi) match preceding atom like a full pattern"
186 let s:doc['\@='] = "(assertion) require match for preceding atom"
187 let s:doc['\@!'] = "(assertion) forbid match for preceding atom"
189 func! s:DocBefore(bull, hulit, item) "{{{
190 let rest = a:bull.Bite('^\d*\%[<[=!]]')
192 call a:hulit.Print(a:item.rest, "(assertion) require match for preceding atom to the left")
194 call a:hulit.Print(a:item.rest, "(assertion) forbid match for preceding atom to the left")
195 elseif rest =~ '^\d\+<='
196 call a:hulit.Print(a:item.rest, printf("(assertion) like `\\@<=', looking back at most %s bytes (since Vim 7.3.1037)", s:SillyCheck(matchstr(rest, '\d\+'))))
197 elseif rest =~ '^\d\+<!'
198 call a:hulit.Print(a:item.rest, printf("(assertion) like `\\@<!', looking back at most %s bytes (since Vim 7.3.1037)", s:SillyCheck(matchstr(rest, '\d\+'))))
200 call a:hulit.Print(a:item.rest, "(invalid) incomplete item")
204 let s:doc['\@'] = function("s:DocBefore")
206 func! s:DocCircumFlex(bull, hulit, item) "{{{
207 if s:at_begin_of_pat >= 1
208 call a:hulit.Print(a:item, "(assertion) require match at start of line")
209 " after `^' is not at begin of pattern ... handle special case `^*' here:
210 if a:bull.Bite('^\*') == "*"
211 call a:hulit.AddLiteral("*")
214 " call a:hulit.Print(a:item, "(not at begin of pattern) literal `^'")
215 call a:hulit.AddLiteral(a:item)
219 " let s:doc['^'] = "(assertion) require match at start of line"
220 let s:doc['^'] = function("s:DocCircumFlex")
222 let s:doc['\_^'] = "(assertion) like `^', allowed anywhere in the pattern"
224 func! s:DocDollar(bull, hulit, item) "{{{
225 if a:bull.Rest() =~ '^$\|^\\[&|)n]'
226 call a:hulit.Print(a:item, "(assertion) require match at end of line")
228 call a:hulit.AddLiteral(a:item)
232 " let s:doc['$'] = "(assertion) require match at end of line"
233 let s:doc['$'] = function("s:DocDollar")
235 let s:doc['\_$'] = "(assertion) like `$', allowed anywhere in the pattern"
236 let s:doc['.'] = "match any character"
237 let s:doc['\_.'] = "match any character or newline"
239 func! s:DocUnderscore(bull, hulit, item) "{{{
240 let cclass = a:bull.Bite('^\a')
242 let cclass_doc = get(s:doc, '\'. cclass, '(invalid character class)')
243 call a:hulit.Print(a:item. cclass, printf('%s or end-of-line', cclass_doc))
245 call a:hulit.Print(a:item, "(invalid) `\\_' should be followed by a letter or `[...]'")
246 " echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.'))
250 let s:doc['\_'] = function("s:DocUnderscore")
251 let s:doc['\<'] = "(assertion) require match at begin of word, :h word"
252 let s:doc['\>'] = "(assertion) require match at end of word, :h word"
253 let s:doc['\zs'] = "set begin of match here"
254 let s:doc['\ze'] = "set end of match here"
255 let s:doc['\%^'] = "(assertion) match at begin of buffer"
256 let s:doc['\%$'] = "(assertion) match at end of buffer"
257 let s:doc['\%V'] = "(assertion) match within the Visual area"
258 let s:doc['\%#'] = "(assertion) match with cursor position"
260 func! s:DocRegexEngine(bull, hulit, item) "{{{
261 let engine = a:bull.Bite('^[012]')
263 call a:hulit.Print(a:item.engine, 'Force automatic selection of the regexp engine (since v7.3.970).')
265 call a:hulit.Print(a:item.engine, 'Force using the old engine (since v7.3.970).')
267 call a:hulit.Print(a:item.engine, 'Force using the NFA engine (since v7.3.970).')
269 call a:hulit.Print(a:item, '(invalid) \%#= can only be followed by 0, 1, or 2')
273 let s:doc['\%#='] = function("s:DocRegexEngine")
276 " \%23l \%<23l \%>23l
277 " \%23c \%<23c \%>23c
278 " \%23v \%<23v \%>23v
279 " backslash percent at/before/after
280 func! s:DocBspercAt(bull, hulit, item) "{{{
281 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
283 call a:hulit.Print(a:item.rest, "(assertion) match with position of mark ". rest[1])
285 let number = rest[:-2]
288 call a:hulit.Print(a:item.rest, "match in line ". number)
290 call a:hulit.Print(a:item.rest, "match in column ". number)
292 call a:hulit.Print(a:item.rest, "match in virtual column ". number)
294 call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%' item")
295 " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
299 func! s:DocBspercBefore(bull, hulit, item) "{{{
300 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
302 call a:hulit.Print(a:item.rest, "(assertion) match before position of mark ". rest[1])
304 let number = rest[:-2]
307 call a:hulit.Print(a:item.rest, printf("match above line %d (towards start of buffer)", number))
309 call a:hulit.Print(a:item.rest, "match before column ". number)
311 call a:hulit.Print(a:item.rest, "match before virtual column ". number)
313 call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%<' item")
314 " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
318 func! s:DocBspercAfter(bull, hulit, item) "{{{
319 let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
321 call a:hulit.Print(a:item.rest, "(assertion) match after position of mark ". rest[1])
323 let number = rest[:-2]
326 call a:hulit.Print(a:item.rest, printf("match below line %d (towards end of buffer)", number))
328 call a:hulit.Print(a:item.rest, "match after column ". number)
330 call a:hulit.Print(a:item.rest, "match after virtual column ". number)
332 call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%>' item")
333 " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
338 let s:doc['\%'] = function("s:DocBspercAt")
339 let s:doc['\%<'] = function("s:DocBspercBefore")
340 let s:doc['\%>'] = function("s:DocBspercAfter")
342 let s:doc['\i'] = "identifier character (see 'isident' option)"
343 let s:doc['\I'] = "like \"\\i\", but excluding digits"
344 let s:doc['\k'] = "keyword character (see 'iskeyword' option)"
345 let s:doc['\K'] = "like \"\\k\", but excluding digits"
346 let s:doc['\f'] = "file name character (see 'isfname' option)"
347 let s:doc['\F'] = "like \"\\f\", but excluding digits"
348 let s:doc['\p'] = "printable character (see 'isprint' option)"
349 let s:doc['\P'] = "like \"\\p\", but excluding digits"
350 let s:doc['\s'] = "whitespace character: <Space> and <Tab>"
351 let s:doc['\S'] = "non-whitespace character; opposite of \\s"
352 let s:doc['\d'] = "digit: [0-9]"
353 let s:doc['\D'] = "non-digit: [^0-9]"
354 let s:doc['\x'] = "hex digit: [0-9A-Fa-f]"
355 let s:doc['\X'] = "non-hex digit: [^0-9A-Fa-f]"
356 let s:doc['\o'] = "octal digit: [0-7]"
357 let s:doc['\O'] = "non-octal digit: [^0-7]"
358 let s:doc['\w'] = "word character: [0-9A-Za-z_]"
359 let s:doc['\W'] = "non-word character: [^0-9A-Za-z_]"
360 let s:doc['\h'] = "head of word character: [A-Za-z_]"
361 let s:doc['\H'] = "non-head of word character: [^A-Za-z_]"
362 let s:doc['\a'] = "alphabetic character: [A-Za-z]"
363 let s:doc['\A'] = "non-alphabetic character: [^A-Za-z]"
364 let s:doc['\l'] = "lowercase character: [a-z]"
365 let s:doc['\L'] = "non-lowercase character: [^a-z]"
366 let s:doc['\u'] = "uppercase character: [A-Z]"
367 let s:doc['\U'] = "non-uppercase character: [^A-Z]"
369 let s:doc['\e'] = "match <Esc>"
370 let s:doc['\t'] = "match <Tab>"
371 let s:doc['\r'] = "match <CR>"
372 let s:doc['\b'] = "match CTRL-H"
373 let s:doc['\n'] = [function("s:DocBeginOfPat"), "match a newline"]
374 let s:doc['~'] = "match the last given substitute string"
375 let s:doc['\1'] = "match first captured string"
376 let s:doc['\2'] = "match second captured string"
377 let s:doc['\3'] = "match third captured string"
378 let s:doc['\4'] = "match fourth captured string "
379 let s:doc['\5'] = "match fifth captured string"
380 let s:doc['\6'] = "match sixth captured string"
381 let s:doc['\7'] = "match seventh captured string"
382 let s:doc['\8'] = "match eighth captured string"
383 let s:doc['\9'] = "match ninth captured string"
385 let s:doc['\z1'] = 'match same string matched by first "external" group'
386 let s:doc['\z2'] = 'match same string matched by second "external" group'
387 let s:doc['\z3'] = 'match same string matched by third "external" group'
388 let s:doc['\z4'] = 'match same string matched by fourth "external" group '
389 let s:doc['\z5'] = 'match same string matched by fifth "external" group'
390 let s:doc['\z6'] = 'match same string matched by sixth "external" group'
391 let s:doc['\z7'] = 'match same string matched by seventh "external" group'
392 let s:doc['\z8'] = 'match same string matched by eighth "external" group'
393 let s:doc['\z9'] = 'match same string matched by ninth "external" group'
396 " skip the rest of a collection
397 let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|\[=.=]\|\[\..\.]\|[^\]]\)\@>\)*]'
399 func! s:DocCollection(bull, hulit, item) "{{{
400 let collstr = a:bull.Bite(s:coll_skip_pat)
401 if collstr == "" || collstr == "]"
402 call a:hulit.AddLiteral('['. collstr)
404 let inverse = collstr =~ '^\^'
405 let with_nl = a:item == '\_['
406 let descr = inverse ? printf('collection not matching [%s', collstr[1:]) : 'collection'
407 let descr_nl = printf("%s%s", (inverse && with_nl ? ', but' : ''), (with_nl ? ' with end-of-line added' : ''))
408 call a:hulit.Print(a:item. collstr, descr. descr_nl)
412 let s:doc['['] = function("s:DocCollection")
413 let s:doc['\_['] = function("s:DocCollection")
415 func! s:DocOptAtoms(bull, hulit, item) "{{{
417 call a:hulit.Print(a:item, "start a sequence of optionally matched atoms")
418 let s:in_opt_atoms = 1
419 call a:hulit.AddIndent('. ')
422 call a:hulit.RemIndent()
423 call a:hulit.Print(a:item, "end of optionally matched atoms")
424 let s:in_opt_atoms = 0
426 call a:hulit.AddLiteral(a:item)
431 " let s:doc['\%['] = "start a sequence of optionally matched atoms"
432 let s:doc['\%['] = function("s:DocOptAtoms")
433 let s:doc[']'] = function("s:DocOptAtoms")
435 func! s:DocAnywhere(bull, hulit, item, msg) "{{{
436 call a:hulit.Print(a:item, a:msg)
438 let s:at_begin_of_pat += 1
441 let s:doc['\c'] = [function("s:DocAnywhere"), "ignore case while matching the pattern"]
442 let s:doc['\C'] = [function("s:DocAnywhere"), "match case while matching the pattern"]
443 let s:doc['\Z'] = [function("s:DocAnywhere"), "ignore composing characters in the pattern"]
451 func! s:DocBspercDecimal(bull, hulit, item) "{{{
452 let number = a:bull.Bite('^\d\{,3}')
453 let char = strtrans(nr2char(str2nr(number)))
454 call a:hulit.Print(a:item. number, printf("match character specified by decimal number %s (%s)", number, char))
456 func! s:DocBspercHexTwo(bull, hulit, item) "{{{
457 let number = a:bull.Bite('^\x\{,2}')
458 let char = strtrans(nr2char(str2nr(number,16)))
459 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s (%s)", number, char))
461 func! s:DocBspercOctal(bull, hulit, item) "{{{
462 let number = a:bull.Bite('^\o\{,4}')
463 let char = strtrans(nr2char(str2nr(number,8)))
464 call a:hulit.Print(a:item. number, printf("match character specified with octal number 0%s (%s)", substitute(number, '^0*', '', ''), char))
466 func! s:DocBspercHexFour(bull, hulit, item) "{{{
467 let number = a:bull.Bite('^\x\{,4}')
468 let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : ''
469 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char))
471 func! s:DocBspercHexEight(bull, hulit, item) "{{{
472 let number = a:bull.Bite('^\x\{,8}')
473 let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : ''
474 call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char))
477 let s:doc['\%d'] = function("s:DocBspercDecimal") " 123
478 let s:doc['\%x'] = function("s:DocBspercHexTwo") " 2a
479 let s:doc['\%o'] = function("s:DocBspercOctal") " 0377
480 let s:doc['\%u'] = function("s:DocBspercHexFour") " 20AC
481 let s:doc['\%U'] = function("s:DocBspercHexEight") " 1234abcd
490 func! s:SillyCheck(digits) "{{{
491 return strlen(a:digits) < 10 ? a:digits : '{silly large number}'
495 func! explainpat#NewHelpPrinter() "{{{
497 let obj.literals = ''
499 let obj.len = 0 " can be negative (!)
501 func! obj.Print(str, ...) "{{{
502 call self.FlushLiterals()
503 let indstr = join(self.indents, '')
510 " echo indstr. printf("`%s' %s", a:str, a:1)
512 echon printf("%-10s", a:str)
515 echon printf(" %s", a:1)
520 func! obj.AddLiteral(item) "{{{
521 let self.literals .= a:item
524 func! obj.FlushLiterals() "{{{
525 if self.literals == ''
528 let indstr = join(self.indents, '')
532 if self.literals =~ '^\s\|\s$'
533 echon printf("%-10s", '"'. self.literals. '"')
535 echon printf("%-10s", self.literals)
538 echon " literal string"
539 if exists("*strchars")
540 if self.literals =~ '\\'
541 let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
543 let spconly = self.literals =~ '[^ ]' ? '' : ', spaces only'
544 let nlit = strchars(self.literals)
545 echon " (". nlit. (nlit==1 ? " atom" : " atoms"). spconly.")"
548 let self.literals = ''
551 func! obj.AddIndent(...) "{{{
552 call self.FlushLiterals()
554 call extend(self.indents, copy(a:000))
555 elseif self.len + a:0 >= 1
556 call extend(self.indents, a:000[-(self.len+a:0):])
561 func! obj.RemIndent(...) "{{{
562 call self.FlushLiterals()
565 call remove(self.indents, -1)
570 call remove(self.indents, -a:1, -1)
572 call remove(self.indents, 0, -1)
581 func! s:NewTokenBiter(str) "{{{
582 " {str} string to eat pieces from
583 let obj = {'str': a:str}
585 " consume piece from start of input matching {pat}
586 func! obj.Bite(pat) "{{{
587 " {pat} should start with '^'
588 let bite = matchstr(self.str, a:pat)
589 let self.str = strpart(self.str, strlen(bite))
593 " get the unparsed rest of input (not consuming)
594 func! obj.Rest() "{{{
598 " check if end of input reached
599 func! obj.AtEnd() "{{{
600 return self.str == ""
607 let &cpo = s:cpo_save
609 " vim:ts=8:fdm=marker: