blob: 47fabe954da055bb0cc04d713b268e94569c93ce [file] [log] [blame]
Bram Moolenaar939a1ab2016-04-10 01:31:25 +02001" LogiPat: Boolean logical pattern matcher
Bram Moolenaare2db6c92015-06-19 18:48:41 +02002" Author: Charles E. Campbell
Bram Moolenaar939a1ab2016-04-10 01:31:25 +02003" Date: Apr 04, 2016
4" Version: 4
Bram Moolenaare2db6c92015-06-19 18:48:41 +02005" Purpose: to do Boolean-logic based regular expression pattern matching
6" Copyright: Copyright (C) 1999-2011 Charles E. Campbell {{{1
7" Permission is hereby granted to use and distribute this code,
8" with or without modifications, provided that this copyright
9" notice is copied with it. Like most anything else that's free,
10" LogiPat.vim is provided *as is* and comes with no warranty
11" of any kind, either expressed or implied. By using this
12" plugin, you agree that in no event will the copyright
13" holder be liable for any damages resulting from the use
14" of this software.
15"
16" Usage: {{{1
17" :LogiPat ...
18"
19" Boolean logic supported:
20" () grouping operators
21" ! not the following pattern
22" | logical or
23" & logical and
24" "..pattern.."
25" Example: {{{1
26" :LogiPat !("january"|"february")
27" would match all strings not containing the strings january
28" or february
29" GetLatestVimScripts: 1290 1 :AutoInstall: LogiPat.vim
30"
31" Behold, you will conceive in your womb, and bring forth a son, {{{1
32" and will call his name Jesus. He will be great, and will be
33" called the Son of the Most High. The Lord God will give him the
34" throne of his father, David, and he will reign over the house of
35" Jacob forever. There will be no end to his kingdom. (Luke 1:31-33 WEB)
36
37" ---------------------------------------------------------------------
38" Load Once: {{{1
Bram Moolenaar939a1ab2016-04-10 01:31:25 +020039if &cp || exists("loaded_logiPat")
Bram Moolenaare2db6c92015-06-19 18:48:41 +020040 finish
41endif
Bram Moolenaar939a1ab2016-04-10 01:31:25 +020042let g:loaded_logiPat = "v4"
Bram Moolenaare2db6c92015-06-19 18:48:41 +020043let s:keepcpo = &cpo
44set cpo&vim
45"DechoRemOn
46
47" ---------------------------------------------------------------------
48" Public Interface: {{{1
Bram Moolenaar939a1ab2016-04-10 01:31:25 +020049com! -nargs=* LogiPat call LogiPat(<q-args>,1)
50sil! com -nargs=* LP call LogiPat(<q-args>,1)
51sil! com -nargs=* LPR call LogiPat(<q-args>,1,"r")
52com! -nargs=+ LPE echomsg LogiPat(<q-args>)
53com! -nargs=+ LogiPatFlags let s:LogiPatFlags="<args>"
54sil! com -nargs=+ LPF let s:LogiPatFlags="<args>"
Bram Moolenaare2db6c92015-06-19 18:48:41 +020055
56" =====================================================================
57" Functions: {{{1
58
59" ---------------------------------------------------------------------
60" LogiPat: this function interprets the boolean-logic pattern {{{2
61fun! LogiPat(pat,...)
62" call Dfunc("LogiPat(pat<".a:pat.">)")
63
64 " LogiPat(pat,dosearch)
65 if a:0 > 0
66 let dosearch= a:1
67 else
68 let dosearch= 0
69 endif
Bram Moolenaar939a1ab2016-04-10 01:31:25 +020070 if a:0 >= 3
71 let s:LogiPatFlags= a:3
72 endif
Bram Moolenaare2db6c92015-06-19 18:48:41 +020073
74 let s:npatstack = 0
75 let s:nopstack = 0
76 let s:preclvl = 0
77 let expr = a:pat
78
79 " Lexer/Parser
80 while expr != ""
81" call Decho("expr<".expr.">")
82
83 if expr =~ '^"'
84 " push a Pattern; accept "" as a single " in the pattern
85 let expr = substitute(expr,'^\s*"','','')
86 let pat = substitute(expr,'^\(\%([^"]\|\"\"\)\{-}\)"\([^"].*$\|$\)','\1','')
87 let pat = substitute(pat,'""','"','g')
88 let expr = substitute(expr,'^\(\%([^"]\|\"\"\)\{-}\)"\([^"].*$\|$\)','\2','')
89 let expr = substitute(expr,'^\s*','','')
90" call Decho("pat<".pat."> expr<".expr.">")
91
92 call s:LP_PatPush('.*'.pat.'.*')
93
94 elseif expr =~ '^[!()|&]'
95 " push an operator
96 let op = strpart(expr,0,1)
97 let expr = strpart(expr,strlen(op))
98 " allow for those who can't resist doubling their and/or operators
99 if op =~ '[|&]' && expr[0] == op
100 let expr = strpart(expr,strlen(op))
101 endif
102 call s:LP_OpPush(op)
103
104 elseif expr =~ '^\s'
105 " skip whitespace
106 let expr= strpart(expr,1)
107
108 else
109 echoerr "operator<".strpart(expr,0,1)."> not supported (yet)"
110 let expr= strpart(expr,1)
111 endif
112
113 endwhile
114
115 " Final Execution
116 call s:LP_OpPush('Z')
117
118 let result= s:LP_PatPop(1)
119" call Decho("result=".result)
120
121 " sanity checks and cleanup
122 if s:npatstack > 0
123 echoerr s:npatstack." patterns left on stack!"
124 let s:npatstack= 0
125 endif
126 if s:nopstack > 0
127 echoerr s:nopstack." operators left on stack!"
128 let s:nopstack= 0
129 endif
130
131 " perform the indicated search
132 if dosearch
Bram Moolenaar939a1ab2016-04-10 01:31:25 +0200133 if exists("s:LogiPatFlags") && s:LogiPatFlags != ""
Bram Moolenaare2db6c92015-06-19 18:48:41 +0200134" call Decho("search(result<".result."> LogiPatFlags<".s:LogiPatFlags.">)")
135 call search(result,s:LogiPatFlags)
136 else
137" call Decho("search(result<".result.">)")
138 call search(result)
139 endif
140 let @/= result
141 endif
142
143" call Dret("LogiPat ".result)
144 return result
145endfun
146
147" ---------------------------------------------------------------------
148" s:String: Vim6.4 doesn't have string() {{{2
149func! s:String(str)
150 return "'".escape(a:str, '"')."'"
151endfunc
152
153" ---------------------------------------------------------------------
154" LP_PatPush: {{{2
155fun! s:LP_PatPush(pat)
156" call Dfunc("LP_PatPush(pat<".a:pat.">)")
157 let s:npatstack = s:npatstack + 1
158 let s:patstack_{s:npatstack} = a:pat
159" call s:StackLook("patpush") "Decho
160" call Dret("LP_PatPush : npatstack=".s:npatstack)
161endfun
162
163" ---------------------------------------------------------------------
164" LP_PatPop: pop a number/variable from LogiPat's pattern stack {{{2
165fun! s:LP_PatPop(lookup)
166" call Dfunc("LP_PatPop(lookup=".a:lookup.")")
167 if s:npatstack > 0
168 let ret = s:patstack_{s:npatstack}
169 let s:npatstack = s:npatstack - 1
170 else
171 let ret= "---error---"
172 echoerr "(LogiPat) invalid expression"
173 endif
174" call s:StackLook("patpop") "Decho
175" call Dret("LP_PatPop ".ret)
176 return ret
177endfun
178
179" ---------------------------------------------------------------------
180" LP_OpPush: {{{2
181fun! s:LP_OpPush(op)
182" call Dfunc("LP_OpPush(op<".a:op.">)")
183
184 " determine new operator's precedence level
185 if a:op == '('
186 let s:preclvl= s:preclvl + 10
187 let preclvl = s:preclvl
188 elseif a:op == ')'
189 let s:preclvl= s:preclvl - 10
190 if s:preclvl < 0
191 let s:preclvl= 0
192 echoerr "too many )s"
193 endif
194 let preclvl= s:preclvl
195 elseif a:op =~ '|'
196 let preclvl= s:preclvl + 2
197 elseif a:op =~ '&'
198 let preclvl= s:preclvl + 4
199 elseif a:op == '!'
200 let preclvl= s:preclvl + 6
201 elseif a:op == 'Z'
202 let preclvl= -1
203 else
204 echoerr "expr<".expr."> not supported (yet)"
205 let preclvl= s:preclvl
206 endif
207" call Decho("new operator<".a:op."> preclvl=".preclvl)
208
209 " execute higher-precdence operators
210" call Decho("execute higher-precedence operators")
211 call s:LP_Execute(preclvl)
212
213 " push new operator onto operator-stack
214" call Decho("push new operator<".a:op."> onto stack with preclvl=".preclvl." at nopstack=".(s:nopstack+1))
215 if a:op =~ '!'
216 let s:nopstack = s:nopstack + 1
217 let s:opprec_{s:nopstack} = preclvl
218 let s:opstack_{s:nopstack} = a:op
219 elseif a:op =~ '|'
220 let s:nopstack = s:nopstack + 1
221 let s:opprec_{s:nopstack} = preclvl
222 let s:opstack_{s:nopstack} = a:op
223 elseif a:op == '&'
224 let s:nopstack = s:nopstack + 1
225 let s:opprec_{s:nopstack} = preclvl
226 let s:opstack_{s:nopstack} = a:op
227 endif
228
229" call s:StackLook("oppush") "Decho
230" call Dret("LP_OpPush : s:preclvl=".s:preclvl)
231endfun
232
233" ---------------------------------------------------------------------
234" LP_Execute: execute operators from opstack using pattern stack {{{2
235fun! s:LP_Execute(preclvl)
236" call Dfunc("LP_Execute(preclvl=".a:preclvl.") npatstack=".s:npatstack." nopstack=".s:nopstack)
237
238 " execute all higher precedence operators
239 while s:nopstack > 0 && a:preclvl < s:opprec_{s:nopstack}
240 let op= s:opstack_{s:nopstack}
241" call Decho("op<".op."> nop=".s:nopstack." [preclvl=".a:preclvl."] < [opprec_".s:nopstack."=".s:opprec_{s:nopstack}."]")
242
243 let s:nopstack = s:nopstack - 1
244
245 if op == '!'
246 let n1= s:LP_PatPop(1)
247 call s:LP_PatPush(s:LP_Not(n1))
248
249 elseif op == '|'
250 let n1= s:LP_PatPop(1)
251 let n2= s:LP_PatPop(1)
252 call s:LP_PatPush(s:LP_Or(n2,n1))
253
254 elseif op =~ '&'
255 let n1= s:LP_PatPop(1)
256 let n2= s:LP_PatPop(1)
257 call s:LP_PatPush(s:LP_And(n2,n1))
258 endif
259
260" call s:StackLook("execute") "Decho
261 endwhile
262
263" call Dret("LP_Execute")
264endfun
265
266" ---------------------------------------------------------------------
267" LP_Not: writes a logical-not for a pattern {{{2
268fun! s:LP_Not(pat)
269" call Dfunc("LP_Not(pat<".a:pat.">)")
270 if a:pat =~ '^\.\*' && a:pat =~ '\.\*$'
271 let pat= substitute(a:pat,'^\.\*\(.*\)\.\*$','\1','')
272 let ret= '^\%(\%('.pat.'\)\@!.\)*$'
273 else
274 let ret= '^\%(\%('.a:pat.'\)\@!.\)*$'
275 endif
276" call Dret("LP_Not ".ret)
277 return ret
278endfun
279
280" ---------------------------------------------------------------------
281" LP_Or: writes a logical-or branch using two patterns {{{2
282fun! s:LP_Or(pat1,pat2)
283" call Dfunc("LP_Or(pat1<".a:pat1."> pat2<".a:pat2.">)")
284 let ret= '\%('.a:pat1.'\|'.a:pat2.'\)'
285" call Dret("LP_Or ".ret)
286 return ret
287endfun
288
289" ---------------------------------------------------------------------
290" LP_And: writes a logical-and concat using two patterns {{{2
291fun! s:LP_And(pat1,pat2)
292" call Dfunc("LP_And(pat1<".a:pat1."> pat2<".a:pat2.">)")
293 let ret= '\%('.a:pat1.'\&'.a:pat2.'\)'
294" call Dret("LP_And ".ret)
295 return ret
296endfun
297
298" ---------------------------------------------------------------------
299" StackLook: {{{2
300fun! s:StackLook(description)
301" call Dfunc("StackLook(description<".a:description.">)")
302 let iop = 1
303 let ifp = 1
304" call Decho("Pattern Operator")
305
306 " print both pattern and operator
307 while ifp <= s:npatstack && iop <= s:nopstack
308 let fp = s:patstack_{ifp}
309 let op = s:opstack_{iop}." (P".s:opprec_{s:nopstack}.')'
310 let fplen= strlen(fp)
311 if fplen < 30
312 let fp= fp.strpart(" ",1,30-fplen)
313 endif
314" call Decho(fp.op)
315 let ifp = ifp + 1
316 let iop = iop + 1
317 endwhile
318
319 " print just pattern
320 while ifp <= s:npatstack
321 let fp = s:patstack_{ifp}
322" call Decho(fp)
323 let ifp = ifp + 1
324 endwhile
325
326 " print just operator
327 while iop <= s:nopstack
328 let op = s:opstack_{iop}." (P".s:opprec_{s:nopstack}.')'
329" call Decho(" ".op)
330 let iop = iop + 1
331 endwhile
332" call Dret("StackLook")
333endfun
334
335" ---------------------------------------------------------------------
336" Cleanup And Modeline: {{{1
337let &cpo= s:keepcpo
338unlet s:keepcpo
339" vim: ts=4 fdm=marker