// Copyright 2018 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import std namespace flatbuffers class handle: buf_:string pos_:int // More strongly typed than a naked int, at no cost. struct offset: o:int enum sizeof: sz_8 = 1 sz_16 = 2 sz_32 = 4 sz_64 = 8 sz_voffset = 2 sz_uoffset = 4 sz_soffset = 4 sz_metadata_fields = 2 class builder: buf = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" current_vtable:[int] = [] head = 0 minalign = 1 object_end = 0 vtables:[int] = [] nested = false finished = false // Optionally call this right after creating the builder for a larger initial buffer. def Initial(initial_size:int): buf = "\x00".repeat_string(initial_size) def Start(): // Get the start of useful data in the underlying byte buffer. return buf.length - head def Offset(): // Offset relative to the end of the buffer. return offset { head } // Returns a copy of the part of the buffer containing only the finished FlatBuffer def SizedCopy(): assert finished return buf.substring(Start(), -1) def StartNesting(): assert not nested nested = true def EndNesting(): assert nested nested = false def StartObject(numfields): StartNesting() current_vtable = map(numfields): 0 object_end = head minalign = 1 def EndObject(): EndNesting() // Prepend a zero scalar to the object. Later in this function we'll // write an offset here that points to the object's vtable: PrependInt32(0) let object_offset = head // Write out new vtable speculatively. let vtable_size = (current_vtable.length + sz_metadata_fields) * sz_voffset while current_vtable.length: let o = current_vtable.pop() PrependVOffsetT(if o: object_offset - o else: 0) // The two metadata fields are written last. // First, store the object bytesize: PrependVOffsetT(object_offset - object_end) // Second, store the vtable bytesize: PrependVOffsetT(vtable_size) // Search backwards through existing vtables, because similar vtables // are likely to have been recently appended. See // BenchmarkVtableDeduplication for a case in which this heuristic // saves about 30% of the time used in writing objects with duplicate // tables. def find_existing_table(): reverse(vtables) vt2_offset: // Find the other vtable: let vt2_start = buf.length - vt2_offset let vt2_len = buf.read_int16_le(vt2_start) // Compare the other vtable to the one under consideration. // If they are equal, return the offset: if vtable_size == vt2_len and not compare_substring(buf, Start(), buf, vt2_start, vtable_size): return vt2_offset return 0 let existing_vtable = find_existing_table() if existing_vtable: // Found a duplicate vtable, remove the one we wrote. head = object_offset // Write the offset to the found vtable in the // already-allocated offset at the beginning of this object: buf.write_int32_le(Start(), existing_vtable - object_offset) else: // Did not find a vtable, so keep the one we wrote. // Next, write the offset to the new vtable in the // already-allocated offset at the beginning of this object: buf.write_int32_le(buf.length - object_offset, head - object_offset) // Finally, store this vtable in memory for future // deduplication: vtables.push(head) return offset { object_offset } def Pad(n): for(n): buf, head = buf.write_int8_le_back(head, 0) def Prep(size, additional_bytes): // Track the biggest thing we've ever aligned to. if size > minalign: minalign = size // Find the amount of alignment needed such that `size` is properly // aligned after `additionalBytes`: let align_size = ((~(head + additional_bytes)) + 1) & (size - 1) Pad(align_size) def PrependUOffsetTRelative(off:offset): // Prepends an unsigned offset into vector data, relative to where it will be written. Prep(sz_uoffset, 0) assert off.o <= head PlaceUOffsetT(head - off.o + sz_uoffset) def StartVector(elem_size, num_elems, alignment): // Initializes bookkeeping for writing a new vector. StartNesting() Prep(sz_32, elem_size * num_elems) Prep(alignment, elem_size * num_elems) // In case alignment > int. return Offset() def EndVector(vector_num_elems): EndNesting() // we already made space for this, so write without PrependUint32 PlaceUOffsetT(vector_num_elems) return Offset() def CreateString(s:string): // writes a null-terminated byte string. StartNesting() Prep(sz_32, s.length + 1) buf, head = buf.write_substring_back(head, s, true) return EndVector(s.length) def CreateByteVector(s:string): // writes a non-null-terminated byte string. StartNesting() Prep(sz_32, s.length) buf, head = buf.write_substring_back(head, s, false) return EndVector(s.length) def Slot(slotnum): assert nested while current_vtable.length <= slotnum: current_vtable.push(0) current_vtable[slotnum] = head def __Finish(root_table:offset, size_prefix:int, file_identifier:string?): // Finish finalizes a buffer, pointing to the given root_table assert not finished assert not nested var prep_size = sz_32 if file_identifier: prep_size += sz_32 if size_prefix: prep_size += sz_32 Prep(minalign, prep_size) if file_identifier: assert file_identifier.length == 4 buf, head = buf.write_substring_back(head, file_identifier, false) PrependUOffsetTRelative(root_table) if size_prefix: PrependInt32(head) finished = true return Start() def Finish(root_table:offset, file_identifier:string? = nil): return __Finish(root_table, false, file_identifier) def FinishSizePrefixed(root_table:offset, file_identifier:string? = nil): return __Finish(root_table, true, file_identifier) def PrependBool(x): buf, head = buf.write_int8_le_back(head, x) def PrependByte(x): buf, head = buf.write_int8_le_back(head, x) def PrependUint8(x): buf, head = buf.write_int8_le_back(head, x) def PrependUint16(x): Prep(sz_16, 0) buf, head = buf.write_int16_le_back(head, x) def PrependUint32(x): Prep(sz_32, 0) buf, head = buf.write_int32_le_back(head, x) def PrependUint64(x): Prep(sz_64, 0) buf, head = buf.write_int64_le_back(head, x) def PrependInt8(x): buf, head = buf.write_int8_le_back(head, x) def PrependInt16(x): Prep(sz_16, 0) buf, head = buf.write_int16_le_back(head, x) def PrependInt32(x): Prep(sz_32, 0) buf, head = buf.write_int32_le_back(head, x) def PrependInt64(x): Prep(sz_64, 0) buf, head = buf.write_int64_le_back(head, x) def PrependFloat32(x): Prep(sz_32, 0) buf, head = buf.write_float32_le_back(head, x) def PrependFloat64(x): Prep(sz_64, 0) buf, head = buf.write_float64_le_back(head, x) def PrependVOffsetT(x): Prep(sz_voffset, 0) buf, head = buf.write_int16_le_back(head, x) def PlaceVOffsetT(x): buf, head = buf.write_int16_le_back(head, x) def PlaceSOffsetT(x): buf, head = buf.write_int32_le_back(head, x) def PlaceUOffsetT(x): buf, head = buf.write_int32_le_back(head, x) def PrependSlot(o:int, x, d, f): if x != d: f(x) Slot(o) def PrependSlot(o:int, x, f): f(x) Slot(o) def PrependBoolSlot(o, x, d): PrependSlot(o, x, d): PrependBool(_) def PrependByteSlot(o, x, d): PrependSlot(o, x, d): PrependByte(_) def PrependUint8Slot(o, x, d): PrependSlot(o, x, d): PrependUint8(_) def PrependUint16Slot(o, x, d): PrependSlot(o, x, d): PrependUint16(_) def PrependUint32Slot(o, x, d): PrependSlot(o, x, d): PrependUint32(_) def PrependUint64Slot(o, x, d): PrependSlot(o, x, d): PrependUint64(_) def PrependInt8Slot(o, x, d): PrependSlot(o, x, d): PrependInt8(_) def PrependInt16Slot(o, x, d): PrependSlot(o, x, d): PrependInt16(_) def PrependInt32Slot(o, x, d): PrependSlot(o, x, d): PrependInt32(_) def PrependInt64Slot(o, x, d): PrependSlot(o, x, d): PrependInt64(_) def PrependFloat32Slot(o, x, d): PrependSlot(o, x, d): PrependFloat32(_) def PrependFloat64Slot(o, x, d): PrependSlot(o, x, d): PrependFloat64(_) def PrependBoolSlot(o, x): PrependSlot(o, x): PrependBool(_) def PrependByteSlot(o, x): PrependSlot(o, x): PrependByte(_) def PrependUint8Slot(o, x): PrependSlot(o, x): PrependUint8(_) def PrependUint16Slot(o, x): PrependSlot(o, x): PrependUint16(_) def PrependUint32Slot(o, x): PrependSlot(o, x): PrependUint32(_) def PrependUint64Slot(o, x): PrependSlot(o, x): PrependUint64(_) def PrependInt8Slot(o, x): PrependSlot(o, x): PrependInt8(_) def PrependInt16Slot(o, x): PrependSlot(o, x): PrependInt16(_) def PrependInt32Slot(o, x): PrependSlot(o, x): PrependInt32(_) def PrependInt64Slot(o, x): PrependSlot(o, x): PrependInt64(_) def PrependFloat32Slot(o, x): PrependSlot(o, x): PrependFloat32(_) def PrependFloat64Slot(o, x): PrependSlot(o, x): PrependFloat64(_) def PrependUOffsetTRelativeSlot(o:int, x:offset): if x.o: PrependUOffsetTRelative(x) Slot(o) def PrependStructSlot(v:int, x:offset): if x.o: // Structs are always stored inline, so need to be created right // where they are used. You'll get this error if you created it // elsewhere. assert x.o == head Slot(v) def has_identifier(buf:string, file_identifier:string): assert file_identifier.length == 4 return buf.length >= 8 and buf.substring(4, 4) == file_identifier