package testo-diff

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file Testo_diff.ml

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
(*
   Translated to OCaml by Gabriel Jaldon from the Python implementation
   by Paul Butler

   Python source:
   https://github.com/paulgb/simplediff/blob/master/python/simplediff/__init__.py
*)

module type Comparable = sig
  type t

  val compare : t -> t -> int
end

module type S = sig
  type item

  type diff =
    | Deleted of item array
    | Added of item array
    | Equal of item array

  type t = diff list

  val get_diff : item array -> item array -> t

  val recover_input : t -> item array * item array
  (** Recover the original input passed to {!get_diff}. *)
end

module Make (Item : Comparable) = struct
  type item = Item.t

  type diff =
    | Deleted of item array
    | Added of item array
    | Equal of item array

  type t = diff list

  type subsequence_info = {
    (* Starting index of longest subsequence in the list of new values *)
    sub_start_new : int;
    (* Starting index of longest subsequence in the list of old values *)
    sub_start_old : int;
    (* The length of the longest subsequence *)
    longest_subsequence : int;
  }

  module CounterMap = Map.Make (Item)

  (* Returns a map with the line as key and a list of indices as value.
     Represents counts of all the lines. *)
  let map_counter keys =
    let keys_and_indices = Array.mapi (fun index key -> (index, key)) keys in
    Array.fold_left
      (fun map (index, key) ->
        let indices =
          try CounterMap.find key map with
          | Not_found -> []
        in
        CounterMap.add key (index :: indices) map)
      CounterMap.empty keys_and_indices

  (* Computes longest subsequence and returns data on the length of longest
     subsequence and the starting index for the longest subsequence in the old
     and new versions. *)
  let get_longest_subsequence old_lines new_lines =
    let prev_overlap = ref (Hashtbl.create 0) in
    let old_values_counter = map_counter old_lines in
    let sub_start_old = ref 0 in
    let sub_start_new = ref 0 in
    let longest_subsequence = ref 0 in

    Array.iteri
      (fun new_index new_value ->
        let indices =
          try CounterMap.find new_value old_values_counter with
          | Not_found -> []
        in
        let overlap = Hashtbl.create 100 in
        List.iter
          (fun old_index ->
            let prev_subsequence =
              try Hashtbl.find !prev_overlap (old_index - 1) with
              | Not_found -> 0
            in
            let new_subsequence = prev_subsequence + 1 in
            Hashtbl.add overlap old_index new_subsequence;

            if new_subsequence > !longest_subsequence then (
              sub_start_old := old_index - new_subsequence + 1;
              sub_start_new := new_index - new_subsequence + 1;
              longest_subsequence := new_subsequence))
          indices;
        prev_overlap := overlap)
      new_lines;

    {
      sub_start_new = !sub_start_new;
      sub_start_old = !sub_start_old;
      longest_subsequence = !longest_subsequence;
    }

  let rec get_diff old_lines new_lines =
    match (old_lines, new_lines) with
    | [||], [||] -> []
    | _, _ ->
        let { sub_start_new; sub_start_old; longest_subsequence } =
          get_longest_subsequence old_lines new_lines
        in

        if longest_subsequence == 0 then [ Deleted old_lines; Added new_lines ]
        else
          let old_lines_presubseq = Array.sub old_lines 0 sub_start_old in
          let new_lines_presubseq = Array.sub new_lines 0 sub_start_new in
          let old_lines_postsubseq =
            let start_index = sub_start_old + longest_subsequence in
            let end_index = Array.length old_lines - start_index in
            Array.sub old_lines start_index end_index
          in
          let new_lines_postsubseq =
            let start_index = sub_start_new + longest_subsequence in
            let end_index = Array.length new_lines - start_index in
            Array.sub new_lines start_index end_index
          in
          let unchanged_lines =
            Array.sub new_lines sub_start_new longest_subsequence
          in
          get_diff old_lines_presubseq new_lines_presubseq
          @ [ Equal unchanged_lines ]
          @ get_diff old_lines_postsubseq new_lines_postsubseq

  let recover_old_input diffs =
    List.fold_left
      (fun acc diff ->
        match diff with
        | Deleted x
        | Equal x ->
            x :: acc
        | Added _ -> acc)
      [] diffs
    |> List.rev |> Array.concat

  let recover_new_input diffs =
    List.fold_left
      (fun acc diff ->
        match diff with
        | Added x
        | Equal x ->
            x :: acc
        | Deleted _ -> acc)
      [] diffs
    |> List.rev |> Array.concat

  (* This is intended for tests, that's why we return arrays like in the
     original input even though lists are usually more convenient for
     the user. *)
  let recover_input diffs = (recover_old_input diffs, recover_new_input diffs)
end