2 * Copyright 2010, INRIA, University of Copenhagen
3 * Julia Lawall, Rene Rydhof Hansen, Gilles Muller, Nicolas Palix
4 * Copyright 2005-2009, Ecole des Mines de Nantes, University of Copenhagen
5 * Yoann Padioleau, Julia Lawall, Rene Rydhof Hansen, Henrik Stuart, Gilles Muller, Nicolas Palix
6 * This file is part of Coccinelle.
8 * Coccinelle is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, according to version 2 of the License.
12 * Coccinelle is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with Coccinelle. If not, see <http://www.gnu.org/licenses/>.
20 * The authors reserve the right to distribute this or future versions of
21 * Coccinelle under other licenses.
25 module Ast
= Ast_cocci
26 module V
= Visitor_ast
27 module TC
= Type_cocci
31 1. If a rule X depends on a rule Y (in a positive way), then we can ignore
34 2. If a rule X contains a metavariable that is not under a disjunction and
35 that is inherited from rule Y, then we can ignore the constants in X.
37 3. If a rule contains a constant x in + code then subsequent rules that
38 have it in - or context should not include it in their list of required
42 (* This doesn't do the . -> trick of get_constants for record fields, as
43 that does not fit well with the recursive structure. It was not clear
44 that that was completely safe either, although eg putting a newline
45 after the . or -> is probably unusual. *)
47 (* ----------------------------------------------------------------------- *)
48 (* This phase collects everything. One can then filter out what it not
51 (* True means nothing was found
52 False should never drift to the top, it is the neutral element of or
53 and an or is never empty *)
55 And
of combine list
| Or
of combine list
| Elem
of string | False
| True
57 (* glimpse often fails on large queries. We can safely remove arguments of
58 && as long as we don't remove all of them (note that there is no negation).
59 This tries just removing one of them and then orders the results by
60 increasing number of ors (ors are long, increasing the chance of failure,
61 and are less restrictive, possibly increasing the chance of irrelevant
63 let reduce_glimpse x
=
67 | And
[x
] -> loop x
(function changed_l
-> k
(And
[changed_l
])) q
70 (function changed_l
-> k
(And changed_l
))
77 rloop xs
(function changed_xs
-> k
(x
:: changed_xs
)) in
78 rloop l
(function changed_l
-> k
(And changed_l
)))
79 | Or l
-> kloop l
(function changed_l
-> k
(Or changed_l
)) q
80 | _
-> failwith
"not possible"
86 (function changed_x
-> k
(changed_x
::xs
))
89 (function changed_xs
-> k
(x
:: changed_xs
))
91 let rec count_ors = function
93 | And l
-> List.fold_left
(+) 0 (List.map
count_ors l
)
95 ((List.length l
) - 1) +
96 (List.fold_left
(+) 0 (List.map
count_ors l
))
97 | _
-> failwith
"not possible" in
98 let res = loop x
(function x
-> x
) (function _
-> []) in
99 let res = List.map
(function x
-> (count_ors x
,x
)) res in
100 let res = List.sort compare
res in
101 List.map
(function (_
,x
) -> x
) res
103 let interpret_glimpse strict x
=
104 let rec loop = function
108 | And l
-> Printf.sprintf
"{%s}" (String.concat
";" (List.map
loop l
))
109 | Or l
-> Printf.sprintf
"{%s}" (String.concat
"," (List.map
loop l
))
112 then failwith
"True should not be in the final result"
116 then failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
120 | False
when strict
->
121 failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
123 Some
(if strict
then List.map
loop (x
::reduce_glimpse x
) else [loop x
])
125 (* grep only does or *)
126 let interpret_grep strict x
=
127 let rec loop = function
129 | And l
-> List.concat
(List.map
loop l
)
130 | Or l
-> List.concat
(List.map
loop l
)
133 then failwith
"True should not be in the final result"
137 then failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
141 | False
when strict
->
142 failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
145 let interpret_google strict x
=
147 let rec dnf = function
149 | Or l
-> List.fold_left
Common.union_set
[] (List.map
dnf l
)
151 let l = List.map
dnf l in
155 List.fold_left
Common.union_set
[]
158 List.map
(function y
-> Printf.sprintf
"%s %s" x y
) prev
)
164 then failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
168 | False
when strict
->
169 failwith
"False should not be in the final result. Perhaps your rule doesn't contain any +/-/* code"
173 match interpret_glimpse false x
with
175 | Some x
-> String.concat
" || " x
178 And
l -> And
(List.sort compare
l)
179 | Or
l -> Or
(List.sort compare
l)
182 let rec merge l1 l2
=
187 (match compare x y
with
188 -1 -> x
::(merge xs l2
)
189 | 0 -> x
::(merge xs ys
)
190 | 1 -> y
::(merge l1 ys
)
191 | _
-> failwith
"not possible")
193 let intersect l1 l2
= List.filter
(function l1e
-> List.mem l1e l2
) l1
195 let minus_set l1 l2
= List.filter
(function l1e
-> not
(List.mem l1e l2
)) l1
197 let rec insert x
l = merge [x
] l
199 let rec build_and x y
=
204 (True
,x
) | (x
,True
) -> x
205 | (False
,x
) | (x
,False
) -> False
206 | (And l1
,And l2
) -> And
(merge l1 l2
)
207 | (x
,Or
l) when List.mem x
l -> x
208 | (Or
l,x
) when List.mem x
l -> x
209 | (Or l1
,Or l2
) when not
((intersect l1 l2
) = []) ->
212 (List.fold_left build_or False
(minus_set l1 l2
))
213 (List.fold_left build_or False
(minus_set l2 l1
)) in
214 List.fold_left build_or
inner (intersect l1 l2
)
215 | (x
,And
l) | (And
l,x
) ->
222 Or
l -> not
(List.mem x
l)
225 And
(insert x
others)
226 | (x
,y
) -> norm(And
[x
;y
])
233 (True
,x
) | (x
,True
) -> True
234 | (False
,x
) | (x
,False
) -> x
235 | (Or l1
,Or l2
) -> Or
(merge l1 l2
)
236 | (x
,And
l) when List.mem x
l -> x
237 | (And
l,x
) when List.mem x
l -> x
238 | (And l1
,And l2
) when not
((intersect l1 l2
) = []) ->
241 (List.fold_left
build_and True
(minus_set l1 l2
))
242 (List.fold_left
build_and True
(minus_set l2 l1
)) in
243 List.fold_left
build_and inner (intersect l1 l2
)
244 | (x
,Or
l) | (Or
l,x
) ->
251 And
l -> not
(List.mem x
l)
255 | (x
,y
) -> norm(Or
[x
;y
])
260 let do_get_constants constants keywords env neg_pos
=
261 let donothing r k e
= k e
in
262 let option_default = True
in
263 let bind = build_and in
264 let inherited ((nm1
,_
) as x
) =
265 (* ignore virtuals *)
266 if nm1
= "virtual" then option_default
267 (* perhaps inherited, but value not required, so no constraints *)
268 else if List.mem x neg_pos
then option_default
269 else (try List.assoc nm1 env
with Not_found
-> False
) in
270 let minherited name
= inherited (Ast.unwrap_mcode name
) in
272 List.fold_left
bind option_default
274 (function Ast.MetaPos
(name
,constraints
,_
,keep,inh
) -> minherited name
)
275 (Ast.get_pos_var x
)) in
277 (* if one branch gives no information, then we have to take anything *)
278 let disj_union_all = List.fold_left build_or False
in
281 match Ast.unwrap i
with
284 (match Ast.unwrap_mcode name
with
285 "NULL" -> keywords
"NULL"
286 | nm
-> constants nm
)
287 | Ast.MetaId
(name
,_
,_
,_
) | Ast.MetaFunc
(name
,_
,_
,_
)
288 | Ast.MetaLocalFunc
(name
,_
,_
,_
) -> bind (k i
) (minherited name
)
289 | Ast.DisjId
(ids
) -> disj_union_all (List.map r
.V.combiner_ident ids
)
292 let rec type_collect res = function
293 TC.ConstVol
(_
,ty
) | TC.Pointer
(ty
) | TC.FunctionPointer
(ty
)
294 | TC.Array
(ty
) -> type_collect res ty
295 | TC.MetaType
(tyname
,_
,_
) ->
297 | TC.TypeName
(s
) -> constants s
298 | TC.EnumName
(TC.Name s
) -> constants s
299 | TC.StructUnionName
(_
,TC.Name s
) -> constants s
302 (* no point to do anything special for records because glimpse is
304 let expression r k e
=
305 match Ast.unwrap e
with
306 Ast.Constant
(const
) ->
308 (match Ast.unwrap_mcode const
with
309 Ast.String s
-> constants s
310 | Ast.Char
"\\0" -> option_default (* glimpse doesn't like it *)
311 | Ast.Char s
-> option_default (* probably not chars either *)
312 (* the following were eg keywords "1", but not good for glimpse *)
313 | Ast.Int s
-> option_default (* glimpse doesn't index integers *)
314 | Ast.Float s
-> option_default (* probably not floats either *))
315 | Ast.MetaExpr
(name
,_
,_
,Some type_list
,_
,_
) ->
316 let types = List.fold_left
type_collect option_default type_list
in
317 bind (k e
) (bind (minherited name
) types)
318 | Ast.MetaErr
(name
,_
,_
,_
) | Ast.MetaExpr
(name
,_
,_
,_
,_
,_
) ->
319 bind (k e
) (minherited name
)
320 | Ast.MetaExprList
(name
,Ast.MetaListLen
(lenname
,_
,_
),_
,_
) ->
321 bind (k e
) (bind (minherited name
) (minherited lenname
))
322 | Ast.MetaExprList
(name
,_
,_
,_
) -> minherited name
323 | Ast.SizeOfExpr
(sizeof
,exp
) -> bind (keywords
"sizeof") (k e
)
324 | Ast.SizeOfType
(sizeof
,lp
,ty
,rp
) -> bind (keywords
"sizeof") (k e
)
325 | Ast.NestExpr
(starter
,expr_dots
,ender
,wc
,false) -> option_default
326 | Ast.NestExpr
(starter
,expr_dots
,ender
,wc
,true) ->
327 r
.V.combiner_expression_dots expr_dots
328 | Ast.DisjExpr
(exps
) ->
329 disj_union_all (List.map r
.V.combiner_expression exps
)
330 | Ast.OptExp
(exp
) -> option_default
331 | Ast.Edots
(_
,_
) | Ast.Ecircles
(_
,_
) | Ast.Estars
(_
,_
) -> option_default
334 let fullType r k ft
=
335 match Ast.unwrap ft
with
336 Ast.DisjType
(decls
) ->
337 disj_union_all (List.map r
.V.combiner_fullType decls
)
338 | Ast.OptType
(ty
) -> option_default
341 let baseType = function
342 Ast.VoidType
-> keywords
"void"
343 | Ast.CharType
-> keywords
"char"
344 | Ast.ShortType
-> keywords
"short"
345 | Ast.IntType
-> keywords
"int"
346 | Ast.DoubleType
-> keywords
"double"
347 | Ast.FloatType
-> keywords
"float"
348 | Ast.LongType
| Ast.LongLongType
-> keywords
"long"
349 | Ast.SizeType
-> keywords
"size_t"
350 | Ast.SSizeType
-> keywords
"ssize_t"
351 | Ast.PtrDiffType
-> keywords
"ptrdiff_t" in
354 match Ast.unwrap ty
with
355 Ast.BaseType
(ty1
,strings
) -> bind (k ty
) (baseType ty1
)
356 | Ast.TypeName
(name
) -> bind (k ty
) (constants
(Ast.unwrap_mcode name
))
357 | Ast.MetaType
(name
,_
,_
) -> bind (minherited name
) (k ty
)
360 let declaration r k d
=
361 match Ast.unwrap d
with
362 Ast.MetaDecl
(name
,_
,_
) | Ast.MetaField
(name
,_
,_
) ->
363 bind (k d
) (minherited name
)
364 | Ast.MetaFieldList
(name
,Ast.MetaListLen
(lenname
,_
,_
),_
,_
) ->
365 bind (minherited name
) (bind (minherited lenname
) (k d
))
366 | Ast.DisjDecl
(decls
) ->
367 disj_union_all (List.map r
.V.combiner_declaration decls
)
368 | Ast.OptDecl
(decl
) -> option_default
369 | Ast.Ddots
(dots
,whencode
) -> option_default
372 let initialiser r k i
=
373 match Ast.unwrap i
with
374 Ast.OptIni
(ini
) -> option_default
377 let parameter r k p
=
378 match Ast.unwrap p
with
379 Ast.OptParam
(param
) -> option_default
380 | Ast.MetaParam
(name
,_
,_
) -> bind (k p
) (minherited name
)
381 | Ast.MetaParamList
(name
,Ast.MetaListLen
(lenname
,_
,_
),_
,_
) ->
382 bind (minherited name
) (bind (minherited lenname
) (k p
))
383 | Ast.MetaParamList
(name
,_
,_
,_
) -> bind (k p
) (minherited name
)
386 let rule_elem r k re
=
387 match Ast.unwrap re
with
388 Ast.MetaRuleElem
(name
,_
,_
) | Ast.MetaStmt
(name
,_
,_
,_
)
389 | Ast.MetaStmtList
(name
,_
,_
) -> bind (minherited name
) (k re
)
390 | Ast.WhileHeader
(whl
,lp
,exp
,rp
) ->
391 bind (keywords
"while") (k re
)
392 | Ast.WhileTail
(whl
,lp
,exp
,rp
,sem
) ->
393 bind (keywords
"do") (k re
)
394 | Ast.ForHeader
(fr
,lp
,e1
,sem1
,e2
,sem2
,e3
,rp
) ->
395 bind (keywords
"for") (k re
)
396 | Ast.SwitchHeader
(switch
,lp
,exp
,rp
) ->
397 bind (keywords
"switch") (k re
)
398 | Ast.Break
(br
,sem
) ->
399 bind (keywords
"break") (k re
)
400 | Ast.Continue
(cont
,sem
) ->
401 bind (keywords
"continue") (k re
)
403 bind (keywords
"goto") (k re
)
404 | Ast.Default
(def
,colon
) ->
405 bind (keywords
"default") (k re
)
406 | Ast.Include
(inc
,s
) ->
408 (match Ast.unwrap_mcode s
with
409 Ast.Local
l | Ast.NonLocal
l ->
414 (* just take the last thing, probably the most
415 specific. everything is necessary anyway. *)
416 Ast.IncPath s
-> [Elem s
]
417 | Ast.IncDots
-> prev
)
421 | x
::xs
-> List.fold_left
bind x xs
))
422 | Ast.DisjRuleElem
(res) ->
423 disj_union_all (List.map r
.V.combiner_rule_elem
res)
426 let statement r k s
=
427 match Ast.unwrap s
with
428 Ast.Disj
(stmt_dots
) ->
429 disj_union_all (List.map r
.V.combiner_statement_dots stmt_dots
)
430 | Ast.Nest
(starter
,stmt_dots
,ender
,whn
,false,_
,_
) -> option_default
431 | Ast.Nest
(starter
,stmt_dots
,ender
,whn
,true,_
,_
) ->
432 r
.V.combiner_statement_dots stmt_dots
433 | Ast.OptStm
(s
) -> option_default
434 | Ast.Dots
(d
,whn
,_
,_
) | Ast.Circles
(d
,whn
,_
,_
) | Ast.Stars
(d
,whn
,_
,_
) ->
438 V.combiner
bind option_default
439 mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode
440 donothing donothing donothing donothing donothing
441 ident expression fullType typeC initialiser parameter declaration
442 rule_elem statement donothing donothing donothing
444 (* ------------------------------------------------------------------------ *)
446 let filter_combine combine to_drop
=
447 let rec and_loop = function
448 Elem x
when List.mem x to_drop
-> True
449 | Or
l -> List.fold_left build_or False
(List.map or_loop
l)
451 and or_loop
= function
452 Elem x
when List.mem x to_drop
-> False
453 | And
l -> List.fold_left
build_and True
(List.map
and_loop l)
457 (* ------------------------------------------------------------------------ *)
459 let get_all_constants minus_only
=
460 let donothing r k e
= k e
in
461 let bind = Common.union_set
in
462 let option_default = [] in
463 let mcode r
(x
,_
,mcodekind
,_
) =
465 Ast.MINUS
(_
,_
,_
,_
) -> [x
]
466 | _
when minus_only
-> []
468 let other r _
= [] in
470 V.combiner
bind option_default
471 other mcode other other other other other other other other other other
473 donothing donothing donothing donothing donothing
474 donothing donothing donothing donothing donothing donothing donothing
475 donothing donothing donothing donothing donothing
477 (* ------------------------------------------------------------------------ *)
479 let get_plus_constants =
480 let donothing r k e
= k e
in
481 let bind = Common.union_set
in
482 let option_default = [] in
489 bind ((get_all_constants false).V.combiner_anything cur
) prev
))
491 let process_mcodekind = function
492 Ast.MINUS
(_
,_
,_
,Ast.REPLACEMENT
(anythings
,_
)) -> recurse anythings
493 | Ast.CONTEXT
(_
,Ast.BEFORE
(a
,_
)) -> recurse a
494 | Ast.CONTEXT
(_
,Ast.AFTER
(a
,_
)) -> recurse a
495 | Ast.CONTEXT
(_
,Ast.BEFOREAFTER
(a1
,a2
,_
)) ->
496 Common.union_set
(recurse a1
) (recurse a2
)
499 let mcode r mc
= process_mcodekind (Ast.get_mcodekind mc
) in
500 let end_info (_
,_
,_
,mc
) = process_mcodekind mc
in
502 let rule_elem r k e
=
503 match Ast.unwrap e
with
504 Ast.FunHeader
(bef
,_
,_
,_
,_
,_
,_
)
505 | Ast.Decl
(bef
,_
,_
) -> bind (process_mcodekind bef
) (k e
)
508 let statement r k e
=
509 match Ast.unwrap e
with
510 Ast.IfThen
(_
,_
,ei
) | Ast.IfThenElse
(_
,_
,_
,_
,ei
)
511 | Ast.While
(_
,_
,ei
) | Ast.For
(_
,_
,ei
)
512 | Ast.Iterator
(_
,_
,ei
) -> bind (k e
) (end_info ei
)
515 V.combiner
bind option_default
516 mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode
517 donothing donothing donothing donothing donothing
518 donothing donothing donothing donothing donothing donothing donothing
519 rule_elem statement donothing donothing donothing
521 (* ------------------------------------------------------------------------ *)
523 (* true means the rule should be analyzed, false means it should be ignored *)
524 let rec dependencies env
= function
525 Ast.Dep s
-> (try List.assoc s env
with Not_found
-> False
)
526 | Ast.AntiDep s
-> True
527 | Ast.EverDep s
-> (try List.assoc s env
with Not_found
-> False
)
528 | Ast.NeverDep s
-> True
529 | Ast.AndDep
(d1
,d2
) -> build_and (dependencies env d1
) (dependencies env d2
)
530 | Ast.OrDep
(d1
,d2
) -> build_or
(dependencies env d1
) (dependencies env d2
)
532 | Ast.FailDep
-> False
534 (* ------------------------------------------------------------------------ *)
537 let bind x y
= x
&& y
in
538 let option_default = true in
540 let donothing recursor k e
= k e
in
542 let process_mcodekind = function
543 Ast.CONTEXT
(_
,Ast.NOTHING
) -> true
546 let mcode r e
= process_mcodekind (Ast.get_mcodekind e
) in
548 let end_info (_
,_
,_
,mc
) = process_mcodekind mc
in
550 let initialiser r k e
=
551 match Ast.unwrap e
with
552 Ast.StrInitList
(all_minus
,_
,_
,_
,_
) ->
556 let rule_elem r k e
=
557 match Ast.unwrap e
with
558 Ast.FunHeader
(bef
,_
,_
,_
,_
,_
,_
)
559 | Ast.Decl
(bef
,_
,_
) -> bind (process_mcodekind bef
) (k e
)
562 let statement r k e
=
563 match Ast.unwrap e
with
564 Ast.IfThen
(_
,_
,ei
) | Ast.IfThenElse
(_
,_
,_
,_
,ei
)
565 | Ast.While
(_
,_
,ei
) | Ast.For
(_
,_
,ei
)
566 | Ast.Iterator
(_
,_
,ei
) -> bind (k e
) (end_info ei
)
569 V.combiner
bind option_default
570 mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode mcode
571 donothing donothing donothing donothing donothing
572 donothing donothing donothing donothing initialiser donothing
573 donothing rule_elem statement donothing donothing donothing
575 (* ------------------------------------------------------------------------ *)
577 let rule_fn tls in_plus env neg_pos
=
579 (function (rest_info
,in_plus
) ->
580 function (cur
,neg_pos
) ->
582 let getter = do_get_constants keep drop env neg_pos
in
583 getter.V.combiner_top_level cur
in
586 then [] (* nothing removed for sgrep *)
587 else (get_all_constants true).V.combiner_top_level cur
in
588 let plusses = get_plus_constants.V.combiner_top_level cur
in
589 (* the following is for eg -foo(2) +foo(x) then in another rule
590 -foo(10); don't want to consider that foo is guaranteed to be
591 created by the rule. not sure this works completely: what if foo is
592 in both - and +, but in an or, so the cases aren't related?
593 not sure this whole thing is a good idea. how do we know that
594 something that is only in plus is really freshly created? *)
595 let plusses = Common.minus_set plusses all_minuses in
596 let was_bot = minuses = True
in
597 let new_minuses = filter_combine minuses in_plus
in
598 let new_plusses = Common.union_set
plusses in_plus
in
599 (* perhaps it should be build_and here? we don't realy have multiple
600 minirules anymore anyway. *)
601 match new_minuses with
603 let getter = do_get_constants drop keep env neg_pos
in
604 let retry = getter.V.combiner_top_level cur
in
606 True
when not
was_bot -> (rest_info
, new_plusses)
607 | x
-> (build_or x rest_info
, new_plusses))
608 | x
-> (build_or x rest_info
, new_plusses))
609 (False
,in_plus
) (List.combine tls neg_pos
)
611 let run rules neg_pos_vars
=
614 (function (rest_info
,in_plus
,env
,locals
(*dom of env*)) ->
616 (Ast.ScriptRule
(nm
,_
,deps
,mv
,_
,_
),_
) ->
620 function (_
,(rule
,_
),_
) ->
623 else Ast.AndDep
(Ast.Dep rule
,prev
))
625 (match dependencies env
extra_deps with
626 False
-> (rest_info
, in_plus
, (nm
,True
)::env
, nm
::locals
)
628 (build_or
dependencies rest_info
, in_plus
, env
, locals
))
629 | (Ast.InitialScriptRule
(_
,_
,deps
,_
),_
)
630 | (Ast.FinalScriptRule
(_
,_
,deps
,_
),_
) ->
631 (* initialize and finalize dependencies are irrelevant to
633 (rest_info
, in_plus
, env
, locals
)
634 | (Ast.CocciRule
(nm
,(dep
,_
,_
),cur
,_
,_
),neg_pos_vars
) ->
635 let (cur_info
,cur_plus
) =
636 rule_fn cur in_plus
((nm
,True
)::env
)
638 (match dependencies env dep
with
639 False
-> (rest_info
,cur_plus
,env
,locals
)
641 if List.for_all
all_context.V.combiner_top_level cur
642 then (rest_info
,cur_plus
,(nm
,cur_info
)::env
,nm
::locals
)
644 (* no constants if dependent on another rule; then we need to
645 find the constants of that rule *)
646 (build_or
(build_and dependencies cur_info
) rest_info
,
647 cur_plus
,env
,locals
)))
649 (List.combine
(rules
: Ast.rule list
) neg_pos_vars
) in
652 let get_constants rules neg_pos_vars
=
653 match !Flag.scanner
with
654 Flag.NoScanner
-> (None
,None
,None
)
656 let res = run rules neg_pos_vars
in
657 (interpret_grep true res,None
,None
)
659 let res = run rules neg_pos_vars
in
660 (interpret_grep true res,interpret_glimpse true res,None
)
662 let res = run rules neg_pos_vars
in
663 (interpret_grep true res,interpret_google true res,None
)
665 let res = run rules neg_pos_vars
in
666 (interpret_grep true res,None
,Some
res)