blob: 62ac2edb0befc2b187af8c20025718f822c51eba [file] [log] [blame]
James Kuszmaul3b15b0c2022-11-08 14:03:16 -08001import math
2import table
3
4
5const MAX_BUFFER_SIZE* = 2^31
6
7
8type Builder* = ref object of RootObj
9 bytes*: seq[byte]
10 minalign*: int
11 current_vtable*: seq[uoffset]
12 objectEnd*: uoffset
13 vtables*: seq[uoffset] #?
14 head*: uoffset
15 nested*: bool
16 finished*: bool
17 vectorNumElems*: uoffset
18
19using this: var Builder
20
21func newBuilder*(size: int): Builder =
22 result = new Builder
23 result.bytes.setLen(size)
24 result.minalign = 1
25 result.head = size.uoffset
26 result.nested = false
27 result.finished = false
28 result.vectorNumElems = 0
29
30proc FinishedBytes*(this): seq[byte] =
31 if not this.finished:
32 quit("Builder not finished, Incorrect use of FinishedBytes(): must call 'Finish' first.")
33 result = this.bytes[this.head..^1]
34
35proc Output*(this): seq[byte] =
36 if not this.finished:
37 quit("Builder not finished, Incorrect use of Output(): must call 'Finish' first.")
38
39 result = this.bytes[this.head..^1]
40
41func Offset*(this): uoffset =
42 result = this.bytes.len.uoffset - this.head
43
44proc StartObject*(this; numfields: int) =
45 if this.nested:
46 quit("builder is nested")
47
48 this.current_vtable.setLen(numfields)
49 for i in this.current_vtable.mitems():
50 i = 0
51 this.objectEnd = this.Offset()
52 this.nested = true
53
54proc GrowByteBuffer*(this) =
55 if this.bytes.len == MAX_BUFFER_SIZE:
56 quit("flatbuffers: cannot grow buffer beyond 2 gigabytes")
57 let oldLen = this.bytes.len
58 var newLen = min(this.bytes.len * 2, MAX_BUFFER_SIZE)
59 if newLen == 0:
60 newLen = 1
61 this.bytes.setLen(newLen)
62 var j = this.bytes.len - 1
63 while j >= 0:
64 if j >= newLen - oldLen:
65 this.bytes[j] = this.bytes[j - (newLen - oldLen)]
66 else:
67 this.bytes[j] = 0
68 dec(j)
69
70proc Place*[T](this; x: T) =
71 this.head -= uoffset x.sizeof
72 WriteVal(this.bytes, this.head, x)
73
74func Pad*(this; n: int) =
75 for i in 0..<n:
76 this.Place(0.byte)
77
78proc Prep*(this; size: int; additionalBytes: int) =
79 if size > this.minalign:
80 this.minalign = size
81 var alignsize = (not (this.bytes.len - this.head.int + additionalBytes)) + 1
82 alignsize = alignsize and (size - 1)
83
84 while this.head.int < alignsize + size + additionalBytes:
85 let oldbufSize = this.bytes.len
86 this.GrowByteBuffer()
87 this.head = (this.head.int + this.bytes.len - oldbufSize).uoffset
88 this.Pad(alignsize)
89
90proc PrependOffsetRelative*[T: Offsets](this; off: T) =
91 when T is voffset:
92 this.Prep(T.sizeof, 0)
93 if not off.uoffset <= this.Offset:
94 quit("flatbuffers: Offset arithmetic error.")
95 this.Place(off)
96 else:
97 this.Prep(T.sizeof, 0)
98 if not off.uoffset <= this.Offset:
99 quit("flatbuffers: Offset arithmetic error.")
100 let off2: T = this.Offset.T - off + sizeof(T).T
101 this.Place(off2)
102
103
104proc Prepend*[T](this; x: T) =
105 this.Prep(x.sizeof, 0)
106 this.Place(x)
107
108proc Slot*(this; slotnum: int) =
109 this.current_vtable[slotnum] = this.Offset
110
111proc PrependSlot*[T](this; o: int; x, d: T) =
112 if x != d:
113 when T is uoffset or T is soffset or T is voffset:
114 this.PrependOffsetRelative(x)
115 else:
116 this.Prepend(x)
117 this.Slot(o)
118
119proc AssertStuctInline(this; obj: uoffset) =
120 if obj != this.Offset:
121 quit("flatbuffers: Tried to write a Struct at an Offset that is different from the current Offset of the Builder.")
122
123proc PrependStructSlot*(this; o: int; x: uoffset; d: uoffset) =
124 if x != d:
125 this.AssertStuctInline(x)
126 this.Slot(o)
127
128proc Add*[T](this; n: T) =
129 this.Prep(T.sizeof, 0)
130 WriteVal(this.bytes, this.head, n)
131
132proc VtableEqual*(a: seq[uoffset]; objectStart: uoffset; b: seq[byte]): bool =
133 if a.len * voffset.sizeof != b.len:
134 return false
135
136 var i = 0
137 while i < a.len:
138 var seq = b[i * voffset.sizeof..<(i + 1) * voffset.sizeof]
139 let x = GetVal[voffset](addr seq)
140
141 if x == 0 and a[i] == 0:
142 inc i
143 continue
144
145 let y = objectStart.soffset - a[i].soffset
146 if x.soffset != y:
147 return false
148 inc i
149 return true
150
151proc WriteVtable*(this): uoffset =
152 this.PrependOffsetRelative(0.soffset)
153
154 let objectOffset = this.Offset
155 var existingVtable = uoffset 0
156
157 var i = this.current_vtable.len - 1
158 while i >= 0 and this.current_vtable[i] == 0: dec i
159
160 this.current_vtable = this.current_vtable[0..i]
161 for i in countdown(this.vtables.len - 1, 0):
162 let
163 vt2Offset: uoffset = this.vtables[i]
164 vt2Start: int = this.bytes.len - int vt2Offset
165
166 var seq = this.bytes[vt2Start..<this.bytes.len]
167 let
168 vt2Len = GetVal[voffset](addr seq)
169 metadata = 2 * voffset.sizeof # VtableMetadataFields * SizeVOffsetT
170 vt2End = vt2Start + vt2Len.int
171 vt2 = this.bytes[this.bytes.len - vt2Offset.int + metadata..<vt2End]
172
173 if VtableEqual(this.current_vtable, objectOffset, vt2):
174 existingVtable = vt2Offset
175 break
176
177 if existingVtable == 0:
178 for i in countdown(this.current_vtable.len - 1, 0):
179 var off: uoffset
180 if this.current_vtable[i] != 0:
181 off = objectOffset - this.current_vtable[i]
182
183 this.PrependOffsetRelative(off.voffset)
184
185 let objectSize = objectOffset - this.objectEnd
186 this.PrependOffsetRelative(objectSize.voffset)
187
188 let vBytes = (this.current_vtable.len + 2) * voffset.sizeof
189 this.PrependOffsetRelative(vBytes.voffset)
190
191 let objectStart: uoffset = (this.bytes.len.uoffset - objectOffset)
192 WriteVal(this.bytes, objectStart, (this.Offset - objectOffset).soffset)
193 this.vtables.add this.Offset
194 else:
195 let objectStart: uoffset = this.bytes.len.uoffset - objectOffset
196 this.head = objectStart
197 WriteVal(this.bytes, this.head,
198 (existingVtable.soffset - objectOffset.soffset))
199
200 this.current_vtable = @[]
201 result = objectOffset
202
203proc EndObject*(this): uoffset =
204 if not this.nested:
205 quit("builder is not nested")
206 result = this.WriteVtable()
207 this.nested = false
208
209proc End*(this: var Builder): uoffset =
210 result = this.EndObject()
211
212proc StartVector*(this; elemSize: int; numElems: uoffset;
213 alignment: int) =
214 if this.nested:
215 quit("builder is nested")
216 this.nested = true
217 this.vectorNumElems = numElems
218 this.Prep(sizeof(uint32), elemSize * numElems.int)
219 this.Prep(alignment, elemSize * numElems.int)
220
221proc EndVector*(this): uoffset =
222 if not this.nested:
223 quit("builder is not nested")
224 this.nested = false
225 this.Place(this.vectorNumElems)
226 this.vectorNumElems = 0
227 result = this.Offset
228
229proc getChars*(str: seq[byte]): string =
230 var bytes = str
231 result = GetVal[string](addr bytes)
232
233proc getBytes*(str: string | cstring): seq[byte] =
234 for chr in str:
235 result.add byte chr
236 result.add byte 0
237
238proc Create*[T](this; s: T): uoffset = # Both CreateString and CreateByteVector functionality
239 if this.nested:
240 quit("builder is nested")
241 this.nested = true
242 when T is cstring or T is string:
243 let x = s.getBytes()
244 let l = x.len.uoffset
245 this.vectorNumElems = l-1
246 else:
247 let x = s
248 let l = x.len.uoffset
249 this.vectorNumElems = l
250 this.Prep(uoffset.sizeof, l.int * byte.sizeof)
251 this.head -= l
252 this.bytes[this.head..<this.head+l] = x
253 result = this.EndVector()
254
255proc Finish*(this; rootTable: uoffset) =
256 if this.nested:
257 quit("builder is nested")
258 this.nested = true
259
260 this.Prep(this.minalign, uoffset.sizeof)
261 this.PrependOffsetRelative(rootTable)
262 this.finished = true