]> 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:

181099c1446de6c3b55082199561fe5d096aebcd
[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.1
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]\)')
181     if rest[0] == "'"
182         call a:hulit.Print("(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("match in line ". number)
188         elseif type == "c"
189             call a:hulit.Print("match in column ". number)
190         elseif type == "v"
191             call a:hulit.Print("match in virtual column ". number)
192         endif
193     endif
194 endfunc "}}}
195 func! s:DocBspercBefore(bull, hulit, item) "{{{
196     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)')
197     if rest[0] == "'"
198         call a:hulit.Print("(assertion) match before position of mark ". rest[1])
199     else
200         let number = rest[:-2]
201         let type = rest[-1:]
202         if type == "l"
203             call a:hulit.Print("match above line ". number)
204         elseif type == "c"
205             call a:hulit.Print("match before column ". number)
206         elseif type == "v"
207             call a:hulit.Print("match before virtual column ". number)
208         endif
209     endif
210 endfunc "}}}
211 func! s:DocBspercAfter(bull, hulit, item) "{{{
212     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)')
213     if rest[0] == "'"
214         call a:hulit.Print("(assertion) match after position of mark ". rest[1])
215     else
216         let number = rest[:-2]
217         let type = rest[-1:]
218         if type == "l"
219             call a:hulit.Print("match below line ". number)
220         elseif type == "c"
221             call a:hulit.Print("match after column ". number)
222         elseif type == "v"
223             call a:hulit.Print("match after virtual column ". number)
224         endif
225     endif
226 endfunc "}}}
227
228 let s:doc['\%'] = function("s:DocBspercAt")
229 let s:doc['\%<'] = function("s:DocBspercBefore")
230 let s:doc['\%>'] = function("s:DocBspercAfter")
231
232 let s:doc['\i'] = "identifier character (see 'isident' option)"
233 let s:doc['\I'] = "like \"\\i\", but excluding digits"
234 let s:doc['\k'] = "keyword character (see 'iskeyword' option)"
235 let s:doc['\K'] = "like \"\\k\", but excluding digits"
236 let s:doc['\f'] = "file name character (see 'isfname' option)"
237 let s:doc['\F'] = "like \"\\f\", but excluding digits"
238 let s:doc['\p'] = "printable character (see 'isprint' option)"
239 let s:doc['\P'] = "like \"\\p\", but excluding digits"
240 let s:doc['\s'] = "whitespace character: <Space> and <Tab>"
241 let s:doc['\S'] = "non-whitespace character; opposite of \\s"
242 let s:doc['\d'] = "digit: [0-9]"
243 let s:doc['\D'] = "non-digit: [^0-9]"
244 let s:doc['\x'] = "hex digit: [0-9A-Fa-f]"
245 let s:doc['\X'] = "non-hex digit: [^0-9A-Fa-f]"
246 let s:doc['\o'] = "octal digit: [0-7]"
247 let s:doc['\O'] = "non-octal digit: [^0-7]"
248 let s:doc['\w'] = "word character: [0-9A-Za-z_]"
249 let s:doc['\W'] = "non-word character: [^0-9A-Za-z_]"
250 let s:doc['\h'] = "head of word character: [A-Za-z_]"
251 let s:doc['\H'] = "non-head of word character: [^A-Za-z_]"
252 let s:doc['\a'] = "alphabetic character: [A-Za-z]"
253 let s:doc['\A'] = "non-alphabetic character: [^A-Za-z]"
254 let s:doc['\l'] = "lowercase character: [a-z]"
255 let s:doc['\L'] = "non-lowercase character: [^a-z]"
256 let s:doc['\u'] = "uppercase character: [A-Z]"
257 let s:doc['\U'] = "non-uppercase character: [^A-Z]"
258
259 let s:doc['\e'] = "match <Esc>"
260 let s:doc['\t'] = "match <Tab>"
261 let s:doc['\r'] = "match <CR>"
262 let s:doc['\b'] = "match CTRL-H"
263 let s:doc['\n'] = "match a newline"
264 let s:doc['~'] = "match the last given substitute string"
265 let s:doc['\1'] = "match first captured string"
266 let s:doc['\2'] = "match second captured string"
267 let s:doc['\3'] = "match third captured string"
268 let s:doc['\4'] = "match fourth captured string "
269 let s:doc['\5'] = "match fifth captured string"
270 let s:doc['\6'] = "match sixth captured string"
271 let s:doc['\7'] = "match seventh captured string"
272 let s:doc['\8'] = "match eighth captured string"
273 let s:doc['\9'] = "match ninth captured string"
274
275 " \z1
276 " \z2
277 " \z9
278
279 " from MakeMagic()
280 " skip the rest of a collection
281 let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]'
282
283 func! s:DocCollection(bull, hulit, item) "{{{
284     let collstr = a:bull.Bite(s:coll_skip_pat)
285     call a:hulit.Print(a:item. collstr, 'collection'. (a:item=='\_[' ? ' with end-of-line added' : ''))
286 endfunc "}}}
287
288 let s:doc['['] = function("s:DocCollection")
289 let s:doc['\_['] = function("s:DocCollection")
290
291 func! s:DocOptAtoms(bull, hulit, item) "{{{
292     if a:item == '\%['
293         call a:hulit.Print(a:item, "start a sequence of optionally matched atoms")
294         let s:in_opt_atoms = 1
295         call a:hulit.AddIndent('. ')
296     else " a:item == ']'
297         if s:in_opt_atoms
298             call a:hulit.RemIndent()
299             call a:hulit.Print(a:item, "end of optionally matched atoms")
300             let s:in_opt_atoms = 0
301         else
302             call a:hulit.AddLiteral(a:item)
303         endif
304     endif
305 endfunc "}}}
306
307 " let s:doc['\%['] = "start a sequence of optionally matched atoms"
308 let s:doc['\%['] = function("s:DocOptAtoms")
309 let s:doc[']'] = function("s:DocOptAtoms")
310
311 let s:doc['\c'] = "ignore case while matching the pattern"
312 let s:doc['\C'] = "match case while matching the pattern"
313 let s:doc['\Z'] = "ignore composing characters in the pattern"
314
315 " \%d 123
316 " \%x 2a
317 " \%o 0377
318 " \%u 20AC
319 " \%U 1234abcd
320
321 func! s:DocBspercDecimal(bull, hulit, item) "{{{
322     let number = a:bull.Bite('^\d\{,3}')
323     call a:hulit.Print(a:item. number, "match character specified by decimal number ". number)
324 endfunc "}}}
325 func! s:DocBspercHexTwo(bull, hulit, item) "{{{
326     let number = a:bull.Bite('^\x\{,2}')
327     call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
328 endfunc "}}}
329 func! s:DocBspercOctal(bull, hulit, item) "{{{
330     let number = a:bull.Bite('^\o\{,4}')
331     call a:hulit.Print(a:item. number, "match character specified with octal number 0". substitute(number, '^0*', '', ''))
332 endfunc "}}}
333 func! s:DocBspercHexFour(bull, hulit, item) "{{{
334     let number = a:bull.Bite('^\x\{,4}')
335     call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
336 endfunc "}}}
337 func! s:DocBspercHexEight(bull, hulit, item) "{{{
338     let number = a:bull.Bite('^\x\{,8}')
339     call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number)
340 endfunc "}}}
341
342 let s:doc['\%d'] = function("s:DocBspercDecimal") " 123
343 let s:doc['\%x'] = function("s:DocBspercHexTwo") " 2a
344 let s:doc['\%o'] = function("s:DocBspercOctal") " 0377
345 let s:doc['\%u'] = function("s:DocBspercHexFour") " 20AC
346 let s:doc['\%U'] = function("s:DocBspercHexEight") " 1234abcd
347
348 " \m
349 " \M
350 " \v
351 " \V
352 "}}}
353
354 func! s:NewHelpPrinter() "{{{
355     let obj = {}
356     let obj.literals = ''
357     let obj.indents = []
358     let obj.len = 0         " can be negative (!)
359
360     func! obj.Print(str, ...) "{{{
361         call self.FlushLiterals()
362         let indstr = join(self.indents, '')
363         echohl Comment
364         echo indstr
365         echohl None
366         if a:0 == 0
367             echon a:str
368         else
369             " echo indstr. printf("`%s'   %s", a:str, a:1)
370             echohl PreProc
371             echon printf("%-10s", a:str)
372             echohl None
373             echohl Comment
374             echon printf(" %s", a:1)
375             echohl None
376         endif
377     endfunc "}}}
378
379     func! obj.AddLiteral(item) "{{{
380         let self.literals .= a:item
381     endfunc "}}}
382
383     func! obj.FlushLiterals() "{{{
384         if self.literals == ''
385             return
386         endif
387         let indstr = join(self.indents, '')
388         echohl Comment
389         echo indstr
390         echohl None
391         if self.literals =~ '^\s\|\s$'
392             echon printf("%-10s", '"'. self.literals. '"')
393         else
394             echon printf("%-10s", self.literals)
395         endif
396         echohl Comment
397         echon " literal string"
398         if exists("*strchars")
399             if self.literals =~ '\\'
400                 let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
401             endif
402             let so = self.literals =~ '[^ ]' ? '' : ', spaces only'
403             echon " (". strchars(self.literals). " atom(s)". so.")"
404         endif
405         echohl None
406         let self.literals = ''
407     endfunc  "}}}
408
409     func! obj.AddIndent(...) "{{{
410         call self.FlushLiterals()
411         if self.len >= 0
412             call extend(self.indents, copy(a:000))
413         elseif self.len + a:0 >= 1
414             call extend(self.indents, a:000[-(self.len+a:0):])
415         endif
416         let self.len += a:0
417     endfunc "}}}
418
419     func! obj.RemIndent(...) "{{{
420         call self.FlushLiterals()
421         if a:0 == 0
422             if self.len >= 1
423                 call remove(self.indents, -1)
424             endif
425             let self.len -= 1
426         else
427             if self.len > a:1
428                 call remove(self.indents, -a:1, -1)
429             elseif self.len >= 1
430                 call remove(self.indents, 0, -1)
431             endif
432             let self.len -= a:1
433         endif
434     endfunc "}}}
435
436     return obj
437 endfunc "}}}
438
439 func! s:NewTokenBiter(str, ...) "{{{
440     " {str}     string to eat pieces from
441     " {a:1}     pattern to skip separators
442     let whitepat = a:0>=1 ? a:1 : '^\s*'
443     let obj = {'str': a:str, 'whitepat': whitepat}
444
445     " consume piece from start of input matching {pat}
446     func! obj.Bite(pat) "{{{
447         " {pat}     should start with '^'
448         let skip = matchend(self.str, self.whitepat)
449         let bite = matchstr(self.str, a:pat, skip)
450         let self.str = strpart(self.str, matchend(self.str, self.whitepat, skip + strlen(bite)))
451         return bite
452     endfunc "}}}
453
454     " get the unparsed rest of input
455     func! obj.Rest() "{{{
456         return self.str
457     endfunc "}}}
458
459     " check if end of input reached
460     func! obj.AtEnd() "{{{
461         return self.str == ""
462     endfunc "}}}
463
464     return obj
465 endfunc "}}}
466
467 " Modeline: {{{1
468 let &cpo = s:cpo_save
469 unlet s:cpo_save
470 " vim:ts=8:fdm=marker: