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