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