redis-diff_match_patch 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2364 @@
1
+ --[[
2
+ * Diff Match and Patch
3
+ *
4
+ * Copyright 2006 Google Inc.
5
+ * http://code.google.com/p/google-diff-match-patch/
6
+ *
7
+ * Based on the JavaScript implementation by Neil Fraser.
8
+ * Ported to Lua by Duncan Cross.
9
+ *
10
+ * Licensed under the Apache License, Version 2.0 (the "License");
11
+ * you may not use this file except in compliance with the License.
12
+ * You may obtain a copy of the License at
13
+ *
14
+ * http://www.apache.org/licenses/LICENSE-2.0
15
+ *
16
+ * Unless required by applicable law or agreed to in writing, software
17
+ * distributed under the License is distributed on an "AS IS" BASIS,
18
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19
+ * See the License for the specific language governing permissions and
20
+ * limitations under the License.
21
+ --]]
22
+
23
+ --[[
24
+ -- Lua 5.1 and earlier requires the external BitOp library.
25
+ -- This library is built-in from Lua 5.2 and later as 'bit32'.
26
+ require 'bit' -- <http://bitop.luajit.org/>
27
+ local band, bor, lshift
28
+ = bit.band, bit.bor, bit.lshift
29
+ --]]
30
+
31
+ local hex2bin = {
32
+ ["0"] = "0000",
33
+ ["1"] = "0001",
34
+ ["2"] = "0010",
35
+ ["3"] = "0011",
36
+ ["4"] = "0100",
37
+ ["5"] = "0101",
38
+ ["6"] = "0110",
39
+ ["7"] = "0111",
40
+ ["8"] = "1000",
41
+ ["9"] = "1001",
42
+ ["a"] = "1010",
43
+ ["b"] = "1011",
44
+ ["c"] = "1100",
45
+ ["d"] = "1101",
46
+ ["e"] = "1110",
47
+ ["f"] = "1111"
48
+ }
49
+
50
+ local bin2hex = {
51
+ ["0000"] = "0",
52
+ ["0001"] = "1",
53
+ ["0010"] = "2",
54
+ ["0011"] = "3",
55
+ ["0100"] = "4",
56
+ ["0101"] = "5",
57
+ ["0110"] = "6",
58
+ ["0111"] = "7",
59
+ ["1000"] = "8",
60
+ ["1001"] = "9",
61
+ ["1010"] = "A",
62
+ ["1011"] = "B",
63
+ ["1100"] = "C",
64
+ ["1101"] = "D",
65
+ ["1110"] = "E",
66
+ ["1111"] = "F"
67
+ }
68
+
69
+ function Hex2Bin(s)
70
+ -- s -> hexadecimal string
71
+
72
+ local ret = ""
73
+ local i = 0
74
+
75
+ for i in string.gfind(s, ".") do
76
+ i = string.lower(i)
77
+
78
+ ret = ret..hex2bin[i]
79
+
80
+ end
81
+
82
+ return ret
83
+ end
84
+
85
+
86
+ function Bin2Hex(s)
87
+ -- s -> binary string
88
+
89
+ local l = 0
90
+ local h = ""
91
+ local b = ""
92
+ local rem
93
+
94
+ l = string.len(s)
95
+ rem = l % 4
96
+ l = l-1
97
+ h = ""
98
+
99
+ -- need to prepend zeros to eliminate mod 4
100
+ if (rem > 0) then
101
+ s = string.rep("0", 4 - rem)..s
102
+ end
103
+
104
+ for i = 1, l, 4 do
105
+ b = string.sub(s, i, i+3)
106
+ h = h..bin2hex[b]
107
+ end
108
+
109
+ return h
110
+ end
111
+
112
+ function BMAnd(v, m)
113
+ -- v -> hex string to be masked
114
+ -- m -> hex string mask
115
+ -- s -> hex string as masked
116
+ -- bv -> binary string of v
117
+ -- bm -> binary string mask
118
+
119
+ local bv = Hex2Bin(v)
120
+ local bm = Hex2Bin(m)
121
+
122
+ local i = 0
123
+ local s = ""
124
+
125
+ while (string.len(bv) < 32) do
126
+ bv = "0000"..bv
127
+ end
128
+
129
+ while (string.len(bm) < 32) do
130
+ bm = "0000"..bm
131
+ end
132
+
133
+
134
+ for i = 1, 32 do
135
+ cv = string.sub(bv, i, i)
136
+ cm = string.sub(bm, i, i)
137
+ if cv == cm then
138
+ if cv == "1" then
139
+ s = s.."1"
140
+ else
141
+ s = s.."0"
142
+ end
143
+ else
144
+ s = s.."0"
145
+
146
+ end
147
+ end
148
+
149
+ return Bin2Hex(s)
150
+ end
151
+
152
+ function BMOr(v, m)
153
+ -- v -> hex string to be masked
154
+ -- m -> hex string mask
155
+ -- s -> hex string as masked
156
+ -- bv -> binary string of v
157
+ -- bm -> binary string mask
158
+
159
+ local bv = Hex2Bin(v)
160
+ local bm = Hex2Bin(m)
161
+
162
+ local i = 0
163
+ local s = ""
164
+
165
+ while (string.len(bv) < 32) do
166
+ bv = "0000"..bv
167
+ end
168
+
169
+ while (string.len(bm) < 32) do
170
+ bm = "0000"..bm
171
+ end
172
+
173
+
174
+ for i = 1, 32 do
175
+ cv = string.sub(bv, i, i)
176
+ cm = string.sub(bm, i, i)
177
+ if cv == "1" then
178
+ s = s.."1"
179
+ elseif cm == "1" then
180
+ s = s.."1"
181
+ else
182
+ s = s.."0"
183
+ end
184
+ end
185
+
186
+ return Bin2Hex(s)
187
+ end
188
+
189
+ function BShLeft(v, nb)
190
+ -- v -> hexstring value to be shifted
191
+ -- nb -> number of bits to shift to the right
192
+ -- s -> binary string of v
193
+
194
+ local s = Hex2Bin(v)
195
+
196
+ while (string.len(s) < 32) do
197
+ s = "0000"..s
198
+ end
199
+
200
+ s = string.sub(s, nb + 1, 32)
201
+
202
+ while (string.len(s) < 32) do
203
+ s = s.."0"
204
+ end
205
+
206
+ return Bin2Hex(s)
207
+ end
208
+
209
+ local band, bor, lshift
210
+ = BMAnd, BMOr, BShLeft
211
+ local type, setmetatable, ipairs, select
212
+ = type, setmetatable, ipairs, select
213
+ local unpack, tonumber, error
214
+ = unpack, tonumber, error
215
+ local strsub, strbyte, strchar, gmatch, gsub
216
+ = string.sub, string.byte, string.char, string.gmatch, string.gsub
217
+ local strmatch, strfind, strformat
218
+ = string.match, string.find, string.format
219
+ local tinsert, tremove, tconcat
220
+ = table.insert, table.remove, table.concat
221
+ local max, min, floor, ceil, abs
222
+ = math.max, math.min, math.floor, math.ceil, math.abs
223
+
224
+
225
+
226
+ -- Utility functions.
227
+
228
+ local percentEncode_pattern = '[^A-Za-z0-9%-=;\',./~!@#$%&*%(%)_%+ %?]'
229
+ local function percentEncode_replace(v)
230
+ return strformat('%%%02X', strbyte(v))
231
+ end
232
+
233
+ local function tsplice(t, idx, deletions, ...)
234
+ local insertions = select('#', ...)
235
+ for i = 1, deletions do
236
+ tremove(t, idx)
237
+ end
238
+ for i = insertions, 1, -1 do
239
+ -- do not remove parentheses around select
240
+ tinsert(t, idx, (select(i, ...)))
241
+ end
242
+ end
243
+
244
+ local function strelement(str, i)
245
+ return strsub(str, i, i)
246
+ end
247
+
248
+ local function indexOf(a, b, start)
249
+ if (#b == 0) then
250
+ return nil
251
+ end
252
+ return strfind(a, b, start, true)
253
+ end
254
+
255
+ local htmlEncode_pattern = '[&<>\n]'
256
+ local htmlEncode_replace = {
257
+ ['&'] = '&amp;', ['<'] = '&lt;', ['>'] = '&gt;', ['\n'] = '&para;<br>'
258
+ }
259
+
260
+ -- Public API Functions
261
+ -- (Exported at the end of the script)
262
+
263
+ local diff_main,
264
+ diff_cleanupSemantic,
265
+ diff_cleanupEfficiency,
266
+ diff_levenshtein,
267
+ diff_prettyHtml
268
+
269
+ local match_main
270
+
271
+ local patch_make,
272
+ patch_toText,
273
+ patch_fromText,
274
+ patch_apply
275
+
276
+ --[[
277
+ * The data structure representing a diff is an array of tuples:
278
+ * {{DIFF_DELETE, 'Hello'}, {DIFF_INSERT, 'Goodbye'}, {DIFF_EQUAL, ' world.'}}
279
+ * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
280
+ --]]
281
+ local DIFF_DELETE = -1
282
+ local DIFF_INSERT = 1
283
+ local DIFF_EQUAL = 0
284
+
285
+ -- Number of seconds to map a diff before giving up (0 for infinity).
286
+ local Diff_Timeout = 1.0
287
+ -- Cost of an empty edit operation in terms of edit characters.
288
+ local Diff_EditCost = 4
289
+ -- At what point is no match declared (0.0 = perfection, 1.0 = very loose).
290
+ local Match_Threshold = 0.5
291
+ -- How far to search for a match (0 = exact location, 1000+ = broad match).
292
+ -- A match this many characters away from the expected location will add
293
+ -- 1.0 to the score (0.0 is a perfect match).
294
+ local Match_Distance = 1000
295
+ -- When deleting a large block of text (over ~64 characters), how close do
296
+ -- the contents have to be to match the expected contents. (0.0 = perfection,
297
+ -- 1.0 = very loose). Note that Match_Threshold controls how closely the
298
+ -- end points of a delete need to match.
299
+ local Patch_DeleteThreshold = 0.5
300
+ -- Chunk size for context length.
301
+ local Patch_Margin = 4
302
+ -- The number of bits in an int.
303
+ local Match_MaxBits = 32
304
+
305
+ function settings(new)
306
+ if new then
307
+ Diff_Timeout = new.Diff_Timeout or Diff_Timeout
308
+ Diff_EditCost = new.Diff_EditCost or Diff_EditCost
309
+ Match_Threshold = new.Match_Threshold or Match_Threshold
310
+ Match_Distance = new.Match_Distance or Match_Distance
311
+ Patch_DeleteThreshold = new.Patch_DeleteThreshold or Patch_DeleteThreshold
312
+ Patch_Margin = new.Patch_Margin or Patch_Margin
313
+ Match_MaxBits = new.Match_MaxBits or Match_MaxBits
314
+ else
315
+ return {
316
+ Diff_Timeout = Diff_Timeout;
317
+ Diff_EditCost = Diff_EditCost;
318
+ Match_Threshold = Match_Threshold;
319
+ Match_Distance = Match_Distance;
320
+ Patch_DeleteThreshold = Patch_DeleteThreshold;
321
+ Patch_Margin = Patch_Margin;
322
+ Match_MaxBits = Match_MaxBits;
323
+ }
324
+ end
325
+ end
326
+
327
+ -- ---------------------------------------------------------------------------
328
+ -- DIFF API
329
+ -- ---------------------------------------------------------------------------
330
+
331
+ -- The private diff functions
332
+ local _diff_compute,
333
+ _diff_bisect,
334
+ _diff_halfMatchI,
335
+ _diff_halfMatch,
336
+ _diff_cleanupSemanticScore,
337
+ _diff_cleanupSemanticLossless,
338
+ _diff_cleanupMerge,
339
+ _diff_commonPrefix,
340
+ _diff_commonSuffix,
341
+ _diff_commonOverlap,
342
+ _diff_xIndex,
343
+ _diff_text1,
344
+ _diff_text2,
345
+ _diff_toDelta,
346
+ _diff_fromDelta
347
+
348
+ --[[
349
+ * Find the differences between two texts. Simplifies the problem by stripping
350
+ * any common prefix or suffix off the texts before diffing.
351
+ * @param {string} text1 Old string to be diffed.
352
+ * @param {string} text2 New string to be diffed.
353
+ * @param {boolean} opt_checklines Has no effect in Lua.
354
+ * by. Used internally for recursive calls. Users should set DiffTimeout
355
+ * instead.
356
+ * @return {Array.<Array.<number|string>>} Array of diff tuples.
357
+ --]]
358
+ function diff_main(text1, text2, opt_checklines)
359
+ -- Check for null inputs.
360
+ if text1 == nil or text1 == nil then
361
+ error('Null inputs. (diff_main)')
362
+ end
363
+
364
+ -- Check for equality (speedup).
365
+ if text1 == text2 then
366
+ if #text1 > 0 then
367
+ return {{DIFF_EQUAL, text1}}
368
+ end
369
+ return {}
370
+ end
371
+
372
+ -- LUANOTE: Due to the lack of Unicode support, Lua is incapable of
373
+ -- implementing the line-mode speedup.
374
+ local checklines = false
375
+
376
+ -- Trim off common prefix (speedup).
377
+ local commonlength = _diff_commonPrefix(text1, text2)
378
+ local commonprefix
379
+ if commonlength > 0 then
380
+ commonprefix = strsub(text1, 1, commonlength)
381
+ text1 = strsub(text1, commonlength + 1)
382
+ text2 = strsub(text2, commonlength + 1)
383
+ end
384
+
385
+ -- Trim off common suffix (speedup).
386
+ commonlength = _diff_commonSuffix(text1, text2)
387
+ local commonsuffix
388
+ if commonlength > 0 then
389
+ commonsuffix = strsub(text1, -commonlength)
390
+ text1 = strsub(text1, 1, -commonlength - 1)
391
+ text2 = strsub(text2, 1, -commonlength - 1)
392
+ end
393
+
394
+ -- Compute the diff on the middle block.
395
+ local diffs = _diff_compute(text1, text2, checklines)
396
+
397
+ -- Restore the prefix and suffix.
398
+ if commonprefix then
399
+ tinsert(diffs, 1, {DIFF_EQUAL, commonprefix})
400
+ end
401
+ if commonsuffix then
402
+ diffs[#diffs + 1] = {DIFF_EQUAL, commonsuffix}
403
+ end
404
+
405
+ _diff_cleanupMerge(diffs)
406
+ return diffs
407
+ end
408
+
409
+ --[[
410
+ * Reduce the number of edits by eliminating semantically trivial equalities.
411
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
412
+ --]]
413
+ function diff_cleanupSemantic(diffs)
414
+ local changes = false
415
+ local equalities = {} -- Stack of indices where equalities are found.
416
+ local equalitiesLength = 0 -- Keeping our own length var is faster.
417
+ local lastequality = nil
418
+ -- Always equal to diffs[equalities[equalitiesLength]][2]
419
+ local pointer = 1 -- Index of current position.
420
+ -- Number of characters that changed prior to the equality.
421
+ local length_insertions1 = 0
422
+ local length_deletions1 = 0
423
+ -- Number of characters that changed after the equality.
424
+ local length_insertions2 = 0
425
+ local length_deletions2 = 0
426
+
427
+ while diffs[pointer] do
428
+ if diffs[pointer][1] == DIFF_EQUAL then -- Equality found.
429
+ equalitiesLength = equalitiesLength + 1
430
+ equalities[equalitiesLength] = pointer
431
+ length_insertions1 = length_insertions2
432
+ length_deletions1 = length_deletions2
433
+ length_insertions2 = 0
434
+ length_deletions2 = 0
435
+ lastequality = diffs[pointer][2]
436
+ else -- An insertion or deletion.
437
+ if diffs[pointer][1] == DIFF_INSERT then
438
+ length_insertions2 = length_insertions2 + #(diffs[pointer][2])
439
+ else
440
+ length_deletions2 = length_deletions2 + #(diffs[pointer][2])
441
+ end
442
+ -- Eliminate an equality that is smaller or equal to the edits on both
443
+ -- sides of it.
444
+ if lastequality
445
+ and (#lastequality <= max(length_insertions1, length_deletions1))
446
+ and (#lastequality <= max(length_insertions2, length_deletions2)) then
447
+ -- Duplicate record.
448
+ tinsert(diffs, equalities[equalitiesLength],
449
+ {DIFF_DELETE, lastequality})
450
+ -- Change second copy to insert.
451
+ diffs[equalities[equalitiesLength] + 1][1] = DIFF_INSERT
452
+ -- Throw away the equality we just deleted.
453
+ equalitiesLength = equalitiesLength - 1
454
+ -- Throw away the previous equality (it needs to be reevaluated).
455
+ equalitiesLength = equalitiesLength - 1
456
+ pointer = (equalitiesLength > 0) and equalities[equalitiesLength] or 0
457
+ length_insertions1, length_deletions1 = 0, 0 -- Reset the counters.
458
+ length_insertions2, length_deletions2 = 0, 0
459
+ lastequality = nil
460
+ changes = true
461
+ end
462
+ end
463
+ pointer = pointer + 1
464
+ end
465
+
466
+ -- Normalize the diff.
467
+ if changes then
468
+ _diff_cleanupMerge(diffs)
469
+ end
470
+ _diff_cleanupSemanticLossless(diffs)
471
+
472
+ -- Find any overlaps between deletions and insertions.
473
+ -- e.g: <del>abcxxx</del><ins>xxxdef</ins>
474
+ -- -> <del>abc</del>xxx<ins>def</ins>
475
+ -- e.g: <del>xxxabc</del><ins>defxxx</ins>
476
+ -- -> <ins>def</ins>xxx<del>abc</del>
477
+ -- Only extract an overlap if it is as big as the edit ahead or behind it.
478
+ pointer = 2
479
+ while diffs[pointer] do
480
+ if (diffs[pointer - 1][1] == DIFF_DELETE and
481
+ diffs[pointer][1] == DIFF_INSERT) then
482
+ local deletion = diffs[pointer - 1][2]
483
+ local insertion = diffs[pointer][2]
484
+ local overlap_length1 = _diff_commonOverlap(deletion, insertion)
485
+ local overlap_length2 = _diff_commonOverlap(insertion, deletion)
486
+ if (overlap_length1 >= overlap_length2) then
487
+ if (overlap_length1 >= #deletion / 2 or
488
+ overlap_length1 >= #insertion / 2) then
489
+ -- Overlap found. Insert an equality and trim the surrounding edits.
490
+ tinsert(diffs, pointer,
491
+ {DIFF_EQUAL, strsub(insertion, 1, overlap_length1)})
492
+ diffs[pointer - 1][2] =
493
+ strsub(deletion, 1, #deletion - overlap_length1)
494
+ diffs[pointer + 1][2] = strsub(insertion, overlap_length1 + 1)
495
+ pointer = pointer + 1
496
+ end
497
+ else
498
+ if (overlap_length2 >= #deletion / 2 or
499
+ overlap_length2 >= #insertion / 2) then
500
+ -- Reverse overlap found.
501
+ -- Insert an equality and swap and trim the surrounding edits.
502
+ tinsert(diffs, pointer,
503
+ {DIFF_EQUAL, strsub(deletion, 1, overlap_length2)})
504
+ diffs[pointer - 1] = {DIFF_INSERT,
505
+ strsub(insertion, 1, #insertion - overlap_length2)}
506
+ diffs[pointer + 1] = {DIFF_DELETE,
507
+ strsub(deletion, overlap_length2 + 1)}
508
+ pointer = pointer + 1
509
+ end
510
+ end
511
+ pointer = pointer + 1
512
+ end
513
+ pointer = pointer + 1
514
+ end
515
+ end
516
+
517
+ --[[
518
+ * Reduce the number of edits by eliminating operationally trivial equalities.
519
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
520
+ --]]
521
+ function diff_cleanupEfficiency(diffs)
522
+ local changes = false
523
+ -- Stack of indices where equalities are found.
524
+ local equalities = {}
525
+ -- Keeping our own length var is faster.
526
+ local equalitiesLength = 0
527
+ -- Always equal to diffs[equalities[equalitiesLength]][2]
528
+ local lastequality = nil
529
+ -- Index of current position.
530
+ local pointer = 1
531
+
532
+ -- The following four are really booleans but are stored as numbers because
533
+ -- they are used at one point like this:
534
+ --
535
+ -- (pre_ins + pre_del + post_ins + post_del) == 3
536
+ --
537
+ -- ...i.e. checking that 3 of them are true and 1 of them is false.
538
+
539
+ -- Is there an insertion operation before the last equality.
540
+ local pre_ins = 0
541
+ -- Is there a deletion operation before the last equality.
542
+ local pre_del = 0
543
+ -- Is there an insertion operation after the last equality.
544
+ local post_ins = 0
545
+ -- Is there a deletion operation after the last equality.
546
+ local post_del = 0
547
+
548
+ while diffs[pointer] do
549
+ if diffs[pointer][1] == DIFF_EQUAL then -- Equality found.
550
+ local diffText = diffs[pointer][2]
551
+ if (#diffText < Diff_EditCost) and (post_ins == 1 or post_del == 1) then
552
+ -- Candidate found.
553
+ equalitiesLength = equalitiesLength + 1
554
+ equalities[equalitiesLength] = pointer
555
+ pre_ins, pre_del = post_ins, post_del
556
+ lastequality = diffText
557
+ else
558
+ -- Not a candidate, and can never become one.
559
+ equalitiesLength = 0
560
+ lastequality = nil
561
+ end
562
+ post_ins, post_del = 0, 0
563
+ else -- An insertion or deletion.
564
+ if diffs[pointer][1] == DIFF_DELETE then
565
+ post_del = 1
566
+ else
567
+ post_ins = 1
568
+ end
569
+ --[[
570
+ * Five types to be split:
571
+ * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
572
+ * <ins>A</ins>X<ins>C</ins><del>D</del>
573
+ * <ins>A</ins><del>B</del>X<ins>C</ins>
574
+ * <ins>A</del>X<ins>C</ins><del>D</del>
575
+ * <ins>A</ins><del>B</del>X<del>C</del>
576
+ --]]
577
+ if lastequality and (
578
+ (pre_ins+pre_del+post_ins+post_del == 4)
579
+ or
580
+ (
581
+ (#lastequality < Diff_EditCost / 2)
582
+ and
583
+ (pre_ins+pre_del+post_ins+post_del == 3)
584
+ )) then
585
+ -- Duplicate record.
586
+ tinsert(diffs, equalities[equalitiesLength],
587
+ {DIFF_DELETE, lastequality})
588
+ -- Change second copy to insert.
589
+ diffs[equalities[equalitiesLength] + 1][1] = DIFF_INSERT
590
+ -- Throw away the equality we just deleted.
591
+ equalitiesLength = equalitiesLength - 1
592
+ lastequality = nil
593
+ if (pre_ins == 1) and (pre_del == 1) then
594
+ -- No changes made which could affect previous entry, keep going.
595
+ post_ins, post_del = 1, 1
596
+ equalitiesLength = 0
597
+ else
598
+ -- Throw away the previous equality.
599
+ equalitiesLength = equalitiesLength - 1
600
+ pointer = (equalitiesLength > 0) and equalities[equalitiesLength] or 0
601
+ post_ins, post_del = 0, 0
602
+ end
603
+ changes = true
604
+ end
605
+ end
606
+ pointer = pointer + 1
607
+ end
608
+
609
+ if changes then
610
+ _diff_cleanupMerge(diffs)
611
+ end
612
+ end
613
+
614
+ --[[
615
+ * Compute the Levenshtein distance; the number of inserted, deleted or
616
+ * substituted characters.
617
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
618
+ * @return {number} Number of changes.
619
+ --]]
620
+ function diff_levenshtein(diffs)
621
+ local levenshtein = 0
622
+ local insertions, deletions = 0, 0
623
+ for x, diff in ipairs(diffs) do
624
+ local op, data = diff[1], diff[2]
625
+ if (op == DIFF_INSERT) then
626
+ insertions = insertions + #data
627
+ elseif (op == DIFF_DELETE) then
628
+ deletions = deletions + #data
629
+ elseif (op == DIFF_EQUAL) then
630
+ -- A deletion and an insertion is one substitution.
631
+ levenshtein = levenshtein + max(insertions, deletions)
632
+ insertions = 0
633
+ deletions = 0
634
+ end
635
+ end
636
+ levenshtein = levenshtein + max(insertions, deletions)
637
+ return levenshtein
638
+ end
639
+
640
+ --[[
641
+ * Convert a diff array into a pretty HTML report.
642
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
643
+ * @return {string} HTML representation.
644
+ --]]
645
+ function diff_prettyHtml(diffs)
646
+ local html = {}
647
+ for x, diff in ipairs(diffs) do
648
+ local op = diff[1] -- Operation (insert, delete, equal)
649
+ local data = diff[2] -- Text of change.
650
+ local text = gsub(data, htmlEncode_pattern, htmlEncode_replace)
651
+ if op == DIFF_INSERT then
652
+ html[x] = '<ins style="background:#e6ffe6;">' .. text .. '</ins>'
653
+ elseif op == DIFF_DELETE then
654
+ html[x] = '<del style="background:#ffe6e6;">' .. text .. '</del>'
655
+ elseif op == DIFF_EQUAL then
656
+ html[x] = '<span>' .. text .. '</span>'
657
+ end
658
+ end
659
+ return tconcat(html)
660
+ end
661
+
662
+ -- ---------------------------------------------------------------------------
663
+ -- UNOFFICIAL/PRIVATE DIFF FUNCTIONS
664
+ -- ---------------------------------------------------------------------------
665
+
666
+ --[[
667
+ * Find the differences between two texts. Assumes that the texts do not
668
+ * have any common prefix or suffix.
669
+ * @param {string} text1 Old string to be diffed.
670
+ * @param {string} text2 New string to be diffed.
671
+ * @param {boolean} checklines Has no effect in Lua.
672
+ * @return {Array.<Array.<number|string>>} Array of diff tuples.
673
+ * @private
674
+ --]]
675
+ function _diff_compute(text1, text2, checklines)
676
+ if #text1 == 0 then
677
+ -- Just add some text (speedup).
678
+ return {{DIFF_INSERT, text2}}
679
+ end
680
+
681
+ if #text2 == 0 then
682
+ -- Just delete some text (speedup).
683
+ return {{DIFF_DELETE, text1}}
684
+ end
685
+
686
+ local diffs
687
+
688
+ local longtext = (#text1 > #text2) and text1 or text2
689
+ local shorttext = (#text1 > #text2) and text2 or text1
690
+ local i = indexOf(longtext, shorttext)
691
+
692
+ if i ~= nil then
693
+ -- Shorter text is inside the longer text (speedup).
694
+ diffs = {
695
+ {DIFF_INSERT, strsub(longtext, 1, i - 1)},
696
+ {DIFF_EQUAL, shorttext},
697
+ {DIFF_INSERT, strsub(longtext, i + #shorttext)}
698
+ }
699
+ -- Swap insertions for deletions if diff is reversed.
700
+ if #text1 > #text2 then
701
+ diffs[1][1], diffs[3][1] = DIFF_DELETE, DIFF_DELETE
702
+ end
703
+ return diffs
704
+ end
705
+
706
+ if #shorttext == 1 then
707
+ -- Single character string.
708
+ -- After the previous speedup, the character can't be an equality.
709
+ return {{DIFF_DELETE, text1}, {DIFF_INSERT, text2}}
710
+ end
711
+ longtext, shorttext = nil, nil -- Garbage collect.
712
+
713
+ -- Check to see if the problem can be split in two.
714
+ do
715
+ local
716
+ text1_a, text1_b,
717
+ text2_a, text2_b,
718
+ mid_common = _diff_halfMatch(text1, text2)
719
+
720
+ if text1_a then
721
+ -- A half-match was found, sort out the return data.
722
+ -- Send both pairs off for separate processing.
723
+ local diffs_a = diff_main(text1_a, text2_a, checklines)
724
+ local diffs_b = diff_main(text1_b, text2_b, checklines)
725
+ -- Merge the results.
726
+ local diffs_a_len = #diffs_a
727
+ diffs = diffs_a
728
+ diffs[diffs_a_len + 1] = {DIFF_EQUAL, mid_common}
729
+ for i, b_diff in ipairs(diffs_b) do
730
+ diffs[diffs_a_len + 1 + i] = b_diff
731
+ end
732
+ return diffs
733
+ end
734
+ end
735
+
736
+ return _diff_bisect(text1, text2)
737
+ end
738
+
739
+ --[[
740
+ * Find the 'middle snake' of a diff, split the problem in two
741
+ * and return the recursively constructed diff.
742
+ * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations.
743
+ * @param {string} text1 Old string to be diffed.
744
+ * @param {string} text2 New string to be diffed.
745
+ * @return {Array.<Array.<number|string>>} Array of diff tuples.
746
+ * @private
747
+ --]]
748
+ function _diff_bisect(text1, text2)
749
+ -- Cache the text lengths to prevent multiple calls.
750
+ local text1_length = #text1
751
+ local text2_length = #text2
752
+ local _sub, _element
753
+ local max_d = ceil((text1_length + text2_length) / 2)
754
+ local v_offset = max_d
755
+ local v_length = 2 * max_d
756
+ local v1 = {}
757
+ local v2 = {}
758
+ -- Setting all elements to -1 is faster in Lua than mixing integers and nil.
759
+ for x = 0, v_length - 1 do
760
+ v1[x] = -1
761
+ v2[x] = -1
762
+ end
763
+ v1[v_offset + 1] = 0
764
+ v2[v_offset + 1] = 0
765
+ local delta = text1_length - text2_length
766
+ -- If the total number of characters is odd, then
767
+ -- the front path will collide with the reverse path.
768
+ local front = (delta % 2 ~= 0)
769
+ -- Offsets for start and end of k loop.
770
+ -- Prevents mapping of space beyond the grid.
771
+ local k1start = 0
772
+ local k1end = 0
773
+ local k2start = 0
774
+ local k2end = 0
775
+ for d = 0, max_d - 1 do
776
+ -- Walk the front path one step.
777
+ for k1 = -d + k1start, d - k1end, 2 do
778
+ local k1_offset = v_offset + k1
779
+ local x1
780
+ if (k1 == -d) or ((k1 ~= d) and
781
+ (v1[k1_offset - 1] < v1[k1_offset + 1])) then
782
+ x1 = v1[k1_offset + 1]
783
+ else
784
+ x1 = v1[k1_offset - 1] + 1
785
+ end
786
+ local y1 = x1 - k1
787
+ while (x1 <= text1_length) and (y1 <= text2_length)
788
+ and (strelement(text1, x1) == strelement(text2, y1)) do
789
+ x1 = x1 + 1
790
+ y1 = y1 + 1
791
+ end
792
+ v1[k1_offset] = x1
793
+ if x1 > text1_length + 1 then
794
+ -- Ran off the right of the graph.
795
+ k1end = k1end + 2
796
+ elseif y1 > text2_length + 1 then
797
+ -- Ran off the bottom of the graph.
798
+ k1start = k1start + 2
799
+ elseif front then
800
+ local k2_offset = v_offset + delta - k1
801
+ if k2_offset >= 0 and k2_offset < v_length and v2[k2_offset] ~= -1 then
802
+ -- Mirror x2 onto top-left coordinate system.
803
+ local x2 = text1_length - v2[k2_offset] + 1
804
+ if x1 > x2 then
805
+ -- Overlap detected.
806
+ return _diff_bisectSplit(text1, text2, x1, y1)
807
+ end
808
+ end
809
+ end
810
+ end
811
+
812
+ -- Walk the reverse path one step.
813
+ for k2 = -d + k2start, d - k2end, 2 do
814
+ local k2_offset = v_offset + k2
815
+ local x2
816
+ if (k2 == -d) or ((k2 ~= d) and
817
+ (v2[k2_offset - 1] < v2[k2_offset + 1])) then
818
+ x2 = v2[k2_offset + 1]
819
+ else
820
+ x2 = v2[k2_offset - 1] + 1
821
+ end
822
+ local y2 = x2 - k2
823
+ while (x2 <= text1_length) and (y2 <= text2_length)
824
+ and (strelement(text1, -x2) == strelement(text2, -y2)) do
825
+ x2 = x2 + 1
826
+ y2 = y2 + 1
827
+ end
828
+ v2[k2_offset] = x2
829
+ if x2 > text1_length + 1 then
830
+ -- Ran off the left of the graph.
831
+ k2end = k2end + 2
832
+ elseif y2 > text2_length + 1 then
833
+ -- Ran off the top of the graph.
834
+ k2start = k2start + 2
835
+ elseif not front then
836
+ local k1_offset = v_offset + delta - k2
837
+ if k1_offset >= 0 and k1_offset < v_length and v1[k1_offset] ~= -1 then
838
+ local x1 = v1[k1_offset]
839
+ local y1 = v_offset + x1 - k1_offset
840
+ -- Mirror x2 onto top-left coordinate system.
841
+ x2 = text1_length - x2 + 1
842
+ if x1 > x2 then
843
+ -- Overlap detected.
844
+ return _diff_bisectSplit(text1, text2, x1, y1)
845
+ end
846
+ end
847
+ end
848
+ end
849
+ end
850
+ -- Diff took too long and hit the deadline or
851
+ -- number of diffs equals number of characters, no commonality at all.
852
+ return {{DIFF_DELETE, text1}, {DIFF_INSERT, text2}}
853
+ end
854
+
855
+ --[[
856
+ * Given the location of the 'middle snake', split the diff in two parts
857
+ * and recurse.
858
+ * @param {string} text1 Old string to be diffed.
859
+ * @param {string} text2 New string to be diffed.
860
+ * @param {number} x Index of split point in text1.
861
+ * @param {number} y Index of split point in text2.
862
+ * @return {Array.<Array.<number|string>>} Array of diff tuples.
863
+ * @private
864
+ --]]
865
+ function _diff_bisectSplit(text1, text2, x, y)
866
+ local text1a = strsub(text1, 1, x - 1)
867
+ local text2a = strsub(text2, 1, y - 1)
868
+ local text1b = strsub(text1, x)
869
+ local text2b = strsub(text2, y)
870
+
871
+ -- Compute both diffs serially.
872
+ local diffs = diff_main(text1a, text2a, false)
873
+ local diffsb = diff_main(text1b, text2b, false)
874
+
875
+ local diffs_len = #diffs
876
+ for i, v in ipairs(diffsb) do
877
+ diffs[diffs_len + i] = v
878
+ end
879
+ return diffs
880
+ end
881
+
882
+ --[[
883
+ * Determine the common prefix of two strings.
884
+ * @param {string} text1 First string.
885
+ * @param {string} text2 Second string.
886
+ * @return {number} The number of characters common to the start of each
887
+ * string.
888
+ --]]
889
+ function _diff_commonPrefix(text1, text2)
890
+ -- Quick check for common null cases.
891
+ if (#text1 == 0) or (#text2 == 0) or (strbyte(text1, 1) ~= strbyte(text2, 1))
892
+ then
893
+ return 0
894
+ end
895
+ -- Binary search.
896
+ -- Performance analysis: http://neil.fraser.name/news/2007/10/09/
897
+ local pointermin = 1
898
+ local pointermax = min(#text1, #text2)
899
+ local pointermid = pointermax
900
+ local pointerstart = 1
901
+ while (pointermin < pointermid) do
902
+ if (strsub(text1, pointerstart, pointermid)
903
+ == strsub(text2, pointerstart, pointermid)) then
904
+ pointermin = pointermid
905
+ pointerstart = pointermin
906
+ else
907
+ pointermax = pointermid
908
+ end
909
+ pointermid = floor(pointermin + (pointermax - pointermin) / 2)
910
+ end
911
+ return pointermid
912
+ end
913
+
914
+ --[[
915
+ * Determine the common suffix of two strings.
916
+ * @param {string} text1 First string.
917
+ * @param {string} text2 Second string.
918
+ * @return {number} The number of characters common to the end of each string.
919
+ --]]
920
+ function _diff_commonSuffix(text1, text2)
921
+ -- Quick check for common null cases.
922
+ if (#text1 == 0) or (#text2 == 0)
923
+ or (strbyte(text1, -1) ~= strbyte(text2, -1)) then
924
+ return 0
925
+ end
926
+ -- Binary search.
927
+ -- Performance analysis: http://neil.fraser.name/news/2007/10/09/
928
+ local pointermin = 1
929
+ local pointermax = min(#text1, #text2)
930
+ local pointermid = pointermax
931
+ local pointerend = 1
932
+ while (pointermin < pointermid) do
933
+ if (strsub(text1, -pointermid, -pointerend)
934
+ == strsub(text2, -pointermid, -pointerend)) then
935
+ pointermin = pointermid
936
+ pointerend = pointermin
937
+ else
938
+ pointermax = pointermid
939
+ end
940
+ pointermid = floor(pointermin + (pointermax - pointermin) / 2)
941
+ end
942
+ return pointermid
943
+ end
944
+
945
+ --[[
946
+ * Determine if the suffix of one string is the prefix of another.
947
+ * @param {string} text1 First string.
948
+ * @param {string} text2 Second string.
949
+ * @return {number} The number of characters common to the end of the first
950
+ * string and the start of the second string.
951
+ * @private
952
+ --]]
953
+ function _diff_commonOverlap(text1, text2)
954
+ -- Cache the text lengths to prevent multiple calls.
955
+ local text1_length = #text1
956
+ local text2_length = #text2
957
+ -- Eliminate the null case.
958
+ if text1_length == 0 or text2_length == 0 then
959
+ return 0
960
+ end
961
+ -- Truncate the longer string.
962
+ if text1_length > text2_length then
963
+ text1 = strsub(text1, text1_length - text2_length + 1)
964
+ elseif text1_length < text2_length then
965
+ text2 = strsub(text2, 1, text1_length)
966
+ end
967
+ local text_length = min(text1_length, text2_length)
968
+ -- Quick check for the worst case.
969
+ if text1 == text2 then
970
+ return text_length
971
+ end
972
+
973
+ -- Start by looking for a single character match
974
+ -- and increase length until no match is found.
975
+ -- Performance analysis: http://neil.fraser.name/news/2010/11/04/
976
+ local best = 0
977
+ local length = 1
978
+ while true do
979
+ local pattern = strsub(text1, text_length - length + 1)
980
+ local found = strfind(text2, pattern, 1, true)
981
+ if found == nil then
982
+ return best
983
+ end
984
+ length = length + found - 1
985
+ if found == 1 or strsub(text1, text_length - length + 1) ==
986
+ strsub(text2, 1, length) then
987
+ best = length
988
+ length = length + 1
989
+ end
990
+ end
991
+ end
992
+
993
+ --[[
994
+ * Does a substring of shorttext exist within longtext such that the substring
995
+ * is at least half the length of longtext?
996
+ * This speedup can produce non-minimal diffs.
997
+ * Closure, but does not reference any external variables.
998
+ * @param {string} longtext Longer string.
999
+ * @param {string} shorttext Shorter string.
1000
+ * @param {number} i Start index of quarter length substring within longtext.
1001
+ * @return {?Array.<string>} Five element Array, containing the prefix of
1002
+ * longtext, the suffix of longtext, the prefix of shorttext, the suffix
1003
+ * of shorttext and the common middle. Or nil if there was no match.
1004
+ * @private
1005
+ --]]
1006
+ function _diff_halfMatchI(longtext, shorttext, i)
1007
+ -- Start with a 1/4 length substring at position i as a seed.
1008
+ local seed = strsub(longtext, i, i + floor(#longtext / 4))
1009
+ local j = 0 -- LUANOTE: do not change to 1, was originally -1
1010
+ local best_common = ''
1011
+ local best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b
1012
+ while true do
1013
+ j = indexOf(shorttext, seed, j + 1)
1014
+ if (j == nil) then
1015
+ break
1016
+ end
1017
+ local prefixLength = _diff_commonPrefix(strsub(longtext, i),
1018
+ strsub(shorttext, j))
1019
+ local suffixLength = _diff_commonSuffix(strsub(longtext, 1, i - 1),
1020
+ strsub(shorttext, 1, j - 1))
1021
+ if #best_common < suffixLength + prefixLength then
1022
+ best_common = strsub(shorttext, j - suffixLength, j - 1)
1023
+ .. strsub(shorttext, j, j + prefixLength - 1)
1024
+ best_longtext_a = strsub(longtext, 1, i - suffixLength - 1)
1025
+ best_longtext_b = strsub(longtext, i + prefixLength)
1026
+ best_shorttext_a = strsub(shorttext, 1, j - suffixLength - 1)
1027
+ best_shorttext_b = strsub(shorttext, j + prefixLength)
1028
+ end
1029
+ end
1030
+ if #best_common * 2 >= #longtext then
1031
+ return {best_longtext_a, best_longtext_b,
1032
+ best_shorttext_a, best_shorttext_b, best_common}
1033
+ else
1034
+ return nil
1035
+ end
1036
+ end
1037
+
1038
+ --[[
1039
+ * Do the two texts share a substring which is at least half the length of the
1040
+ * longer text?
1041
+ * @param {string} text1 First string.
1042
+ * @param {string} text2 Second string.
1043
+ * @return {?Array.<string>} Five element Array, containing the prefix of
1044
+ * text1, the suffix of text1, the prefix of text2, the suffix of
1045
+ * text2 and the common middle. Or nil if there was no match.
1046
+ * @private
1047
+ --]]
1048
+ function _diff_halfMatch(text1, text2)
1049
+ if Diff_Timeout <= 0 then
1050
+ -- Don't risk returning a non-optimal diff if we have unlimited time.
1051
+ return nil
1052
+ end
1053
+ local longtext = (#text1 > #text2) and text1 or text2
1054
+ local shorttext = (#text1 > #text2) and text2 or text1
1055
+ if (#longtext < 4) or (#shorttext * 2 < #longtext) then
1056
+ return nil -- Pointless.
1057
+ end
1058
+
1059
+ -- First check if the second quarter is the seed for a half-match.
1060
+ local hm1 = _diff_halfMatchI(longtext, shorttext, ceil(#longtext / 4))
1061
+ -- Check again based on the third quarter.
1062
+ local hm2 = _diff_halfMatchI(longtext, shorttext, ceil(#longtext / 2))
1063
+ local hm
1064
+ if not hm1 and not hm2 then
1065
+ return nil
1066
+ elseif not hm2 then
1067
+ hm = hm1
1068
+ elseif not hm1 then
1069
+ hm = hm2
1070
+ else
1071
+ -- Both matched. Select the longest.
1072
+ hm = (#hm1[5] > #hm2[5]) and hm1 or hm2
1073
+ end
1074
+
1075
+ -- A half-match was found, sort out the return data.
1076
+ local text1_a, text1_b, text2_a, text2_b
1077
+ if (#text1 > #text2) then
1078
+ text1_a, text1_b = hm[1], hm[2]
1079
+ text2_a, text2_b = hm[3], hm[4]
1080
+ else
1081
+ text2_a, text2_b = hm[1], hm[2]
1082
+ text1_a, text1_b = hm[3], hm[4]
1083
+ end
1084
+ local mid_common = hm[5]
1085
+ return text1_a, text1_b, text2_a, text2_b, mid_common
1086
+ end
1087
+
1088
+ --[[
1089
+ * Given two strings, compute a score representing whether the internal
1090
+ * boundary falls on logical boundaries.
1091
+ * Scores range from 6 (best) to 0 (worst).
1092
+ * @param {string} one First string.
1093
+ * @param {string} two Second string.
1094
+ * @return {number} The score.
1095
+ * @private
1096
+ --]]
1097
+ function _diff_cleanupSemanticScore(one, two)
1098
+ if (#one == 0) or (#two == 0) then
1099
+ -- Edges are the best.
1100
+ return 6
1101
+ end
1102
+
1103
+ -- Each port of this function behaves slightly differently due to
1104
+ -- subtle differences in each language's definition of things like
1105
+ -- 'whitespace'. Since this function's purpose is largely cosmetic,
1106
+ -- the choice has been made to use each language's native features
1107
+ -- rather than force total conformity.
1108
+ local char1 = strsub(one, -1)
1109
+ local char2 = strsub(two, 1, 1)
1110
+ local nonAlphaNumeric1 = strmatch(char1, '%W')
1111
+ local nonAlphaNumeric2 = strmatch(char2, '%W')
1112
+ local whitespace1 = nonAlphaNumeric1 and strmatch(char1, '%s')
1113
+ local whitespace2 = nonAlphaNumeric2 and strmatch(char2, '%s')
1114
+ local lineBreak1 = whitespace1 and strmatch(char1, '%c')
1115
+ local lineBreak2 = whitespace2 and strmatch(char2, '%c')
1116
+ local blankLine1 = lineBreak1 and strmatch(one, '\n\r?\n$')
1117
+ local blankLine2 = lineBreak2 and strmatch(two, '^\r?\n\r?\n')
1118
+
1119
+ if blankLine1 or blankLine2 then
1120
+ -- Five points for blank lines.
1121
+ return 5
1122
+ elseif lineBreak1 or lineBreak2 then
1123
+ -- Four points for line breaks.
1124
+ return 4
1125
+ elseif nonAlphaNumeric1 and not whitespace1 and whitespace2 then
1126
+ -- Three points for end of sentences.
1127
+ return 3
1128
+ elseif whitespace1 or whitespace2 then
1129
+ -- Two points for whitespace.
1130
+ return 2
1131
+ elseif nonAlphaNumeric1 or nonAlphaNumeric2 then
1132
+ -- One point for non-alphanumeric.
1133
+ return 1
1134
+ end
1135
+ return 0
1136
+ end
1137
+
1138
+ --[[
1139
+ * Look for single edits surrounded on both sides by equalities
1140
+ * which can be shifted sideways to align the edit to a word boundary.
1141
+ * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
1142
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1143
+ --]]
1144
+ function _diff_cleanupSemanticLossless(diffs)
1145
+ local pointer = 2
1146
+ -- Intentionally ignore the first and last element (don't need checking).
1147
+ while diffs[pointer + 1] do
1148
+ local prevDiff, nextDiff = diffs[pointer - 1], diffs[pointer + 1]
1149
+ if (prevDiff[1] == DIFF_EQUAL) and (nextDiff[1] == DIFF_EQUAL) then
1150
+ -- This is a single edit surrounded by equalities.
1151
+ local diff = diffs[pointer]
1152
+
1153
+ local equality1 = prevDiff[2]
1154
+ local edit = diff[2]
1155
+ local equality2 = nextDiff[2]
1156
+
1157
+ -- First, shift the edit as far left as possible.
1158
+ local commonOffset = _diff_commonSuffix(equality1, edit)
1159
+ if commonOffset > 0 then
1160
+ local commonString = strsub(edit, -commonOffset)
1161
+ equality1 = strsub(equality1, 1, -commonOffset - 1)
1162
+ edit = commonString .. strsub(edit, 1, -commonOffset - 1)
1163
+ equality2 = commonString .. equality2
1164
+ end
1165
+
1166
+ -- Second, step character by character right, looking for the best fit.
1167
+ local bestEquality1 = equality1
1168
+ local bestEdit = edit
1169
+ local bestEquality2 = equality2
1170
+ local bestScore = _diff_cleanupSemanticScore(equality1, edit)
1171
+ + _diff_cleanupSemanticScore(edit, equality2)
1172
+
1173
+ while strbyte(edit, 1) == strbyte(equality2, 1) do
1174
+ equality1 = equality1 .. strsub(edit, 1, 1)
1175
+ edit = strsub(edit, 2) .. strsub(equality2, 1, 1)
1176
+ equality2 = strsub(equality2, 2)
1177
+ local score = _diff_cleanupSemanticScore(equality1, edit)
1178
+ + _diff_cleanupSemanticScore(edit, equality2)
1179
+ -- The >= encourages trailing rather than leading whitespace on edits.
1180
+ if score >= bestScore then
1181
+ bestScore = score
1182
+ bestEquality1 = equality1
1183
+ bestEdit = edit
1184
+ bestEquality2 = equality2
1185
+ end
1186
+ end
1187
+ if prevDiff[2] ~= bestEquality1 then
1188
+ -- We have an improvement, save it back to the diff.
1189
+ if #bestEquality1 > 0 then
1190
+ diffs[pointer - 1][2] = bestEquality1
1191
+ else
1192
+ tremove(diffs, pointer - 1)
1193
+ pointer = pointer - 1
1194
+ end
1195
+ diffs[pointer][2] = bestEdit
1196
+ if #bestEquality2 > 0 then
1197
+ diffs[pointer + 1][2] = bestEquality2
1198
+ else
1199
+ tremove(diffs, pointer + 1, 1)
1200
+ pointer = pointer - 1
1201
+ end
1202
+ end
1203
+ end
1204
+ pointer = pointer + 1
1205
+ end
1206
+ end
1207
+
1208
+ --[[
1209
+ * Reorder and merge like edit sections. Merge equalities.
1210
+ * Any edit section can move as long as it doesn't cross an equality.
1211
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1212
+ --]]
1213
+ function _diff_cleanupMerge(diffs)
1214
+ diffs[#diffs + 1] = {DIFF_EQUAL, ''} -- Add a dummy entry at the end.
1215
+ local pointer = 1
1216
+ local count_delete, count_insert = 0, 0
1217
+ local text_delete, text_insert = '', ''
1218
+ local commonlength
1219
+ while diffs[pointer] do
1220
+ local diff_type = diffs[pointer][1]
1221
+ if diff_type == DIFF_INSERT then
1222
+ count_insert = count_insert + 1
1223
+ text_insert = text_insert .. diffs[pointer][2]
1224
+ pointer = pointer + 1
1225
+ elseif diff_type == DIFF_DELETE then
1226
+ count_delete = count_delete + 1
1227
+ text_delete = text_delete .. diffs[pointer][2]
1228
+ pointer = pointer + 1
1229
+ elseif diff_type == DIFF_EQUAL then
1230
+ -- Upon reaching an equality, check for prior redundancies.
1231
+ if count_delete + count_insert > 1 then
1232
+ if (count_delete > 0) and (count_insert > 0) then
1233
+ -- Factor out any common prefixies.
1234
+ commonlength = _diff_commonPrefix(text_insert, text_delete)
1235
+ if commonlength > 0 then
1236
+ local back_pointer = pointer - count_delete - count_insert
1237
+ if (back_pointer > 1) and (diffs[back_pointer - 1][1] == DIFF_EQUAL)
1238
+ then
1239
+ diffs[back_pointer - 1][2] = diffs[back_pointer - 1][2]
1240
+ .. strsub(text_insert, 1, commonlength)
1241
+ else
1242
+ tinsert(diffs, 1,
1243
+ {DIFF_EQUAL, strsub(text_insert, 1, commonlength)})
1244
+ pointer = pointer + 1
1245
+ end
1246
+ text_insert = strsub(text_insert, commonlength + 1)
1247
+ text_delete = strsub(text_delete, commonlength + 1)
1248
+ end
1249
+ -- Factor out any common suffixies.
1250
+ commonlength = _diff_commonSuffix(text_insert, text_delete)
1251
+ if commonlength ~= 0 then
1252
+ diffs[pointer][2] =
1253
+ strsub(text_insert, -commonlength) .. diffs[pointer][2]
1254
+ text_insert = strsub(text_insert, 1, -commonlength - 1)
1255
+ text_delete = strsub(text_delete, 1, -commonlength - 1)
1256
+ end
1257
+ end
1258
+ -- Delete the offending records and add the merged ones.
1259
+ if count_delete == 0 then
1260
+ tsplice(diffs, pointer - count_insert,
1261
+ count_insert, {DIFF_INSERT, text_insert})
1262
+ elseif count_insert == 0 then
1263
+ tsplice(diffs, pointer - count_delete,
1264
+ count_delete, {DIFF_DELETE, text_delete})
1265
+ else
1266
+ tsplice(diffs, pointer - count_delete - count_insert,
1267
+ count_delete + count_insert,
1268
+ {DIFF_DELETE, text_delete}, {DIFF_INSERT, text_insert})
1269
+ end
1270
+ pointer = pointer - count_delete - count_insert
1271
+ + (count_delete>0 and 1 or 0) + (count_insert>0 and 1 or 0) + 1
1272
+ elseif (pointer > 1) and (diffs[pointer - 1][1] == DIFF_EQUAL) then
1273
+ -- Merge this equality with the previous one.
1274
+ diffs[pointer - 1][2] = diffs[pointer - 1][2] .. diffs[pointer][2]
1275
+ tremove(diffs, pointer)
1276
+ else
1277
+ pointer = pointer + 1
1278
+ end
1279
+ count_insert, count_delete = 0, 0
1280
+ text_delete, text_insert = '', ''
1281
+ end
1282
+ end
1283
+ if diffs[#diffs][2] == '' then
1284
+ diffs[#diffs] = nil -- Remove the dummy entry at the end.
1285
+ end
1286
+
1287
+ -- Second pass: look for single edits surrounded on both sides by equalities
1288
+ -- which can be shifted sideways to eliminate an equality.
1289
+ -- e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
1290
+ local changes = false
1291
+ pointer = 2
1292
+ -- Intentionally ignore the first and last element (don't need checking).
1293
+ while pointer < #diffs do
1294
+ local prevDiff, nextDiff = diffs[pointer - 1], diffs[pointer + 1]
1295
+ if (prevDiff[1] == DIFF_EQUAL) and (nextDiff[1] == DIFF_EQUAL) then
1296
+ -- This is a single edit surrounded by equalities.
1297
+ local diff = diffs[pointer]
1298
+ local currentText = diff[2]
1299
+ local prevText = prevDiff[2]
1300
+ local nextText = nextDiff[2]
1301
+ if strsub(currentText, -#prevText) == prevText then
1302
+ -- Shift the edit over the previous equality.
1303
+ diff[2] = prevText .. strsub(currentText, 1, -#prevText - 1)
1304
+ nextDiff[2] = prevText .. nextDiff[2]
1305
+ tremove(diffs, pointer - 1)
1306
+ changes = true
1307
+ elseif strsub(currentText, 1, #nextText) == nextText then
1308
+ -- Shift the edit over the next equality.
1309
+ prevDiff[2] = prevText .. nextText
1310
+ diff[2] = strsub(currentText, #nextText + 1) .. nextText
1311
+ tremove(diffs, pointer + 1)
1312
+ changes = true
1313
+ end
1314
+ end
1315
+ pointer = pointer + 1
1316
+ end
1317
+ -- If shifts were made, the diff needs reordering and another shift sweep.
1318
+ if changes then
1319
+ -- LUANOTE: no return value, but necessary to use 'return' to get
1320
+ -- tail calls.
1321
+ return _diff_cleanupMerge(diffs)
1322
+ end
1323
+ end
1324
+
1325
+ --[[
1326
+ * loc is a location in text1, compute and return the equivalent location in
1327
+ * text2.
1328
+ * e.g. 'The cat' vs 'The big cat', 1->1, 5->8
1329
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1330
+ * @param {number} loc Location within text1.
1331
+ * @return {number} Location within text2.
1332
+ --]]
1333
+ function _diff_xIndex(diffs, loc)
1334
+ local chars1 = 1
1335
+ local chars2 = 1
1336
+ local last_chars1 = 1
1337
+ local last_chars2 = 1
1338
+ local x
1339
+ for _x, diff in ipairs(diffs) do
1340
+ x = _x
1341
+ if diff[1] ~= DIFF_INSERT then -- Equality or deletion.
1342
+ chars1 = chars1 + #diff[2]
1343
+ end
1344
+ if diff[1] ~= DIFF_DELETE then -- Equality or insertion.
1345
+ chars2 = chars2 + #diff[2]
1346
+ end
1347
+ if chars1 > loc then -- Overshot the location.
1348
+ break
1349
+ end
1350
+ last_chars1 = chars1
1351
+ last_chars2 = chars2
1352
+ end
1353
+ -- Was the location deleted?
1354
+ if diffs[x + 1] and (diffs[x][1] == DIFF_DELETE) then
1355
+ return last_chars2
1356
+ end
1357
+ -- Add the remaining character length.
1358
+ return last_chars2 + (loc - last_chars1)
1359
+ end
1360
+
1361
+ --[[
1362
+ * Compute and return the source text (all equalities and deletions).
1363
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1364
+ * @return {string} Source text.
1365
+ --]]
1366
+ function _diff_text1(diffs)
1367
+ local text = {}
1368
+ for x, diff in ipairs(diffs) do
1369
+ if diff[1] ~= DIFF_INSERT then
1370
+ text[#text + 1] = diff[2]
1371
+ end
1372
+ end
1373
+ return tconcat(text)
1374
+ end
1375
+
1376
+ --[[
1377
+ * Compute and return the destination text (all equalities and insertions).
1378
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1379
+ * @return {string} Destination text.
1380
+ --]]
1381
+ function _diff_text2(diffs)
1382
+ local text = {}
1383
+ for x, diff in ipairs(diffs) do
1384
+ if diff[1] ~= DIFF_DELETE then
1385
+ text[#text + 1] = diff[2]
1386
+ end
1387
+ end
1388
+ return tconcat(text)
1389
+ end
1390
+
1391
+ --[[
1392
+ * Crush the diff into an encoded string which describes the operations
1393
+ * required to transform text1 into text2.
1394
+ * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'.
1395
+ * Operations are tab-separated. Inserted text is escaped using %xx notation.
1396
+ * @param {Array.<Array.<number|string>>} diffs Array of diff tuples.
1397
+ * @return {string} Delta text.
1398
+ --]]
1399
+ function _diff_toDelta(diffs)
1400
+ local text = {}
1401
+ for x, diff in ipairs(diffs) do
1402
+ local op, data = diff[1], diff[2]
1403
+ if op == DIFF_INSERT then
1404
+ text[x] = '+' .. gsub(data, percentEncode_pattern, percentEncode_replace)
1405
+ elseif op == DIFF_DELETE then
1406
+ text[x] = '-' .. #data
1407
+ elseif op == DIFF_EQUAL then
1408
+ text[x] = '=' .. #data
1409
+ end
1410
+ end
1411
+ return tconcat(text, '\t')
1412
+ end
1413
+
1414
+ --[[
1415
+ * Given the original text1, and an encoded string which describes the
1416
+ * operations required to transform text1 into text2, compute the full diff.
1417
+ * @param {string} text1 Source string for the diff.
1418
+ * @param {string} delta Delta text.
1419
+ * @return {Array.<Array.<number|string>>} Array of diff tuples.
1420
+ * @throws {Errorend If invalid input.
1421
+ --]]
1422
+ function _diff_fromDelta(text1, delta)
1423
+ local diffs = {}
1424
+ local diffsLength = 0 -- Keeping our own length var is faster
1425
+ local pointer = 1 -- Cursor in text1
1426
+ for token in gmatch(delta, '[^\t]+') do
1427
+ -- Each token begins with a one character parameter which specifies the
1428
+ -- operation of this token (delete, insert, equality).
1429
+ local tokenchar, param = strsub(token, 1, 1), strsub(token, 2)
1430
+ if (tokenchar == '+') then
1431
+ local invalidDecode = false
1432
+ local decoded = gsub(param, '%%(.?.?)',
1433
+ function(c)
1434
+ local n = tonumber(c, 16)
1435
+ if (#c ~= 2) or (n == nil) then
1436
+ invalidDecode = true
1437
+ return ''
1438
+ end
1439
+ return strchar(n)
1440
+ end)
1441
+ if invalidDecode then
1442
+ -- Malformed URI sequence.
1443
+ error('Illegal escape in _diff_fromDelta: ' .. param)
1444
+ end
1445
+ diffsLength = diffsLength + 1
1446
+ diffs[diffsLength] = {DIFF_INSERT, decoded}
1447
+ elseif (tokenchar == '-') or (tokenchar == '=') then
1448
+ local n = tonumber(param)
1449
+ if (n == nil) or (n < 0) then
1450
+ error('Invalid number in _diff_fromDelta: ' .. param)
1451
+ end
1452
+ local text = strsub(text1, pointer, pointer + n - 1)
1453
+ pointer = pointer + n
1454
+ if (tokenchar == '=') then
1455
+ diffsLength = diffsLength + 1
1456
+ diffs[diffsLength] = {DIFF_EQUAL, text}
1457
+ else
1458
+ diffsLength = diffsLength + 1
1459
+ diffs[diffsLength] = {DIFF_DELETE, text}
1460
+ end
1461
+ else
1462
+ error('Invalid diff operation in _diff_fromDelta: ' .. token)
1463
+ end
1464
+ end
1465
+ if (pointer ~= #text1 + 1) then
1466
+ error('Delta length (' .. (pointer - 1)
1467
+ .. ') does not equal source text length (' .. #text1 .. ').')
1468
+ end
1469
+ return diffs
1470
+ end
1471
+
1472
+ -- ---------------------------------------------------------------------------
1473
+ -- MATCH API
1474
+ -- ---------------------------------------------------------------------------
1475
+
1476
+ local _match_bitap, _match_alphabet
1477
+
1478
+ --[[
1479
+ * Locate the best instance of 'pattern' in 'text' near 'loc'.
1480
+ * @param {string} text The text to search.
1481
+ * @param {string} pattern The pattern to search for.
1482
+ * @param {number} loc The location to search around.
1483
+ * @return {number} Best match index or -1.
1484
+ --]]
1485
+ function match_main(text, pattern, loc)
1486
+ -- Check for null inputs.
1487
+ if text == nil or pattern == nil or loc == nil then
1488
+ error('Null inputs. (match_main)')
1489
+ end
1490
+
1491
+ if text == pattern then
1492
+ -- Shortcut (potentially not guaranteed by the algorithm)
1493
+ return 1
1494
+ elseif #text == 0 then
1495
+ -- Nothing to match.
1496
+ return -1
1497
+ end
1498
+ loc = max(1, min(loc, #text))
1499
+ if strsub(text, loc, loc + #pattern - 1) == pattern then
1500
+ -- Perfect match at the perfect spot! (Includes case of null pattern)
1501
+ return loc
1502
+ else
1503
+ -- Do a fuzzy compare.
1504
+ return _match_bitap(text, pattern, loc)
1505
+ end
1506
+ end
1507
+
1508
+ -- ---------------------------------------------------------------------------
1509
+ -- UNOFFICIAL/PRIVATE MATCH FUNCTIONS
1510
+ -- ---------------------------------------------------------------------------
1511
+
1512
+ --[[
1513
+ * Initialise the alphabet for the Bitap algorithm.
1514
+ * @param {string} pattern The text to encode.
1515
+ * @return {Object} Hash of character locations.
1516
+ * @private
1517
+ --]]
1518
+ function _match_alphabet(pattern)
1519
+ local s = {}
1520
+ local i = 0
1521
+ for c in gmatch(pattern, '.') do
1522
+ s[c] = bor(s[c] or 0, lshift(1, #pattern - i - 1))
1523
+ i = i + 1
1524
+ end
1525
+ return s
1526
+ end
1527
+
1528
+ --[[
1529
+ * Locate the best instance of 'pattern' in 'text' near 'loc' using the
1530
+ * Bitap algorithm.
1531
+ * @param {string} text The text to search.
1532
+ * @param {string} pattern The pattern to search for.
1533
+ * @param {number} loc The location to search around.
1534
+ * @return {number} Best match index or -1.
1535
+ * @private
1536
+ --]]
1537
+ function _match_bitap(text, pattern, loc)
1538
+ if #pattern > Match_MaxBits then
1539
+ error('Pattern too long.')
1540
+ end
1541
+
1542
+ -- Initialise the alphabet.
1543
+ local s = _match_alphabet(pattern)
1544
+
1545
+ --[[
1546
+ * Compute and return the score for a match with e errors and x location.
1547
+ * Accesses loc and pattern through being a closure.
1548
+ * @param {number} e Number of errors in match.
1549
+ * @param {number} x Location of match.
1550
+ * @return {number} Overall score for match (0.0 = good, 1.0 = bad).
1551
+ * @private
1552
+ --]]
1553
+ local function _match_bitapScore(e, x)
1554
+ local accuracy = e / #pattern
1555
+ local proximity = abs(loc - x)
1556
+ if (Match_Distance == 0) then
1557
+ -- Dodge divide by zero error.
1558
+ return (proximity == 0) and 1 or accuracy
1559
+ end
1560
+ return accuracy + (proximity / Match_Distance)
1561
+ end
1562
+
1563
+ -- Highest score beyond which we give up.
1564
+ local score_threshold = Match_Threshold
1565
+ -- Is there a nearby exact match? (speedup)
1566
+ local best_loc = indexOf(text, pattern, loc)
1567
+ if best_loc then
1568
+ score_threshold = min(_match_bitapScore(0, best_loc), score_threshold)
1569
+ -- LUANOTE: Ideally we'd also check from the other direction, but Lua
1570
+ -- doesn't have an efficent lastIndexOf function.
1571
+ end
1572
+
1573
+ -- Initialise the bit arrays.
1574
+ local matchmask = lshift(1, #pattern - 1)
1575
+ best_loc = -1
1576
+
1577
+ local bin_min, bin_mid
1578
+ local bin_max = #pattern + #text
1579
+ local last_rd
1580
+ for d = 0, #pattern - 1, 1 do
1581
+ -- Scan for the best match; each iteration allows for one more error.
1582
+ -- Run a binary search to determine how far from 'loc' we can stray at this
1583
+ -- error level.
1584
+ bin_min = 0
1585
+ bin_mid = bin_max
1586
+ while (bin_min < bin_mid) do
1587
+ if (_match_bitapScore(d, loc + bin_mid) <= score_threshold) then
1588
+ bin_min = bin_mid
1589
+ else
1590
+ bin_max = bin_mid
1591
+ end
1592
+ bin_mid = floor(bin_min + (bin_max - bin_min) / 2)
1593
+ end
1594
+ -- Use the result from this iteration as the maximum for the next.
1595
+ bin_max = bin_mid
1596
+ local start = max(1, loc - bin_mid + 1)
1597
+ local finish = min(loc + bin_mid, #text) + #pattern
1598
+
1599
+ local rd = {}
1600
+ for j = start, finish do
1601
+ rd[j] = 0
1602
+ end
1603
+ rd[finish + 1] = lshift(1, d) - 1
1604
+ for j = finish, start, -1 do
1605
+ local charMatch = s[strsub(text, j - 1, j - 1)] or 0
1606
+ if (d == 0) then -- First pass: exact match.
1607
+ rd[j] = band(bor((rd[j + 1] * 2), 1), charMatch)
1608
+ else
1609
+ -- Subsequent passes: fuzzy match.
1610
+ -- Functions instead of operators make this hella messy.
1611
+ rd[j] = bor(
1612
+ band(
1613
+ bor(
1614
+ lshift(rd[j + 1], 1),
1615
+ 1
1616
+ ),
1617
+ charMatch
1618
+ ),
1619
+ bor(
1620
+ bor(
1621
+ lshift(bor(last_rd[j + 1], last_rd[j]), 1),
1622
+ 1
1623
+ ),
1624
+ last_rd[j + 1]
1625
+ )
1626
+ )
1627
+ end
1628
+ if (band(rd[j], matchmask) ~= 0) then
1629
+ local score = _match_bitapScore(d, j - 1)
1630
+ -- This match will almost certainly be better than any existing match.
1631
+ -- But check anyway.
1632
+ if (score <= score_threshold) then
1633
+ -- Told you so.
1634
+ score_threshold = score
1635
+ best_loc = j - 1
1636
+ if (best_loc > loc) then
1637
+ -- When passing loc, don't exceed our current distance from loc.
1638
+ start = max(1, loc * 2 - best_loc)
1639
+ else
1640
+ -- Already passed loc, downhill from here on in.
1641
+ break
1642
+ end
1643
+ end
1644
+ end
1645
+ end
1646
+ -- No hope for a (better) match at greater error levels.
1647
+ if (_match_bitapScore(d + 1, loc) > score_threshold) then
1648
+ break
1649
+ end
1650
+ last_rd = rd
1651
+ end
1652
+ return best_loc
1653
+ end
1654
+
1655
+ -- -----------------------------------------------------------------------------
1656
+ -- PATCH API
1657
+ -- -----------------------------------------------------------------------------
1658
+
1659
+ local _patch_addContext,
1660
+ _patch_deepCopy,
1661
+ _patch_addPadding,
1662
+ _patch_splitMax,
1663
+ _patch_appendText,
1664
+ _new_patch_obj
1665
+
1666
+ --[[
1667
+ * Compute a list of patches to turn text1 into text2.
1668
+ * Use diffs if provided, otherwise compute it ourselves.
1669
+ * There are four ways to call this function, depending on what data is
1670
+ * available to the caller:
1671
+ * Method 1:
1672
+ * a = text1, b = text2
1673
+ * Method 2:
1674
+ * a = diffs
1675
+ * Method 3 (optimal):
1676
+ * a = text1, b = diffs
1677
+ * Method 4 (deprecated, use method 3):
1678
+ * a = text1, b = text2, c = diffs
1679
+ *
1680
+ * @param {string|Array.<Array.<number|string>>} a text1 (methods 1,3,4) or
1681
+ * Array of diff tuples for text1 to text2 (method 2).
1682
+ * @param {string|Array.<Array.<number|string>>} opt_b text2 (methods 1,4) or
1683
+ * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2).
1684
+ * @param {string|Array.<Array.<number|string>>} opt_c Array of diff tuples for
1685
+ * text1 to text2 (method 4) or undefined (methods 1,2,3).
1686
+ * @return {Array.<_new_patch_obj>} Array of patch objects.
1687
+ --]]
1688
+ function patch_make(a, opt_b, opt_c)
1689
+ local text1, diffs
1690
+ local type_a, type_b, type_c = type(a), type(opt_b), type(opt_c)
1691
+ if (type_a == 'string') and (type_b == 'string') and (type_c == 'nil') then
1692
+ -- Method 1: text1, text2
1693
+ -- Compute diffs from text1 and text2.
1694
+ text1 = a
1695
+ diffs = diff_main(text1, opt_b, true)
1696
+ if (#diffs > 2) then
1697
+ diff_cleanupSemantic(diffs)
1698
+ diff_cleanupEfficiency(diffs)
1699
+ end
1700
+ elseif (type_a == 'table') and (type_b == 'nil') and (type_c == 'nil') then
1701
+ -- Method 2: diffs
1702
+ -- Compute text1 from diffs.
1703
+ diffs = a
1704
+ text1 = _diff_text1(diffs)
1705
+ elseif (type_a == 'string') and (type_b == 'table') and (type_c == 'nil') then
1706
+ -- Method 3: text1, diffs
1707
+ text1 = a
1708
+ diffs = opt_b
1709
+ elseif (type_a == 'string') and (type_b == 'string') and (type_c == 'table')
1710
+ then
1711
+ -- Method 4: text1, text2, diffs
1712
+ -- text2 is not used.
1713
+ text1 = a
1714
+ diffs = opt_c
1715
+ else
1716
+ error('Unknown call format to patch_make.')
1717
+ end
1718
+
1719
+ if (diffs[1] == nil) then
1720
+ return {} -- Get rid of the null case.
1721
+ end
1722
+
1723
+ local patches = {}
1724
+ local patch = _new_patch_obj()
1725
+ local patchDiffLength = 0 -- Keeping our own length var is faster.
1726
+ local char_count1 = 0 -- Number of characters into the text1 string.
1727
+ local char_count2 = 0 -- Number of characters into the text2 string.
1728
+ -- Start with text1 (prepatch_text) and apply the diffs until we arrive at
1729
+ -- text2 (postpatch_text). We recreate the patches one by one to determine
1730
+ -- context info.
1731
+ local prepatch_text, postpatch_text = text1, text1
1732
+ for x, diff in ipairs(diffs) do
1733
+ local diff_type, diff_text = diff[1], diff[2]
1734
+
1735
+ if (patchDiffLength == 0) and (diff_type ~= DIFF_EQUAL) then
1736
+ -- A new patch starts here.
1737
+ patch.start1 = char_count1 + 1
1738
+ patch.start2 = char_count2 + 1
1739
+ end
1740
+
1741
+ if (diff_type == DIFF_INSERT) then
1742
+ patchDiffLength = patchDiffLength + 1
1743
+ patch.diffs[patchDiffLength] = diff
1744
+ patch.length2 = patch.length2 + #diff_text
1745
+ postpatch_text = strsub(postpatch_text, 1, char_count2)
1746
+ .. diff_text .. strsub(postpatch_text, char_count2 + 1)
1747
+ elseif (diff_type == DIFF_DELETE) then
1748
+ patch.length1 = patch.length1 + #diff_text
1749
+ patchDiffLength = patchDiffLength + 1
1750
+ patch.diffs[patchDiffLength] = diff
1751
+ postpatch_text = strsub(postpatch_text, 1, char_count2)
1752
+ .. strsub(postpatch_text, char_count2 + #diff_text + 1)
1753
+ elseif (diff_type == DIFF_EQUAL) then
1754
+ if (#diff_text <= Patch_Margin * 2)
1755
+ and (patchDiffLength ~= 0) and (#diffs ~= x) then
1756
+ -- Small equality inside a patch.
1757
+ patchDiffLength = patchDiffLength + 1
1758
+ patch.diffs[patchDiffLength] = diff
1759
+ patch.length1 = patch.length1 + #diff_text
1760
+ patch.length2 = patch.length2 + #diff_text
1761
+ elseif (#diff_text >= Patch_Margin * 2) then
1762
+ -- Time for a new patch.
1763
+ if (patchDiffLength ~= 0) then
1764
+ _patch_addContext(patch, prepatch_text)
1765
+ patches[#patches + 1] = patch
1766
+ patch = _new_patch_obj()
1767
+ patchDiffLength = 0
1768
+ -- Unlike Unidiff, our patch lists have a rolling context.
1769
+ -- http://code.google.com/p/google-diff-match-patch/wiki/Unidiff
1770
+ -- Update prepatch text & pos to reflect the application of the
1771
+ -- just completed patch.
1772
+ prepatch_text = postpatch_text
1773
+ char_count1 = char_count2
1774
+ end
1775
+ end
1776
+ end
1777
+
1778
+ -- Update the current character count.
1779
+ if (diff_type ~= DIFF_INSERT) then
1780
+ char_count1 = char_count1 + #diff_text
1781
+ end
1782
+ if (diff_type ~= DIFF_DELETE) then
1783
+ char_count2 = char_count2 + #diff_text
1784
+ end
1785
+ end
1786
+
1787
+ -- Pick up the leftover patch if not empty.
1788
+ if (patchDiffLength > 0) then
1789
+ _patch_addContext(patch, prepatch_text)
1790
+ patches[#patches + 1] = patch
1791
+ end
1792
+
1793
+ return patches
1794
+ end
1795
+
1796
+ --[[
1797
+ * Merge a set of patches onto the text. Return a patched text, as well
1798
+ * as a list of true/false values indicating which patches were applied.
1799
+ * @param {Array.<_new_patch_obj>} patches Array of patch objects.
1800
+ * @param {string} text Old text.
1801
+ * @return {Array.<string|Array.<boolean>>} Two return values, the
1802
+ * new text and an array of boolean values.
1803
+ --]]
1804
+ function patch_apply(patches, text)
1805
+ if patches[1] == nil then
1806
+ return text, {}
1807
+ end
1808
+
1809
+ -- Deep copy the patches so that no changes are made to originals.
1810
+ patches = _patch_deepCopy(patches)
1811
+
1812
+ local nullPadding = _patch_addPadding(patches)
1813
+ text = nullPadding .. text .. nullPadding
1814
+
1815
+ _patch_splitMax(patches)
1816
+ -- delta keeps track of the offset between the expected and actual location
1817
+ -- of the previous patch. If there are patches expected at positions 10 and
1818
+ -- 20, but the first patch was found at 12, delta is 2 and the second patch
1819
+ -- has an effective expected position of 22.
1820
+ local delta = 0
1821
+ local results = {}
1822
+ for x, patch in ipairs(patches) do
1823
+ local expected_loc = patch.start2 + delta
1824
+ local text1 = _diff_text1(patch.diffs)
1825
+ local start_loc
1826
+ local end_loc = -1
1827
+ if #text1 > Match_MaxBits then
1828
+ -- _patch_splitMax will only provide an oversized pattern in
1829
+ -- the case of a monster delete.
1830
+ start_loc = match_main(text,
1831
+ strsub(text1, 1, Match_MaxBits), expected_loc)
1832
+ if start_loc ~= -1 then
1833
+ end_loc = match_main(text, strsub(text1, -Match_MaxBits),
1834
+ expected_loc + #text1 - Match_MaxBits)
1835
+ if end_loc == -1 or start_loc >= end_loc then
1836
+ -- Can't find valid trailing context. Drop this patch.
1837
+ start_loc = -1
1838
+ end
1839
+ end
1840
+ else
1841
+ start_loc = match_main(text, text1, expected_loc)
1842
+ end
1843
+ if start_loc == -1 then
1844
+ -- No match found. :(
1845
+ results[x] = false
1846
+ -- Subtract the delta for this failed patch from subsequent patches.
1847
+ delta = delta - patch.length2 - patch.length1
1848
+ else
1849
+ -- Found a match. :)
1850
+ results[x] = true
1851
+ delta = start_loc - expected_loc
1852
+ local text2
1853
+ if end_loc == -1 then
1854
+ text2 = strsub(text, start_loc, start_loc + #text1 - 1)
1855
+ else
1856
+ text2 = strsub(text, start_loc, end_loc + Match_MaxBits - 1)
1857
+ end
1858
+ if text1 == text2 then
1859
+ -- Perfect match, just shove the replacement text in.
1860
+ text = strsub(text, 1, start_loc - 1) .. _diff_text2(patch.diffs)
1861
+ .. strsub(text, start_loc + #text1)
1862
+ else
1863
+ -- Imperfect match. Run a diff to get a framework of equivalent
1864
+ -- indices.
1865
+ local diffs = diff_main(text1, text2, false)
1866
+ if (#text1 > Match_MaxBits)
1867
+ and (diff_levenshtein(diffs) / #text1 > Patch_DeleteThreshold) then
1868
+ -- The end points match, but the content is unacceptably bad.
1869
+ results[x] = false
1870
+ else
1871
+ _diff_cleanupSemanticLossless(diffs)
1872
+ local index1 = 1
1873
+ local index2
1874
+ for y, mod in ipairs(patch.diffs) do
1875
+ if mod[1] ~= DIFF_EQUAL then
1876
+ index2 = _diff_xIndex(diffs, index1)
1877
+ end
1878
+ if mod[1] == DIFF_INSERT then
1879
+ text = strsub(text, 1, start_loc + index2 - 2)
1880
+ .. mod[2] .. strsub(text, start_loc + index2 - 1)
1881
+ elseif mod[1] == DIFF_DELETE then
1882
+ text = strsub(text, 1, start_loc + index2 - 2) .. strsub(text,
1883
+ start_loc + _diff_xIndex(diffs, index1 + #mod[2] - 1))
1884
+ end
1885
+ if mod[1] ~= DIFF_DELETE then
1886
+ index1 = index1 + #mod[2]
1887
+ end
1888
+ end
1889
+ end
1890
+ end
1891
+ end
1892
+ end
1893
+ -- Strip the padding off.
1894
+ text = strsub(text, #nullPadding + 1, -#nullPadding - 1)
1895
+ return text, results
1896
+ end
1897
+
1898
+ --[[
1899
+ * Take a list of patches and return a textual representation.
1900
+ * @param {Array.<_new_patch_obj>} patches Array of patch objects.
1901
+ * @return {string} Text representation of patches.
1902
+ --]]
1903
+ function patch_toText(patches)
1904
+ local text = {}
1905
+ for x, patch in ipairs(patches) do
1906
+ _patch_appendText(patch, text)
1907
+ end
1908
+ return tconcat(text)
1909
+ end
1910
+
1911
+ --[[
1912
+ * Parse a textual representation of patches and return a list of patch objects.
1913
+ * @param {string} textline Text representation of patches.
1914
+ * @return {Array.<_new_patch_obj>} Array of patch objects.
1915
+ * @throws {Error} If invalid input.
1916
+ --]]
1917
+ function patch_fromText(textline)
1918
+ local patches = {}
1919
+ if (#textline == 0) then
1920
+ return patches
1921
+ end
1922
+ local text = {}
1923
+ for line in gmatch(textline, '([^\n]*)') do
1924
+ text[#text + 1] = line
1925
+ end
1926
+ local textPointer = 1
1927
+ while (textPointer <= #text) do
1928
+ local start1, length1, start2, length2
1929
+ = strmatch(text[textPointer], '^@@ %-(%d+),?(%d*) %+(%d+),?(%d*) @@$')
1930
+ if (start1 == nil) then
1931
+ error('Invalid patch string: "' .. text[textPointer] .. '"')
1932
+ end
1933
+ local patch = _new_patch_obj()
1934
+ patches[#patches + 1] = patch
1935
+
1936
+ start1 = tonumber(start1)
1937
+ length1 = tonumber(length1) or 1
1938
+ if (length1 == 0) then
1939
+ start1 = start1 + 1
1940
+ end
1941
+ patch.start1 = start1
1942
+ patch.length1 = length1
1943
+
1944
+ start2 = tonumber(start2)
1945
+ length2 = tonumber(length2) or 1
1946
+ if (length2 == 0) then
1947
+ start2 = start2 + 1
1948
+ end
1949
+ patch.start2 = start2
1950
+ patch.length2 = length2
1951
+
1952
+ textPointer = textPointer + 1
1953
+
1954
+ while true do
1955
+ local line = text[textPointer]
1956
+ if (line == nil) then
1957
+ break
1958
+ end
1959
+ local sign; sign, line = strsub(line, 1, 1), strsub(line, 2)
1960
+
1961
+ local invalidDecode = false
1962
+ local decoded = gsub(line, '%%(.?.?)',
1963
+ function(c)
1964
+ local n = tonumber(c, 16)
1965
+ if (#c ~= 2) or (n == nil) then
1966
+ invalidDecode = true
1967
+ return ''
1968
+ end
1969
+ return strchar(n)
1970
+ end)
1971
+ if invalidDecode then
1972
+ -- Malformed URI sequence.
1973
+ error('Illegal escape in patch_fromText: ' .. line)
1974
+ end
1975
+
1976
+ line = decoded
1977
+
1978
+ if (sign == '-') then
1979
+ -- Deletion.
1980
+ patch.diffs[#patch.diffs + 1] = {DIFF_DELETE, line}
1981
+ elseif (sign == '+') then
1982
+ -- Insertion.
1983
+ patch.diffs[#patch.diffs + 1] = {DIFF_INSERT, line}
1984
+ elseif (sign == ' ') then
1985
+ -- Minor equality.
1986
+ patch.diffs[#patch.diffs + 1] = {DIFF_EQUAL, line}
1987
+ elseif (sign == '@') then
1988
+ -- Start of next patch.
1989
+ break
1990
+ elseif (sign == '') then
1991
+ -- Blank line? Whatever.
1992
+ else
1993
+ -- WTF?
1994
+ error('Invalid patch mode "' .. sign .. '" in: ' .. line)
1995
+ end
1996
+ textPointer = textPointer + 1
1997
+ end
1998
+ end
1999
+ return patches
2000
+ end
2001
+
2002
+ -- ---------------------------------------------------------------------------
2003
+ -- UNOFFICIAL/PRIVATE PATCH FUNCTIONS
2004
+ -- ---------------------------------------------------------------------------
2005
+
2006
+ local patch_meta = {
2007
+ __tostring = function(patch)
2008
+ local buf = {}
2009
+ _patch_appendText(patch, buf)
2010
+ return tconcat(buf)
2011
+ end
2012
+ }
2013
+
2014
+ --[[
2015
+ * Class representing one patch operation.
2016
+ * @constructor
2017
+ --]]
2018
+ function _new_patch_obj()
2019
+ return setmetatable({
2020
+ --[[ @type {Array.<Array.<number|string>>} ]]
2021
+ diffs = {};
2022
+ --[[ @type {?number} ]]
2023
+ start1 = 1; -- nil;
2024
+ --[[ @type {?number} ]]
2025
+ start2 = 1; -- nil;
2026
+ --[[ @type {number} ]]
2027
+ length1 = 0;
2028
+ --[[ @type {number} ]]
2029
+ length2 = 0;
2030
+ }, patch_meta)
2031
+ end
2032
+
2033
+ --[[
2034
+ * Increase the context until it is unique,
2035
+ * but don't let the pattern expand beyond Match_MaxBits.
2036
+ * @param {_new_patch_obj} patch The patch to grow.
2037
+ * @param {string} text Source text.
2038
+ * @private
2039
+ --]]
2040
+ function _patch_addContext(patch, text)
2041
+ if (#text == 0) then
2042
+ return
2043
+ end
2044
+ local pattern = strsub(text, patch.start2, patch.start2 + patch.length1 - 1)
2045
+ local padding = 0
2046
+
2047
+ -- LUANOTE: Lua's lack of a lastIndexOf function results in slightly
2048
+ -- different logic here than in other language ports.
2049
+ -- Look for the first two matches of pattern in text. If two are found,
2050
+ -- increase the pattern length.
2051
+ local firstMatch = indexOf(text, pattern)
2052
+ local secondMatch = nil
2053
+ if (firstMatch ~= nil) then
2054
+ secondMatch = indexOf(text, pattern, firstMatch + 1)
2055
+ end
2056
+ while (#pattern == 0 or secondMatch ~= nil)
2057
+ and (#pattern < Match_MaxBits - Patch_Margin - Patch_Margin) do
2058
+ padding = padding + Patch_Margin
2059
+ pattern = strsub(text, max(1, patch.start2 - padding),
2060
+ patch.start2 + patch.length1 - 1 + padding)
2061
+ firstMatch = indexOf(text, pattern)
2062
+ if (firstMatch ~= nil) then
2063
+ secondMatch = indexOf(text, pattern, firstMatch + 1)
2064
+ else
2065
+ secondMatch = nil
2066
+ end
2067
+ end
2068
+ -- Add one chunk for good luck.
2069
+ padding = padding + Patch_Margin
2070
+
2071
+ -- Add the prefix.
2072
+ local prefix = strsub(text, max(1, patch.start2 - padding), patch.start2 - 1)
2073
+ if (#prefix > 0) then
2074
+ tinsert(patch.diffs, 1, {DIFF_EQUAL, prefix})
2075
+ end
2076
+ -- Add the suffix.
2077
+ local suffix = strsub(text, patch.start2 + patch.length1,
2078
+ patch.start2 + patch.length1 - 1 + padding)
2079
+ if (#suffix > 0) then
2080
+ patch.diffs[#patch.diffs + 1] = {DIFF_EQUAL, suffix}
2081
+ end
2082
+
2083
+ -- Roll back the start points.
2084
+ patch.start1 = patch.start1 - #prefix
2085
+ patch.start2 = patch.start2 - #prefix
2086
+ -- Extend the lengths.
2087
+ patch.length1 = patch.length1 + #prefix + #suffix
2088
+ patch.length2 = patch.length2 + #prefix + #suffix
2089
+ end
2090
+
2091
+ --[[
2092
+ * Given an array of patches, return another array that is identical.
2093
+ * @param {Array.<_new_patch_obj>} patches Array of patch objects.
2094
+ * @return {Array.<_new_patch_obj>} Array of patch objects.
2095
+ --]]
2096
+ function _patch_deepCopy(patches)
2097
+ local patchesCopy = {}
2098
+ for x, patch in ipairs(patches) do
2099
+ local patchCopy = _new_patch_obj()
2100
+ local diffsCopy = {}
2101
+ for i, diff in ipairs(patch.diffs) do
2102
+ diffsCopy[i] = {diff[1], diff[2]}
2103
+ end
2104
+ patchCopy.diffs = diffsCopy
2105
+ patchCopy.start1 = patch.start1
2106
+ patchCopy.start2 = patch.start2
2107
+ patchCopy.length1 = patch.length1
2108
+ patchCopy.length2 = patch.length2
2109
+ patchesCopy[x] = patchCopy
2110
+ end
2111
+ return patchesCopy
2112
+ end
2113
+
2114
+ --[[
2115
+ * Add some padding on text start and end so that edges can match something.
2116
+ * Intended to be called only from within patch_apply.
2117
+ * @param {Array.<_new_patch_obj>} patches Array of patch objects.
2118
+ * @return {string} The padding string added to each side.
2119
+ --]]
2120
+ function _patch_addPadding(patches)
2121
+ local paddingLength = Patch_Margin
2122
+ local nullPadding = ''
2123
+ for x = 1, paddingLength do
2124
+ nullPadding = nullPadding .. strchar(x)
2125
+ end
2126
+
2127
+ -- Bump all the patches forward.
2128
+ for x, patch in ipairs(patches) do
2129
+ patch.start1 = patch.start1 + paddingLength
2130
+ patch.start2 = patch.start2 + paddingLength
2131
+ end
2132
+
2133
+ -- Add some padding on start of first diff.
2134
+ local patch = patches[1]
2135
+ local diffs = patch.diffs
2136
+ local firstDiff = diffs[1]
2137
+ if (firstDiff == nil) or (firstDiff[1] ~= DIFF_EQUAL) then
2138
+ -- Add nullPadding equality.
2139
+ tinsert(diffs, 1, {DIFF_EQUAL, nullPadding})
2140
+ patch.start1 = patch.start1 - paddingLength -- Should be 0.
2141
+ patch.start2 = patch.start2 - paddingLength -- Should be 0.
2142
+ patch.length1 = patch.length1 + paddingLength
2143
+ patch.length2 = patch.length2 + paddingLength
2144
+ elseif (paddingLength > #firstDiff[2]) then
2145
+ -- Grow first equality.
2146
+ local extraLength = paddingLength - #firstDiff[2]
2147
+ firstDiff[2] = strsub(nullPadding, #firstDiff[2] + 1) .. firstDiff[2]
2148
+ patch.start1 = patch.start1 - extraLength
2149
+ patch.start2 = patch.start2 - extraLength
2150
+ patch.length1 = patch.length1 + extraLength
2151
+ patch.length2 = patch.length2 + extraLength
2152
+ end
2153
+
2154
+ -- Add some padding on end of last diff.
2155
+ patch = patches[#patches]
2156
+ diffs = patch.diffs
2157
+ local lastDiff = diffs[#diffs]
2158
+ if (lastDiff == nil) or (lastDiff[1] ~= DIFF_EQUAL) then
2159
+ -- Add nullPadding equality.
2160
+ diffs[#diffs + 1] = {DIFF_EQUAL, nullPadding}
2161
+ patch.length1 = patch.length1 + paddingLength
2162
+ patch.length2 = patch.length2 + paddingLength
2163
+ elseif (paddingLength > #lastDiff[2]) then
2164
+ -- Grow last equality.
2165
+ local extraLength = paddingLength - #lastDiff[2]
2166
+ lastDiff[2] = lastDiff[2] .. strsub(nullPadding, 1, extraLength)
2167
+ patch.length1 = patch.length1 + extraLength
2168
+ patch.length2 = patch.length2 + extraLength
2169
+ end
2170
+
2171
+ return nullPadding
2172
+ end
2173
+
2174
+ --[[
2175
+ * Look through the patches and break up any which are longer than the maximum
2176
+ * limit of the match algorithm.
2177
+ * Intended to be called only from within patch_apply.
2178
+ * @param {Array.<_new_patch_obj>} patches Array of patch objects.
2179
+ --]]
2180
+ function _patch_splitMax(patches)
2181
+ local patch_size = Match_MaxBits
2182
+ local x = 1
2183
+ while true do
2184
+ local patch = patches[x]
2185
+ if patch == nil then
2186
+ return
2187
+ end
2188
+ if patch.length1 > patch_size then
2189
+ local bigpatch = patch
2190
+ -- Remove the big old patch.
2191
+ tremove(patches, x)
2192
+ x = x - 1
2193
+ local start1 = bigpatch.start1
2194
+ local start2 = bigpatch.start2
2195
+ local precontext = ''
2196
+ while bigpatch.diffs[1] do
2197
+ -- Create one of several smaller patches.
2198
+ local patch = _new_patch_obj()
2199
+ local empty = true
2200
+ patch.start1 = start1 - #precontext
2201
+ patch.start2 = start2 - #precontext
2202
+ if precontext ~= '' then
2203
+ patch.length1, patch.length2 = #precontext, #precontext
2204
+ patch.diffs[#patch.diffs + 1] = {DIFF_EQUAL, precontext}
2205
+ end
2206
+ while bigpatch.diffs[1] and (patch.length1 < patch_size-Patch_Margin) do
2207
+ local diff_type = bigpatch.diffs[1][1]
2208
+ local diff_text = bigpatch.diffs[1][2]
2209
+ if (diff_type == DIFF_INSERT) then
2210
+ -- Insertions are harmless.
2211
+ patch.length2 = patch.length2 + #diff_text
2212
+ start2 = start2 + #diff_text
2213
+ patch.diffs[#(patch.diffs) + 1] = bigpatch.diffs[1]
2214
+ tremove(bigpatch.diffs, 1)
2215
+ empty = false
2216
+ elseif (diff_type == DIFF_DELETE) and (#patch.diffs == 1)
2217
+ and (patch.diffs[1][1] == DIFF_EQUAL)
2218
+ and (#diff_text > 2 * patch_size) then
2219
+ -- This is a large deletion. Let it pass in one chunk.
2220
+ patch.length1 = patch.length1 + #diff_text
2221
+ start1 = start1 + #diff_text
2222
+ empty = false
2223
+ patch.diffs[#patch.diffs + 1] = {diff_type, diff_text}
2224
+ tremove(bigpatch.diffs, 1)
2225
+ else
2226
+ -- Deletion or equality.
2227
+ -- Only take as much as we can stomach.
2228
+ diff_text = strsub(diff_text, 1,
2229
+ patch_size - patch.length1 - Patch_Margin)
2230
+ patch.length1 = patch.length1 + #diff_text
2231
+ start1 = start1 + #diff_text
2232
+ if (diff_type == DIFF_EQUAL) then
2233
+ patch.length2 = patch.length2 + #diff_text
2234
+ start2 = start2 + #diff_text
2235
+ else
2236
+ empty = false
2237
+ end
2238
+ patch.diffs[#patch.diffs + 1] = {diff_type, diff_text}
2239
+ if (diff_text == bigpatch.diffs[1][2]) then
2240
+ tremove(bigpatch.diffs, 1)
2241
+ else
2242
+ bigpatch.diffs[1][2]
2243
+ = strsub(bigpatch.diffs[1][2], #diff_text + 1)
2244
+ end
2245
+ end
2246
+ end
2247
+ -- Compute the head context for the next patch.
2248
+ precontext = _diff_text2(patch.diffs)
2249
+ precontext = strsub(precontext, -Patch_Margin)
2250
+ -- Append the end context for this patch.
2251
+ local postcontext = strsub(_diff_text1(bigpatch.diffs), 1, Patch_Margin)
2252
+ if postcontext ~= '' then
2253
+ patch.length1 = patch.length1 + #postcontext
2254
+ patch.length2 = patch.length2 + #postcontext
2255
+ if patch.diffs[1]
2256
+ and (patch.diffs[#patch.diffs][1] == DIFF_EQUAL) then
2257
+ patch.diffs[#patch.diffs][2] = patch.diffs[#patch.diffs][2]
2258
+ .. postcontext
2259
+ else
2260
+ patch.diffs[#patch.diffs + 1] = {DIFF_EQUAL, postcontext}
2261
+ end
2262
+ end
2263
+ if not empty then
2264
+ x = x + 1
2265
+ tinsert(patches, x, patch)
2266
+ end
2267
+ end
2268
+ end
2269
+ x = x + 1
2270
+ end
2271
+ end
2272
+
2273
+ --[[
2274
+ * Emulate GNU diff's format.
2275
+ * Header: @@ -382,8 +481,9 @@
2276
+ * @return {string} The GNU diff string.
2277
+ --]]
2278
+ function _patch_appendText(patch, text)
2279
+ local coords1, coords2
2280
+ local length1, length2 = patch.length1, patch.length2
2281
+ local start1, start2 = patch.start1, patch.start2
2282
+ local diffs = patch.diffs
2283
+
2284
+ if length1 == 1 then
2285
+ coords1 = start1
2286
+ else
2287
+ coords1 = ((length1 == 0) and (start1 - 1) or start1) .. ',' .. length1
2288
+ end
2289
+
2290
+ if length2 == 1 then
2291
+ coords2 = start2
2292
+ else
2293
+ coords2 = ((length2 == 0) and (start2 - 1) or start2) .. ',' .. length2
2294
+ end
2295
+ text[#text + 1] = '@@ -' .. coords1 .. ' +' .. coords2 .. ' @@\n'
2296
+
2297
+ local op
2298
+ -- Escape the body of the patch with %xx notation.
2299
+ for x, diff in ipairs(patch.diffs) do
2300
+ local diff_type = diff[1]
2301
+ if diff_type == DIFF_INSERT then
2302
+ op = '+'
2303
+ elseif diff_type == DIFF_DELETE then
2304
+ op = '-'
2305
+ elseif diff_type == DIFF_EQUAL then
2306
+ op = ' '
2307
+ end
2308
+ text[#text + 1] = op
2309
+ .. gsub(diffs[x][2], percentEncode_pattern, percentEncode_replace)
2310
+ .. '\n'
2311
+ end
2312
+
2313
+ return text
2314
+ end
2315
+
2316
+ local src = redis.call("get", KEYS[1])
2317
+ local dst1 = redis.call("get", KEYS[2])
2318
+ local dst2 = redis.call("get", KEYS[3])
2319
+ local patches = patch_make(src, dst2)
2320
+ local result = patch_apply(patches, dst1)
2321
+
2322
+ if KEYS[4] then
2323
+ redis.call("set", KEYS[4], result)
2324
+ end
2325
+
2326
+ return result
2327
+
2328
+ -- -- Expose the API
2329
+ -- _M.DIFF_DELETE = DIFF_DELETE
2330
+ -- _M.DIFF_INSERT = DIFF_INSERT
2331
+ -- _M.DIFF_EQUAL = DIFF_EQUAL
2332
+ --
2333
+ -- _M.diff_main = diff_main
2334
+ -- _M.diff_cleanupSemantic = diff_cleanupSemantic
2335
+ -- _M.diff_cleanupEfficiency = diff_cleanupEfficiency
2336
+ -- _M.diff_levenshtein = diff_levenshtein
2337
+ -- _M.diff_prettyHtml = diff_prettyHtml
2338
+ --
2339
+ -- _M.match_main = match_main
2340
+ --
2341
+ -- _M.patch_make = patch_make
2342
+ -- _M.patch_toText = patch_toText
2343
+ -- _M.patch_fromText = patch_fromText
2344
+ -- _M.patch_apply = patch_apply
2345
+ --
2346
+ -- -- Expose some non-API functions as well, for testing purposes etc.
2347
+ -- _M.diff_commonPrefix = _diff_commonPrefix
2348
+ -- _M.diff_commonSuffix = _diff_commonSuffix
2349
+ -- _M.diff_commonOverlap = _diff_commonOverlap
2350
+ -- _M.diff_halfMatch = _diff_halfMatch
2351
+ -- _M.diff_bisect = _diff_bisect
2352
+ -- _M.diff_cleanupMerge = _diff_cleanupMerge
2353
+ -- _M.diff_cleanupSemanticLossless = _diff_cleanupSemanticLossless
2354
+ -- _M.diff_text1 = _diff_text1
2355
+ -- _M.diff_text2 = _diff_text2
2356
+ -- _M.diff_toDelta = _diff_toDelta
2357
+ -- _M.diff_fromDelta = _diff_fromDelta
2358
+ -- _M.diff_xIndex = _diff_xIndex
2359
+ -- _M.match_alphabet = _match_alphabet
2360
+ -- _M.match_bitap = _match_bitap
2361
+ -- _M.new_patch_obj = _new_patch_obj
2362
+ -- _M.patch_addContext = _patch_addContext
2363
+ -- _M.patch_splitMax = _patch_splitMax
2364
+ -- _M.patch_addPadding = _patch_addPadding