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

v0.9
[etc/vim.git] / autoload / explainpat.vim
1 " File:         explainpat.vim
2 " Created:      2011 Nov 02
3 " Last Change:  2017 Dec 15
4 " Version:      0.9
5 " Author:       Andy Wokula <anwoku@yahoo.de>
6 " License:      Vim License, see :h license
7
8 " Implements :ExplainPattern [pattern]
9
10 " History: "{{{
11 " 2013 Jun 21   AND/OR text is confusing, removed
12 " 2013 Apr 20   ...
13 "}}}
14
15 " TODO {{{
16 " - add something like "(empty) ... match everywhere" ... example: '\v(&&|str)'
17 "   Pattern: \v(&&|str)
18 "   Magic Pattern: \(\&\&\|str\)
19 "     \(         start of first capturing group
20 "     |                  (empty) match everywhere
21 "     |   \&         AND
22 "     |                  (empty) match everywhere
23 "     |   \&         AND
24 "     |                  (empty) match everywhere
25 "     | \|         OR
26 "     |   str        literal string (3 atom(s))
27 "     \)         end of group
28 " - more testing, completeness check
29 " ? detailed collections
30 " ? literal string: also print the unescaped magic items
31 " ? literal string: show leading/trailing spaces
32 "
33 "}}}
34
35 " Init Folklore {{{
36 let s:cpo_save = &cpo
37 set cpo&vim
38 let g:explainpat#loaded = 1
39 "}}}
40
41 func! explainpat#ExplainPattern(cmd_arg, ...) "{{{
42     " {a:1}     alternative help printer object (caution, no sanity check)
43     "           (for test running)
44     if a:cmd_arg == ""
45         " let pattern_str = nwo#vis#Get()
46         echo "(usage) :ExplainPattern [{register} | {pattern}]"
47         return
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)
51     else
52         let pattern_str = a:cmd_arg
53     endif
54
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)
59     endif
60
61     " we need state:
62     " set flag when in `\%[ ... ]' (optionally matched atoms):
63     let s:in_opt_atoms = 0
64     " counter for `\(':
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
68
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)
72     while !bull.AtEnd()
73         let item = bull.Bite(s:magic_item_pattern)
74         if item != ''
75             let Doc = get(s:doc, item, '')
76             if empty(Doc)
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]])
84             endif
85             let s:at_begin_of_pat -= 1
86         else
87             echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest())
88             break
89         endif
90         unlet Doc
91     endwhile
92     call hulit.FlushLiterals()
93 endfunc "}}}
94
95 " s: types {{{
96 let s:STRING = type("")
97 let s:DICT   = type({})
98 let s:FUNCREF = type(function("tr"))
99 let s:LIST = type([])
100 " }}}
101
102 let s:magic_item_pattern = '\C^\%(\\\%(%#=\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|@[>=!]\=\|_[[^$.]\=\|.\)\|.\)'
103
104 let s:doc = {} " {{{
105 " this is all the help data ...
106 "   strings, funcrefs and intermixed s:DocFoo() functions
107 " strongly depends on s:magic_item_pattern
108
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
114 endfunc "}}}
115
116 let s:doc['\|'] = function("s:DocOrBranch")
117
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
121 endfunc "}}}
122
123 let s:doc['\&'] = [function("s:DocBeginOfPat"), "AND"]
124
125 let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth')
126
127 func! s:DocGroupStart(bull, hulit, item) "{{{
128     if a: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)')
135     endif
136     call a:hulit.AddIndent('| ', '  ')
137     let s:at_begin_of_pat = 2
138 endfunc "}}}
139 func! s:DocGroupEnd(bull, hulit, item) "{{{
140     call a:hulit.RemIndent(2)
141     call a:hulit.Print(a:item, "end of group")
142 endfunc "}}}
143
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")
149
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)
154     else
155         call a:hulit.Print(a:item, "(multi) zero or more of the preceding atom")
156     endif
157 endfunc "}}}
158
159 " let s:doc['*'] = "(multi) zero or more of the preceding atom"
160 let s:doc['*'] = function("s:DocStar")
161
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"
167
168 func! s:DocBraceMulti(bull, hulit, item) "{{{
169     let rest = a:bull.Bite('^-\=\d*\%(,\d*\)\=\\\=}')
170     if rest != ""
171         if rest == '-}'
172             call a:hulit.Print(a:item. rest, "non-greedy version of `*'")
173         elseif rest =~ '^-'
174             call a:hulit.Print(a:item. rest, "(multi) N to M, non-greedy")
175         else
176             call a:hulit.Print(a:item. rest, "(multi) N to M, greedy")
177         endif
178     else
179         call a:hulit.Print(a:item, "(invalid) incomplete `\\{...}' item")
180     endif
181 endfunc "}}}
182
183 let s:doc['\{'] = function("s:DocBraceMulti")
184
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"
188
189 func! s:DocBefore(bull, hulit, item) "{{{
190     let rest = a:bull.Bite('^\d*\%[<[=!]]')
191     if rest == "<="
192         call a:hulit.Print(a:item.rest, "(assertion) require match for preceding atom to the left")
193     elseif rest == "<!"
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\+'))))
199     else
200         call a:hulit.Print(a:item.rest, "(invalid) incomplete item")
201     endif
202 endfunc "}}}
203
204 let s:doc['\@'] = function("s:DocBefore")
205
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("*")
212         endif
213     else
214         " call a:hulit.Print(a:item, "(not at begin of pattern) literal `^'")
215         call a:hulit.AddLiteral(a:item)
216     endif
217 endfunc "}}}
218
219 " let s:doc['^'] = "(assertion) require match at start of line"
220 let s:doc['^'] = function("s:DocCircumFlex")
221
222 let s:doc['\_^'] = "(assertion) like `^', allowed anywhere in the pattern"
223
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")
227     else
228         call a:hulit.AddLiteral(a:item)
229     endif
230 endfunc "}}}
231
232 " let s:doc['$'] = "(assertion) require match at end of line"
233 let s:doc['$'] = function("s:DocDollar")
234
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"
238
239 func! s:DocUnderscore(bull, hulit, item) "{{{
240     let cclass = a:bull.Bite('^\a')
241     if cclass != ''
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))
244     else
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(), '.'))
247     endif
248 endfunc "}}}
249
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"
259
260 func! s:DocRegexEngine(bull, hulit, item) "{{{
261     let engine = a:bull.Bite('^[012]')
262     if engine == "0"
263         call a:hulit.Print(a:item.engine, 'Force automatic selection of the regexp engine (since v7.3.970).')
264     elseif engine == "1" 
265         call a:hulit.Print(a:item.engine, 'Force using the old engine (since v7.3.970).')
266     elseif engine == "2"
267         call a:hulit.Print(a:item.engine, 'Force using the NFA engine (since v7.3.970).')
268     else
269         call a:hulit.Print(a:item, '(invalid) \%#= can only be followed by 0, 1, or 2')
270     endif
271 endfunc "}}}
272
273 let s:doc['\%#='] = function("s:DocRegexEngine")
274
275 " \%'m   \%<'m   \%>'m
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')
282     if rest[0] == "'"
283         call a:hulit.Print(a:item.rest, "(assertion) match with position of mark ". rest[1])
284     else
285         let number = rest[:-2]
286         let type = rest[-1:]
287         if type ==# "l"
288             call a:hulit.Print(a:item.rest, "match in line ". number)
289         elseif type ==# "c"
290             call a:hulit.Print(a:item.rest, "match in column ". number)
291         elseif type ==# "v"
292             call a:hulit.Print(a:item.rest, "match in virtual column ". number)
293         else
294             call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%' item")
295             " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
296         endif
297     endif
298 endfunc "}}}
299 func! s:DocBspercBefore(bull, hulit, item) "{{{
300     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
301     if rest[0] == "'"
302         call a:hulit.Print(a:item.rest, "(assertion) match before position of mark ". rest[1])
303     else
304         let number = rest[:-2]
305         let type = rest[-1:]
306         if type ==# "l"
307             call a:hulit.Print(a:item.rest, printf("match above line %d (towards start of buffer)", number))
308         elseif type ==# "c"
309             call a:hulit.Print(a:item.rest, "match before column ". number)
310         elseif type ==# "v"
311             call a:hulit.Print(a:item.rest, "match before virtual column ". number)
312         else
313             call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%<' item")
314             " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
315         endif
316     endif
317 endfunc "}}}
318 func! s:DocBspercAfter(bull, hulit, item) "{{{
319     let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C')
320     if rest[0] == "'"
321         call a:hulit.Print(a:item.rest, "(assertion) match after position of mark ". rest[1])
322     else
323         let number = rest[:-2]
324         let type = rest[-1:]
325         if type ==# "l"
326             call a:hulit.Print(a:item.rest, printf("match below line %d (towards end of buffer)", number))
327         elseif type ==# "c"
328             call a:hulit.Print(a:item.rest, "match after column ". number)
329         elseif type ==# "v"
330             call a:hulit.Print(a:item.rest, "match after virtual column ". number)
331         else
332             call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%>' item")
333             " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest)
334         endif
335     endif
336 endfunc "}}}
337
338 let s:doc['\%'] = function("s:DocBspercAt")
339 let s:doc['\%<'] = function("s:DocBspercBefore")
340 let s:doc['\%>'] = function("s:DocBspercAfter")
341
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]"
368
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"
384
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'
394
395 " from MakeMagic()
396 " skip the rest of a collection
397 let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|\[=.=]\|\[\..\.]\|[^\]]\)\@>\)*]'
398
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)
403     else
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)
409     endif
410 endfunc "}}}
411
412 let s:doc['['] = function("s:DocCollection")
413 let s:doc['\_['] = function("s:DocCollection")
414
415 func! s:DocOptAtoms(bull, hulit, item) "{{{
416     if a: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('. ')
420     else " a:item == ']'
421         if s:in_opt_atoms
422             call a:hulit.RemIndent()
423             call a:hulit.Print(a:item, "end of optionally matched atoms")
424             let s:in_opt_atoms = 0
425         else
426             call a:hulit.AddLiteral(a:item)
427         endif
428     endif
429 endfunc "}}}
430
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")
434
435 func! s:DocAnywhere(bull, hulit, item, msg) "{{{
436     call a:hulit.Print(a:item, a:msg)
437     " keep state:
438     let s:at_begin_of_pat += 1
439 endfunc "}}}
440
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"]
444
445 " \%d 123
446 " \%x 2a
447 " \%o 0377
448 " \%u 20AC
449 " \%U 1234abcd
450
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))
455 endfunc "}}}
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))
460 endfunc "}}}
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))
465 endfunc "}}}
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))
470 endfunc "}}}
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))
475 endfunc "}}}
476
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
482
483 " \m
484 " \M
485 " \v
486 " \V
487 "}}}
488
489 " {{{
490 func! s:SillyCheck(digits) "{{{
491     return strlen(a:digits) < 10 ? a:digits : '{silly large number}'
492 endfunc "}}}
493 " }}}
494
495 func! explainpat#NewHelpPrinter() "{{{
496     let obj = {}
497     let obj.literals = ''
498     let obj.indents = []
499     let obj.len = 0         " can be negative (!)
500
501     func! obj.Print(str, ...) "{{{
502         call self.FlushLiterals()
503         let indstr = join(self.indents, '')
504         echohl Comment
505         echo indstr
506         echohl None
507         if a:0 == 0
508             echon a:str
509         else
510             " echo indstr. printf("`%s'   %s", a:str, a:1)
511             echohl PreProc
512             echon printf("%-10s", a:str)
513             echohl None
514             echohl Comment
515             echon printf(" %s", a:1)
516             echohl None
517         endif
518     endfunc "}}}
519
520     func! obj.AddLiteral(item) "{{{
521         let self.literals .= a:item
522     endfunc "}}}
523
524     func! obj.FlushLiterals() "{{{
525         if self.literals == ''
526             return
527         endif
528         let indstr = join(self.indents, '')
529         echohl Comment
530         echo indstr
531         echohl None
532         if self.literals =~ '^\s\|\s$'
533             echon printf("%-10s", '"'. self.literals. '"')
534         else
535             echon printf("%-10s", self.literals)
536         endif
537         echohl Comment
538         echon " literal string"
539         if exists("*strchars")
540             if self.literals =~ '\\'
541                 let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g')
542             endif
543             let spconly = self.literals =~ '[^ ]' ? '' : ', spaces only'
544             let nlit = strchars(self.literals)
545             echon " (". nlit. (nlit==1 ? " atom" : " atoms"). spconly.")"
546         endif
547         echohl None
548         let self.literals = ''
549     endfunc  "}}}
550
551     func! obj.AddIndent(...) "{{{
552         call self.FlushLiterals()
553         if self.len >= 0
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):])
557         endif
558         let self.len += a:0
559     endfunc "}}}
560
561     func! obj.RemIndent(...) "{{{
562         call self.FlushLiterals()
563         if a:0 == 0
564             if self.len >= 1
565                 call remove(self.indents, -1)
566             endif
567             let self.len -= 1
568         else
569             if self.len > a:1
570                 call remove(self.indents, -a:1, -1)
571             elseif self.len >= 1
572                 call remove(self.indents, 0, -1)
573             endif
574             let self.len -= a:1
575         endif
576     endfunc "}}}
577
578     return obj
579 endfunc "}}}
580
581 func! s:NewTokenBiter(str) "{{{
582     " {str}     string to eat pieces from
583     let obj = {'str': a:str}
584
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))
590         return bite
591     endfunc "}}}
592
593     " get the unparsed rest of input (not consuming)
594     func! obj.Rest() "{{{
595         return self.str
596     endfunc "}}}
597
598     " check if end of input reached
599     func! obj.AtEnd() "{{{
600         return self.str == ""
601     endfunc "}}}
602
603     return obj
604 endfunc "}}}
605
606 " Modeline: {{{1
607 let &cpo = s:cpo_save
608 unlet s:cpo_save
609 " vim:ts=8:fdm=marker: