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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
let cfprintf oc = Printf.(kfprintf (fun oc -> fprintf oc "") oc)
let log_out_ch = ref stdout
let cprintf s = cfprintf !log_out_ch s
module StringDiff = Diff2.Make(struct
type elem = String.t
type t = elem array
let get t i = Array.get t i
let length t = Array.length t
end)
type diff_type =
[ `Removed
| `Added
| `Common
]
type diff_list = StringDiff.elem Diff2.edit list
let db_print_diffs fmt diffs =
let open Format in
let print_diff = function
| `Common (opos, npos, s) ->
fprintf fmt "Common '%s' opos = %d npos = %d\n" s opos npos;
| `Removed (pos, s) ->
fprintf fmt "Removed '%s' opos = %d\n" s pos;
| `Added (pos, s) ->
fprintf fmt "Added '%s' npos = %d\n" s pos;
in
pp_open_vbox fmt 0;
List.iter print_diff diffs;
pp_close_box fmt ();
pp_print_flush fmt ()
let string_of_diffs diffs =
Format.asprintf "%a" db_print_diffs diffs
let shorten_diff_span dtype diff_list =
let changed = ref false in
let diffs = Array.of_list diff_list in
let len = Array.length diffs in
let vinfo index =
match diffs.(index) with
| `Common (opos, npos, s) -> (`Common, opos, npos, s)
| `Removed (pos, s) -> (`Removed, pos, 0, s)
| `Added (pos, s) -> (`Added, 0, pos, s) in
let get_variant index =
let (v, _, _, _) = vinfo index in
v in
let get_str index =
let (_, _, _, s) = vinfo index in
s in
let iter start len lt incr = begin
let src = ref start in
let dst = ref start in
while (lt !src len) do
if (get_variant !src) = dtype then begin
if (lt !dst !src) then
dst := !src;
while (lt !dst len) && (get_variant !dst) = dtype do
dst := !dst + incr;
done;
if (lt !dst len) && (get_str !src) = (get_str !dst) then begin
let (_, c_opos, c_npos, str) = vinfo !dst
and (_, v_opos, v_npos, _) = vinfo !src in
changed := true;
if dtype = `Added then begin
diffs.(!src) <- `Common (c_opos, v_npos, str);
diffs.(!dst) <- `Added (c_npos, str);
end else begin
diffs.(!src) <- `Common (v_opos, c_npos, str);
diffs.(!dst) <- `Removed (c_opos, str)
end
end
end;
src := !src + incr
done
end in
iter 0 len (<) 1;
iter (len-1) (-1) (>) (-1);
if !changed then Array.to_list diffs else diff_list
let has_changes diffs =
let rec has_changes_r diffs added removed =
match diffs with
| `Added _ :: t -> has_changes_r t true removed
| `Removed _ :: t -> has_changes_r t added true
| h :: t -> has_changes_r t added removed
| [] -> (added, removed) in
has_changes_r diffs false false
let diff_strs old_strs new_strs =
let diffs = List.rev (StringDiff.diff old_strs new_strs) in
shorten_diff_span `Removed (shorten_diff_span `Added diffs)
let def_tokenize_string s =
let limit = (String.length s) - 1 in
let strs : string list ref = ref [] in
for i = 0 to limit do
strs := (String.make 1 s.[i]) :: !strs
done;
List.rev !strs
let diff_str ?(tokenize_string=def_tokenize_string) old_str new_str =
let old_toks = Array.of_list (tokenize_string old_str)
and new_toks = Array.of_list (tokenize_string new_str) in
diff_strs old_toks new_toks
let get_dinfo = function
| `Common (_, _, s) -> (`Common, s)
| `Removed (_, s) -> (`Removed, s)
| `Added (_, s) -> (`Added, s)
[@@@ocaml.warning "-32"]
let string_of_diff_type = function
| `Common -> "Common"
| `Removed -> "Removed"
| `Added -> "Added"
[@@@ocaml.warning "+32"]
let wrap_in_bg diff_tag pp =
let open Pp in
(tag (Pp.start_pfx ^ diff_tag ^ ".bg") (str "")) ++ pp ++
(tag (Pp.end_pfx ^ diff_tag ^ ".bg") (str ""))
exception Diff_Failure of string
let add_diff_tags which pp diffs =
let open Pp in
let diff_tag = if which = `Added then "diff.added" else "diff.removed" in
let diffs : diff_list ref = ref diffs in
let in_diff = ref false in
let in_span = ref false in
let trans = ref false in
let buf = Buffer.create 16 in
let acc_pp = ref [] in
let diff_str, diff_ind, diff_len = ref "", ref 0, ref 0 in
let prev_dtype, dtype, next_dtype = ref `Common, ref `Common, ref `Common in
let is_white c = List.mem c [' '; '\t'; '\n'; '\r'] in
let skip () =
while !diffs <> [] &&
(let (t, _) = get_dinfo (List.hd !diffs) in
t <> `Common && t <> which)
do
diffs := List.tl !diffs
done
in
let put_tagged case =
if Buffer.length buf > 0 then begin
let pp = str (Buffer.contents buf) in
Buffer.clear buf;
let tagged = match case with
| "" -> pp
| "tag" -> tag diff_tag pp
| "start" -> in_span := true; tag (start_pfx ^ diff_tag) pp
| "end" -> in_span := false; tag (end_pfx ^ diff_tag) pp
| _ -> raise (Diff_Failure "invalid tag id in put_tagged, should be impossible") in
acc_pp := tagged :: !acc_pp
end
in
let output_pps () =
let next_diff_char_hl = if !diff_ind < !diff_len then !dtype = which else !next_dtype = which in
let tag = if not !in_diff then ""
else if !in_span then
if next_diff_char_hl then "" else "end"
else
if next_diff_char_hl then "start" else "tag" in
put_tagged tag;
let l = !acc_pp in
acc_pp := [];
match List.length l with
| 0 -> str ""
| 1 -> List.hd l
| _ -> seq (List.rev l)
in
let maybe_next_diff () =
if !diff_ind = !diff_len && (skip(); !diffs <> []) then begin
let (t, s) = get_dinfo (List.hd !diffs) in
diff_str := s; diff_ind := 0; diff_len := String.length !diff_str;
diffs := List.tl !diffs; skip();
prev_dtype := !dtype;
dtype := t;
next_dtype := (match !diffs with
| diff2 :: _ -> let (nt, _) = get_dinfo diff2 in nt
| [] -> `Common);
trans := !dtype <> !prev_dtype
end;
in
let s_char c =
maybe_next_diff ();
if !diff_ind < !diff_len && c = !diff_str.[!diff_ind] then begin
if !dtype = which && !trans && !diff_ind = 0 then begin
put_tagged "";
in_diff := true
end;
Buffer.add_char buf c;
diff_ind := !diff_ind + 1;
if !dtype = which && !dtype <> !next_dtype && !diff_ind = !diff_len then begin
put_tagged (if !in_span then "end" else "tag");
in_diff := false
end
end else if is_white c then
Buffer.add_char buf c
else begin
cprintf "mismatch: expected '%c' but got '%c'\n" !diff_str.[!diff_ind] c;
raise (Diff_Failure "string mismatch, should be impossible")
end
in
let reorder_tags child pp_tag pp =
match repr child with
| Ppcmd_tag (t1, pp) -> tag t1 (tag pp_tag pp)
| Ppcmd_glue l ->
if List.exists (fun x ->
match repr x with
| Ppcmd_tag (_, _) -> true
| _ -> false) l
then seq (List.map (fun x ->
match repr x with
| Ppcmd_tag (t2, pp2) -> tag t2 (tag pp_tag pp2)
| pp2 -> tag pp_tag (unrepr pp2)) l)
else child
| _ -> tag pp_tag child
in
let rec add_tags_r pp =
let r_pp = repr pp in
match r_pp with
| Ppcmd_string s -> String.iter s_char s; output_pps ()
| Ppcmd_glue l -> seq (List.map add_tags_r l)
| Ppcmd_box (block_type, pp) -> unrepr (Ppcmd_box (block_type, add_tags_r pp))
| Ppcmd_tag (pp_tag, pp) -> reorder_tags (add_tags_r pp) pp_tag pp
| _ -> pp
in
let (has_added, has_removed) = has_changes !diffs in
let rv = add_tags_r pp in
skip ();
if !diffs <> [] then
raise (Diff_Failure "left-over diff info at end of Pp.t, should be impossible");
if has_added || has_removed then wrap_in_bg diff_tag rv else rv
let diff_pp ?(tokenize_string=def_tokenize_string) o_pp n_pp =
let open Pp in
let o_str = string_of_ppcmds o_pp in
let n_str = string_of_ppcmds n_pp in
let diffs = diff_str ~tokenize_string o_str n_str in
(add_diff_tags `Removed o_pp diffs, add_diff_tags `Added n_pp diffs)
let diff_pp_combined ?(tokenize_string=def_tokenize_string) ?(show_removed=false) o_pp n_pp =
let open Pp in
let o_str = string_of_ppcmds o_pp in
let n_str = string_of_ppcmds n_pp in
let diffs = diff_str ~tokenize_string o_str n_str in
let (_, has_removed) = has_changes diffs in
let added = add_diff_tags `Added n_pp diffs in
if show_removed && has_removed then
let removed = add_diff_tags `Removed o_pp diffs in
(v 0 (removed ++ cut() ++ added))
else added