Merge commit '0f6aab9da6fe982218a01f4a5b896e65fcced437' as 'third_party/flatbuffers'

This commit is contained in:
Siarhei Fedartsou
2024-06-22 13:33:34 +02:00
1814 changed files with 326902 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
load("@aspect_rules_js//npm:defs.bzl", "npm_package")
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
filegroup(
name = "distribution",
srcs = [
"BUILD.bazel",
"compile_flat_file.sh",
] + glob([
"*.ts",
]),
visibility = ["//visibility:public"],
)
# Add an index to emulate the top-level package.json's "main" entry.
genrule(
name = "generate_index.ts",
outs = ["index.ts"],
cmd = """echo "export * from './flatbuffers.js'" > $(OUTS)""",
)
ts_project(
name = "flatbuffers_ts",
srcs = [
"builder.ts",
"byte-buffer.ts",
"constants.ts",
"encoding.ts",
"flatbuffers.ts",
"types.ts",
"utils.ts",
":index.ts",
],
declaration = True,
tsconfig = {
"compilerOptions": {
"module": "commonjs",
"declaration": True,
"moduleResolution": "node",
"lib": [
"ES2015",
"ES2020.BigInt",
"DOM",
],
"types": ["node"],
"strict": True,
},
},
visibility = ["//visibility:public"],
deps = [
# Because the main repository instantiates the @npm repository, we need
# to depend on the main repository's node import.
"@//:node_modules/@types/node",
],
)
npm_package(
name = "flatbuffers",
srcs = [":flatbuffers_ts"],
include_external_repositories = ["*"],
package = "flatbuffers",
visibility = ["//visibility:public"],
)
sh_binary(
name = "compile_flat_file",
srcs = ["compile_flat_file.sh"],
data = [
"@com_github_google_flatbuffers//:flatc",
"@nodejs_linux_amd64//:node_bin",
],
# We just depend directly on the linux amd64 nodejs binary, so only support
# running this script on amd64 for now.
target_compatible_with = [
"@platforms//cpu:x86_64",
"@platforms//os:linux",
],
visibility = ["//visibility:public"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)
+607
View File
@@ -0,0 +1,607 @@
import { ByteBuffer } from "./byte-buffer.js"
import { SIZEOF_SHORT, SIZE_PREFIX_LENGTH, SIZEOF_INT, FILE_IDENTIFIER_LENGTH } from "./constants.js"
import { Offset, IGeneratedObject } from "./types.js"
export class Builder {
private bb: ByteBuffer
/** Remaining space in the ByteBuffer. */
private space: number
/** Minimum alignment encountered so far. */
private minalign = 1
/** The vtable for the current table. */
private vtable: number[] | null = null
/** The amount of fields we're actually using. */
private vtable_in_use = 0
/** Whether we are currently serializing a table. */
private isNested = false;
/** Starting offset of the current struct/table. */
private object_start = 0
/** List of offsets of all vtables. */
private vtables: number[] = []
/** For the current vector being built. */
private vector_num_elems = 0
/** False omits default values from the serialized data */
private force_defaults = false;
private string_maps: Map<string | Uint8Array, number> | null = null;
private text_encoder = new TextEncoder();
/**
* Create a FlatBufferBuilder.
*/
constructor(opt_initial_size?: number) {
let initial_size: number;
if (!opt_initial_size) {
initial_size = 1024;
} else {
initial_size = opt_initial_size;
}
/**
* @type {ByteBuffer}
* @private
*/
this.bb = ByteBuffer.allocate(initial_size);
this.space = initial_size;
}
clear(): void {
this.bb.clear();
this.space = this.bb.capacity();
this.minalign = 1;
this.vtable = null;
this.vtable_in_use = 0;
this.isNested = false;
this.object_start = 0;
this.vtables = [];
this.vector_num_elems = 0;
this.force_defaults = false;
this.string_maps = null;
}
/**
* In order to save space, fields that are set to their default value
* don't get serialized into the buffer. Forcing defaults provides a
* way to manually disable this optimization.
*
* @param forceDefaults true always serializes default values
*/
forceDefaults(forceDefaults: boolean): void {
this.force_defaults = forceDefaults;
}
/**
* Get the ByteBuffer representing the FlatBuffer. Only call this after you've
* called finish(). The actual data starts at the ByteBuffer's current position,
* not necessarily at 0.
*/
dataBuffer(): ByteBuffer {
return this.bb;
}
/**
* Get the bytes representing the FlatBuffer. Only call this after you've
* called finish().
*/
asUint8Array(): Uint8Array {
return this.bb.bytes().subarray(this.bb.position(), this.bb.position() + this.offset());
}
/**
* Prepare to write an element of `size` after `additional_bytes` have been
* written, e.g. if you write a string, you need to align such the int length
* field is aligned to 4 bytes, and the string data follows it directly. If all
* you need to do is alignment, `additional_bytes` will be 0.
*
* @param size This is the of the new element to write
* @param additional_bytes The padding size
*/
prep(size: number, additional_bytes: number): void {
// Track the biggest thing we've ever aligned to.
if (size > this.minalign) {
this.minalign = size;
}
// Find the amount of alignment needed such that `size` is properly
// aligned after `additional_bytes`
const align_size = ((~(this.bb.capacity() - this.space + additional_bytes)) + 1) & (size - 1);
// Reallocate the buffer if needed.
while (this.space < align_size + size + additional_bytes) {
const old_buf_size = this.bb.capacity();
this.bb = Builder.growByteBuffer(this.bb);
this.space += this.bb.capacity() - old_buf_size;
}
this.pad(align_size);
}
pad(byte_size: number): void {
for (let i = 0; i < byte_size; i++) {
this.bb.writeInt8(--this.space, 0);
}
}
writeInt8(value: number): void {
this.bb.writeInt8(this.space -= 1, value);
}
writeInt16(value: number): void {
this.bb.writeInt16(this.space -= 2, value);
}
writeInt32(value: number): void {
this.bb.writeInt32(this.space -= 4, value);
}
writeInt64(value: bigint): void {
this.bb.writeInt64(this.space -= 8, value);
}
writeFloat32(value: number): void {
this.bb.writeFloat32(this.space -= 4, value);
}
writeFloat64(value: number): void {
this.bb.writeFloat64(this.space -= 8, value);
}
/**
* Add an `int8` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int8` to add the buffer.
*/
addInt8(value: number): void {
this.prep(1, 0);
this.writeInt8(value);
}
/**
* Add an `int16` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int16` to add the buffer.
*/
addInt16(value: number): void {
this.prep(2, 0);
this.writeInt16(value);
}
/**
* Add an `int32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int32` to add the buffer.
*/
addInt32(value: number): void {
this.prep(4, 0);
this.writeInt32(value);
}
/**
* Add an `int64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `int64` to add the buffer.
*/
addInt64(value: bigint): void {
this.prep(8, 0);
this.writeInt64(value);
}
/**
* Add a `float32` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float32` to add the buffer.
*/
addFloat32(value: number): void {
this.prep(4, 0);
this.writeFloat32(value);
}
/**
* Add a `float64` to the buffer, properly aligned, and grows the buffer (if necessary).
* @param value The `float64` to add the buffer.
*/
addFloat64(value: number): void {
this.prep(8, 0);
this.writeFloat64(value);
}
addFieldInt8(voffset: number, value: number, defaultValue: number|null): void {
if (this.force_defaults || value != defaultValue) {
this.addInt8(value);
this.slot(voffset);
}
}
addFieldInt16(voffset: number, value: number, defaultValue: number|null): void {
if (this.force_defaults || value != defaultValue) {
this.addInt16(value);
this.slot(voffset);
}
}
addFieldInt32(voffset: number, value: number, defaultValue: number|null): void {
if (this.force_defaults || value != defaultValue) {
this.addInt32(value);
this.slot(voffset);
}
}
addFieldInt64(voffset: number, value: bigint, defaultValue: bigint|null): void {
if (this.force_defaults || value !== defaultValue) {
this.addInt64(value);
this.slot(voffset);
}
}
addFieldFloat32(voffset: number, value: number, defaultValue: number|null): void {
if (this.force_defaults || value != defaultValue) {
this.addFloat32(value);
this.slot(voffset);
}
}
addFieldFloat64(voffset: number, value: number, defaultValue: number|null): void {
if (this.force_defaults || value != defaultValue) {
this.addFloat64(value);
this.slot(voffset);
}
}
addFieldOffset(voffset: number, value: Offset, defaultValue: Offset): void {
if (this.force_defaults || value != defaultValue) {
this.addOffset(value);
this.slot(voffset);
}
}
/**
* Structs are stored inline, so nothing additional is being added. `d` is always 0.
*/
addFieldStruct(voffset: number, value: Offset, defaultValue: Offset): void {
if (value != defaultValue) {
this.nested(value);
this.slot(voffset);
}
}
/**
* Structures are always stored inline, they need to be created right
* where they're used. You'll get this assertion failure if you
* created it elsewhere.
*/
nested(obj: Offset): void {
if (obj != this.offset()) {
throw new TypeError('FlatBuffers: struct must be serialized inline.');
}
}
/**
* Should not be creating any other object, string or vector
* while an object is being constructed
*/
notNested(): void {
if (this.isNested) {
throw new TypeError('FlatBuffers: object serialization must not be nested.');
}
}
/**
* Set the current vtable at `voffset` to the current location in the buffer.
*/
slot(voffset: number): void {
if (this.vtable !== null)
this.vtable[voffset] = this.offset();
}
/**
* @returns Offset relative to the end of the buffer.
*/
offset(): Offset {
return this.bb.capacity() - this.space;
}
/**
* Doubles the size of the backing ByteBuffer and copies the old data towards
* the end of the new buffer (since we build the buffer backwards).
*
* @param bb The current buffer with the existing data
* @returns A new byte buffer with the old data copied
* to it. The data is located at the end of the buffer.
*
* uint8Array.set() formally takes {Array<number>|ArrayBufferView}, so to pass
* it a uint8Array we need to suppress the type check:
* @suppress {checkTypes}
*/
static growByteBuffer(bb: ByteBuffer): ByteBuffer {
const old_buf_size = bb.capacity();
// Ensure we don't grow beyond what fits in an int.
if (old_buf_size & 0xC0000000) {
throw new Error('FlatBuffers: cannot grow buffer beyond 2 gigabytes.');
}
const new_buf_size = old_buf_size << 1;
const nbb = ByteBuffer.allocate(new_buf_size);
nbb.setPosition(new_buf_size - old_buf_size);
nbb.bytes().set(bb.bytes(), new_buf_size - old_buf_size);
return nbb;
}
/**
* Adds on offset, relative to where it will be written.
*
* @param offset The offset to add.
*/
addOffset(offset: Offset): void {
this.prep(SIZEOF_INT, 0); // Ensure alignment is already done.
this.writeInt32(this.offset() - offset + SIZEOF_INT);
}
/**
* Start encoding a new object in the buffer. Users will not usually need to
* call this directly. The FlatBuffers compiler will generate helper methods
* that call this method internally.
*/
startObject(numfields: number): void {
this.notNested();
if (this.vtable == null) {
this.vtable = [];
}
this.vtable_in_use = numfields;
for (let i = 0; i < numfields; i++) {
this.vtable[i] = 0; // This will push additional elements as needed
}
this.isNested = true;
this.object_start = this.offset();
}
/**
* Finish off writing the object that is under construction.
*
* @returns The offset to the object inside `dataBuffer`
*/
endObject(): Offset {
if (this.vtable == null || !this.isNested) {
throw new Error('FlatBuffers: endObject called without startObject');
}
this.addInt32(0);
const vtableloc = this.offset();
// Trim trailing zeroes.
let i = this.vtable_in_use - 1;
// eslint-disable-next-line no-empty
for (; i >= 0 && this.vtable[i] == 0; i--) {}
const trimmed_size = i + 1;
// Write out the current vtable.
for (; i >= 0; i--) {
// Offset relative to the start of the table.
this.addInt16(this.vtable[i] != 0 ? vtableloc - this.vtable[i] : 0);
}
const standard_fields = 2; // The fields below:
this.addInt16(vtableloc - this.object_start);
const len = (trimmed_size + standard_fields) * SIZEOF_SHORT;
this.addInt16(len);
// Search for an existing vtable that matches the current one.
let existing_vtable = 0;
const vt1 = this.space;
outer_loop:
for (i = 0; i < this.vtables.length; i++) {
const vt2 = this.bb.capacity() - this.vtables[i];
if (len == this.bb.readInt16(vt2)) {
for (let j = SIZEOF_SHORT; j < len; j += SIZEOF_SHORT) {
if (this.bb.readInt16(vt1 + j) != this.bb.readInt16(vt2 + j)) {
continue outer_loop;
}
}
existing_vtable = this.vtables[i];
break;
}
}
if (existing_vtable) {
// Found a match:
// Remove the current vtable.
this.space = this.bb.capacity() - vtableloc;
// Point table to existing vtable.
this.bb.writeInt32(this.space, existing_vtable - vtableloc);
} else {
// No match:
// Add the location of the current vtable to the list of vtables.
this.vtables.push(this.offset());
// Point table to current vtable.
this.bb.writeInt32(this.bb.capacity() - vtableloc, this.offset() - vtableloc);
}
this.isNested = false;
return vtableloc as Offset;
}
/**
* Finalize a buffer, poiting to the given `root_table`.
*/
finish(root_table: Offset, opt_file_identifier?: string, opt_size_prefix?: boolean): void {
const size_prefix = opt_size_prefix ? SIZE_PREFIX_LENGTH : 0;
if (opt_file_identifier) {
const file_identifier = opt_file_identifier;
this.prep(this.minalign, SIZEOF_INT +
FILE_IDENTIFIER_LENGTH + size_prefix);
if (file_identifier.length != FILE_IDENTIFIER_LENGTH) {
throw new TypeError('FlatBuffers: file identifier must be length ' +
FILE_IDENTIFIER_LENGTH);
}
for (let i = FILE_IDENTIFIER_LENGTH - 1; i >= 0; i--) {
this.writeInt8(file_identifier.charCodeAt(i));
}
}
this.prep(this.minalign, SIZEOF_INT + size_prefix);
this.addOffset(root_table);
if (size_prefix) {
this.addInt32(this.bb.capacity() - this.space);
}
this.bb.setPosition(this.space);
}
/**
* Finalize a size prefixed buffer, pointing to the given `root_table`.
*/
finishSizePrefixed(this: Builder, root_table: Offset, opt_file_identifier?: string): void {
this.finish(root_table, opt_file_identifier, true);
}
/**
* This checks a required field has been set in a given table that has
* just been constructed.
*/
requiredField(table: Offset, field: number): void {
const table_start = this.bb.capacity() - table;
const vtable_start = table_start - this.bb.readInt32(table_start);
const ok = field < this.bb.readInt16(vtable_start) &&
this.bb.readInt16(vtable_start + field) != 0;
// If this fails, the caller will show what field needs to be set.
if (!ok) {
throw new TypeError('FlatBuffers: field ' + field + ' must be set');
}
}
/**
* Start a new array/vector of objects. Users usually will not call
* this directly. The FlatBuffers compiler will create a start/end
* method for vector types in generated code.
*
* @param elem_size The size of each element in the array
* @param num_elems The number of elements in the array
* @param alignment The alignment of the array
*/
startVector(elem_size: number, num_elems: number, alignment: number): void {
this.notNested();
this.vector_num_elems = num_elems;
this.prep(SIZEOF_INT, elem_size * num_elems);
this.prep(alignment, elem_size * num_elems); // Just in case alignment > int.
}
/**
* Finish off the creation of an array and all its elements. The array must be
* created with `startVector`.
*
* @returns The offset at which the newly created array
* starts.
*/
endVector(): Offset {
this.writeInt32(this.vector_num_elems);
return this.offset();
}
/**
* Encode the string `s` in the buffer using UTF-8. If the string passed has
* already been seen, we return the offset of the already written string
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createSharedString(s: string | Uint8Array): Offset {
if (!s) { return 0 }
if (!this.string_maps) {
this.string_maps = new Map();
}
if (this.string_maps.has(s)) {
return this.string_maps.get(s) as Offset
}
const offset = this.createString(s)
this.string_maps.set(s, offset)
return offset
}
/**
* Encode the string `s` in the buffer using UTF-8. If a Uint8Array is passed
* instead of a string, it is assumed to contain valid UTF-8 encoded data.
*
* @param s The string to encode
* @return The offset in the buffer where the encoded string starts
*/
createString(s: string | Uint8Array | null | undefined): Offset {
if (s === null || s === undefined) {
return 0;
}
let utf8: string | Uint8Array | number[];
if (s instanceof Uint8Array) {
utf8 = s;
} else {
utf8 = this.text_encoder.encode(s);
}
this.addInt8(0);
this.startVector(1, utf8.length, 1);
this.bb.setPosition(this.space -= utf8.length);
this.bb.bytes().set(utf8, this.space);
return this.endVector();
}
/**
* Create a byte vector.
*
* @param v The bytes to add
* @returns The offset in the buffer where the byte vector starts
*/
createByteVector(v: Uint8Array | null | undefined): Offset {
if (v === null || v === undefined) {
return 0;
}
this.startVector(1, v.length, 1);
this.bb.setPosition(this.space -= v.length);
this.bb.bytes().set(v, this.space);
return this.endVector();
}
/**
* A helper function to pack an object
*
* @returns offset of obj
*/
createObjectOffset(obj: string | IGeneratedObject | null): Offset {
if(obj === null) {
return 0
}
if(typeof obj === 'string') {
return this.createString(obj);
} else {
return obj.pack(this);
}
}
/**
* A helper function to pack a list of object
*
* @returns list of offsets of each non null object
*/
createObjectOffsetList(list: (string | IGeneratedObject)[]): Offset[] {
const ret: number[] = [];
for(let i = 0; i < list.length; ++i) {
const val = list[i];
if(val !== null) {
ret.push(this.createObjectOffset(val));
} else {
throw new TypeError(
'FlatBuffers: Argument for createObjectOffsetList cannot contain null.');
}
}
return ret;
}
createStructOffsetList(list: (string | IGeneratedObject)[], startFunc: (builder: Builder, length: number) => void): Offset {
startFunc(this, list.length);
this.createObjectOffsetList(list.slice().reverse());
return this.endVector();
}
}
+287
View File
@@ -0,0 +1,287 @@
import { FILE_IDENTIFIER_LENGTH, SIZEOF_INT } from "./constants.js";
import { int32, isLittleEndian, float32, float64 } from "./utils.js";
import { Offset, Table, IGeneratedObject, IUnpackableObject } from "./types.js";
import { Encoding } from "./encoding.js";
export class ByteBuffer {
private position_ = 0;
private text_decoder_ = new TextDecoder();
/**
* Create a new ByteBuffer with a given array of bytes (`Uint8Array`)
*/
constructor(private bytes_: Uint8Array) { }
/**
* Create and allocate a new ByteBuffer with a given size.
*/
static allocate(byte_size: number): ByteBuffer {
return new ByteBuffer(new Uint8Array(byte_size));
}
clear(): void {
this.position_ = 0;
}
/**
* Get the underlying `Uint8Array`.
*/
bytes(): Uint8Array {
return this.bytes_;
}
/**
* Get the buffer's position.
*/
position(): number {
return this.position_;
}
/**
* Set the buffer's position.
*/
setPosition(position: number): void {
this.position_ = position;
}
/**
* Get the buffer's capacity.
*/
capacity(): number {
return this.bytes_.length;
}
readInt8(offset: number): number {
return this.readUint8(offset) << 24 >> 24;
}
readUint8(offset: number): number {
return this.bytes_[offset];
}
readInt16(offset: number): number {
return this.readUint16(offset) << 16 >> 16;
}
readUint16(offset: number): number {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8;
}
readInt32(offset: number): number {
return this.bytes_[offset] | this.bytes_[offset + 1] << 8 | this.bytes_[offset + 2] << 16 | this.bytes_[offset + 3] << 24;
}
readUint32(offset: number): number {
return this.readInt32(offset) >>> 0;
}
readInt64(offset: number): bigint {
return BigInt.asIntN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readUint64(offset: number): bigint {
return BigInt.asUintN(64, BigInt(this.readUint32(offset)) + (BigInt(this.readUint32(offset + 4)) << BigInt(32)));
}
readFloat32(offset: number): number {
int32[0] = this.readInt32(offset);
return float32[0];
}
readFloat64(offset: number): number {
int32[isLittleEndian ? 0 : 1] = this.readInt32(offset);
int32[isLittleEndian ? 1 : 0] = this.readInt32(offset + 4);
return float64[0];
}
writeInt8(offset: number, value: number): void {
this.bytes_[offset] = value;
}
writeUint8(offset: number, value: number): void {
this.bytes_[offset] = value;
}
writeInt16(offset: number, value: number): void {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeUint16(offset: number, value: number): void {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
}
writeInt32(offset: number, value: number): void {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeUint32(offset: number, value: number): void {
this.bytes_[offset] = value;
this.bytes_[offset + 1] = value >> 8;
this.bytes_[offset + 2] = value >> 16;
this.bytes_[offset + 3] = value >> 24;
}
writeInt64(offset: number, value: bigint): void {
this.writeInt32(offset, Number(BigInt.asIntN(32, value)));
this.writeInt32(offset + 4, Number(BigInt.asIntN(32, value >> BigInt(32))));
}
writeUint64(offset: number, value: bigint): void {
this.writeUint32(offset, Number(BigInt.asUintN(32, value)));
this.writeUint32(offset + 4, Number(BigInt.asUintN(32, value >> BigInt(32))));
}
writeFloat32(offset: number, value: number): void {
float32[0] = value;
this.writeInt32(offset, int32[0]);
}
writeFloat64(offset: number, value: number): void {
float64[0] = value;
this.writeInt32(offset, int32[isLittleEndian ? 0 : 1]);
this.writeInt32(offset + 4, int32[isLittleEndian ? 1 : 0]);
}
/**
* Return the file identifier. Behavior is undefined for FlatBuffers whose
* schema does not include a file_identifier (likely points at padding or the
* start of a the root vtable).
*/
getBufferIdentifier(): string {
if (this.bytes_.length < this.position_ + SIZEOF_INT +
FILE_IDENTIFIER_LENGTH) {
throw new Error(
'FlatBuffers: ByteBuffer is too short to contain an identifier.');
}
let result = "";
for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) {
result += String.fromCharCode(
this.readInt8(this.position_ + SIZEOF_INT + i));
}
return result;
}
/**
* Look up a field in the vtable, return an offset into the object, or 0 if the
* field is not present.
*/
__offset(bb_pos: number, vtable_offset: number): Offset {
const vtable = bb_pos - this.readInt32(bb_pos);
return vtable_offset < this.readInt16(vtable) ? this.readInt16(vtable + vtable_offset) : 0;
}
/**
* Initialize any Table-derived type to point to the union at the given offset.
*/
__union(t: Table, offset: number): Table {
t.bb_pos = offset + this.readInt32(offset);
t.bb = this;
return t;
}
/**
* Create a JavaScript string from UTF-8 data stored inside the FlatBuffer.
* This allocates a new string and converts to wide chars upon each access.
*
* To avoid the conversion to string, pass Encoding.UTF8_BYTES as the
* "optionalEncoding" argument. This is useful for avoiding conversion when
* the data will just be packaged back up in another FlatBuffer later on.
*
* @param offset
* @param opt_encoding Defaults to UTF16_STRING
*/
__string(offset: number, opt_encoding?: Encoding): string | Uint8Array {
offset += this.readInt32(offset);
const length = this.readInt32(offset);
offset += SIZEOF_INT;
const utf8bytes = this.bytes_.subarray(offset, offset + length);
if (opt_encoding === Encoding.UTF8_BYTES)
return utf8bytes;
else
return this.text_decoder_.decode(utf8bytes);
}
/**
* Handle unions that can contain string as its member, if a Table-derived type then initialize it,
* if a string then return a new one
*
* WARNING: strings are immutable in JS so we can't change the string that the user gave us, this
* makes the behaviour of __union_with_string different compared to __union
*/
__union_with_string(o: Table | string, offset: number) : Table | string {
if(typeof o === 'string') {
return this.__string(offset) as string;
}
return this.__union(o, offset);
}
/**
* Retrieve the relative offset stored at "offset"
*/
__indirect(offset: Offset): Offset {
return offset + this.readInt32(offset);
}
/**
* Get the start of data of a vector whose offset is stored at "offset" in this object.
*/
__vector(offset: Offset): Offset {
return offset + this.readInt32(offset) + SIZEOF_INT; // data starts after the length
}
/**
* Get the length of a vector whose offset is stored at "offset" in this object.
*/
__vector_len(offset: Offset): Offset {
return this.readInt32(offset + this.readInt32(offset));
}
__has_identifier(ident: string): boolean {
if (ident.length != FILE_IDENTIFIER_LENGTH) {
throw new Error('FlatBuffers: file identifier must be length ' +
FILE_IDENTIFIER_LENGTH);
}
for (let i = 0; i < FILE_IDENTIFIER_LENGTH; i++) {
if (ident.charCodeAt(i) != this.readInt8(this.position() + SIZEOF_INT + i)) {
return false;
}
}
return true;
}
/**
* A helper function for generating list for obj api
*/
createScalarList<T>(listAccessor: (i: number) => T | null, listLength: number): T[] {
const ret: T[] = [];
for(let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if(val !== null) {
ret.push(val);
}
}
return ret;
}
/**
* A helper function for generating list for obj api
* @param listAccessor function that accepts an index and return data at that index
* @param listLength listLength
* @param res result list
*/
createObjList<T1 extends IUnpackableObject<T2>, T2 extends IGeneratedObject>(listAccessor: (i: number) => T1 | null, listLength: number): T2[] {
const ret: T2[] = [];
for(let i = 0; i < listLength; ++i) {
const val = listAccessor(i);
if(val !== null) {
ret.push(val.unpack());
}
}
return ret;
}
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
# This is a script used by the typescript flatbuffer bazel rules to compile
# a flatbuffer schema (.fbs file) to typescript and then use esbuild to
# generate a single output.
# Note: This relies on parsing the stdout of flatc to figure out how to
# run esbuild.
# --- begin runfiles.bash initialization v2 ---
# Copy-pasted from the Bazel Bash runfiles library v2.
set -uo pipefail; set +e; f=bazel_tools/tools/bash/runfiles/runfiles.bash
source "${RUNFILES_DIR:-/dev/null}/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "${RUNFILES_MANIFEST_FILE:-/dev/null}" | cut -f2- -d' ')" 2>/dev/null || \
source "$0.runfiles/$f" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \
{ echo>&2 "ERROR: cannot find $f"; exit 1; }; f=; set -e
# --- end runfiles.bash initialization v2 ---
set -eu
runfiles_export_envvars
FLATC=$(rlocation com_github_google_flatbuffers/flatc)
TS_FILE=$(${FLATC} $@ | grep "Entry point.*generated" | grep -o "bazel-out.*ts")
export PATH="$(rlocation nodejs_linux_amd64/bin/nodejs/bin):${PATH}"
${ESBUILD_BIN} ${TS_FILE} --format=cjs --bundle --outfile="${OUTPUT_FILE}" --external:flatbuffers --log-level=warning
+4
View File
@@ -0,0 +1,4 @@
export const SIZEOF_SHORT = 2;
export const SIZEOF_INT = 4;
export const FILE_IDENTIFIER_LENGTH = 4;
export const SIZE_PREFIX_LENGTH = 4;
+4
View File
@@ -0,0 +1,4 @@
export enum Encoding {
UTF8_BYTES = 1,
UTF16_STRING = 2
}
+12
View File
@@ -0,0 +1,12 @@
export { SIZEOF_SHORT } from './constants.js'
export { SIZEOF_INT } from './constants.js'
export { FILE_IDENTIFIER_LENGTH } from './constants.js'
export { SIZE_PREFIX_LENGTH } from './constants.js'
export { Table, Offset, IGeneratedObject, IUnpackableObject } from './types.js'
export { int32, float32, float64, isLittleEndian } from './utils.js'
export { Encoding } from './encoding.js'
export { Builder } from './builder.js'
export { ByteBuffer } from './byte-buffer.js'
+18
View File
@@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { Builder } from './flexbuffers/builder.js'
import { toReference } from './flexbuffers/reference.js'
export { toReference } from './flexbuffers/reference.js'
export function builder(): Builder {
return new Builder();
}
export function toObject(buffer: ArrayBuffer): unknown {
return toReference(buffer).toObject();
}
export function encode(object: unknown, size = 2048, deduplicateStrings = true, deduplicateKeys = true, deduplicateKeyVectors = true): Uint8Array {
const builder = new Builder(size > 0 ? size : 2048, deduplicateStrings, deduplicateKeys, deduplicateKeyVectors);
builder.add(object);
return builder.finish();
}
@@ -0,0 +1,34 @@
import { BitWidth } from './bit-width.js'
export function toByteWidth(bitWidth: BitWidth): number {
return 1 << bitWidth;
}
export function iwidth(value: number | bigint): BitWidth {
if (value >= -128 && value <= 127) return BitWidth.WIDTH8;
if (value >= -32768 && value <= 32767) return BitWidth.WIDTH16;
if (value >= -2147483648 && value <= 2147483647) return BitWidth.WIDTH32;
return BitWidth.WIDTH64;
}
export function fwidth(value: number): BitWidth {
return value === Math.fround(value) ? BitWidth.WIDTH32 : BitWidth.WIDTH64;
}
export function uwidth(value: number): BitWidth {
if (value <= 255) return BitWidth.WIDTH8;
if (value <= 65535) return BitWidth.WIDTH16;
if (value <= 4294967295) return BitWidth.WIDTH32;
return BitWidth.WIDTH64;
}
export function fromByteWidth(value: number): BitWidth {
if (value === 1) return BitWidth.WIDTH8;
if (value === 2) return BitWidth.WIDTH16;
if (value === 4) return BitWidth.WIDTH32;
return BitWidth.WIDTH64;
}
export function paddingSize(bufSize: number, scalarSize: number): number {
return (~bufSize + 1) & (scalarSize - 1);
}
+6
View File
@@ -0,0 +1,6 @@
export enum BitWidth {
WIDTH8 = 0,
WIDTH16 = 1,
WIDTH32 = 2,
WIDTH64 = 3,
}
+549
View File
@@ -0,0 +1,549 @@
import { BitWidth } from './bit-width.js'
import { paddingSize, iwidth, uwidth, fwidth, toByteWidth, fromByteWidth } from './bit-width-util.js'
import { toUTF8Array } from './flexbuffers-util.js'
import { ValueType } from './value-type.js'
import { isNumber, isTypedVectorElement, toTypedVector } from './value-type-util.js'
import { StackValue } from './stack-value.js'
interface StackPointer {
stackPosition: number,
isVector: boolean
presorted?: boolean
}
export class Builder {
buffer: ArrayBuffer
view: DataView
readonly stack: Array<StackValue> = [];
readonly stackPointers: Array<StackPointer> = [];
offset = 0;
finished = false;
readonly stringLookup: Record<string, StackValue> = {};
readonly keyLookup: Record<string, StackValue> = {};
readonly keyVectorLookup: Record<string, StackValue> = {};
readonly indirectIntLookup: Record<number, StackValue> = {};
readonly indirectUIntLookup: Record<number, StackValue> = {};
readonly indirectFloatLookup: Record<number, StackValue> = {};
constructor(size = 2048, private dedupStrings = true, private dedupKeys = true, private dedupKeyVectors = true) {
this.buffer = new ArrayBuffer(size > 0 ? size : 2048);
this.view = new DataView(this.buffer);
}
private align(width: BitWidth) {
const byteWidth = toByteWidth(width);
this.offset += paddingSize(this.offset, byteWidth);
return byteWidth;
}
computeOffset(newValueSize: number): number {
const targetOffset = this.offset + newValueSize;
let size = this.buffer.byteLength;
const prevSize = size;
while (size < targetOffset) {
size <<= 1;
}
if (prevSize < size) {
const prevBuffer = this.buffer;
this.buffer = new ArrayBuffer(size);
this.view = new DataView(this.buffer);
new Uint8Array(this.buffer).set(new Uint8Array(prevBuffer), 0);
}
return targetOffset;
}
pushInt(value: number, width: BitWidth): void {
if (width === BitWidth.WIDTH8) {
this.view.setInt8(this.offset, value);
} else if (width === BitWidth.WIDTH16) {
this.view.setInt16(this.offset, value, true);
} else if (width === BitWidth.WIDTH32) {
this.view.setInt32(this.offset, value, true);
} else if (width === BitWidth.WIDTH64) {
this.view.setBigInt64(this.offset, BigInt(value), true);
} else {
throw `Unexpected width: ${width} for value: ${value}`;
}
}
pushUInt(value: number, width: BitWidth): void {
if (width === BitWidth.WIDTH8) {
this.view.setUint8(this.offset, value);
} else if (width === BitWidth.WIDTH16) {
this.view.setUint16(this.offset, value, true);
} else if (width === BitWidth.WIDTH32) {
this.view.setUint32(this.offset, value, true);
} else if (width === BitWidth.WIDTH64) {
this.view.setBigUint64(this.offset, BigInt(value), true);
} else {
throw `Unexpected width: ${width} for value: ${value}`;
}
}
private writeInt(value: number, byteWidth: number) {
const newOffset = this.computeOffset(byteWidth);
this.pushInt(value, fromByteWidth(byteWidth));
this.offset = newOffset;
}
private writeUInt(value: number, byteWidth: number) {
const newOffset = this.computeOffset(byteWidth);
this.pushUInt(value, fromByteWidth(byteWidth));
this.offset = newOffset;
}
private writeBlob(arrayBuffer: ArrayBuffer) {
const length = arrayBuffer.byteLength;
const bitWidth = uwidth(length);
const byteWidth = this.align(bitWidth);
this.writeUInt(length, byteWidth);
const blobOffset = this.offset;
const newOffset = this.computeOffset(length);
new Uint8Array(this.buffer).set(new Uint8Array(arrayBuffer), blobOffset);
this.stack.push(this.offsetStackValue(blobOffset, ValueType.BLOB, bitWidth));
this.offset = newOffset;
}
private writeString(str: string): void {
if (this.dedupStrings && Object.prototype.hasOwnProperty.call(this.stringLookup, str)) {
this.stack.push(this.stringLookup[str]);
return;
}
const utf8 = toUTF8Array(str);
const length = utf8.length;
const bitWidth = uwidth(length);
const byteWidth = this.align(bitWidth);
this.writeUInt(length, byteWidth);
const stringOffset = this.offset;
const newOffset = this.computeOffset(length + 1);
new Uint8Array(this.buffer).set(utf8, stringOffset);
const stackValue = this.offsetStackValue(stringOffset, ValueType.STRING, bitWidth);
this.stack.push(stackValue);
if (this.dedupStrings) {
this.stringLookup[str] = stackValue;
}
this.offset = newOffset;
}
private writeKey(str: string): void {
if (this.dedupKeys && Object.prototype.hasOwnProperty.call(this.keyLookup, str)) {
this.stack.push(this.keyLookup[str]);
return;
}
const utf8 = toUTF8Array(str);
const length = utf8.length;
const newOffset = this.computeOffset(length + 1);
new Uint8Array(this.buffer).set(utf8, this.offset);
const stackValue = this.offsetStackValue(this.offset, ValueType.KEY, BitWidth.WIDTH8);
this.stack.push(stackValue);
if (this.dedupKeys) {
this.keyLookup[str] = stackValue;
}
this.offset = newOffset;
}
private writeStackValue(value: StackValue, byteWidth: number): void {
const newOffset = this.computeOffset(byteWidth);
if (value.isOffset()) {
const relativeOffset = this.offset - value.offset;
if (byteWidth === 8 || BigInt(relativeOffset) < (BigInt(1) << BigInt(byteWidth * 8))) {
this.writeUInt(relativeOffset, byteWidth);
} else {
throw `Unexpected size ${byteWidth}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`
}
} else {
value.writeToBuffer(byteWidth);
}
this.offset = newOffset;
}
private integrityCheckOnValueAddition() {
if (this.finished) {
throw "Adding values after finish is prohibited";
}
if (this.stackPointers.length !== 0 && this.stackPointers[this.stackPointers.length - 1].isVector === false) {
if (this.stack[this.stack.length - 1].type !== ValueType.KEY) {
throw "Adding value to a map before adding a key is prohibited";
}
}
}
private integrityCheckOnKeyAddition() {
if (this.finished) {
throw "Adding values after finish is prohibited";
}
if (this.stackPointers.length === 0 || this.stackPointers[this.stackPointers.length - 1].isVector) {
throw "Adding key before starting a map is prohibited";
}
}
startVector(): void {
this.stackPointers.push({ stackPosition: this.stack.length, isVector: true });
}
startMap(presorted = false): void {
this.stackPointers.push({ stackPosition: this.stack.length, isVector: false, presorted: presorted });
}
private endVector(stackPointer: StackPointer) {
const vecLength = this.stack.length - stackPointer.stackPosition;
const vec = this.createVector(stackPointer.stackPosition, vecLength, 1);
this.stack.splice(stackPointer.stackPosition, vecLength);
this.stack.push(vec);
}
private endMap(stackPointer: StackPointer) {
if (!stackPointer.presorted) {
this.sort(stackPointer);
}
let keyVectorHash = "";
for (let i = stackPointer.stackPosition; i < this.stack.length; i += 2) {
keyVectorHash += `,${this.stack[i].offset}`;
}
const vecLength = (this.stack.length - stackPointer.stackPosition) >> 1;
if (this.dedupKeyVectors && !Object.prototype.hasOwnProperty.call(this.keyVectorLookup, keyVectorHash)) {
this.keyVectorLookup[keyVectorHash] = this.createVector(stackPointer.stackPosition, vecLength, 2);
}
const keysStackValue = this.dedupKeyVectors ? this.keyVectorLookup[keyVectorHash] : this.createVector(stackPointer.stackPosition, vecLength, 2);
const valuesStackValue = this.createVector(stackPointer.stackPosition + 1, vecLength, 2, keysStackValue);
this.stack.splice(stackPointer.stackPosition, vecLength << 1);
this.stack.push(valuesStackValue);
}
private sort(stackPointer: StackPointer) {
const view = this.view
const stack = this.stack
function shouldFlip(v1: StackValue, v2: StackValue) {
if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) {
throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
}
let c1, c2;
let index = 0;
do {
c1 = view.getUint8(v1.offset + index);
c2 = view.getUint8(v2.offset + index);
if (c2 < c1) return true;
if (c1 < c2) return false;
index += 1;
} while (c1 !== 0 && c2 !== 0);
return false;
}
function swap(stack: Array<StackValue>, flipIndex: number, i: number) {
if (flipIndex === i) return;
const k = stack[flipIndex];
const v = stack[flipIndex + 1];
stack[flipIndex] = stack[i];
stack[flipIndex + 1] = stack[i + 1];
stack[i] = k;
stack[i + 1] = v;
}
function selectionSort() {
for (let i = stackPointer.stackPosition; i < stack.length; i += 2) {
let flipIndex = i;
for (let j = i + 2; j < stack.length; j += 2) {
if (shouldFlip(stack[flipIndex], stack[j])) {
flipIndex = j;
}
}
if (flipIndex !== i) {
swap(stack, flipIndex, i);
}
}
}
function smaller(v1: StackValue, v2: StackValue) {
if (v1.type !== ValueType.KEY || v2.type !== ValueType.KEY) {
throw `Stack values are not keys ${v1} | ${v2}. Check if you combined [addKey] with add... method calls properly.`
}
if (v1.offset === v2.offset) {
return false;
}
let c1, c2;
let index = 0;
do {
c1 = view.getUint8(v1.offset + index);
c2 = view.getUint8(v2.offset + index);
if (c1 < c2) return true;
if (c2 < c1) return false;
index += 1;
} while (c1 !== 0 && c2 !== 0);
return false;
}
function quickSort(left: number, right: number) {
if (left < right) {
const mid = left + (((right - left) >> 2)) * 2;
const pivot = stack[mid];
let left_new = left;
let right_new = right;
do {
while (smaller(stack[left_new], pivot)) {
left_new += 2;
}
while (smaller(pivot, stack[right_new])) {
right_new -= 2;
}
if (left_new <= right_new) {
swap(stack, left_new, right_new);
left_new += 2;
right_new -= 2;
}
} while (left_new <= right_new);
quickSort(left, right_new);
quickSort(left_new, right);
}
}
let sorted = true;
for (let i = stackPointer.stackPosition; i < this.stack.length - 2; i += 2) {
if (shouldFlip(this.stack[i], this.stack[i + 2])) {
sorted = false;
break;
}
}
if (!sorted) {
if (this.stack.length - stackPointer.stackPosition > 40) {
quickSort(stackPointer.stackPosition, this.stack.length - 2);
} else {
selectionSort();
}
}
}
end(): void {
if (this.stackPointers.length < 1) return;
const pointer = this.stackPointers.pop() as StackPointer;
if (pointer.isVector) {
this.endVector(pointer);
} else {
this.endMap(pointer);
}
}
private createVector(start: number, vecLength: number, step: number, keys: StackValue | null = null) {
let bitWidth = uwidth(vecLength);
let prefixElements = 1;
if (keys !== null) {
const elementWidth = keys.elementWidth(this.offset, 0);
if (elementWidth > bitWidth) {
bitWidth = elementWidth;
}
prefixElements += 2;
}
let vectorType = ValueType.KEY;
let typed = keys === null;
for (let i = start; i < this.stack.length; i += step) {
const elementWidth = this.stack[i].elementWidth(this.offset, i + prefixElements);
if (elementWidth > bitWidth) {
bitWidth = elementWidth;
}
if (i === start) {
vectorType = this.stack[i].type;
typed = typed && isTypedVectorElement(vectorType);
} else {
if (vectorType !== this.stack[i].type) {
typed = false;
}
}
}
const byteWidth = this.align(bitWidth);
const fix = typed && isNumber(vectorType) && vecLength >= 2 && vecLength <= 4;
if (keys !== null) {
this.writeStackValue(keys, byteWidth);
this.writeUInt(1 << keys.width, byteWidth);
}
if (!fix) {
this.writeUInt(vecLength, byteWidth);
}
const vecOffset = this.offset;
for (let i = start; i < this.stack.length; i += step) {
this.writeStackValue(this.stack[i], byteWidth);
}
if (!typed) {
for (let i = start; i < this.stack.length; i += step) {
this.writeUInt(this.stack[i].storedPackedType(), 1);
}
}
if (keys !== null) {
return this.offsetStackValue(vecOffset, ValueType.MAP, bitWidth);
}
if (typed) {
const vType = toTypedVector(vectorType, fix ? vecLength : 0);
return this.offsetStackValue(vecOffset, vType, bitWidth);
}
return this.offsetStackValue(vecOffset, ValueType.VECTOR, bitWidth);
}
private nullStackValue() {
return new StackValue(this, ValueType.NULL, BitWidth.WIDTH8);
}
private boolStackValue(value: boolean) {
return new StackValue(this, ValueType.BOOL, BitWidth.WIDTH8, value);
}
private intStackValue(value: number | bigint) {
return new StackValue(this, ValueType.INT, iwidth(value), value as number);
}
private uintStackValue(value: number) {
return new StackValue(this, ValueType.UINT, uwidth(value), value);
}
private floatStackValue(value: number) {
return new StackValue(this, ValueType.FLOAT, fwidth(value), value);
}
private offsetStackValue(offset: number, valueType: ValueType, bitWidth: BitWidth): StackValue {
return new StackValue(this, valueType, bitWidth, null, offset);
}
private finishBuffer() {
if (this.stack.length !== 1) {
throw `Stack has to be exactly 1, but it is ${this.stack.length}. You have to end all started vectors and maps before calling [finish]`;
}
const value = this.stack[0];
const byteWidth = this.align(value.elementWidth(this.offset, 0));
this.writeStackValue(value, byteWidth);
this.writeUInt(value.storedPackedType(), 1);
this.writeUInt(byteWidth, 1);
this.finished = true;
}
add(value: undefined | null | boolean | bigint | number | DataView | string | Array<unknown> | Record<string, unknown> | unknown): void {
this.integrityCheckOnValueAddition();
if (typeof value === 'undefined') {
throw "You need to provide a value";
}
if (value === null) {
this.stack.push(this.nullStackValue());
} else if (typeof value === "boolean") {
this.stack.push(this.boolStackValue(value));
} else if (typeof value === "bigint") {
this.stack.push(this.intStackValue(value));
} else if (typeof value == 'number') {
if (Number.isInteger(value)) {
this.stack.push(this.intStackValue(value));
} else {
this.stack.push(this.floatStackValue(value));
}
} else if (ArrayBuffer.isView(value)) {
this.writeBlob(value.buffer);
} else if (typeof value === 'string' || value instanceof String) {
this.writeString(value as string);
} else if (Array.isArray(value)) {
this.startVector();
for (let i = 0; i < value.length; i++) {
this.add(value[i]);
}
this.end();
} else if (typeof value === 'object') {
const properties = Object.getOwnPropertyNames(value).sort();
this.startMap(true);
for (let i = 0; i < properties.length; i++) {
const key = properties[i];
this.addKey(key);
this.add((value as Record<string, unknown>)[key]);
}
this.end();
} else {
throw `Unexpected value input ${value}`;
}
}
finish(): Uint8Array {
if (!this.finished) {
this.finishBuffer();
}
const result = this.buffer.slice(0, this.offset);
return new Uint8Array(result);
}
isFinished(): boolean {
return this.finished;
}
addKey(key: string): void {
this.integrityCheckOnKeyAddition();
this.writeKey(key);
}
addInt(value: number, indirect = false, deduplicate = false): void {
this.integrityCheckOnValueAddition();
if (!indirect) {
this.stack.push(this.intStackValue(value));
return;
}
if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectIntLookup, value)) {
this.stack.push(this.indirectIntLookup[value]);
return;
}
const stackValue = this.intStackValue(value);
const byteWidth = this.align(stackValue.width);
const newOffset = this.computeOffset(byteWidth);
const valueOffset = this.offset;
stackValue.writeToBuffer(byteWidth);
const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_INT, stackValue.width);
this.stack.push(stackOffset);
this.offset = newOffset;
if (deduplicate) {
this.indirectIntLookup[value] = stackOffset;
}
}
addUInt(value: number, indirect = false, deduplicate = false): void {
this.integrityCheckOnValueAddition();
if (!indirect) {
this.stack.push(this.uintStackValue(value));
return;
}
if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectUIntLookup, value)) {
this.stack.push(this.indirectUIntLookup[value]);
return;
}
const stackValue = this.uintStackValue(value);
const byteWidth = this.align(stackValue.width);
const newOffset = this.computeOffset(byteWidth);
const valueOffset = this.offset;
stackValue.writeToBuffer(byteWidth);
const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_UINT, stackValue.width);
this.stack.push(stackOffset);
this.offset = newOffset;
if (deduplicate) {
this.indirectUIntLookup[value] = stackOffset;
}
}
addFloat(value: number, indirect = false, deduplicate = false): void {
this.integrityCheckOnValueAddition();
if (!indirect) {
this.stack.push(this.floatStackValue(value));
return;
}
if (deduplicate && Object.prototype.hasOwnProperty.call(this.indirectFloatLookup, value)) {
this.stack.push(this.indirectFloatLookup[value]);
return;
}
const stackValue = this.floatStackValue(value);
const byteWidth = this.align(stackValue.width);
const newOffset = this.computeOffset(byteWidth);
const valueOffset = this.offset;
stackValue.writeToBuffer(byteWidth);
const stackOffset = this.offsetStackValue(valueOffset, ValueType.INDIRECT_FLOAT, stackValue.width);
this.stack.push(stackOffset);
this.offset = newOffset;
if (deduplicate) {
this.indirectFloatLookup[value] = stackOffset;
}
}
}
@@ -0,0 +1,9 @@
export function fromUTF8Array(data: BufferSource): string {
const decoder = new TextDecoder();
return decoder.decode(data);
}
export function toUTF8Array(str: string) : Uint8Array {
const encoder = new TextEncoder();
return encoder.encode(str);
}
+109
View File
@@ -0,0 +1,109 @@
import { BitWidth } from './bit-width.js'
import { toByteWidth, fromByteWidth } from './bit-width-util.js'
import { toUTF8Array, fromUTF8Array } from './flexbuffers-util.js'
export function validateOffset(dataView: DataView, offset: number, width: number): void {
if (dataView.byteLength <= offset + width || (offset & (toByteWidth(width) - 1)) !== 0) {
throw "Bad offset: " + offset + ", width: " + width;
}
}
export function readInt(dataView: DataView, offset: number, width: number): number | bigint {
if (width < 2) {
if (width < 1) {
return dataView.getInt8(offset);
} else {
return dataView.getInt16(offset, true);
}
} else {
if (width < 3) {
return dataView.getInt32(offset, true)
} else {
if (dataView.setBigInt64 === undefined) {
return BigInt(dataView.getUint32(offset, true)) + (BigInt(dataView.getUint32(offset + 4, true)) << BigInt(32));
}
return dataView.getBigInt64(offset, true)
}
}
}
export function readUInt(dataView: DataView, offset: number, width: number): number | bigint {
if (width < 2) {
if (width < 1) {
return dataView.getUint8(offset);
} else {
return dataView.getUint16(offset, true);
}
} else {
if (width < 3) {
return dataView.getUint32(offset, true)
} else {
if (dataView.getBigUint64 === undefined) {
return BigInt(dataView.getUint32(offset, true)) + (BigInt(dataView.getUint32(offset + 4, true)) << BigInt(32));
}
return dataView.getBigUint64(offset, true)
}
}
}
export function readFloat(dataView: DataView, offset: number, width: number): number {
if (width < BitWidth.WIDTH32) {
throw "Bad width: " + width;
}
if (width === BitWidth.WIDTH32) {
return dataView.getFloat32(offset, true);
}
return dataView.getFloat64(offset, true);
}
export function indirect(dataView: DataView, offset: number, width: number): number {
const step = readUInt(dataView, offset, width) as number;
return offset - step;
}
export function keyIndex(key: string, dataView: DataView, offset: number, parentWidth: number, byteWidth: number, length: number): number | null {
const input = toUTF8Array(key);
const keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3;
const bitWidth = fromByteWidth(byteWidth);
const indirectOffset = keysVectorOffset - Number(readUInt(dataView, keysVectorOffset, bitWidth));
const _byteWidth = Number(readUInt(dataView, keysVectorOffset + byteWidth, bitWidth));
let low = 0;
let high = length - 1;
while (low <= high) {
const mid = (high + low) >> 1;
const dif = diffKeys(input, mid, dataView, indirectOffset, _byteWidth);
if (dif === 0) return mid;
if (dif < 0) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return null;
}
export function diffKeys(input: Uint8Array, index: number, dataView: DataView, offset: number, width: number): number {
const keyOffset = offset + index * width;
const keyIndirectOffset = keyOffset - Number(readUInt(dataView, keyOffset, fromByteWidth(width)));
for (let i = 0; i < input.length; i++) {
const dif = input[i] - dataView.getUint8(keyIndirectOffset + i);
if (dif !== 0) {
return dif;
}
}
return dataView.getUint8(keyIndirectOffset + input.length) === 0 ? 0 : -1;
}
export function keyForIndex(index: number, dataView: DataView, offset: number, parentWidth: number, byteWidth: number): string {
const keysVectorOffset = indirect(dataView, offset, parentWidth) - byteWidth * 3;
const bitWidth = fromByteWidth(byteWidth);
const indirectOffset = keysVectorOffset - Number(readUInt(dataView, keysVectorOffset, bitWidth));
const _byteWidth = Number(readUInt(dataView, keysVectorOffset + byteWidth, bitWidth));
const keyOffset = indirectOffset + index * _byteWidth;
const keyIndirectOffset = keyOffset - Number(readUInt(dataView, keyOffset, fromByteWidth(_byteWidth)));
let length = 0;
while (dataView.getUint8(keyIndirectOffset + length) !== 0) {
length++;
}
return fromUTF8Array(new Uint8Array(dataView.buffer, keyIndirectOffset, length));
}
+191
View File
@@ -0,0 +1,191 @@
import { fromByteWidth } from './bit-width-util.js'
import { ValueType } from './value-type.js'
import { isNumber, isIndirectNumber, isAVector, fixedTypedVectorElementSize, isFixedTypedVector, isTypedVector, typedVectorElementType, packedType, fixedTypedVectorElementType } from './value-type-util.js'
import { indirect, keyForIndex, keyIndex, readFloat, readInt, readUInt } from './reference-util.js'
import { fromUTF8Array } from './flexbuffers-util.js';
import { BitWidth } from './bit-width.js';
export function toReference(buffer: ArrayBuffer): Reference {
const len = buffer.byteLength;
if (len < 3) {
throw "Buffer needs to be bigger than 3";
}
const dataView = new DataView(buffer);
const byteWidth = dataView.getUint8(len - 1);
const packedType = dataView.getUint8(len - 2);
const parentWidth = fromByteWidth(byteWidth);
const offset = len - byteWidth - 2;
return new Reference(dataView, offset, parentWidth, packedType, "/")
}
function valueForIndexWithKey(index: number, key: string, dataView: DataView, offset: number, parentWidth: number, byteWidth: number, length: number, path: string): Reference {
const _indirect = indirect(dataView, offset, parentWidth);
const elementOffset = _indirect + index * byteWidth;
const packedType = dataView.getUint8(_indirect + length * byteWidth + index);
return new Reference(dataView, elementOffset, fromByteWidth(byteWidth), packedType, `${path}/${key}`)
}
export class Reference {
private readonly byteWidth: number
private readonly valueType: ValueType
private _length = -1
constructor(private dataView: DataView, private offset: number, private parentWidth: number, private packedType: ValueType, private path: string) {
this.byteWidth = 1 << (packedType & 3)
this.valueType = packedType >> 2
}
isNull(): boolean { return this.valueType === ValueType.NULL; }
isNumber(): boolean { return isNumber(this.valueType) || isIndirectNumber(this.valueType); }
isFloat(): boolean { return ValueType.FLOAT === this.valueType || ValueType.INDIRECT_FLOAT === this.valueType; }
isInt(): boolean { return this.isNumber() && !this.isFloat(); }
isString(): boolean { return ValueType.STRING === this.valueType || ValueType.KEY === this.valueType; }
isBool(): boolean { return ValueType.BOOL === this.valueType; }
isBlob(): boolean { return ValueType.BLOB === this.valueType; }
isVector(): boolean { return isAVector(this.valueType); }
isMap(): boolean { return ValueType.MAP === this.valueType; }
boolValue(): boolean | null {
if (this.isBool()) {
return readInt(this.dataView, this.offset, this.parentWidth) > 0;
}
return null;
}
intValue(): number | bigint | null {
if (this.valueType === ValueType.INT) {
return readInt(this.dataView, this.offset, this.parentWidth);
}
if (this.valueType === ValueType.UINT) {
return readUInt(this.dataView, this.offset, this.parentWidth);
}
if (this.valueType === ValueType.INDIRECT_INT) {
return readInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
}
if (this.valueType === ValueType.INDIRECT_UINT) {
return readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
}
return null;
}
floatValue(): number | null {
if (this.valueType === ValueType.FLOAT) {
return readFloat(this.dataView, this.offset, this.parentWidth);
}
if (this.valueType === ValueType.INDIRECT_FLOAT) {
return readFloat(this.dataView, indirect(this.dataView, this.offset, this.parentWidth), fromByteWidth(this.byteWidth));
}
return null;
}
numericValue(): number | bigint | null { return this.floatValue() || this.intValue()}
stringValue(): string | null {
if (this.valueType === ValueType.STRING || this.valueType === ValueType.KEY) {
const begin = indirect(this.dataView, this.offset, this.parentWidth);
return fromUTF8Array(new Uint8Array(this.dataView.buffer, begin, this.length()));
}
return null;
}
blobValue(): Uint8Array | null {
if (this.isBlob()) {
const begin = indirect(this.dataView, this.offset, this.parentWidth);
return new Uint8Array(this.dataView.buffer, begin, this.length());
}
return null;
}
get(key: number): Reference {
const length = this.length();
if (Number.isInteger(key) && isAVector(this.valueType)) {
if (key >= length || key < 0) {
throw `Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`;
}
const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
const elementOffset = _indirect + key * this.byteWidth;
let _packedType = this.dataView.getUint8(_indirect + length * this.byteWidth + key);
if (isTypedVector(this.valueType)) {
const _valueType = typedVectorElementType(this.valueType);
_packedType = packedType(_valueType, BitWidth.WIDTH8);
} else if (isFixedTypedVector(this.valueType)) {
const _valueType = fixedTypedVectorElementType(this.valueType);
_packedType = packedType(_valueType, BitWidth.WIDTH8);
}
return new Reference(this.dataView, elementOffset, fromByteWidth(this.byteWidth), _packedType, `${this.path}[${key}]`);
}
if (typeof key === 'string') {
const index = keyIndex(key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length);
if (index !== null) {
return valueForIndexWithKey(index, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path)
}
}
throw `Key [${key}] is not applicable on ${this.path} of ${this.valueType}`;
}
length(): number {
let size;
if (this._length > -1) {
return this._length;
}
if (isFixedTypedVector(this.valueType)) {
this._length = fixedTypedVectorElementSize(this.valueType);
} else if (this.valueType === ValueType.BLOB
|| this.valueType === ValueType.MAP
|| isAVector(this.valueType)) {
this._length = readUInt(this.dataView, indirect(this.dataView, this.offset, this.parentWidth) - this.byteWidth, fromByteWidth(this.byteWidth)) as number
} else if (this.valueType === ValueType.NULL) {
this._length = 0;
} else if (this.valueType === ValueType.STRING) {
const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
let sizeByteWidth = this.byteWidth;
size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth));
while (this.dataView.getInt8(_indirect + (size as number)) !== 0) {
sizeByteWidth <<= 1;
size = readUInt(this.dataView, _indirect - sizeByteWidth, fromByteWidth(this.byteWidth));
}
this._length = size as number;
} else if (this.valueType === ValueType.KEY) {
const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
size = 1;
while (this.dataView.getInt8(_indirect + size) !== 0) {
size++;
}
this._length = size;
} else {
this._length = 1;
}
return Number(this._length);
}
toObject(): unknown {
const length = this.length();
if (this.isVector()) {
const result = [];
for (let i = 0; i < length; i++) {
result.push(this.get(i).toObject());
}
return result;
}
if (this.isMap()) {
const result: Record<string, unknown> = {};
for (let i = 0; i < length; i++) {
const key = keyForIndex(i, this.dataView, this.offset, this.parentWidth, this.byteWidth);
result[key] = valueForIndexWithKey(i, key, this.dataView, this.offset, this.parentWidth, this.byteWidth, length, this.path).toObject();
}
return result;
}
if (this.isNull()) {
return null;
}
if (this.isBool()) {
return this.boolValue();
}
if (this.isNumber()) {
return this.numericValue();
}
return this.blobValue() || this.stringValue();
}
}
+61
View File
@@ -0,0 +1,61 @@
import { Builder } from './builder.js'
import { BitWidth } from './bit-width.js'
import { paddingSize, uwidth, fromByteWidth } from './bit-width-util.js'
import { ValueType } from './value-type.js'
import { isInline, packedType } from './value-type-util.js'
export class StackValue {
constructor(private builder: Builder, public type: ValueType, public width: number, public value: number | boolean | null = null, public offset: number = 0) {
}
elementWidth(size: number, index: number): BitWidth {
if (isInline(this.type)) return this.width;
for (let i = 0; i < 4; i++) {
const width = 1 << i;
const offsetLoc = size + paddingSize(size, width) + index * width;
const offset = offsetLoc - this.offset;
const bitWidth = uwidth(offset);
if (1 << bitWidth === width) {
return bitWidth;
}
}
throw `Element is unknown. Size: ${size} at index: ${index}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`;
}
writeToBuffer(byteWidth: number): void {
const newOffset = this.builder.computeOffset(byteWidth);
if (this.type === ValueType.FLOAT) {
if (this.width === BitWidth.WIDTH32) {
this.builder.view.setFloat32(this.builder.offset, this.value as number, true);
} else {
this.builder.view.setFloat64(this.builder.offset, this.value as number, true);
}
} else if (this.type === ValueType.INT) {
const bitWidth = fromByteWidth(byteWidth);
this.builder.pushInt(this.value as number, bitWidth);
} else if (this.type === ValueType.UINT) {
const bitWidth = fromByteWidth(byteWidth);
this.builder.pushUInt(this.value as number, bitWidth);
} else if (this.type === ValueType.NULL) {
this.builder.pushInt(0, this.width);
} else if (this.type === ValueType.BOOL) {
this.builder.pushInt(this.value ? 1 : 0, this.width);
} else {
throw `Unexpected type: ${this.type}. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new`
}
this.offset = newOffset;
}
storedWidth(width = BitWidth.WIDTH8): BitWidth {
return isInline(this.type) ? Math.max(width, this.width) : this.width;
}
storedPackedType(width = BitWidth.WIDTH8): ValueType {
return packedType(this.type, this.storedWidth(width));
}
isOffset(): boolean {
return !isInline(this.type)
}
}
@@ -0,0 +1,64 @@
import { ValueType } from './value-type.js'
export function isInline(value: ValueType): boolean {
return value === ValueType.BOOL
|| value <= ValueType.FLOAT;
}
export function isNumber(value: ValueType): boolean {
return value >= ValueType.INT
&& value <= ValueType.FLOAT;
}
export function isIndirectNumber(value: ValueType): boolean {
return value >= ValueType.INDIRECT_INT
&& value <= ValueType.INDIRECT_FLOAT;
}
export function isTypedVectorElement(value: ValueType): boolean {
return value === ValueType.BOOL
|| (value >= ValueType.INT
&& value <= ValueType.STRING);
}
export function isTypedVector(value: ValueType): boolean {
return value === ValueType.VECTOR_BOOL
|| (value >= ValueType.VECTOR_INT
&& value <= ValueType.VECTOR_STRING_DEPRECATED);
}
export function isFixedTypedVector(value: ValueType): boolean {
return value >= ValueType.VECTOR_INT2
&& value <= ValueType.VECTOR_FLOAT4;
}
export function isAVector(value: ValueType): boolean {
return isTypedVector(value)
|| isFixedTypedVector(value)
|| value === ValueType.VECTOR;
}
export function toTypedVector(valueType: ValueType, length: number): ValueType {
if (length === 0) return valueType - ValueType.INT + ValueType.VECTOR_INT;
if (length === 2) return valueType - ValueType.INT + ValueType.VECTOR_INT2;
if (length === 3) return valueType - ValueType.INT + ValueType.VECTOR_INT3;
if (length === 4) return valueType - ValueType.INT + ValueType.VECTOR_INT4;
throw "Unexpected length " + length;
}
export function typedVectorElementType(valueType: ValueType): ValueType {
return valueType - ValueType.VECTOR_INT + ValueType.INT;
}
export function fixedTypedVectorElementType(valueType: ValueType): ValueType {
return ((valueType - ValueType.VECTOR_INT2) % 3) + ValueType.INT;
}
export function fixedTypedVectorElementSize(valueType: ValueType): ValueType {
// The x / y >> 0 trick is to have an int division. Suppose to be faster than Math.floor()
return (((valueType - ValueType.VECTOR_INT2) / 3) >> 0) + 2;
}
export function packedType(valueType: ValueType, bitWidth: number): ValueType {
return bitWidth | (valueType << 2);
}
+30
View File
@@ -0,0 +1,30 @@
export enum ValueType {
NULL = 0,
INT = 1,
UINT = 2,
FLOAT = 3,
KEY = 4,
STRING = 5,
INDIRECT_INT = 6,
INDIRECT_UINT = 7,
INDIRECT_FLOAT = 8,
MAP = 9,
VECTOR = 10,
VECTOR_INT = 11,
VECTOR_UINT = 12,
VECTOR_FLOAT = 13,
VECTOR_KEY = 14,
VECTOR_STRING_DEPRECATED = 15,
VECTOR_INT2 = 16,
VECTOR_UINT2 = 17,
VECTOR_FLOAT2 = 18,
VECTOR_INT3 = 19,
VECTOR_UINT3 = 20,
VECTOR_FLOAT3 = 21,
VECTOR_INT4 = 22,
VECTOR_UINT4 = 23,
VECTOR_FLOAT4 = 24,
BLOB = 25,
BOOL = 26,
VECTOR_BOOL = 36,
}
+17
View File
@@ -0,0 +1,17 @@
import { ByteBuffer } from './byte-buffer.js'
import { Builder } from './builder.js'
export type Offset = number;
export type Table = {
bb: ByteBuffer
bb_pos: number
};
export interface IGeneratedObject {
pack(builder:Builder): Offset
}
export interface IUnpackableObject<T> {
unpack(): T
}
+4
View File
@@ -0,0 +1,4 @@
export const int32 = new Int32Array(2);
export const float32 = new Float32Array(int32.buffer);
export const float64 = new Float64Array(int32.buffer);
export const isLittleEndian = new Uint16Array(new Uint8Array([1, 0]).buffer)[0] === 1;