]> git.madduck.net Git - etc/vim.git/blob - autoload/explainpat.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

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.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Version 0.2: include suggestions from vim_use
[etc/vim.git] / autoload / explainpat.vim
1 " File:         explainpat.vim
2 " Created:      2011 Nov 02
3 " Last Change:  2012 Dec 19
4 " Rev Days:     3
5 " Author:       Andy Wokula <anwoku@yahoo.de>
6 " License:      Vim License, see :h license
7 " Version:      0.2
8
9 " Implements :ExplainPattern [pattern]
10
11 " TODO {{{
12 " - more testing, completeness check
13 " - detailed collections
14 " - \z
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
20 " + \%[...]
21 " + \{, \{-
22 "}}}
23
24 " Init Folklore {{{
25 let s:cpo_save = &cpo
26 set cpo&vim
27 let g:explainpat#loaded = 1
28 "}}}
29
30 func! explainpat#ExplainPattern(cmd_arg) "{{{
31     if a:cmd_arg == ""
32         " let pattern_str = nwo#vis#Get()
33         echo "(usage) :ExplainPattern [{register} | {pattern}]"
34         return
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)
38     else
39         let pattern_str = a:cmd_arg
40     endif
41
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)
46     endif
47
48     " hmm, we need state for \%[ ... ]
49     let s:in_opt_atoms = 0
50     let s:capture_group_nr = 0
51
52     let hulit = s:NewHelpPrinter()
53     call hulit.AddIndent('  ')
54     let bull = s:NewTokenBiter(magicpat, '')
55     while !bull.AtEnd()
56         let item = bull.Bite(s:magic_item_pattern)
57         if item != ''
58             let Doc = get(s:doc, item, '')
59             if empty(Doc)
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])
65             endif
66         else
67             echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest())
68             break
69         endif
70         unlet Doc
71     endwhile
72     call hulit.FlushLiterals()
73 endfunc "}}}
74
75 " s: types {{{
76 let s:STRING = type("")
77 let s:DICT   = type({})
78 let s:FUNCREF = type(function("tr"))
79 " }}}
80
81 let s:magic_item_pattern = '\C^\%(\\\%(@<.\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|{\|@[>=!]\|_[[^$.]\=\|.\)\|.\)'
82
83 let s:doc = {} " {{{
84 " this is all the help data ...
85 "   strings, funcrefs and intermixed s:DocFoo() functions
86 " strongly depends on s:magic_item_pattern
87
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('  ')
92 endfunc "}}}
93
94 let s:doc['\|'] = function("s:DocOrBranch")
95 let s:doc['\&'] = "AND branch"
96
97 let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth')
98
99 func! s:DocGroupStart(bull, hulit, item) "{{{
100     if a: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)')))
105     endif
106     call a:hulit.AddIndent('| ', '  ')
107 endfunc "}}}
108 func! s:DocGroupEnd(bull, hulit, item) "{{{
109     call a:hulit.RemIndent(2)
110     call a:hulit.Print(a:item, "end of group")
111 endfunc "}}}
112
113 let s:doc['\('] = function("s:DocGroupStart")
114 let s:doc['\%('] = function("s:DocGroupStart")
115 let s:doc['\)'] =  function("s:DocGroupEnd")
116
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"
124
125 func! s:DocBraceMulti(bull, hulit, item) "{{{
126     let rest = a:bull.Bite('^-\=\d*\%(,\d*\)\=}')
127     if rest != ""
128         if rest == '-}'
129             call a:hulit.Print(a:item. rest, "non-greedy version of `*'")
130         elseif rest =~ '^-'
131             call a:hulit.Print(a:item. rest, "(multi) N to M, non-greedy")
132         else
133             call a:hulit.Print(a:item. rest, "(multi) N to M, greedy")
134         endif
135     else
136         echoerr printf('ExplainPattern: cannot parse %s', a:item. a:bull.Rest())
137     endif
138 endfunc "}}}
139
140 let s:doc['\{'] = function("s:DocBraceMulti")
141
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"
153
154 func! s:DocUnderscore(bull, hulit, item) "{{{
155     let cclass = a:bull.Bite('^\a')
156     if cclass != ''
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))
159     else
160         echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.'))
161     endif
162 endfunc "}}}
163
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"
173
174 " \%'m   \%<'m   \%>'m
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')
181     if rest[0] == "'"
182         call a:hulit.Print(a:item.rest, "(assertion) match with position of mark ". rest[1])
183     else
184         let number = rest[:-2]
185         let type = rest[-1:]
186         if type ==# "l"
187             call a:hulit.Print(a:item.rest, "match in line ". number)
188         elseif type ==# "c"
189             call a:hulit.Print(a:item.rest, "match in column ". number)
190         elseif type ==# "v"
191             call a:hulit.Print(a:item.rest, "match in virtual column ". number)
192         else
193             echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
194         endif
195     endif
196 endfunc "}}}
197 func! s:DocBspercBefore(bull, hulit, item) "{{{
198     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
199     if rest[0] == "'"
200         call a:hulit.Print(a:item.rest, "(assertion) match before position of mark ". rest[1])
201     else
202         let number = rest[:-2]
203         let type = rest[-1:]
204         if type ==# "l"
205             call a:hulit.Print(a:item.rest, printf("match above line %d (towards start of buffer)", number))
206         elseif type ==# "c"
207             call a:hulit.Print(a:item.rest, "match before column ". number)
208         elseif type ==# "v"
209             call a:hulit.Print(a:item.rest, "match before virtual column ". number)
210         else
211             echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
212         endif
213     endif
214 endfunc "}}}
215 func! s:DocBspercAfter(bull, hulit, item) "{{{
216     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
217     if rest[0] == "'"
218         call a:hulit.Print(a:item.rest, "(assertion) match after position of mark ". rest[1])
219     else
220         let number = rest[:-2]
221         let type = rest[-1:]
222         if type ==# "l"
223             call a:hulit.Print(a:item.rest, printf("match below line %d (towards end of buffer)", number))
224         elseif type ==# "c"
225             call a:hulit.Print(a:item.rest, "match after column ". number)
226         elseif type ==# "v"
227             call a:hulit.Print(a:item.rest, "match after virtual column ". number)
228         else
229             echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
230         endif
231     endif
232 endfunc "}}}
233
234 let s:doc['\%'] = function("s:DocBspercAt")
235 let s:doc['\%<'] = function("s:DocBspercBefore")
236 let s:doc['\%>'] = function("s:DocBspercAfter")
237
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]"
264
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"
280
281 " \z1
282 " \z2
283 " \z9
284
285 " from MakeMagic()
286 " skip the rest of a collection
287 let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]'
288
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)
296 endfunc "}}}
297
298 let s:doc['['] = function("s:DocCollection")
299 let s:doc['\_['] = function("s:DocCollection")
300
301 func! s:DocOptAtoms(bull, hulit, item) "{{{
302     if a: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('. ')
306     else " a:item == ']'
307         if s:in_opt_atoms
308             call a:hulit.RemIndent()
309             call a:hulit.Print(a:item, "end of optionally matched atoms")
310             let s:in_opt_atoms = 0
311         else
312             call a:hulit.AddLiteral(a:item)
313         endif
314     endif
315 endfunc "}}}
316
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")
320
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"
324
325 " \%d 123
326 " \%x 2a
327 " \%o 0377
328 " \%u 20AC
329 " \%U 1234abcd
330
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))
335 endfunc "}}}
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))
340 endfunc "}}}
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))
345 endfunc "}}}
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))
350 endfunc "}}}
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))
355 endfunc "}}}
356
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
362
363 " \m
364 " \M
365 " \v
366 " \V
367 "}}}
368
369 func! s:NewHelpPrinter() "{{{
370     let obj = {}
371     let obj.literals = ''
372     let obj.indents = []
373     let obj.len = 0         " can be negative (!)
374
375     func! obj.Print(str, ...) "{{{
376         call self.FlushLiterals()
377         let indstr = join(self.indents, '')
378         echohl Comment
379         echo indstr
380         echohl None
381         if a:0 == 0
382             echon a:str
383         else
384             " echo indstr. printf("`%s'   %s", a:str, a:1)
385             echohl PreProc
386             echon printf("%-10s", a:str)
387             echohl None
388             echohl Comment
389             echon printf(" %s", a:1)
390             echohl None
391         endif
392     endfunc "}}}
393
394     func! obj.AddLiteral(item) "{{{
395         let self.literals .= a:item
396     endfunc "}}}
397
398     func! obj.FlushLiterals() "{{{
399         if self.literals == ''
400             return
401         endif
402         let indstr = join(self.indents, '')
403         echohl Comment
404         echo indstr
405         echohl None
406         if self.literals =~ '^\s\|\s$'
407             echon printf("%-10s", '"'. self.literals. '"')
408         else
409             echon printf("%-10s", self.literals)
410         endif
411         echohl Comment
412         echon " literal string"
413         if exists("*strchars")
414             if self.literals =~ '\\'
415                 let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
416             endif
417             let so = self.literals =~ '[^ ]' ? '' : ', spaces only'
418             echon " (". strchars(self.literals). " atom(s)". so.")"
419         endif
420         echohl None
421         let self.literals = ''
422     endfunc  "}}}
423
424     func! obj.AddIndent(...) "{{{
425         call self.FlushLiterals()
426         if self.len >= 0
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):])
430         endif
431         let self.len += a:0
432     endfunc "}}}
433
434     func! obj.RemIndent(...) "{{{
435         call self.FlushLiterals()
436         if a:0 == 0
437             if self.len >= 1
438                 call remove(self.indents, -1)
439             endif
440             let self.len -= 1
441         else
442             if self.len > a:1
443                 call remove(self.indents, -a:1, -1)
444             elseif self.len >= 1
445                 call remove(self.indents, 0, -1)
446             endif
447             let self.len -= a:1
448         endif
449     endfunc "}}}
450
451     return obj
452 endfunc "}}}
453
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}
459
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)))
466         return bite
467     endfunc "}}}
468
469     " get the unparsed rest of input
470     func! obj.Rest() "{{{
471         return self.str
472     endfunc "}}}
473
474     " check if end of input reached
475     func! obj.AtEnd() "{{{
476         return self.str == ""
477     endfunc "}}}
478
479     return obj
480 endfunc "}}}
481
482 " Modeline: {{{1
483 let &cpo = s:cpo_save
484 unlet s:cpo_save
485 " vim:ts=8:fdm=marker: