X-Git-Url: https://git.madduck.net/etc/vim.git/blobdiff_plain/f97283de809e63df1b2b87591e85687e797b2ca9..1f1ec255b080e74de602667e8b6285a638efb5ec:/autoload/explainpat.vim diff --git a/autoload/explainpat.vim b/autoload/explainpat.vim index 181099c..6c65567 100644 --- a/autoload/explainpat.vim +++ b/autoload/explainpat.vim @@ -1,24 +1,36 @@ " File: explainpat.vim " Created: 2011 Nov 02 -" Last Change: 2012 Dec 19 -" Rev Days: 3 +" Last Change: 2013 Dec 30 +" Rev Days: 12 " Author: Andy Wokula " License: Vim License, see :h license -" Version: 0.1 +" Version: 0.7 " Implements :ExplainPattern [pattern] +" History: "{{{ +" 2013 Jun 21 AND/OR text is confusing, removed +" 2013 Apr 20 ... +"}}} + " TODO {{{ +" - add something like "(empty) ... match everywhere" ... example: '\v(&&|str)' +" Pattern: \v(&&|str) +" Magic Pattern: \(\&\&\|str\) +" \( start of first capturing group +" | (empty) match everywhere +" | \& AND +" | (empty) match everywhere +" | \& AND +" | (empty) match everywhere +" | \| OR +" | str literal string (3 atom(s)) +" \) end of group " - more testing, completeness check -" - detailed collections -" - \z +" ? detailed collections " ? literal string: also print the unescaped magic items " ? literal string: show leading/trailing spaces -" + start of "first" capturing group, start of 2nd ... -" + `\|' should get less indent than the branches, do we need to create an -" AST? ! no, keep it straight forward -" + \%[...] -" + \{, \{- +" "}}} " Init Folklore {{{ @@ -27,7 +39,9 @@ set cpo&vim let g:explainpat#loaded = 1 "}}} -func! explainpat#ExplainPattern(cmd_arg) "{{{ +func! explainpat#ExplainPattern(cmd_arg, ...) "{{{ + " {a:1} alternative help printer object (caution, no sanity check) + " (for test running) if a:cmd_arg == "" " let pattern_str = nwo#vis#Get() echo "(usage) :ExplainPattern [{register} | {pattern}]" @@ -45,13 +59,17 @@ func! explainpat#ExplainPattern(cmd_arg) "{{{ echo printf('Magic Pattern: %s', magicpat) endif - " hmm, we need state for \%[ ... ] + " we need state: + " set flag when in `\%[ ... ]' (optionally matched atoms): let s:in_opt_atoms = 0 + " counter for `\(': let s:capture_group_nr = 0 + " >=1 at pos 0 or after '\|', '\&', '\(', '\%(' or '\n'; else 0 or less: + let s:at_begin_of_pat = 1 - let hulit = s:NewHelpPrinter() + let hulit = a:0>=1 && type(a:1)==s:DICT ? a:1 : s:NewHelpPrinter() call hulit.AddIndent(' ') - let bull = s:NewTokenBiter(magicpat, '') + let bull = s:NewTokenBiter(magicpat) while !bull.AtEnd() let item = bull.Bite(s:magic_item_pattern) if item != '' @@ -62,7 +80,10 @@ func! explainpat#ExplainPattern(cmd_arg) "{{{ call hulit.Print(item, Doc) elseif type(Doc) == s:FUNCREF call call(Doc, [bull, hulit, item]) + elseif type(Doc) == s:LIST + call call(Doc[0], [bull, hulit, item, Doc[1]]) endif + let s:at_begin_of_pat -= 1 else echoerr printf('ExplainPattern: cannot parse "%s"', bull.Rest()) break @@ -76,9 +97,10 @@ endfunc "}}} let s:STRING = type("") let s:DICT = type({}) let s:FUNCREF = type(function("tr")) +let s:LIST = type([]) " }}} -let s:magic_item_pattern = '\C^\%(\\\%(@<.\|%\%([dxouU[(^$V#]\|[<>]\=\%(''.\)\=\)\|z[1-9se(]\|{\|@[>=!]\|_[[^$.]\=\|.\)\|.\)' +let s:magic_item_pattern = '\C^\%(\\\%(@<\|%#=\|%[dxouU[(^$V#<>]\=\|z[1-9se(]\|@[>=!]\|_[[^$.]\=\|.\)\|.\)' let s:doc = {} " {{{ " this is all the help data ... @@ -87,23 +109,33 @@ let s:doc = {} " {{{ func! s:DocOrBranch(bull, hulit, item) "{{{ call a:hulit.RemIndent() - call a:hulit.Print(a:item, "OR branch") + call a:hulit.Print(a:item, "OR") call a:hulit.AddIndent(' ') + let s:at_begin_of_pat = 2 endfunc "}}} let s:doc['\|'] = function("s:DocOrBranch") -let s:doc['\&'] = "AND branch" + +func! s:DocBeginOfPat(bull, hulit, item, msg) "{{{ + call a:hulit.Print(a:item, a:msg) + let s:at_begin_of_pat = 2 +endfunc "}}} + +let s:doc['\&'] = [function("s:DocBeginOfPat"), "AND"] let s:ord = split('n first second third fourth fifth sixth seventh eighth ninth') func! s:DocGroupStart(bull, hulit, item) "{{{ if a:item == '\%(' call a:hulit.Print(a:item, "start of non-capturing group") - else " a:item == '\(' + elseif a:item == '\(' let s:capture_group_nr += 1 call a:hulit.Print(a:item, printf("start of %s capturing group", get(s:ord, s:capture_group_nr, '(invalid)'))) + else " a:item == '\z(' + call a:hulit.Print(a:item, 'start of "external" group (only usable in :syn-region)') endif call a:hulit.AddIndent('| ', ' ') + let s:at_begin_of_pat = 2 endfunc "}}} func! s:DocGroupEnd(bull, hulit, item) "{{{ call a:hulit.RemIndent(2) @@ -113,9 +145,21 @@ endfunc "}}} let s:doc['\('] = function("s:DocGroupStart") let s:doc['\%('] = function("s:DocGroupStart") let s:doc['\)'] = function("s:DocGroupEnd") +" let s:doc['\z('] = "only in syntax scripts" +let s:doc['\z('] = function("s:DocGroupStart") + +func! s:DocStar(bull, hulit, item) "{{{ + if s:at_begin_of_pat >= 1 + " call a:hulit.Print(a:item, "(at begin of pattern) literal `*'") + call a:hulit.AddLiteral(a:item) + else + call a:hulit.Print(a:item, "(multi) zero or more of the preceding atom") + endif +endfunc "}}} + +" let s:doc['*'] = "(multi) zero or more of the preceding atom" +let s:doc['*'] = function("s:DocStar") -let s:doc['\z('] = "only in syntax scripts" -let s:doc['*'] = "(multi) zero or more of the preceding atom" let s:doc['\+'] = "(multi) one or more of the preceding atom" let s:doc['\='] = "(multi) zero or one of the preceding atom" let s:doc['\?'] = "(multi) zero or one of the preceding atom" @@ -133,7 +177,7 @@ func! s:DocBraceMulti(bull, hulit, item) "{{{ call a:hulit.Print(a:item. rest, "(multi) N to M, greedy") endif else - echoerr printf('ExplainPattern: cannot parse %s', a:item. a:bull.Rest()) + call a:hulit.Print(a:item, "(invalid) incomplete `\\{...}' item") endif endfunc "}}} @@ -142,11 +186,49 @@ let s:doc['\{'] = function("s:DocBraceMulti") let s:doc['\@>'] = "(multi) match preceding atom like a full pattern" let s:doc['\@='] = "(assertion) require match for preceding atom" let s:doc['\@!'] = "(assertion) forbid match for preceding atom" -let s:doc['\@<='] = "(assertion) require match for preceding atom to the left" -let s:doc['\@= 1 + call a:hulit.Print(a:item, "(assertion) require match at start of line") + " after `^' is not at begin of pattern ... handle special case `^*' here: + if a:bull.Bite('^\*') == "*" + call a:hulit.AddLiteral("*") + endif + else + " call a:hulit.Print(a:item, "(not at begin of pattern) literal `^'") + call a:hulit.AddLiteral(a:item) + endif +endfunc "}}} + +" let s:doc['^'] = "(assertion) require match at start of line" +let s:doc['^'] = function("s:DocCircumFlex") + let s:doc['\_^'] = "(assertion) like `^', allowed anywhere in the pattern" -let s:doc['$'] = "(assertion) require match at end of line" + +func! s:DocDollar(bull, hulit, item) "{{{ + if a:bull.Rest() =~ '^$\|^\\[&|)n]' + call a:hulit.Print(a:item, "(assertion) require match at end of line") + else + call a:hulit.AddLiteral(a:item) + endif +endfunc "}}} + +" let s:doc['$'] = "(assertion) require match at end of line" +let s:doc['$'] = function("s:DocDollar") + let s:doc['\_$'] = "(assertion) like `$', allowed anywhere in the pattern" let s:doc['.'] = "match any character" let s:doc['\_.'] = "match any character or newline" @@ -157,7 +239,8 @@ func! s:DocUnderscore(bull, hulit, item) "{{{ let cclass_doc = get(s:doc, '\'. cclass, '(invalid character class)') call a:hulit.Print(a:item. cclass, printf('%s or end-of-line', cclass_doc)) else - echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.')) + call a:hulit.Print(a:item, "(invalid) `\\_' should be followed by a letter or `[...]'") + " echoerr printf('ExplainPattern: cannot parse %s', a:item. matchstr(a:bull.Rest(), '.')) endif endfunc "}}} @@ -171,56 +254,80 @@ let s:doc['\%$'] = "(assertion) match at end of buffer" let s:doc['\%V'] = "(assertion) match within the Visual area" let s:doc['\%#'] = "(assertion) match with cursor position" +func! s:DocRegexEngine(bull, hulit, item) "{{{ + let engine = a:bull.Bite('^[012]') + if engine == "0" + call a:hulit.Print(a:item.engine, 'Force automatic selection of the regexp engine (since v7.3.970).') + elseif engine == "1" + call a:hulit.Print(a:item.engine, 'Force using the old engine (since v7.3.970).') + elseif engine == "2" + call a:hulit.Print(a:item.engine, 'Force using the NFA engine (since v7.3.970).') + else + call a:hulit.Print(a:item, '(invalid) \%#= can only be followed by 0, 1, or 2') + endif +endfunc "}}} + +let s:doc['\%#='] = function("s:DocRegexEngine") + " \%'m \%<'m \%>'m " \%23l \%<23l \%>23l " \%23c \%<23c \%>23c " \%23v \%<23v \%>23v " backslash percent at/before/after func! s:DocBspercAt(bull, hulit, item) "{{{ - let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)') + let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C') if rest[0] == "'" - call a:hulit.Print("(assertion) match with position of mark ". rest[1]) + call a:hulit.Print(a:item.rest, "(assertion) match with position of mark ". rest[1]) else let number = rest[:-2] let type = rest[-1:] - if type == "l" - call a:hulit.Print("match in line ". number) - elseif type == "c" - call a:hulit.Print("match in column ". number) - elseif type == "v" - call a:hulit.Print("match in virtual column ". number) + if type ==# "l" + call a:hulit.Print(a:item.rest, "match in line ". number) + elseif type ==# "c" + call a:hulit.Print(a:item.rest, "match in column ". number) + elseif type ==# "v" + call a:hulit.Print(a:item.rest, "match in virtual column ". number) + else + call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%' item") + " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest) endif endif endfunc "}}} func! s:DocBspercBefore(bull, hulit, item) "{{{ - let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)') + let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C') if rest[0] == "'" - call a:hulit.Print("(assertion) match before position of mark ". rest[1]) + call a:hulit.Print(a:item.rest, "(assertion) match before position of mark ". rest[1]) else let number = rest[:-2] let type = rest[-1:] - if type == "l" - call a:hulit.Print("match above line ". number) - elseif type == "c" - call a:hulit.Print("match before column ". number) - elseif type == "v" - call a:hulit.Print("match before virtual column ". number) + if type ==# "l" + call a:hulit.Print(a:item.rest, printf("match above line %d (towards start of buffer)", number)) + elseif type ==# "c" + call a:hulit.Print(a:item.rest, "match before column ". number) + elseif type ==# "v" + call a:hulit.Print(a:item.rest, "match before virtual column ". number) + else + call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%<' item") + " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest) endif endif endfunc "}}} func! s:DocBspercAfter(bull, hulit, item) "{{{ - let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)') + let rest = a:bull.Bite('^\%(''.\|\d\+[lvc]\)\C') if rest[0] == "'" - call a:hulit.Print("(assertion) match after position of mark ". rest[1]) + call a:hulit.Print(a:item.rest, "(assertion) match after position of mark ". rest[1]) else let number = rest[:-2] let type = rest[-1:] - if type == "l" - call a:hulit.Print("match below line ". number) - elseif type == "c" - call a:hulit.Print("match after column ". number) - elseif type == "v" - call a:hulit.Print("match after virtual column ". number) + if type ==# "l" + call a:hulit.Print(a:item.rest, printf("match below line %d (towards end of buffer)", number)) + elseif type ==# "c" + call a:hulit.Print(a:item.rest, "match after column ". number) + elseif type ==# "v" + call a:hulit.Print(a:item.rest, "match after virtual column ". number) + else + call a:hulit.Print(a:item.rest, "(invalid) incomplete `\\%>' item") + " echoerr printf('ExplainPattern: incomplete item %s', a:item. rest) endif endif endfunc "}}} @@ -260,7 +367,7 @@ let s:doc['\e'] = "match " let s:doc['\t'] = "match " let s:doc['\r'] = "match " let s:doc['\b'] = "match CTRL-H" -let s:doc['\n'] = "match a newline" +let s:doc['\n'] = [function("s:DocBeginOfPat"), "match a newline"] let s:doc['~'] = "match the last given substitute string" let s:doc['\1'] = "match first captured string" let s:doc['\2'] = "match second captured string" @@ -272,17 +379,31 @@ let s:doc['\7'] = "match seventh captured string" let s:doc['\8'] = "match eighth captured string" let s:doc['\9'] = "match ninth captured string" -" \z1 -" \z2 -" \z9 +let s:doc['\z1'] = 'match same string matched by first "external" group' +let s:doc['\z2'] = 'match same string matched by second "external" group' +let s:doc['\z3'] = 'match same string matched by third "external" group' +let s:doc['\z4'] = 'match same string matched by fourth "external" group ' +let s:doc['\z5'] = 'match same string matched by fifth "external" group' +let s:doc['\z6'] = 'match same string matched by sixth "external" group' +let s:doc['\z7'] = 'match same string matched by seventh "external" group' +let s:doc['\z8'] = 'match same string matched by eighth "external" group' +let s:doc['\z9'] = 'match same string matched by ninth "external" group' " from MakeMagic() " skip the rest of a collection -let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|[^\]]\)\@>\)*]' +let s:coll_skip_pat = '^\^\=]\=\%(\%(\\[\^\]\-\\bertn]\|\[:\w\+:]\|\[=.=]\|\[\..\.]\|[^\]]\)\@>\)*]' func! s:DocCollection(bull, hulit, item) "{{{ let collstr = a:bull.Bite(s:coll_skip_pat) - call a:hulit.Print(a:item. collstr, 'collection'. (a:item=='\_[' ? ' with end-of-line added' : '')) + if collstr == "" || collstr == "]" + call a:hulit.AddLiteral('['. collstr) + else + let inverse = collstr =~ '^\^' + let with_nl = a:item == '\_[' + let descr = inverse ? printf('collection not matching [%s', collstr[1:]) : 'collection' + let descr_nl = printf("%s%s", (inverse && with_nl ? ', but' : ''), (with_nl ? ' with end-of-line added' : '')) + call a:hulit.Print(a:item. collstr, descr. descr_nl) + endif endfunc "}}} let s:doc['['] = function("s:DocCollection") @@ -308,9 +429,15 @@ endfunc "}}} let s:doc['\%['] = function("s:DocOptAtoms") let s:doc[']'] = function("s:DocOptAtoms") -let s:doc['\c'] = "ignore case while matching the pattern" -let s:doc['\C'] = "match case while matching the pattern" -let s:doc['\Z'] = "ignore composing characters in the pattern" +func! s:DocAnywhere(bull, hulit, item, msg) "{{{ + call a:hulit.Print(a:item, a:msg) + " keep state: + let s:at_begin_of_pat += 1 +endfunc "}}} + +let s:doc['\c'] = [function("s:DocAnywhere"), "ignore case while matching the pattern"] +let s:doc['\C'] = [function("s:DocAnywhere"), "match case while matching the pattern"] +let s:doc['\Z'] = [function("s:DocAnywhere"), "ignore composing characters in the pattern"] " \%d 123 " \%x 2a @@ -320,23 +447,28 @@ let s:doc['\Z'] = "ignore composing characters in the pattern" func! s:DocBspercDecimal(bull, hulit, item) "{{{ let number = a:bull.Bite('^\d\{,3}') - call a:hulit.Print(a:item. number, "match character specified by decimal number ". number) + let char = strtrans(nr2char(str2nr(number))) + call a:hulit.Print(a:item. number, printf("match character specified by decimal number %s (%s)", number, char)) endfunc "}}} func! s:DocBspercHexTwo(bull, hulit, item) "{{{ let number = a:bull.Bite('^\x\{,2}') - call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number) + let char = strtrans(nr2char(str2nr(number,16))) + call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s (%s)", number, char)) endfunc "}}} func! s:DocBspercOctal(bull, hulit, item) "{{{ let number = a:bull.Bite('^\o\{,4}') - call a:hulit.Print(a:item. number, "match character specified with octal number 0". substitute(number, '^0*', '', '')) + let char = strtrans(nr2char(str2nr(number,8))) + call a:hulit.Print(a:item. number, printf("match character specified with octal number 0%s (%s)", substitute(number, '^0*', '', ''), char)) endfunc "}}} func! s:DocBspercHexFour(bull, hulit, item) "{{{ let number = a:bull.Bite('^\x\{,4}') - call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number) + let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : '' + call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char)) endfunc "}}} func! s:DocBspercHexEight(bull, hulit, item) "{{{ let number = a:bull.Bite('^\x\{,8}') - call a:hulit.Print(a:item. number, "match character specified with hex number 0x". number) + let char = has("multi_byte_encoding") ? ' ('. strtrans(nr2char(str2nr(number,16))).')' : '' + call a:hulit.Print(a:item. number, printf("match character specified with hex number 0x%s%s", number, char)) endfunc "}}} let s:doc['\%d'] = function("s:DocBspercDecimal") " 123 @@ -399,8 +531,9 @@ func! s:NewHelpPrinter() "{{{ if self.literals =~ '\\' let self.literals = substitute(self.literals, '\\\(.\)', '\1', 'g') endif - let so = self.literals =~ '[^ ]' ? '' : ', spaces only' - echon " (". strchars(self.literals). " atom(s)". so.")" + let spconly = self.literals =~ '[^ ]' ? '' : ', spaces only' + let nlit = strchars(self.literals) + echon " (". nlit. (nlit==1 ? " atom" : " atoms"). spconly.")" endif echohl None let self.literals = '' @@ -436,22 +569,19 @@ func! s:NewHelpPrinter() "{{{ return obj endfunc "}}} -func! s:NewTokenBiter(str, ...) "{{{ +func! s:NewTokenBiter(str) "{{{ " {str} string to eat pieces from - " {a:1} pattern to skip separators - let whitepat = a:0>=1 ? a:1 : '^\s*' - let obj = {'str': a:str, 'whitepat': whitepat} + let obj = {'str': a:str} " consume piece from start of input matching {pat} func! obj.Bite(pat) "{{{ " {pat} should start with '^' - let skip = matchend(self.str, self.whitepat) - let bite = matchstr(self.str, a:pat, skip) - let self.str = strpart(self.str, matchend(self.str, self.whitepat, skip + strlen(bite))) + let bite = matchstr(self.str, a:pat) + let self.str = strpart(self.str, strlen(bite)) return bite endfunc "}}} - " get the unparsed rest of input + " get the unparsed rest of input (not consuming) func! obj.Rest() "{{{ return self.str endfunc "}}}