Add a new option to choose tree model at runtime.
[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 open Misc
19 type move = [ `First_child
20             | `Next_sibling
21             | `Parent
22             | `Previous_sibling
23             | `Stay ]
24
25 module Move =
26   struct
27     type t = move
28     type 'a table = 'a array
29     let idx = function
30       | `First_child -> 0
31       | `Next_sibling -> 1
32       | `Parent -> 2
33       | `Previous_sibling -> 3
34       | `Stay -> 4
35     let ridx = function
36       | 0 -> `First_child
37       | 1 -> `Next_sibling
38       | 2 -> `Parent
39       | 3 -> `Previous_sibling
40       | 4 -> `Stay
41       | _ -> assert false
42
43     let create_table a = Array.make 5 a
44     let get m k = m.(idx k)
45     let set m k v = m.(idx k) <- v
46     let iter f m = Array.iteri (fun i v -> f (ridx i) v) m
47     let fold f m acc =
48       let acc = ref acc in
49       iter (fun i v -> acc := f i v !acc) m;
50       !acc
51     let for_all p m =
52       try
53         iter (fun i v -> if not (p i v) then raise Exit) m;
54         true
55       with
56         Exit -> false
57     let for_all2 p m1 m2 =
58       try
59         for i = 0 to 4 do
60           let v1 = m1.(i)
61           and v2 = m2.(i) in
62           if not (p (ridx i) v1 v2) then raise Exit
63         done;
64         true
65       with
66         Exit -> false
67
68     let exists p m =
69       try
70         iter (fun i v -> if p i v then raise Exit) m;
71         false
72       with
73         Exit -> true
74     let print ppf m =
75       match m with
76         `First_child -> fprintf ppf "%s" Pretty.down_arrow
77       | `Next_sibling -> fprintf ppf "%s" Pretty.right_arrow
78       | `Parent -> fprintf ppf "%s" Pretty.up_arrow
79       | `Previous_sibling -> fprintf ppf "%s" Pretty.left_arrow
80       | `Stay -> fprintf ppf "%s" Pretty.bullet
81
82     let print_table pr_e ppf m =
83       iter (fun i v -> fprintf ppf "%a: %a" print i pr_e v;
84         if (idx i) < 4 then fprintf ppf ", ") m
85   end
86
87 type predicate = Move of move * State.t
88                  | Is_first_child
89                  | Is_next_sibling
90                  | Is of Tree.NodeKind.t
91                  | Has_first_child
92                  | Has_next_sibling
93
94 module Atom =
95 struct
96
97   module Node =
98   struct
99     type t = predicate
100     let equal n1 n2 = n1 = n2
101     let hash n = Hashtbl.hash n
102   end
103
104   include Hcons.Make(Node)
105
106   let print ppf a =
107     match a.node with
108     | Move (m, q) ->
109       fprintf ppf "%a%a" Move.print m State.print q
110     | Is_first_child -> fprintf ppf "%s?" Pretty.up_arrow
111     | Is_next_sibling -> fprintf ppf "%s?" Pretty.left_arrow
112     | Is k -> fprintf ppf "is-%a?" Tree.NodeKind.print k
113     | Has_first_child -> fprintf ppf "%s?" Pretty.down_arrow
114     | Has_next_sibling -> fprintf ppf "%s?" Pretty.right_arrow
115
116 end
117
118
119 module Formula =
120 struct
121   include Boolean.Make(Atom)
122   open Tree.NodeKind
123   let mk_atom a = atom_ (Atom.make a)
124   let is k = mk_atom (Is k)
125
126   let has_first_child = mk_atom Has_first_child
127
128   let has_next_sibling = mk_atom Has_next_sibling
129
130   let is_first_child = mk_atom Is_first_child
131
132   let is_next_sibling = mk_atom Is_next_sibling
133
134   let is_attribute = mk_atom (Is Attribute)
135
136   let is_element = mk_atom (Is Element)
137
138   let is_processing_instruction = mk_atom (Is ProcessingInstruction)
139
140   let is_comment = mk_atom (Is Comment)
141
142   let mk_move m q = mk_atom (Move(m,q))
143   let first_child q =
144     and_
145       (mk_move `First_child q)
146       has_first_child
147
148   let next_sibling q =
149   and_
150     (mk_move `Next_sibling q)
151     has_next_sibling
152
153   let parent q =
154   and_
155     (mk_move `Parent  q)
156     is_first_child
157
158   let previous_sibling q =
159   and_
160     (mk_move `Previous_sibling q)
161     is_next_sibling
162
163   let stay q = mk_move `Stay q
164
165   let get_states_by_move phi =
166     let table = Move.create_table StateSet.empty in
167     iter (fun phi ->
168       match expr phi with
169       | Boolean.Atom ({ Atom.node = Move(v,q) ; _ }, _) ->
170         let s = Move.get table v in
171         Move.set table v (StateSet.add q s)
172       | _ -> ()
173     ) phi;
174     table
175   let get_states phi =
176     let table = get_states_by_move phi in
177     Move.fold (fun _ s acc -> StateSet.union s acc) table StateSet.empty
178
179   let rec rename_state phi qfrom qto =
180     let open Boolean in
181     match expr phi with
182       False | True -> phi
183     | Or (phi1, phi2) -> or_ (rename_state phi1 qfrom qto)  (rename_state phi2 qfrom qto)
184     | And (phi1, phi2) ->  and_ (rename_state phi1 qfrom qto)  (rename_state phi2 qfrom qto)
185     | Atom ({ Atom.node = Move(m, q); }, b) when q == qfrom ->
186       let atm = mk_move m qto in if b then atm else not_ atm
187     | Atom _ -> phi
188 end
189
190 module Transition =
191   struct
192     include Hcons.Make (struct
193   type t = State.t * QNameSet.t * Formula.t
194   let equal (a, b, c) (d, e, f) =
195     a == d && b == e && c == f
196   let hash ((a, b, c) : t) =
197     HASHINT4 (PRIME1, ((a) :> int), ((QNameSet.uid b) :> int), ((Formula.uid c) :> int))
198 end)
199     let print ppf t =
200       let q, l, f = t.node in
201       fprintf ppf "%a, %a %s %a"
202         State.print q
203         QNameSet.print l
204         Pretty.left_arrow
205         Formula.print f
206   end
207
208
209 module TransList : sig
210   include Hlist.S with type elt = Transition.t
211   val print : ?sep:string -> Format.formatter -> t -> unit
212 end =
213   struct
214     include Hlist.Make(Transition)
215     let print ?(sep="\n") ppf l =
216       iter (fun t ->
217           fprintf ppf "%a%s" Transition.print t sep) l
218   end
219
220
221
222 type t = {
223   id : Uid.t;
224   mutable states : StateSet.t;
225   mutable starting_states : StateSet.t;
226   mutable selecting_states: StateSet.t;
227   transitions: (State.t, (QNameSet.t*Formula.t) list) Hashtbl.t;
228   mutable ranked_states : StateSet.t array
229 }
230
231 let uid t = t.id
232
233 let get_states a = a.states
234 let get_starting_states a = a.starting_states
235 let get_selecting_states a = a.selecting_states
236 let get_states_by_rank a = a.ranked_states
237 let get_max_rank a = Array.length a.ranked_states - 1
238
239 let _pr_buff = Buffer.create 50
240 let _str_fmt = formatter_of_buffer _pr_buff
241 let _flush_str_fmt () = pp_print_flush _str_fmt ();
242   let s = Buffer.contents _pr_buff in
243   Buffer.clear _pr_buff; s
244
245 let print fmt a =
246   let _ = _flush_str_fmt() in
247   fprintf fmt
248     "Internal UID: %i@\n\
249      States: %a@\n\
250      Number of states: %i@\n\
251      Starting states: %a@\n\
252      Selection states: %a@\n\
253      Ranked states: %a@\n\
254      Alternating transitions:@\n"
255     (a.id :> int)
256     StateSet.print a.states
257     (StateSet.cardinal a.states)
258     StateSet.print a.starting_states
259     StateSet.print a.selecting_states
260     (let r = ref 0 in Pretty.print_array ~sep:", " (fun ppf s ->
261       fprintf ppf "%i:%a" !r StateSet.print s; incr r)) a.ranked_states;
262   let trs =
263     Hashtbl.fold
264       (fun q t acc -> List.fold_left (fun acc (s , f) -> (q,s,f)::acc) acc t)
265       a.transitions
266       []
267   in
268   let sorted_trs = List.stable_sort (fun (q1, s1, _) (q2, s2, _) ->
269     let c = State.compare q2 q1 in if c == 0 then QNameSet.compare s2 s1 else c)
270     trs
271   in
272   let _ = _flush_str_fmt () in
273   let strs_strings, max_pre, max_all =
274     List.fold_left (fun (accl, accp, acca) (q, s, f) ->
275       let s1 = State.print _str_fmt q; _flush_str_fmt () in
276       let s2 = QNameSet.print _str_fmt s;  _flush_str_fmt () in
277       let s3 = Formula.print _str_fmt f;  _flush_str_fmt () in
278       let pre = Pretty.length s1 + Pretty.length s2 in
279       let all = Pretty.length s3 in
280       ( (q, s1, s2, s3) :: accl, max accp pre, max acca all)
281     ) ([], 0, 0) sorted_trs
282   in
283   let line = Pretty.line (max_all + max_pre + 6) in
284   let prev_q = ref State.dummy_state in
285   fprintf fmt "%s@\n" line;
286   List.iter (fun (q, s1, s2, s3) ->
287     if !prev_q != q && !prev_q != State.dummy_state then fprintf fmt "%s@\n"  line;
288     prev_q := q;
289     fprintf fmt "%s, %s" s1 s2;
290     fprintf fmt "%s"
291       (Pretty.padding (max_pre - Pretty.length s1 - Pretty.length s2));
292     fprintf fmt " %s  %s@\n" Pretty.left_arrow s3;
293   ) strs_strings;
294   fprintf fmt "%s@\n" line
295
296
297 let get_trans a tag states =
298   StateSet.fold (fun q acc0 ->
299     try
300       let trs = Hashtbl.find a.transitions q in
301       List.fold_left (fun acc1 (labs, phi) ->
302         if QNameSet.mem tag labs then
303           TransList.cons (Transition.make (q, labs, phi)) acc1
304         else acc1) acc0 trs
305     with Not_found -> acc0
306   ) states TransList.nil
307
308
309 let get_form a tag q =
310   try
311     let trs = Hashtbl.find a.transitions q in
312     List.fold_left (fun aphi (labs, phi) ->
313       if QNameSet.mem tag labs then Formula.or_ aphi phi else aphi
314     ) Formula.false_ trs
315   with
316     Not_found -> Formula.false_
317
318 (*
319   [complete transitions a] ensures that for each state q
320   and each symbols s in the alphabet, a transition q, s exists.
321   (adding q, s -> F when necessary).
322 *)
323
324 let complete_transitions a =
325   StateSet.iter (fun q ->
326     if StateSet.mem q a.starting_states then ()
327     else
328       let qtrans = Hashtbl.find a.transitions q in
329       let rem =
330         List.fold_left (fun rem (labels, _) ->
331           QNameSet.diff rem labels) QNameSet.any qtrans
332       in
333       let nqtrans =
334         if QNameSet.is_empty rem then qtrans
335         else
336           (rem, Formula.false_) :: qtrans
337       in
338       Hashtbl.replace a.transitions q nqtrans
339   ) a.states
340
341 (* [cleanup_states] remove states that do not lead to a
342    selecting states *)
343
344 let cleanup_states a =
345   let memo = ref StateSet.empty in
346   let rec loop q =
347     if not (StateSet.mem q !memo) then begin
348       memo := StateSet.add q !memo;
349       let trs = try Hashtbl.find a.transitions q with Not_found -> [] in
350       List.iter (fun (_, phi) ->
351         StateSet.iter loop (Formula.get_states phi)) trs
352     end
353   in
354   StateSet.iter loop a.selecting_states;
355   let unused = StateSet.diff a.states !memo in
356   StateSet.iter (fun q -> Hashtbl.remove a.transitions q) unused;
357   a.states <- !memo
358
359 (* [normalize_negations a] removes negative atoms in the formula
360    complementing the sub-automaton in the negative states.
361    [TODO check the meaning of negative upward arrows]
362 *)
363
364 let normalize_negations auto =
365   let memo_state = Hashtbl.create 17 in
366   let todo = Queue.create () in
367   let rec flip b f =
368     match Formula.expr f with
369       Boolean.True | Boolean.False -> if b then f else Formula.not_ f
370     | Boolean.Or(f1, f2) ->
371       (if b then Formula.or_ else Formula.and_)(flip b f1) (flip b f2)
372     | Boolean.And(f1, f2) ->
373       (if b then Formula.and_ else Formula.or_)(flip b f1) (flip b f2)
374     | Boolean.Atom(a, b') -> begin
375       match a.Atom.node with
376       | Move (m,  q) ->
377           if b == b' then begin
378           (* a appears positively, either no negation or double negation *)
379             if not (Hashtbl.mem memo_state (q,b)) then Queue.add (q,true) todo;
380             Formula.mk_atom (Move(m, q))
381           end else begin
382         (* need to reverse the atom
383            either we have a positive state deep below a negation
384            or we have a negative state in a positive formula
385            b' = sign of the state
386            b = sign of the enclosing formula
387         *)
388             let not_q =
389               try
390             (* does the inverted state of q exist ? *)
391                 Hashtbl.find memo_state (q, false)
392               with
393                 Not_found ->
394               (* create a new state and add it to the todo queue *)
395                   let nq = State.next () in
396                   auto.states <- StateSet.add nq auto.states;
397                   Hashtbl.add memo_state (q, false) nq;
398                   Queue.add (q, false) todo; nq
399             in
400             Formula.mk_atom (Move (m,not_q))
401           end
402       | _ -> if b then f else Formula.not_ f
403     end
404   in
405   (* states that are not reachable from a selection stat are not interesting *)
406   StateSet.iter (fun q -> Queue.add (q, true) todo) auto.selecting_states;
407
408   while not (Queue.is_empty todo) do
409     let (q, b) as key = Queue.pop todo in
410     if not (StateSet.mem q auto.starting_states) then
411       let q' =
412         try
413           Hashtbl.find memo_state key
414         with
415           Not_found ->
416             let nq = if b then q else
417                 let nq = State.next () in
418                 auto.states <- StateSet.add nq auto.states;
419                 nq
420             in
421             Hashtbl.add memo_state key nq; nq
422       in
423       let trans = try Hashtbl.find auto.transitions q with Not_found -> [] in
424       let trans' = List.map (fun (lab, f) -> lab, flip b f) trans in
425       Hashtbl.replace auto.transitions q' trans';
426   done;
427   cleanup_states auto
428
429 exception Found of State.t * State.t
430
431 let simplify_epsilon auto =
432   let rec loop old_states =
433     if old_states != auto.states then begin
434       let old_states = auto.states in
435       try
436         Hashtbl.iter
437           (fun qfrom v -> match v with
438                [ (labels, phi) ] ->
439                if labels == QNameSet.any then begin
440                  match (Formula.expr phi) with
441                    Boolean.Atom ( {Atom.node = Move(`Stay, qto); _ }, true) -> raise (Found (qfrom, qto))
442                  | _ -> ()
443                end
444              | _ -> ()
445           ) auto.transitions
446       with Found (qfrom, qto) ->
447         Hashtbl.remove auto.transitions qfrom;
448         let new_trans = Hashtbl.fold (fun q tr_lst acc ->
449             let new_tr_lst =
450               List.map (fun (lab, phi) ->
451                   (lab, Formula.rename_state phi qfrom qto))
452                 tr_lst
453             in
454             (q, new_tr_lst) :: acc) auto.transitions []
455         in
456         Hashtbl.reset auto.transitions;
457         List.iter (fun (q, l) -> Hashtbl.add auto.transitions q l) new_trans;
458         auto.states <- StateSet.remove qfrom auto.states;
459         if (StateSet.mem qfrom auto.starting_states) then
460           auto.starting_states <- StateSet.add qto (StateSet.remove qfrom auto.starting_states);
461         if (StateSet.mem qfrom auto.selecting_states) then
462           auto.selecting_states <- StateSet.add qto (StateSet.remove qfrom auto.selecting_states);
463         loop old_states
464     end
465   in
466   loop StateSet.empty
467
468
469
470 (* [compute_dependencies auto] returns a hash table storing for each
471    states [q] a Move.table containing the set of states on which [q]
472    depends (loosely). [q] depends on [q'] if there is a transition
473    [q, {...} -> phi], where [q'] occurs in [phi].
474 *)
475 let compute_dependencies auto =
476   let edges = Hashtbl.create 17 in
477   StateSet.iter
478     (fun q -> Hashtbl.add edges q (Move.create_table StateSet.empty))
479     auto.starting_states;
480   Hashtbl.iter (fun q trans ->
481     let moves = try Hashtbl.find edges q with Not_found ->
482       let m = Move.create_table StateSet.empty in
483       Hashtbl.add edges q m;
484       m
485     in
486     List.iter (fun (_, phi) ->
487       let m_phi = Formula.get_states_by_move phi in
488       Move.iter (fun m set ->
489         Move.set moves m (StateSet.union set (Move.get moves m)))
490         m_phi) trans) auto.transitions;
491
492   edges
493
494 let state_prerequisites dir auto q =
495   Hashtbl.fold (fun q' trans acc ->
496     List.fold_left (fun acc (_, phi) ->
497       let m_phi = Formula.get_states_by_move phi in
498       if StateSet.mem q (Move.get m_phi dir)
499       then StateSet.add q' acc else acc)
500       acc trans) auto.transitions StateSet.empty
501
502 let compute_rank auto =
503   let dependencies = compute_dependencies auto in
504   let upward = [ `Stay ; `Parent ; `Previous_sibling ] in
505   let downward = [ `Stay; `First_child; `Next_sibling ] in
506   let swap dir = if dir == upward then downward else upward in
507   let is_satisfied dir q t =
508     Move.for_all (fun d set ->
509       if List.mem d dir then
510         StateSet.(is_empty (remove q set))
511       else StateSet.is_empty set) t
512   in
513   let update_dependencies dir initacc =
514     let rec loop acc =
515       let new_acc =
516         Hashtbl.fold (fun q deps acc ->
517           let to_remove = StateSet.union acc initacc in
518           List.iter
519             (fun m ->
520               Move.set deps m (StateSet.diff (Move.get deps m) to_remove)
521             )
522             dir;
523           if is_satisfied dir q deps then StateSet.add q acc else acc
524         ) dependencies acc
525       in
526       if acc == new_acc then new_acc else loop new_acc
527     in
528     let satisfied = loop StateSet.empty in
529     StateSet.iter (fun q ->
530       Hashtbl.remove dependencies q) satisfied;
531     satisfied
532   in
533   let current_states = ref StateSet.empty in
534   let rank_list = ref [] in
535   let rank = ref 0 in
536   let current_dir = ref upward in
537   let detect_cycle = ref 0 in
538   while Hashtbl.length dependencies != 0 do
539     let new_sat = update_dependencies !current_dir !current_states in
540     if StateSet.is_empty new_sat then incr detect_cycle;
541     if !detect_cycle > 2 then assert false;
542     rank_list := (!rank, new_sat) :: !rank_list;
543     rank := !rank + 1;
544     current_dir := swap !current_dir;
545     current_states := StateSet.union new_sat !current_states;
546   done;
547   let by_rank = Hashtbl.create 17 in
548   List.iter (fun (r,s) ->
549       let set = try Hashtbl.find by_rank r with Not_found -> StateSet.empty in
550       Hashtbl.replace by_rank r (StateSet.union s set)) !rank_list;
551   auto.ranked_states <-
552     Array.init (Hashtbl.length by_rank) (fun i -> Hashtbl.find by_rank i)
553
554
555 module Builder =
556   struct
557     type auto = t
558     type t = auto
559     let next = Uid.make_maker ()
560
561     let make () =
562       let auto =
563         {
564           id = next ();
565           states = StateSet.empty;
566           starting_states = StateSet.empty;
567           selecting_states = StateSet.empty;
568           transitions = Hashtbl.create MED_H_SIZE;
569           ranked_states = [| |]
570         }
571       in
572       auto
573
574     let add_state a ?(starting=false) ?(selecting=false) q =
575       a.states <- StateSet.add q a.states;
576       if starting then a.starting_states <- StateSet.add q a.starting_states;
577       if selecting then a.selecting_states <- StateSet.add q a.selecting_states
578
579     let add_trans a q s f =
580       if not (StateSet.mem q a.states) then add_state a q;
581       let trs = try Hashtbl.find a.transitions q with Not_found -> [] in
582       let cup, ntrs =
583         List.fold_left (fun (acup, atrs) (labs, phi) ->
584           let lab1 = QNameSet.inter labs s in
585           let lab2 = QNameSet.diff labs s in
586           let tr1 =
587             if QNameSet.is_empty lab1 then []
588             else [ (lab1, Formula.or_ phi f) ]
589           in
590           let tr2 =
591             if QNameSet.is_empty lab2 then []
592             else [ (lab2, Formula.or_ phi f) ]
593           in
594           (QNameSet.union acup labs, tr1@ tr2 @ atrs)
595         ) (QNameSet.empty, []) trs
596       in
597       let rem = QNameSet.diff s cup in
598       let ntrs = if QNameSet.is_empty rem then ntrs
599         else (rem, f) :: ntrs
600       in
601       Hashtbl.replace a.transitions q ntrs
602
603
604
605     let finalize a =
606       complete_transitions a;
607       normalize_negations a;
608       simplify_epsilon a;
609       compute_rank a;
610       a
611   end
612
613
614 let map_set f s =
615   StateSet.fold (fun q a -> StateSet.add (f q) a) s StateSet.empty
616
617 let map_hash fk fv h =
618   let h' = Hashtbl.create (Hashtbl.length h) in
619   let () = Hashtbl.iter (fun k v -> Hashtbl.add h' (fk k) (fv v)) h in
620   h'
621
622 let rec map_form f phi =
623   match Formula.expr phi with
624   | Boolean.Or(phi1, phi2) -> Formula.or_ (map_form f phi1) (map_form f phi2)
625   | Boolean.And(phi1, phi2) -> Formula.and_ (map_form f phi1) (map_form f phi2)
626   | Boolean.Atom({ Atom.node = Move(m,q); _}, b) ->
627       let a = Formula.mk_atom (Move (m,f q)) in
628       if b then a else Formula.not_ a
629   | _ -> phi
630
631 let rename_states mapper a =
632   let rename q = try Hashtbl.find mapper q with Not_found -> q in
633   { Builder.make () with
634     states = map_set rename a.states;
635     starting_states = map_set rename a.starting_states;
636     selecting_states = map_set rename a.selecting_states;
637     transitions =
638       map_hash
639         rename
640         (fun l ->
641           (List.map (fun (labels, form) -> (labels, map_form rename form)) l))
642         a.transitions;
643     ranked_states = Array.map (map_set rename) a.ranked_states
644   }
645
646 let copy a =
647   let mapper = Hashtbl.create MED_H_SIZE in
648   let () =
649     StateSet.iter (fun q -> Hashtbl.add mapper q (State.next())) a.states
650   in
651   rename_states mapper a
652
653
654 let concat a1 a2 =
655   let a1 = copy a1 in
656   let a2 = copy a2 in
657   let link_phi =
658     StateSet.fold
659       (fun q phi -> Formula.(or_ (stay q) phi))
660       a1.selecting_states Formula.false_
661   in
662   Hashtbl.iter (fun q trs -> Hashtbl.add a1.transitions q trs)
663     a2.transitions;
664   StateSet.iter
665     (fun q ->
666       Hashtbl.replace a1.transitions q [(QNameSet.any, link_phi)])
667     a2.starting_states;
668   let a = { a1 with
669     states = StateSet.union a1.states a2.states;
670     selecting_states = a2.selecting_states;
671     transitions = a1.transitions;
672   }
673   in compute_rank a; a
674
675 let merge a1 a2 =
676   let a1 = copy a1 in
677   let a2 = copy a2 in
678   let a = { a1 with
679     states = StateSet.union a1.states a2.states;
680     selecting_states = StateSet.union a1.selecting_states a2.selecting_states;
681     starting_states = StateSet.union a1.starting_states a2.starting_states;
682     transitions =
683       let () =
684         Hashtbl.iter (fun k v -> Hashtbl.add a1.transitions k v) a2.transitions
685       in
686       a1.transitions
687   } in
688   compute_rank a ; a
689
690
691 let link a1 a2 q link_phi =
692   let a = { a1 with
693     states = StateSet.union a1.states a2.states;
694     selecting_states = StateSet.singleton q;
695     starting_states = StateSet.union a1.starting_states a2.starting_states;
696     transitions =
697       let () =
698         Hashtbl.iter (fun k v -> Hashtbl.add a1.transitions k v) a2.transitions
699       in
700       Hashtbl.add a1.transitions q [(QNameSet.any, link_phi)];
701       a1.transitions
702   }
703   in
704   compute_rank a; a
705
706 let union a1 a2 =
707   let a1 = copy a1 in
708   let a2 = copy a2 in
709   let q = State.next () in
710   let link_phi =
711     StateSet.fold
712       (fun q phi -> Formula.(or_ (stay q) phi))
713       (StateSet.union a1.selecting_states a2.selecting_states)
714       Formula.false_
715   in
716   link a1 a2 q link_phi
717
718 let inter a1 a2 =
719   let a1 = copy a1 in
720   let a2 = copy a2 in
721   let q = State.next () in
722   let link_phi =
723     StateSet.fold
724       (fun q phi -> Formula.(and_ (stay q) phi))
725       (StateSet.union a1.selecting_states a2.selecting_states)
726       Formula.true_
727   in
728   link a1 a2 q link_phi
729
730 let neg a =
731   let a = copy a in
732   let q = State.next () in
733   let link_phi =
734     StateSet.fold
735       (fun q phi -> Formula.(and_ (not_(stay q)) phi))
736       a.selecting_states
737       Formula.true_
738   in
739   let () = Hashtbl.add a.transitions q [(QNameSet.any, link_phi)] in
740   let a =
741     { a with
742       selecting_states = StateSet.singleton q;
743     }
744   in
745   normalize_negations a; compute_rank a; a
746
747 let diff a1 a2 = inter a1 (neg a2)