summaryrefslogtreecommitdiff
path: root/src/diff_tree.ml
blob: 3cd53031efe96831e467d4976569c13f44c417b2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
open Diff

module ValueS = Config_tree.ValueS

module Diff_tree = struct
    type t = { left: Config_tree.t;
               right: Config_tree.t;
               add: Config_tree.t;
               sub: Config_tree.t;
               del: Config_tree.t;
               inter: Config_tree.t;
             }

    let make_init l r = { left = l;
                            right = r;
                            add = Config_tree.default;
                            sub = Config_tree.default;
                            del = Config_tree.default;
                            inter = Config_tree.default;
                          }

    let diff_func ?(descent=true) (path : string list) res (m : change) =
        (* raises no exception:
            clone will always be called on extant path of left or right
           alert exn Vytree.get_values:
            [Vytree.Empty_path] not possible as pattern Updated implies non-empty path
            [Vytree.Nonexistent_path] not possible as pattern Updated implies path exists
         *)
        match m with
        | Added -> {res with add = (Config_tree.clone[@alert "-exn"]) res.right res.add path; }
        | Subtracted ->
            {res with sub = (Config_tree.clone[@alert "-exn"]) res.left res.sub path;
             del = (Config_tree.clone[@alert "-exn"]) ~descent:false ~set_values:(Some []) res.left res.del path; }
        | Unchanged ->
            {res with inter = (Config_tree.clone[@alert "-exn"]) ~descent:descent res.left res.inter path; }
        | Updated v ->
                (* if in this case, node at path is guaranteed to exist *)
                let ov = (Config_tree.get_values[@alert "-exn"]) res.left path in
                match ov, v with
                | [_], [_] -> {res with sub = (Config_tree.clone[@alert "-exn"]) res.left res.sub path;
                               del = (Config_tree.clone[@alert "-exn"]) res.left res.del path;
                               add = (Config_tree.clone[@alert "-exn"]) res.right res.add path; }
                | _, _ -> let ov_set = ValueS.of_list ov in
                          let v_set = ValueS.of_list v in
                          let sub_vals = ValueS.elements (ValueS.diff ov_set v_set) in
                          let add_vals = ValueS.elements (ValueS.diff v_set ov_set) in
                          let inter_vals = ValueS.elements (ValueS.inter ov_set v_set) in
                          let sub_tree =
                              if not (Util.is_empty sub_vals) then
                                  (Config_tree.clone[@alert "-exn"]) ~set_values:(Some sub_vals) res.left res.sub path
                              else
                                  res.sub
                          in
                          let del_tree =
                              if not (Util.is_empty sub_vals) then
                                  if (Util.is_empty add_vals) && (Util.is_empty inter_vals) then
                                      (* delete whole node, not just values *)
                                      (Config_tree.clone[@alert "-exn"]) ~set_values:(Some []) res.left res.del path
                                  else
                                      (Config_tree.clone[@alert "-exn"]) ~set_values:(Some sub_vals) res.left res.del path
                              else
                                  res.del
                          in
                          let add_tree =
                              if not (Util.is_empty add_vals) then
                                  (Config_tree.clone[@alert "-exn"]) ~set_values:(Some add_vals) res.right res.add path
                              else
                                  res.add
                          in
                          let inter_tree =
                              if not (Util.is_empty inter_vals) then
                                  (Config_tree.clone[@alert "-exn"]) ~set_values:(Some inter_vals) res.left res.inter path
                              else
                                  res.inter
                          in { res with add = add_tree;
                               sub = sub_tree;
                               del = del_tree;
                               inter = inter_tree; }
end

module D = Diff(Diff_tree)

(* get sub trees for path-relative comparison *)

let tree_at_path path node =
    (* raises:
        [Vytree.Empty_path]
        [Empty_comparison]
       alert exn Vytree.get:
        [Vytree.Empty_path] allow raise
        [Vytree.Nonexistent_path] catch and raise Empty_comparison
     *)
    try
        let node = (Vytree.get[@alert "-exn"]) node path in
        Vytree.make_full Config_tree.default_data "" [node]
    with Vytree.Nonexistent_path -> raise Empty_comparison

(* call recursive diff on Diff_tree.t with Diff_tree.diff_func *)

let diff_trees path left right =
    (* raises:
        [Empty_comparison] from tree_at_path
        [Incommensurable]
     *)
    if (Vytree.name_of_node left) <> (Vytree.name_of_node right) then
        raise Incommensurable
    else
        let (left, right) = if not (path = []) then
            (tree_at_path path left, tree_at_path path right) else (left, right) in
        let trees = Diff_tree.make_init left right in
        D.diff trees left right

(* wrapper to return single tree with diff trees as subtrees *)

let diff_tree path left right =
    (* raises:
        [Incommensurable],
        [Empty_comparison] from compare
     *)
    let trees = diff_trees path left right in
    let add_node =
        Vytree.make_full Config_tree.default_data "add" (Vytree.children_of_node (trees.add)) in
    let sub_node =
        Vytree.make_full Config_tree.default_data "sub" (Vytree.children_of_node (trees.sub)) in
    let del_node =
        Vytree.make_full Config_tree.default_data "del" (Vytree.children_of_node (trees.del)) in
    let int_node =
        Vytree.make_full Config_tree.default_data "inter" (Vytree.children_of_node (trees.inter)) in
    Vytree.make_full Config_tree.default_data "" [add_node; sub_node; del_node; int_node]

(* convenience function needed for commit algorithm:
    we need a hybrid tree between the 'del' tree and the 'sub' tree, namely:
    in case the del tree has a terminal tag node (== all tag values have
    been removed) add tag node values for proper removal in commit execution
 *)

let get_tagged_delete_tree dt =
    (* alert exn Config_tree.is_tag:
        [Vytree.Empty_path] not possible in pattern non-empty path
        [Vytree.Nonexistent_path] not possible in fold_tree_with_path
       alert exn Vytree.is_terminal_path:
        [Vytree.Empty_path] not possible in pattern non-empty path
       alert exn Vytree.children_of_path:
        [Vytree.Empty_path] not possible in pattern non-empty path
        [Vytree.Nonexistent_path] not possible in super-tree of fold_tree_with_path arg
       alert exn Vytree.insert:
        [Vytree.Empty_path]: not possible since called on pattern path non-empty
        [Not_found]: not possible for postion=Lexical
        [Vytree.Duplicate_child]: not possible by condition is_terminal_path
        [Vytree.Insert_error]: not possible since constructed iteratively from existing path
     *)
    let del_tree = Config_tree.get_subtree dt ["del"] in
    let sub_tree = Config_tree.get_subtree dt ["sub"] in
    let f (p, a) _t =
        let q = List.rev p in
        match q with
        | [] -> (p, a)
        | _ ->
        if (Config_tree.is_tag[@alert "-exn"]) a q && (Vytree.is_terminal_path[@alert "-exn"]) a q then
            let children = (Vytree.children_of_path[@alert "-exn"]) sub_tree q in
            let insert_child path node name =
                (Vytree.insert[@alert "-exn"]) ~position:Lexical node (path @ [name]) Config_tree.default_data
            in
            let a' = List.fold_left (insert_child q) a children in
            (p, a')
        else
            (p, a)
    in
    Vytree.fold_tree_with_path f ([], del_tree) del_tree