-
Notifications
You must be signed in to change notification settings - Fork 0
/
diff.nim
210 lines (172 loc) · 5.02 KB
/
diff.nim
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
import dom, htmltags, strutils
type
Action = enum
halt
navUp
navKid
navParent
navGrandParent
navFirstChild
navSecondChild
navNextSibling
elAppend
elRemoveLast
elRemoveMany
attrSet
attrRemove
Patch = object
patchPath: array[256, int]
patchLen: int
scanPath: array[256, int]
scanLen: int
data*: Buf
proc initPatch*(): Patch =
result.data.initBuf(1024*1024)
proc clear*(patch: var Patch) =
patch.patchLen = 0
patch.scanLen = 0
patch.data.clear()
template navigateUp(patch: var Patch, times: int) =
case times:
of 0: discard
of 1:
patch.data.push int(navParent)
of 2:
patch.data.push int(navGrandParent)
else:
patch.data.push int(navUp)
patch.data.push times
template navigateSibling(patch: var Patch) =
patch.data.push int(navNextSibling)
template navigateKid(patch: var Patch, kid: int) =
case kid
of 0:
patch.data.push int(navFirstChild)
of 1:
patch.data.push int(navSecondChild)
else:
patch.data.push int(navKid)
patch.data.push kid
proc navigateChildren(patch: var Patch, pos: int) =
for i in pos..< patch.scanLen:
patch.patchPath[i] = patch.scanPath[i]
patch.navigateKid(patch.scanPath[i])
patch.patchLen = patch.scanLen
proc navigate(patch: var Patch) =
# render navigation commands to take me from `patchPath` to `scanPath`
let len = min(patch.patchLen, patch.scanLen)
var prefixLen = 0
while prefixLen < len and patch.patchPath[prefixLen] == patch.scanPath[prefixLen]: inc(prefixLen)
#echo "navigate: ", patch.patchPath, " -> ", patch.scanPath, " common: ", prefixLen
if patch.patchLen > prefixLen:
# check next sibling
if patch.scanLen > prefixLen and patch.scanPath[prefixLen] == patch.patchPath[prefixLen] + 1:
patch.navigateUp(patch.patchLen - prefixLen - 1)
patch.navigateSibling()
patch.navigateChildren(prefixLen + 1)
patch.patchPath = patch.scanPath
else:
patch.navigateUp(patch.patchLen - prefixLen)
patch.navigateChildren(prefixLen)
else:
patch.navigateChildren(prefixLen)
proc patchElementReplace(patch: var Patch, dst, src: Element) =
echo "patch element replace"
proc patchElementAdd(patch: var Patch, e: Element) =
patch.navigate()
#echo "patch-append: ", e #toHex(cast[int](e))
patch.data.push int(elAppend)
patch.data.push cast[int](e)
proc patchElementRemoveChildren(patch: var Patch, n: int) =
patch.navigate()
if n == 1:
patch.data.push int(elRemoveLast)
else:
patch.data.push int(elRemoveMany)
patch.data.push n
proc patchSetAttribute(patch: var Patch, attr: Attribute) =
patch.navigate()
patch.data.push int(attrSet)
patch.data.push int(attr.attr)
patch.data.push cast[int](attr.value)
proc patchRemoveAttribute(patch: var Patch, attr: int) =
patch.navigate()
patch.data.push int(attrRemove)
patch.data.push int(attr)
proc diffAttrs(patch: var Patch, dst, src: Element) =
var dI, sI: int
let dA = dst.attrs
let sA = src.attrs
while dI < dst.nAttrs and sI < src.nAttrs:
let c = cmp(dA[dI].attr, sA[si].attr)
if c == 0:
if dA[dI].value != sA[si].value:
patch.patchSetAttribute(dA[dI])
inc dI
inc sI
elif c < 0:
# new attribute at destination
patch.patchSetAttribute(dA[dI])
inc dI
else:
# attribute removed at destination
patch.patchRemoveAttribute(sA[sI].attr)
inc sI
for i in dI..<dst.nAttrs:
patch.patchSetAttribute(dA[dI])
for i in sI..<src.nAttrs:
patch.patchRemoveAttribute(sA[sI].attr)
proc diff*(patch: var Patch, dst, src: Element)
proc diffChildren(patch: var Patch, dst, src: Element) =
var i = 0
let d = dst.kids
let s = src.kids
# move down
# echo "move down"
inc patch.scanLen
while i < dst.nKids and i < src.nKids:
patch.scanPath[patch.scanLen - 1] = i
diff(patch, d[i], s[i])
inc i
# moving up
# echo "move up"
dec patch.scanLen
if dst.nKids > src.nKids:
for j in i..<dst.nKids:
patch.patchElementAdd(d[j])
elif src.nKids > dst.nKids:
patch.patchElementRemoveChildren(src.nKids - i)
proc diff*(patch: var Patch, dst, src: Element) =
if dst.tag != int(Tag.DOCUMENT_ROOT):
if dst.tag == src.tag:
patch.diffAttrs(dst, src)
else:
patch.patchElementReplace(dst, src)
diffChildren(patch, dst, src)
proc done*(patch: var Patch) =
patch.data.push(0)
when isMainModule:
import dbmonster, times
GC_Disable()
var a = initDOMBuilder()
var b = initDOMBuilder()
var patch = initPatch()
const ITERS = 1000
let start = cpuTime()
for i in 0..<ITERS:
GC_Enable()
let x = newString(0)
GC_Disable()
patch.clear()
let data = getData()
if i mod 2 == 1:
b.clear()
data.render(b)
patch.diff(b.current, a.current)
else:
a.clear()
data.render(a)
patch.diff(a.current, b.current)
patch.done()
#echo "PATCH SIZE: ", patch.data.mem
echo "Iteration time: ", formatFloat((cpuTime() - start) * 1000 / ITERS, ffDecimal, 3), " ms"