summaryrefslogtreecommitdiff
path: root/src/diff_tree.ml
diff options
context:
space:
mode:
Diffstat (limited to 'src/diff_tree.ml')
-rw-r--r--src/diff_tree.ml169
1 files changed, 169 insertions, 0 deletions
diff --git a/src/diff_tree.ml b/src/diff_tree.ml
new file mode 100644
index 0000000..d592c90
--- /dev/null
+++ b/src/diff_tree.ml
@@ -0,0 +1,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 ?(recurse=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"]) ~recurse:false ~set_values:(Some []) res.left res.del path; }
+ | Unchanged ->
+ {res with inter = (Config_tree.clone[@alert "-exn"]) ~recurse:recurse 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