James Kuszmaul | 3b15b0c | 2022-11-08 14:03:16 -0800 | [diff] [blame^] | 1 | import math |
| 2 | import table |
| 3 | |
| 4 | |
| 5 | const MAX_BUFFER_SIZE* = 2^31 |
| 6 | |
| 7 | |
| 8 | type 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 | |
| 19 | using this: var Builder |
| 20 | |
| 21 | func 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 | |
| 30 | proc 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 | |
| 35 | proc 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 | |
| 41 | func Offset*(this): uoffset = |
| 42 | result = this.bytes.len.uoffset - this.head |
| 43 | |
| 44 | proc 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 | |
| 54 | proc 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 | |
| 70 | proc Place*[T](this; x: T) = |
| 71 | this.head -= uoffset x.sizeof |
| 72 | WriteVal(this.bytes, this.head, x) |
| 73 | |
| 74 | func Pad*(this; n: int) = |
| 75 | for i in 0..<n: |
| 76 | this.Place(0.byte) |
| 77 | |
| 78 | proc 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 | |
| 90 | proc 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 | |
| 104 | proc Prepend*[T](this; x: T) = |
| 105 | this.Prep(x.sizeof, 0) |
| 106 | this.Place(x) |
| 107 | |
| 108 | proc Slot*(this; slotnum: int) = |
| 109 | this.current_vtable[slotnum] = this.Offset |
| 110 | |
| 111 | proc 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 | |
| 119 | proc 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 | |
| 123 | proc PrependStructSlot*(this; o: int; x: uoffset; d: uoffset) = |
| 124 | if x != d: |
| 125 | this.AssertStuctInline(x) |
| 126 | this.Slot(o) |
| 127 | |
| 128 | proc Add*[T](this; n: T) = |
| 129 | this.Prep(T.sizeof, 0) |
| 130 | WriteVal(this.bytes, this.head, n) |
| 131 | |
| 132 | proc 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 | |
| 151 | proc 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 | |
| 203 | proc EndObject*(this): uoffset = |
| 204 | if not this.nested: |
| 205 | quit("builder is not nested") |
| 206 | result = this.WriteVtable() |
| 207 | this.nested = false |
| 208 | |
| 209 | proc End*(this: var Builder): uoffset = |
| 210 | result = this.EndObject() |
| 211 | |
| 212 | proc 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 | |
| 221 | proc 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 | |
| 229 | proc getChars*(str: seq[byte]): string = |
| 230 | var bytes = str |
| 231 | result = GetVal[string](addr bytes) |
| 232 | |
| 233 | proc getBytes*(str: string | cstring): seq[byte] = |
| 234 | for chr in str: |
| 235 | result.add byte chr |
| 236 | result.add byte 0 |
| 237 | |
| 238 | proc 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 | |
| 255 | proc 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 |