3fa2698b9e7b60a56b98390ac6dea502474da507
[tatoo.git] / src / ata.ml
1 (***********************************************************************)
2 (*                                                                     *)
3 (*                               TAToo                                 *)
4 (*                                                                     *)
5 (*                     Kim Nguyen, LRI UMR8623                         *)
6 (*                   Université Paris-Sud & CNRS                       *)
7 (*                                                                     *)
8 (*  Copyright 2010-2013 Université Paris-Sud and Centre National de la *)
9 (*  Recherche Scientifique. All rights reserved.  This file is         *)
10 (*  distributed under the terms of the GNU Lesser General Public       *)
11 (*  License, with the special exception on linking described in file   *)
12 (*  ../LICENSE.                                                        *)
13 (*                                                                     *)
14 (***********************************************************************)
15
16 INCLUDE "utils.ml"
17 open Format
18 type move = [ `First_child
19             | `Next_sibling
20             | `Parent
21             | `Previous_sibling
22             | `Stay ]
23
24 type predicate = Move of move * State.t
25                  | Is_first_child
26                  | Is_next_sibling
27                  | Is of Tree.NodeKind.t
28                  | Has_first_child
29                  | Has_next_sibling
30
31 let is_move = function Move _ -> true | _ -> false
32
33 module Atom : (Boolean.ATOM with type data = predicate) =
34 struct
35
36   module Node =
37   struct
38     type t = predicate
39     let equal n1 n2 = n1 = n2
40     let hash n = Hashtbl.hash n
41   end
42
43   include Hcons.Make(Node)
44
45   let print ppf a =
46     match a.node with
47     | Move (m, q) -> begin
48       match m with
49         `First_child -> fprintf ppf "%s" Pretty.down_arrow
50       | `Next_sibling -> fprintf ppf "%s" Pretty.right_arrow
51       | `Parent -> fprintf ppf "%s" Pretty.up_arrow
52       | `Previous_sibling -> fprintf ppf "%s" Pretty.left_arrow
53       | `Stay -> fprintf ppf "%s" Pretty.bullet
54     end;
55         fprintf ppf "%a" State.print q
56     | Is_first_child -> fprintf ppf "%s?" Pretty.up_arrow
57     | Is_next_sibling -> fprintf ppf "%s?" Pretty.left_arrow
58     | Is k -> fprintf ppf "is-%a?" Tree.NodeKind.print k
59     | Has_first_child -> fprintf ppf "%s?" Pretty.down_arrow
60     | Has_next_sibling -> fprintf ppf "%s?" Pretty.right_arrow
61
62 end
63
64 module Formula =
65 struct
66   include Boolean.Make(Atom)
67   open Tree.NodeKind
68   let mk_atom a = atom_ (Atom.make a)
69   let mk_kind k = mk_atom (Is k)
70
71   let has_first_child = mk_atom Has_first_child
72
73   let has_next_sibling = mk_atom Has_next_sibling
74
75   let is_first_child = mk_atom Is_first_child
76
77   let is_next_sibling = mk_atom Is_next_sibling
78
79   let is_attribute = mk_atom (Is Attribute)
80
81   let is_element = mk_atom (Is Element)
82
83   let is_processing_instruction = mk_atom (Is ProcessingInstruction)
84
85   let is_comment = mk_atom (Is Comment)
86
87   let mk_move m q = mk_atom (Move(m,q))
88   let first_child q =
89     and_
90       (mk_move `First_child q)
91       has_first_child
92
93   let next_sibling q =
94   and_
95     (mk_move `Next_sibling q)
96     has_next_sibling
97
98   let parent q =
99   and_
100     (mk_move `Parent  q)
101     is_first_child
102
103   let previous_sibling q =
104   and_
105     (mk_move `Previous_sibling q)
106     is_next_sibling
107
108   let stay q = mk_move `Stay q
109
110   let get_states phi =
111     fold (fun phi acc ->
112       match expr phi with
113       | Boolean.Atom ({ Atom.node = Move(_,q) ; _ }, _) ->  StateSet.add q acc
114       | _ -> acc
115     ) phi StateSet.empty
116
117 end
118
119
120 module Transition = Hcons.Make (struct
121   type t = State.t * QNameSet.t * Formula.t
122   let equal (a, b, c) (d, e, f) =
123     a == d && b == e && c == f
124   let hash (a, b, c) =
125     HASHINT4 (PRIME1, a, ((QNameSet.uid b) :> int), ((Formula.uid c) :> int))
126 end)
127
128
129 module TransList : sig
130   include Hlist.S with type elt = Transition.t
131   val print : Format.formatter -> ?sep:string -> t -> unit
132 end =
133   struct
134     include Hlist.Make(Transition)
135     let print ppf ?(sep="\n") l =
136       iter (fun t ->
137         let q, lab, f = Transition.node t in
138         fprintf ppf "%a, %a -> %a%s" State.print q QNameSet.print lab Formula.print f sep) l
139   end
140
141
142
143 type node_summary = int
144 let dummy_summary = -1
145 (*
146 4444444444443210
147 4 -> kind
148 3 -> is_left
149 2 -> is_right
150 1 -> has_left
151 0 -> has_right
152 *)
153
154 let has_right (s : node_summary) : bool =
155   Obj.magic (s land 1)
156 let has_left (s : node_summary) : bool =
157   Obj.magic ((s lsr 1) land 1)
158
159 let is_right (s : node_summary) : bool =
160   Obj.magic ((s lsr 2) land 1)
161
162 let is_left (s : node_summary) : bool =
163   Obj.magic ((s lsr 3) land 1)
164
165 let kind (s : node_summary ) : Tree.NodeKind.t =
166   Obj.magic (s lsr 4)
167
168 let node_summary is_left is_right has_left has_right kind =
169   ((Obj.magic kind) lsl 4) lor
170     ((Obj.magic is_left) lsl 3) lor
171     ((Obj.magic is_right) lsl 2) lor
172     ((Obj.magic has_left) lsl 1) lor
173     (Obj.magic has_right)
174
175
176
177 type config = {
178   sat : StateSet.t;
179   unsat : StateSet.t;
180   todo : TransList.t;
181   summary : node_summary;
182 }
183
184 module Config = Hcons.Make(struct
185   type t = config
186   let equal c d =
187     c == d ||
188       c.sat == d.sat &&
189       c.unsat == d.unsat &&
190       c.todo == d.todo &&
191       c.summary == d.summary
192
193   let hash c =
194     HASHINT4((c.sat.StateSet.id :> int),
195              (c.unsat.StateSet.id :> int),
196              (c.todo.TransList.id :> int),
197              c.summary)
198 end
199 )
200
201 type t = {
202   id : Uid.t;
203   mutable states : StateSet.t;
204   mutable selection_states: StateSet.t;
205   transitions: (State.t, (QNameSet.t*Formula.t) list) Hashtbl.t;
206   mutable cache2 : TransList.t Cache.N2.t;
207   mutable cache4 : Config.t Cache.N4.t;
208 }
209
210 let next = Uid.make_maker ()
211
212 let dummy2 = TransList.cons
213   (Transition.make (State.dummy,QNameSet.empty, Formula.false_))
214   TransList.nil
215
216
217
218 let dummy_config =
219   Config.make { sat = StateSet.empty;
220                 unsat = StateSet.empty;
221                 todo = TransList.nil;
222                 summary = dummy_summary
223               }
224
225
226 let create s ss =
227   let auto = { id = next ();
228                states = s;
229                selection_states = ss;
230                transitions = Hashtbl.create 17;
231                cache2 = Cache.N2.create dummy2;
232                cache4 = Cache.N4.create dummy_config;
233              }
234   in
235   at_exit (fun () ->
236     let n4 = ref 0 in
237     let n2 = ref 0 in
238     Cache.N2.iteri (fun _ _ _ b -> if b then incr n2) auto.cache2;
239     Cache.N4.iteri (fun _ _ _ _ _ b -> if b then incr n4) auto.cache4;
240     Logger.msg `STATS "automaton %i, cache2: %i entries, cache6: %i entries"
241       (auto.id :> int) !n2 !n4;
242     let c2l, c2u = Cache.N2.stats auto.cache2 in
243     let c4l, c4u = Cache.N4.stats auto.cache4 in
244     Logger.msg `STATS
245       "cache2: length: %i, used: %i, occupation: %f"
246       c2l c2u (float c2u /. float c2l);
247     Logger.msg `STATS
248       "cache4: length: %i, used: %i, occupation: %f"
249       c4l c4u (float c4u /. float c4l)
250
251   );
252   auto
253
254 let reset a =
255   a.cache4 <- Cache.N4.create (Cache.N4.dummy a.cache4)
256
257 let full_reset a =
258   reset a;
259   a.cache2 <- Cache.N2.create (Cache.N2.dummy a.cache2)
260
261
262 let get_trans_aux a tag states =
263   StateSet.fold (fun q acc0 ->
264     try
265       let trs = Hashtbl.find a.transitions q in
266       List.fold_left (fun acc1 (labs, phi) ->
267         if QNameSet.mem tag labs then TransList.cons (Transition.make (q, labs, phi)) acc1 else acc1) acc0 trs
268     with Not_found -> acc0
269   ) states TransList.nil
270
271
272 let get_trans a tag states =
273   let trs =
274     Cache.N2.find a.cache2
275       (tag.QName.id :> int) (states.StateSet.id :> int)
276   in
277   if trs == dummy2 then
278     let trs = get_trans_aux a tag states in
279     (Cache.N2.add
280        a.cache2
281        (tag.QName.id :> int)
282        (states.StateSet.id :> int) trs; trs)
283   else trs
284
285 let simplify_atom atom pos q { Config.node=config; _ } =
286   if (pos && StateSet.mem q config.sat)
287     || ((not pos) && StateSet.mem q config.unsat) then Formula.true_
288   else if (pos && StateSet.mem q config.unsat)
289       || ((not pos) && StateSet.mem q config.sat) then Formula.false_
290   else atom
291
292 let eval_form phi fcs nss ps ss summary =
293   let rec loop phi =
294     begin match Formula.expr phi with
295       Boolean.True | Boolean.False -> phi
296     | Boolean.Atom (a, b) ->
297         begin
298           match a.Atom.node with
299           | Move (m, q) -> 
300               let states = match m with
301                 `First_child -> fcs
302               | `Next_sibling -> nss
303               | `Parent | `Previous_sibling -> ps
304               | `Stay -> ss
305               in simplify_atom phi b q states
306           | Is_first_child -> Formula.of_bool (b == (is_left summary))
307           | Is_next_sibling -> Formula.of_bool (b == (is_right summary))
308           | Is k -> Formula.of_bool (b == (k == (kind summary)))
309           | Has_first_child -> Formula.of_bool (b == (has_left summary))
310           | Has_next_sibling -> Formula.of_bool (b == (has_right summary))
311         end
312     | Boolean.And(phi1, phi2) -> Formula.and_ (loop phi1) (loop phi2)
313     | Boolean.Or (phi1, phi2) -> Formula.or_  (loop phi1) (loop phi2)
314     end
315   in
316   loop phi
317
318
319
320 let eval_trans auto fcs nss ps ss =
321   let fcsid = (fcs.Config.id :> int) in
322   let nssid = (nss.Config.id :> int) in
323   let psid = (ps.Config.id :> int) in
324   let rec loop old_config =
325     let oid = (old_config.Config.id :> int) in
326     let res =
327       let res = Cache.N4.find auto.cache4 oid fcsid nssid psid in
328       if res != dummy_config then res
329       else
330         let { sat = old_sat;
331               unsat = old_unsat;
332               todo = old_todo;
333               summary = old_summary } = old_config.Config.node
334         in
335         let sat, unsat, removed, kept, todo =
336           TransList.fold
337             (fun trs acc ->
338               let q, lab, phi = Transition.node trs in
339               let a_sat, a_unsat, a_rem, a_kept, a_todo = acc in
340               if StateSet.mem q a_sat || StateSet.mem q a_unsat then acc else
341                 let new_phi =
342                   eval_form phi fcs nss ps old_config old_summary
343                 in
344                 if Formula.is_true new_phi then
345                   StateSet.add q a_sat, a_unsat, StateSet.add q a_rem, a_kept, a_todo
346                 else if Formula.is_false new_phi then
347                   a_sat, StateSet.add q a_unsat, StateSet.add q a_rem, a_kept, a_todo
348                 else
349                   let new_tr = Transition.make (q, lab, new_phi) in
350                   (a_sat, a_unsat, a_rem, StateSet.add q a_kept, (TransList.cons new_tr a_todo))
351             ) old_todo (old_sat, old_unsat, StateSet.empty, StateSet.empty, TransList.nil)
352         in
353         (* States that have been removed from the todo list and not kept are now
354            unsatisfiable *)
355         let unsat = StateSet.union unsat (StateSet.diff removed kept) in
356         (* States that were found once to be satisfiable remain so *)
357         let unsat = StateSet.diff unsat sat in
358         let new_config = Config.make { old_config.Config.node with sat; unsat; todo; } in
359         Cache.N4.add auto.cache4 oid fcsid nssid psid new_config;
360         new_config
361     in
362     if res == old_config then res else loop res
363   in
364   loop ss
365
366 (*
367   [add_trans a q labels f] adds a transition [(q,labels) -> f] to the
368   automaton [a] but ensures that transitions remains pairwise disjoint
369 *)
370
371 let add_trans a q s f =
372   let trs = try Hashtbl.find a.transitions q with Not_found -> [] in
373   let cup, ntrs =
374     List.fold_left (fun (acup, atrs) (labs, phi) ->
375       let lab1 = QNameSet.inter labs s in
376       let lab2 = QNameSet.diff labs s in
377       let tr1 =
378         if QNameSet.is_empty lab1 then []
379         else [ (lab1, Formula.or_ phi f) ]
380       in
381       let tr2 =
382         if QNameSet.is_empty lab2 then []
383         else [ (lab2, Formula.or_ phi f) ]
384       in
385       (QNameSet.union acup labs, tr1@ tr2 @ atrs)
386     ) (QNameSet.empty, []) trs
387   in
388   let rem = QNameSet.diff s cup in
389   let ntrs = if QNameSet.is_empty rem then ntrs
390     else (rem, f) :: ntrs
391   in
392   Hashtbl.replace a.transitions q ntrs
393
394 let _pr_buff = Buffer.create 50
395 let _str_fmt = formatter_of_buffer _pr_buff
396 let _flush_str_fmt () = pp_print_flush _str_fmt ();
397   let s = Buffer.contents _pr_buff in
398   Buffer.clear _pr_buff; s
399
400 let print fmt a =
401   fprintf fmt
402     "Internal UID: %i@\n\
403      States: %a@\n\
404      Selection states: %a@\n\
405      Alternating transitions:@\n"
406     (a.id :> int)
407     StateSet.print a.states
408     StateSet.print a.selection_states;
409   let trs =
410     Hashtbl.fold
411       (fun q t acc -> List.fold_left (fun acc (s , f) -> (q,s,f)::acc) acc t)
412       a.transitions
413       []
414   in
415   let sorted_trs = List.stable_sort (fun (q1, s1, _) (q2, s2, _) ->
416     let c = State.compare q1 q2 in - (if c == 0 then QNameSet.compare s1 s2 else c))
417     trs
418   in
419   let _ = _flush_str_fmt () in
420   let strs_strings, max_pre, max_all = List.fold_left (fun (accl, accp, acca) (q, s, f) ->
421     let s1 = State.print _str_fmt q; _flush_str_fmt () in
422     let s2 = QNameSet.print _str_fmt s;  _flush_str_fmt () in
423     let s3 = Formula.print _str_fmt f;  _flush_str_fmt () in
424     let pre = Pretty.length s1 + Pretty.length s2 in
425     let all = Pretty.length s3 in
426     ( (q, s1, s2, s3) :: accl, max accp pre, max acca all)
427   ) ([], 0, 0) sorted_trs
428   in
429   let line = Pretty.line (max_all + max_pre + 6) in
430   let prev_q = ref State.dummy in
431   fprintf fmt "%s@\n" line;
432   List.iter (fun (q, s1, s2, s3) ->
433     if !prev_q != q && !prev_q != State.dummy then fprintf fmt "%s@\n"  line;
434     prev_q := q;
435     fprintf fmt "%s, %s" s1 s2;
436     fprintf fmt "%s" (Pretty.padding (max_pre - Pretty.length s1 - Pretty.length s2));
437     fprintf fmt " %s  %s@\n" Pretty.right_arrow s3;
438   ) strs_strings;
439   fprintf fmt "%s@\n" line
440
441 (*
442   [complete transitions a] ensures that for each state q
443   and each symbols s in the alphabet, a transition q, s exists.
444   (adding q, s -> F when necessary).
445 *)
446
447 let complete_transitions a =
448   StateSet.iter (fun q ->
449     let qtrans = Hashtbl.find a.transitions q in
450     let rem =
451       List.fold_left (fun rem (labels, _) ->
452         QNameSet.diff rem labels) QNameSet.any qtrans
453     in
454     let nqtrans =
455       if QNameSet.is_empty rem then qtrans
456       else
457         (rem, Formula.false_) :: qtrans
458     in
459     Hashtbl.replace a.transitions q nqtrans
460   ) a.states
461
462 let cleanup_states a =
463   let memo = ref StateSet.empty in
464   let rec loop q =
465     if not (StateSet.mem q !memo) then begin
466       memo := StateSet.add q !memo;
467       let trs = try Hashtbl.find a.transitions q with Not_found -> [] in
468       List.iter (fun (_, phi) ->
469         StateSet.iter loop (Formula.get_states phi)) trs
470     end
471   in
472   StateSet.iter loop a.selection_states;
473   let unused = StateSet.diff a.states !memo in
474   StateSet.iter (fun q -> Hashtbl.remove a.transitions q) unused;
475   a.states <- !memo
476
477 (* [normalize_negations a] removes negative atoms in the formula
478    complementing the sub-automaton in the negative states.
479    [TODO check the meaning of negative upward arrows]
480 *)
481
482 let normalize_negations auto =
483   let memo_state = Hashtbl.create 17 in
484   let todo = Queue.create () in
485   let rec flip b f =
486     match Formula.expr f with
487       Boolean.True | Boolean.False -> if b then f else Formula.not_ f
488     | Boolean.Or(f1, f2) -> (if b then Formula.or_ else Formula.and_)(flip b f1) (flip b f2)
489     | Boolean.And(f1, f2) -> (if b then Formula.and_ else Formula.or_)(flip b f1) (flip b f2)
490     | Boolean.Atom(a, b') -> begin
491       match a.Atom.node with
492       | Move (m,  q) ->
493           if b == b' then begin
494           (* a appears positively, either no negation or double negation *)
495             if not (Hashtbl.mem memo_state (q,b)) then Queue.add (q,true) todo;
496             Formula.mk_atom (Move(m, q))
497           end else begin
498         (* need to reverse the atom
499            either we have a positive state deep below a negation
500            or we have a negative state in a positive formula
501            b' = sign of the state
502            b = sign of the enclosing formula
503         *)
504             let not_q =
505               try
506             (* does the inverted state of q exist ? *)
507                 Hashtbl.find memo_state (q, false)
508               with
509                 Not_found ->
510               (* create a new state and add it to the todo queue *)
511                   let nq = State.make () in
512                   auto.states <- StateSet.add nq auto.states;
513                   Hashtbl.add memo_state (q, false) nq;
514                   Queue.add (q, false) todo; nq
515             in
516             Formula.mk_atom (Move (m,not_q))
517           end
518       | _ -> if b then f else Formula.not_ f
519     end
520   in
521   (* states that are not reachable from a selection stat are not interesting *)
522   StateSet.iter (fun q -> Queue.add (q, true) todo) auto.selection_states;
523
524   while not (Queue.is_empty todo) do
525     let (q, b) as key = Queue.pop todo in
526     let q' =
527       try
528         Hashtbl.find memo_state key
529       with
530         Not_found ->
531           let nq = if b then q else
532               let nq = State.make () in
533               auto.states <- StateSet.add nq auto.states;
534               nq
535           in
536           Hashtbl.add memo_state key nq; nq
537     in
538     let trans = Hashtbl.find auto.transitions q in
539     let trans' = List.map (fun (lab, f) -> lab, flip b f) trans in
540     Hashtbl.replace auto.transitions q' trans';
541   done;
542   cleanup_states auto