1# Copyright 2014 Google Inc. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15from . import number_types as N
16from .number_types import (UOffsetTFlags, SOffsetTFlags, VOffsetTFlags)
17
18from . import encode
19from . import packer
20
21from . import compat
22from .compat import range_func
23from .compat import memoryview_type
24from .compat import import_numpy, NumpyRequiredForThisFeature
25
26np = import_numpy()
27## @file
28## @addtogroup flatbuffers_python_api
29## @{
30
31## @cond FLATBUFFERS_INTERNAL
32class OffsetArithmeticError(RuntimeError):
33    """
34    Error caused by an Offset arithmetic error. Probably caused by bad
35    writing of fields. This is considered an unreachable situation in
36    normal circumstances.
37    """
38    pass
39
40
41class IsNotNestedError(RuntimeError):
42    """
43    Error caused by using a Builder to write Object data when not inside
44    an Object.
45    """
46    pass
47
48
49class IsNestedError(RuntimeError):
50    """
51    Error caused by using a Builder to begin an Object when an Object is
52    already being built.
53    """
54    pass
55
56
57class StructIsNotInlineError(RuntimeError):
58    """
59    Error caused by using a Builder to write a Struct at a location that
60    is not the current Offset.
61    """
62    pass
63
64
65class BuilderSizeError(RuntimeError):
66    """
67    Error caused by causing a Builder to exceed the hardcoded limit of 2
68    gigabytes.
69    """
70    pass
71
72class BuilderNotFinishedError(RuntimeError):
73    """
74    Error caused by not calling `Finish` before calling `Output`.
75    """
76    pass
77
78
79# VtableMetadataFields is the count of metadata fields in each vtable.
80VtableMetadataFields = 2
81## @endcond
82
83class Builder(object):
84    """ A Builder is used to construct one or more FlatBuffers.
85
86    Typically, Builder objects will be used from code generated by the `flatc`
87    compiler.
88
89    A Builder constructs byte buffers in a last-first manner for simplicity and
90    performance during reading.
91
92    Internally, a Builder is a state machine for creating FlatBuffer objects.
93
94    It holds the following internal state:
95        - Bytes: an array of bytes.
96        - current_vtable: a list of integers.
97        - vtables: a hash of vtable entries.
98
99    Attributes:
100      Bytes: The internal `bytearray` for the Builder.
101      finished: A boolean determining if the Builder has been finalized.
102    """
103
104    ## @cond FLATBUFFERS_INTENRAL
105    __slots__ = ("Bytes", "current_vtable", "head", "minalign", "objectEnd",
106                 "vtables", "nested", "forceDefaults", "finished")
107
108    """Maximum buffer size constant, in bytes.
109
110    Builder will never allow it's buffer grow over this size.
111    Currently equals 2Gb.
112    """
113    MAX_BUFFER_SIZE = 2**31
114    ## @endcond
115
116    def __init__(self, initialSize):
117        """Initializes a Builder of size `initial_size`.
118
119        The internal buffer is grown as needed.
120        """
121
122        if not (0 <= initialSize <= Builder.MAX_BUFFER_SIZE):
123            msg = "flatbuffers: Cannot create Builder larger than 2 gigabytes."
124            raise BuilderSizeError(msg)
125
126        self.Bytes = bytearray(initialSize)
127        ## @cond FLATBUFFERS_INTERNAL
128        self.current_vtable = None
129        self.head = UOffsetTFlags.py_type(initialSize)
130        self.minalign = 1
131        self.objectEnd = None
132        self.vtables = {}
133        self.nested = False
134        self.forceDefaults = False
135        ## @endcond
136        self.finished = False
137
138    def Output(self):
139        """Return the portion of the buffer that has been used for writing data.
140
141        This is the typical way to access the FlatBuffer data inside the
142        builder. If you try to access `Builder.Bytes` directly, you would need
143        to manually index it with `Head()`, since the buffer is constructed
144        backwards.
145
146        It raises BuilderNotFinishedError if the buffer has not been finished
147        with `Finish`.
148        """
149
150        if not self.finished:
151            raise BuilderNotFinishedError()
152
153        return self.Bytes[self.Head():]
154
155    ## @cond FLATBUFFERS_INTERNAL
156    def StartObject(self, numfields):
157        """StartObject initializes bookkeeping for writing a new object."""
158
159        self.assertNotNested()
160
161        # use 32-bit offsets so that arithmetic doesn't overflow.
162        self.current_vtable = [0 for _ in range_func(numfields)]
163        self.objectEnd = self.Offset()
164        self.nested = True
165
166    def WriteVtable(self):
167        """
168        WriteVtable serializes the vtable for the current object, if needed.
169
170        Before writing out the vtable, this checks pre-existing vtables for
171        equality to this one. If an equal vtable is found, point the object to
172        the existing vtable and return.
173
174        Because vtable values are sensitive to alignment of object data, not
175        all logically-equal vtables will be deduplicated.
176
177        A vtable has the following format:
178          <VOffsetT: size of the vtable in bytes, including this value>
179          <VOffsetT: size of the object in bytes, including the vtable offset>
180          <VOffsetT: offset for a field> * N, where N is the number of fields
181                     in the schema for this type. Includes deprecated fields.
182        Thus, a vtable is made of 2 + N elements, each VOffsetT bytes wide.
183
184        An object has the following format:
185          <SOffsetT: offset to this object's vtable (may be negative)>
186          <byte: data>+
187        """
188
189        # Prepend a zero scalar to the object. Later in this function we'll
190        # write an offset here that points to the object's vtable:
191        self.PrependSOffsetTRelative(0)
192
193        objectOffset = self.Offset()
194
195        vtKey = []
196        trim = True
197        for elem in reversed(self.current_vtable):
198            if elem == 0:
199                if trim:
200                    continue
201            else:
202                elem = objectOffset - elem
203                trim = False
204
205            vtKey.append(elem)
206
207        vtKey = tuple(vtKey)
208        vt2Offset = self.vtables.get(vtKey)
209        if vt2Offset is None:
210            # Did not find a vtable, so write this one to the buffer.
211
212            # Write out the current vtable in reverse , because
213            # serialization occurs in last-first order:
214            i = len(self.current_vtable) - 1
215            trailing = 0
216            trim = True
217            while i >= 0:
218                off = 0
219                elem = self.current_vtable[i]
220                i -= 1
221
222                if elem == 0:
223                    if trim:
224                        trailing += 1
225                        continue
226                else:
227                    # Forward reference to field;
228                    # use 32bit number to ensure no overflow:
229                    off = objectOffset - elem
230                    trim = False
231
232                self.PrependVOffsetT(off)
233
234            # The two metadata fields are written last.
235
236            # First, store the object bytesize:
237            objectSize = UOffsetTFlags.py_type(objectOffset - self.objectEnd)
238            self.PrependVOffsetT(VOffsetTFlags.py_type(objectSize))
239
240            # Second, store the vtable bytesize:
241            vBytes = len(self.current_vtable) - trailing + VtableMetadataFields
242            vBytes *= N.VOffsetTFlags.bytewidth
243            self.PrependVOffsetT(VOffsetTFlags.py_type(vBytes))
244
245            # Next, write the offset to the new vtable in the
246            # already-allocated SOffsetT at the beginning of this object:
247            objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
248            encode.Write(packer.soffset, self.Bytes, objectStart,
249                         SOffsetTFlags.py_type(self.Offset() - objectOffset))
250
251            # Finally, store this vtable in memory for future
252            # deduplication:
253            self.vtables[vtKey] = self.Offset()
254        else:
255            # Found a duplicate vtable.
256            objectStart = SOffsetTFlags.py_type(len(self.Bytes) - objectOffset)
257            self.head = UOffsetTFlags.py_type(objectStart)
258
259            # Write the offset to the found vtable in the
260            # already-allocated SOffsetT at the beginning of this object:
261            encode.Write(packer.soffset, self.Bytes, self.Head(),
262                         SOffsetTFlags.py_type(vt2Offset - objectOffset))
263
264        self.current_vtable = None
265        return objectOffset
266
267    def EndObject(self):
268        """EndObject writes data necessary to finish object construction."""
269        self.assertNested()
270        self.nested = False
271        return self.WriteVtable()
272
273    def growByteBuffer(self):
274        """Doubles the size of the byteslice, and copies the old data towards
275           the end of the new buffer (since we build the buffer backwards)."""
276        if len(self.Bytes) == Builder.MAX_BUFFER_SIZE:
277            msg = "flatbuffers: cannot grow buffer beyond 2 gigabytes"
278            raise BuilderSizeError(msg)
279
280        newSize = min(len(self.Bytes) * 2, Builder.MAX_BUFFER_SIZE)
281        if newSize == 0:
282            newSize = 1
283        bytes2 = bytearray(newSize)
284        bytes2[newSize-len(self.Bytes):] = self.Bytes
285        self.Bytes = bytes2
286    ## @endcond
287
288    def Head(self):
289        """Get the start of useful data in the underlying byte buffer.
290
291        Note: unlike other functions, this value is interpreted as from the
292        left.
293        """
294        ## @cond FLATBUFFERS_INTERNAL
295        return self.head
296        ## @endcond
297
298    ## @cond FLATBUFFERS_INTERNAL
299    def Offset(self):
300        """Offset relative to the end of the buffer."""
301        return UOffsetTFlags.py_type(len(self.Bytes) - self.Head())
302
303    def Pad(self, n):
304        """Pad places zeros at the current offset."""
305        for i in range_func(n):
306            self.Place(0, N.Uint8Flags)
307
308    def Prep(self, size, additionalBytes):
309        """
310        Prep prepares to write an element of `size` after `additional_bytes`
311        have been written, e.g. if you write a string, you need to align
312        such the int length field is aligned to SizeInt32, and the string
313        data follows it directly.
314        If all you need to do is align, `additionalBytes` will be 0.
315        """
316
317        # Track the biggest thing we've ever aligned to.
318        if size > self.minalign:
319            self.minalign = size
320
321        # Find the amount of alignment needed such that `size` is properly
322        # aligned after `additionalBytes`:
323        alignSize = (~(len(self.Bytes) - self.Head() + additionalBytes)) + 1
324        alignSize &= (size - 1)
325
326        # Reallocate the buffer if needed:
327        while self.Head() < alignSize+size+additionalBytes:
328            oldBufSize = len(self.Bytes)
329            self.growByteBuffer()
330            updated_head = self.head + len(self.Bytes) - oldBufSize
331            self.head = UOffsetTFlags.py_type(updated_head)
332        self.Pad(alignSize)
333
334    def PrependSOffsetTRelative(self, off):
335        """
336        PrependSOffsetTRelative prepends an SOffsetT, relative to where it
337        will be written.
338        """
339
340        # Ensure alignment is already done:
341        self.Prep(N.SOffsetTFlags.bytewidth, 0)
342        if not (off <= self.Offset()):
343            msg = "flatbuffers: Offset arithmetic error."
344            raise OffsetArithmeticError(msg)
345        off2 = self.Offset() - off + N.SOffsetTFlags.bytewidth
346        self.PlaceSOffsetT(off2)
347    ## @endcond
348
349    def PrependUOffsetTRelative(self, off):
350        """Prepends an unsigned offset into vector data, relative to where it
351        will be written.
352        """
353
354        # Ensure alignment is already done:
355        self.Prep(N.UOffsetTFlags.bytewidth, 0)
356        if not (off <= self.Offset()):
357            msg = "flatbuffers: Offset arithmetic error."
358            raise OffsetArithmeticError(msg)
359        off2 = self.Offset() - off + N.UOffsetTFlags.bytewidth
360        self.PlaceUOffsetT(off2)
361
362    ## @cond FLATBUFFERS_INTERNAL
363    def StartVector(self, elemSize, numElems, alignment):
364        """
365        StartVector initializes bookkeeping for writing a new vector.
366
367        A vector has the following format:
368          - <UOffsetT: number of elements in this vector>
369          - <T: data>+, where T is the type of elements of this vector.
370        """
371
372        self.assertNotNested()
373        self.nested = True
374        self.Prep(N.Uint32Flags.bytewidth, elemSize*numElems)
375        self.Prep(alignment, elemSize*numElems)  # In case alignment > int.
376        return self.Offset()
377    ## @endcond
378
379    def EndVector(self, vectorNumElems):
380        """EndVector writes data necessary to finish vector construction."""
381
382        self.assertNested()
383        ## @cond FLATBUFFERS_INTERNAL
384        self.nested = False
385        ## @endcond
386        # we already made space for this, so write without PrependUint32
387        self.PlaceUOffsetT(vectorNumElems)
388        return self.Offset()
389
390    def CreateString(self, s, encoding='utf-8', errors='strict'):
391        """CreateString writes a null-terminated byte string as a vector."""
392
393        self.assertNotNested()
394        ## @cond FLATBUFFERS_INTERNAL
395        self.nested = True
396        ## @endcond
397
398        if isinstance(s, compat.string_types):
399            x = s.encode(encoding, errors)
400        elif isinstance(s, compat.binary_types):
401            x = s
402        else:
403            raise TypeError("non-string passed to CreateString")
404
405        self.Prep(N.UOffsetTFlags.bytewidth, (len(x)+1)*N.Uint8Flags.bytewidth)
406        self.Place(0, N.Uint8Flags)
407
408        l = UOffsetTFlags.py_type(len(s))
409        ## @cond FLATBUFFERS_INTERNAL
410        self.head = UOffsetTFlags.py_type(self.Head() - l)
411        ## @endcond
412        self.Bytes[self.Head():self.Head()+l] = x
413
414        return self.EndVector(len(x))
415
416    def CreateByteVector(self, x):
417        """CreateString writes a byte vector."""
418
419        self.assertNotNested()
420        ## @cond FLATBUFFERS_INTERNAL
421        self.nested = True
422        ## @endcond
423
424        if not isinstance(x, compat.binary_types):
425            raise TypeError("non-byte vector passed to CreateByteVector")
426
427        self.Prep(N.UOffsetTFlags.bytewidth, len(x)*N.Uint8Flags.bytewidth)
428
429        l = UOffsetTFlags.py_type(len(x))
430        ## @cond FLATBUFFERS_INTERNAL
431        self.head = UOffsetTFlags.py_type(self.Head() - l)
432        ## @endcond
433        self.Bytes[self.Head():self.Head()+l] = x
434
435        return self.EndVector(len(x))
436
437    def CreateNumpyVector(self, x):
438        """CreateNumpyVector writes a numpy array into the buffer."""
439
440        if np is None:
441            # Numpy is required for this feature
442            raise NumpyRequiredForThisFeature("Numpy was not found.")
443
444        if not isinstance(x, np.ndarray):
445            raise TypeError("non-numpy-ndarray passed to CreateNumpyVector")
446
447        if x.dtype.kind not in ['b', 'i', 'u', 'f']:
448            raise TypeError("numpy-ndarray holds elements of unsupported datatype")
449
450        if x.ndim > 1:
451            raise TypeError("multidimensional-ndarray passed to CreateNumpyVector")
452
453        self.StartVector(x.itemsize, x.size, x.dtype.alignment)
454
455        # Ensure little endian byte ordering
456        if x.dtype.str[0] == "<":
457            x_lend = x
458        else:
459            x_lend = x.byteswap(inplace=False)
460
461        # Calculate total length
462        l = UOffsetTFlags.py_type(x_lend.itemsize * x_lend.size)
463        ## @cond FLATBUFFERS_INTERNAL
464        self.head = UOffsetTFlags.py_type(self.Head() - l)
465        ## @endcond
466
467        # tobytes ensures c_contiguous ordering
468        self.Bytes[self.Head():self.Head()+l] = x_lend.tobytes(order='C')
469
470        return self.EndVector(x.size)
471
472    ## @cond FLATBUFFERS_INTERNAL
473    def assertNested(self):
474        """
475        Check that we are in the process of building an object.
476        """
477
478        if not self.nested:
479            raise IsNotNestedError()
480
481    def assertNotNested(self):
482        """
483        Check that no other objects are being built while making this
484        object. If not, raise an exception.
485        """
486
487        if self.nested:
488            raise IsNestedError()
489
490    def assertStructIsInline(self, obj):
491        """
492        Structs are always stored inline, so need to be created right
493        where they are used. You'll get this error if you created it
494        elsewhere.
495        """
496
497        N.enforce_number(obj, N.UOffsetTFlags)
498        if obj != self.Offset():
499            msg = ("flatbuffers: Tried to write a Struct at an Offset that "
500                   "is different from the current Offset of the Builder.")
501            raise StructIsNotInlineError(msg)
502
503    def Slot(self, slotnum):
504        """
505        Slot sets the vtable key `voffset` to the current location in the
506        buffer.
507
508        """
509        self.assertNested()
510        self.current_vtable[slotnum] = self.Offset()
511    ## @endcond
512
513    def __Finish(self, rootTable, sizePrefix, file_identifier=None):
514        """Finish finalizes a buffer, pointing to the given `rootTable`."""
515        N.enforce_number(rootTable, N.UOffsetTFlags)
516
517        prepSize = N.UOffsetTFlags.bytewidth
518        if file_identifier is not None:
519            prepSize += N.Int32Flags.bytewidth
520        if sizePrefix:
521            prepSize += N.Int32Flags.bytewidth
522        self.Prep(self.minalign, prepSize)
523
524        if file_identifier is not None:
525            self.Prep(N.UOffsetTFlags.bytewidth, encode.FILE_IDENTIFIER_LENGTH)
526
527            # Convert bytes object file_identifier to an array of 4 8-bit integers,
528            # and use big-endian to enforce size compliance.
529            # https://docs.python.org/2/library/struct.html#format-characters
530            file_identifier = N.struct.unpack(">BBBB", file_identifier)
531            for i in range(encode.FILE_IDENTIFIER_LENGTH-1, -1, -1):
532                # Place the bytes of the file_identifer in reverse order:
533                self.Place(file_identifier[i], N.Uint8Flags)
534
535        self.PrependUOffsetTRelative(rootTable)
536        if sizePrefix:
537            size = len(self.Bytes) - self.Head()
538            N.enforce_number(size, N.Int32Flags)
539            self.PrependInt32(size)
540        self.finished = True
541        return self.Head()
542
543    def Finish(self, rootTable, file_identifier=None):
544        """Finish finalizes a buffer, pointing to the given `rootTable`."""
545        return self.__Finish(rootTable, False, file_identifier=file_identifier)
546
547    def FinishSizePrefixed(self, rootTable, file_identifier=None):
548        """
549        Finish finalizes a buffer, pointing to the given `rootTable`,
550        with the size prefixed.
551        """
552        return self.__Finish(rootTable, True, file_identifier=file_identifier)
553
554    ## @cond FLATBUFFERS_INTERNAL
555    def Prepend(self, flags, off):
556        self.Prep(flags.bytewidth, 0)
557        self.Place(off, flags)
558
559    def PrependSlot(self, flags, o, x, d):
560        N.enforce_number(x, flags)
561        N.enforce_number(d, flags)
562        if x != d or self.forceDefaults:
563            self.Prepend(flags, x)
564            self.Slot(o)
565
566    def PrependBoolSlot(self, *args): self.PrependSlot(N.BoolFlags, *args)
567
568    def PrependByteSlot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
569
570    def PrependUint8Slot(self, *args): self.PrependSlot(N.Uint8Flags, *args)
571
572    def PrependUint16Slot(self, *args): self.PrependSlot(N.Uint16Flags, *args)
573
574    def PrependUint32Slot(self, *args): self.PrependSlot(N.Uint32Flags, *args)
575
576    def PrependUint64Slot(self, *args): self.PrependSlot(N.Uint64Flags, *args)
577
578    def PrependInt8Slot(self, *args): self.PrependSlot(N.Int8Flags, *args)
579
580    def PrependInt16Slot(self, *args): self.PrependSlot(N.Int16Flags, *args)
581
582    def PrependInt32Slot(self, *args): self.PrependSlot(N.Int32Flags, *args)
583
584    def PrependInt64Slot(self, *args): self.PrependSlot(N.Int64Flags, *args)
585
586    def PrependFloat32Slot(self, *args): self.PrependSlot(N.Float32Flags,
587                                                          *args)
588
589    def PrependFloat64Slot(self, *args): self.PrependSlot(N.Float64Flags,
590                                                          *args)
591
592    def PrependUOffsetTRelativeSlot(self, o, x, d):
593        """
594        PrependUOffsetTRelativeSlot prepends an UOffsetT onto the object at
595        vtable slot `o`. If value `x` equals default `d`, then the slot will
596        be set to zero and no other data will be written.
597        """
598
599        if x != d or self.forceDefaults:
600            self.PrependUOffsetTRelative(x)
601            self.Slot(o)
602
603    def PrependStructSlot(self, v, x, d):
604        """
605        PrependStructSlot prepends a struct onto the object at vtable slot `o`.
606        Structs are stored inline, so nothing additional is being added.
607        In generated code, `d` is always 0.
608        """
609
610        N.enforce_number(d, N.UOffsetTFlags)
611        if x != d:
612            self.assertStructIsInline(x)
613            self.Slot(v)
614
615    ## @endcond
616
617    def PrependBool(self, x):
618        """Prepend a `bool` to the Builder buffer.
619
620        Note: aligns and checks for space.
621        """
622        self.Prepend(N.BoolFlags, x)
623
624    def PrependByte(self, x):
625        """Prepend a `byte` to the Builder buffer.
626
627        Note: aligns and checks for space.
628        """
629        self.Prepend(N.Uint8Flags, x)
630
631    def PrependUint8(self, x):
632        """Prepend an `uint8` to the Builder buffer.
633
634        Note: aligns and checks for space.
635        """
636        self.Prepend(N.Uint8Flags, x)
637
638    def PrependUint16(self, x):
639        """Prepend an `uint16` to the Builder buffer.
640
641        Note: aligns and checks for space.
642        """
643        self.Prepend(N.Uint16Flags, x)
644
645    def PrependUint32(self, x):
646        """Prepend an `uint32` to the Builder buffer.
647
648        Note: aligns and checks for space.
649        """
650        self.Prepend(N.Uint32Flags, x)
651
652    def PrependUint64(self, x):
653        """Prepend an `uint64` to the Builder buffer.
654
655        Note: aligns and checks for space.
656        """
657        self.Prepend(N.Uint64Flags, x)
658
659    def PrependInt8(self, x):
660        """Prepend an `int8` to the Builder buffer.
661
662        Note: aligns and checks for space.
663        """
664        self.Prepend(N.Int8Flags, x)
665
666    def PrependInt16(self, x):
667        """Prepend an `int16` to the Builder buffer.
668
669        Note: aligns and checks for space.
670        """
671        self.Prepend(N.Int16Flags, x)
672
673    def PrependInt32(self, x):
674        """Prepend an `int32` to the Builder buffer.
675
676        Note: aligns and checks for space.
677        """
678        self.Prepend(N.Int32Flags, x)
679
680    def PrependInt64(self, x):
681        """Prepend an `int64` to the Builder buffer.
682
683        Note: aligns and checks for space.
684        """
685        self.Prepend(N.Int64Flags, x)
686
687    def PrependFloat32(self, x):
688        """Prepend a `float32` to the Builder buffer.
689
690        Note: aligns and checks for space.
691        """
692        self.Prepend(N.Float32Flags, x)
693
694    def PrependFloat64(self, x):
695        """Prepend a `float64` to the Builder buffer.
696
697        Note: aligns and checks for space.
698        """
699        self.Prepend(N.Float64Flags, x)
700
701    def ForceDefaults(self, forceDefaults):
702        """
703        In order to save space, fields that are set to their default value
704        don't get serialized into the buffer. Forcing defaults provides a
705        way to manually disable this optimization. When set to `True`, will
706        always serialize default values.
707        """
708        self.forceDefaults = forceDefaults
709
710##############################################################
711
712    ## @cond FLATBUFFERS_INTERNAL
713    def PrependVOffsetT(self, x): self.Prepend(N.VOffsetTFlags, x)
714
715    def Place(self, x, flags):
716        """
717        Place prepends a value specified by `flags` to the Builder,
718        without checking for available space.
719        """
720
721        N.enforce_number(x, flags)
722        self.head = self.head - flags.bytewidth
723        encode.Write(flags.packer_type, self.Bytes, self.Head(), x)
724
725    def PlaceVOffsetT(self, x):
726        """PlaceVOffsetT prepends a VOffsetT to the Builder, without checking
727        for space.
728        """
729        N.enforce_number(x, N.VOffsetTFlags)
730        self.head = self.head - N.VOffsetTFlags.bytewidth
731        encode.Write(packer.voffset, self.Bytes, self.Head(), x)
732
733    def PlaceSOffsetT(self, x):
734        """PlaceSOffsetT prepends a SOffsetT to the Builder, without checking
735        for space.
736        """
737        N.enforce_number(x, N.SOffsetTFlags)
738        self.head = self.head - N.SOffsetTFlags.bytewidth
739        encode.Write(packer.soffset, self.Bytes, self.Head(), x)
740
741    def PlaceUOffsetT(self, x):
742        """PlaceUOffsetT prepends a UOffsetT to the Builder, without checking
743        for space.
744        """
745        N.enforce_number(x, N.UOffsetTFlags)
746        self.head = self.head - N.UOffsetTFlags.bytewidth
747        encode.Write(packer.uoffset, self.Bytes, self.Head(), x)
748    ## @endcond
749
750## @cond FLATBUFFERS_INTERNAL
751def vtableEqual(a, objectStart, b):
752    """vtableEqual compares an unwritten vtable to a written vtable."""
753
754    N.enforce_number(objectStart, N.UOffsetTFlags)
755
756    if len(a) * N.VOffsetTFlags.bytewidth != len(b):
757        return False
758
759    for i, elem in enumerate(a):
760        x = encode.Get(packer.voffset, b, i * N.VOffsetTFlags.bytewidth)
761
762        # Skip vtable entries that indicate a default value.
763        if x == 0 and elem == 0:
764            pass
765        else:
766            y = objectStart - elem
767            if x != y:
768                return False
769    return True
770## @endcond
771## @}
772