mirror of
https://github.com/google/flatbuffers.git
synced 2026-06-30 18:50:01 +00:00
[Dart] Adding FlexBuffers support (#5853)
* Adding FlexBuffers support for Dart language
This commit is contained in:
2
dart/lib/flex_buffers.dart
Normal file
2
dart/lib/flex_buffers.dart
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export 'src/builder.dart';
|
||||||
|
export 'src/reference.dart';
|
||||||
648
dart/lib/src/builder.dart
Normal file
648
dart/lib/src/builder.dart
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'types.dart';
|
||||||
|
|
||||||
|
/// The main builder class for creation of a FlexBuffer.
|
||||||
|
class Builder {
|
||||||
|
ByteData _buffer;
|
||||||
|
List<_StackValue> _stack;
|
||||||
|
List<_StackPointer> _stackPointers;
|
||||||
|
int _offset;
|
||||||
|
bool _finished;
|
||||||
|
Map<String, _StackValue> _stringCache;
|
||||||
|
Map<String, _StackValue> _keyCache;
|
||||||
|
Map<_KeysHash, _StackValue> _keyVectorCache;
|
||||||
|
Map<int, _StackValue> _indirectIntCache;
|
||||||
|
Map<double, _StackValue> _indirectDoubleCache;
|
||||||
|
|
||||||
|
/// Instantiate the builder if you intent to gradually build up the buffer by calling
|
||||||
|
/// add... methods and calling [finish] to receive the the resulting byte array.
|
||||||
|
///
|
||||||
|
/// The default size of internal buffer is set to 2048. Provide a different value in order to avoid buffer copies.
|
||||||
|
Builder({int size = 2048}) {
|
||||||
|
_buffer = ByteData(size);
|
||||||
|
_stack = [];
|
||||||
|
_stackPointers = [];
|
||||||
|
_offset = 0;
|
||||||
|
_finished = false;
|
||||||
|
_stringCache = {};
|
||||||
|
_keyCache = {};
|
||||||
|
_keyVectorCache = {};
|
||||||
|
_indirectIntCache = {};
|
||||||
|
_indirectDoubleCache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this method in order to turn an object into a FlexBuffer directly.
|
||||||
|
///
|
||||||
|
/// Use the manual instantiation of the [Builder] and gradual addition of values, if performance is more important than convenience.
|
||||||
|
static ByteBuffer buildFromObject(Object value) {
|
||||||
|
final builder = Builder();
|
||||||
|
builder._add(value);
|
||||||
|
final buffer = builder.finish();
|
||||||
|
final byteData = ByteData(buffer.lengthInBytes);
|
||||||
|
byteData.buffer.asUint8List().setAll(0, buffer);
|
||||||
|
return byteData.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _add(Object value) {
|
||||||
|
if (value == null) {
|
||||||
|
addNull();
|
||||||
|
} else if (value is bool) {
|
||||||
|
addBool(value);
|
||||||
|
} else if (value is int) {
|
||||||
|
addInt(value);
|
||||||
|
} else if (value is double) {
|
||||||
|
addDouble(value);
|
||||||
|
} else if (value is ByteBuffer) {
|
||||||
|
addBlob(value);
|
||||||
|
} else if (value is String) {
|
||||||
|
addString(value);
|
||||||
|
} else if (value is List<dynamic>) {
|
||||||
|
startVector();
|
||||||
|
for (var i = 0; i < value.length; i++) {
|
||||||
|
_add(value[i]);
|
||||||
|
}
|
||||||
|
end();
|
||||||
|
} else if (value is Map<String, dynamic>) {
|
||||||
|
startMap();
|
||||||
|
value.forEach((key, value) {
|
||||||
|
addKey(key);
|
||||||
|
_add(value);
|
||||||
|
});
|
||||||
|
end();
|
||||||
|
} else {
|
||||||
|
throw UnsupportedError('Value of unexpected type: $value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this method if you want to store a null value.
|
||||||
|
///
|
||||||
|
/// Specifically useful when building up a vector where values can be null.
|
||||||
|
void addNull() {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stack.add(_StackValue.WithNull());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a string value.
|
||||||
|
void addInt(int value) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stack.add(_StackValue.WithInt(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a bool value.
|
||||||
|
void addBool(bool value) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stack.add(_StackValue.WithBool(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a double value.
|
||||||
|
void addDouble(double value) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stack.add(_StackValue.WithDouble(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a string value.
|
||||||
|
void addString(String value) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
if (_stringCache.containsKey(value)) {
|
||||||
|
_stack.add(_stringCache[value]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final utf8String = utf8.encode(value);
|
||||||
|
final length = utf8String.length;
|
||||||
|
final bitWidth = BitWidthUtil.width(length);
|
||||||
|
final byteWidth = _align(bitWidth);
|
||||||
|
_writeInt(length, byteWidth);
|
||||||
|
final stringOffset = _offset;
|
||||||
|
final newOffset = _newOffset(length + 1);
|
||||||
|
_pushBuffer(utf8String);
|
||||||
|
_offset = newOffset;
|
||||||
|
final stackValue = _StackValue.WithOffset(stringOffset, ValueType.String, bitWidth);
|
||||||
|
_stack.add(stackValue);
|
||||||
|
_stringCache[value] = stackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This methods adds a key to a map and should be followed by an add... value call.
|
||||||
|
///
|
||||||
|
/// It also implies that you call this method only after you called [startMap].
|
||||||
|
void addKey(String value) {
|
||||||
|
_integrityCheckOnKeyAddition();
|
||||||
|
if (_keyCache.containsKey(value)) {
|
||||||
|
_stack.add(_keyCache[value]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final utf8String = utf8.encode(value);
|
||||||
|
final length = utf8String.length;
|
||||||
|
final keyOffset = _offset;
|
||||||
|
final newOffset = _newOffset(length + 1);
|
||||||
|
_pushBuffer(utf8String);
|
||||||
|
_offset = newOffset;
|
||||||
|
final stackValue = _StackValue.WithOffset(keyOffset, ValueType.Key, BitWidth.width8);
|
||||||
|
_stack.add(stackValue);
|
||||||
|
_keyCache[value] = stackValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a byte array.
|
||||||
|
///
|
||||||
|
/// This method can be used to store any generic BLOB.
|
||||||
|
void addBlob(ByteBuffer value) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
final length = value.lengthInBytes;
|
||||||
|
final bitWidth = BitWidthUtil.width(length);
|
||||||
|
final byteWidth = _align(bitWidth);
|
||||||
|
_writeInt(length, byteWidth);
|
||||||
|
final blobOffset = _offset;
|
||||||
|
final newOffset = _newOffset(length);
|
||||||
|
_pushBuffer(value.asUint8List());
|
||||||
|
_offset = newOffset;
|
||||||
|
final stackValue = _StackValue.WithOffset(blobOffset, ValueType.Blob, bitWidth);
|
||||||
|
_stack.add(stackValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores int value indirectly in the buffer.
|
||||||
|
///
|
||||||
|
/// Adding large integer values indirectly might be beneficial if those values suppose to be store in a vector together with small integer values.
|
||||||
|
/// This is due to the fact that FlexBuffers will add padding to small integer values, if they are stored together with large integer values.
|
||||||
|
/// When we add integer indirectly the vector of ints will contain not the value itself, but only the relative offset to the value.
|
||||||
|
/// By setting the [cache] parameter to true, you make sure that the builder tracks added int value and performs deduplication.
|
||||||
|
void addIntIndirectly(int value, {bool cache = false}) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
if (_indirectIntCache.containsKey(value)) {
|
||||||
|
_stack.add(_indirectIntCache[value]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final stackValue = _StackValue.WithInt(value);
|
||||||
|
final byteWidth = _align(stackValue.width);
|
||||||
|
final newOffset = _newOffset(byteWidth);
|
||||||
|
final valueOffset = _offset;
|
||||||
|
_pushBuffer(stackValue.asU8List(stackValue.width));
|
||||||
|
final stackOffset = _StackValue.WithOffset(valueOffset, ValueType.IndirectInt, stackValue.width);
|
||||||
|
_stack.add(stackOffset);
|
||||||
|
_offset = newOffset;
|
||||||
|
if (cache) {
|
||||||
|
_indirectIntCache[value] = stackOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stores double value indirectly in the buffer.
|
||||||
|
///
|
||||||
|
/// Double are stored as 8 or 4 byte values in FlexBuffers. If they are stored in a mixed vector, values which are smaller than 4 / 8 bytes will be padded.
|
||||||
|
/// When we add double indirectly, the vector will contain not the value itself, but only the relative offset to the value. Which could occupy only 1 or 2 bytes, reducing the odds for unnecessary padding.
|
||||||
|
/// By setting the [cache] parameter to true, you make sure that the builder tracks already added double value and performs deduplication.
|
||||||
|
void addDoubleIndirectly(double value, {bool cache = false}) {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
if (cache && _indirectDoubleCache.containsKey(value)) {
|
||||||
|
_stack.add(_indirectDoubleCache[value]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final stackValue = _StackValue.WithDouble(value);
|
||||||
|
final byteWidth = _align(stackValue.width);
|
||||||
|
final newOffset = _newOffset(byteWidth);
|
||||||
|
final valueOffset = _offset;
|
||||||
|
_pushBuffer(stackValue.asU8List(stackValue.width));
|
||||||
|
final stackOffset = _StackValue.WithOffset(valueOffset, ValueType.IndirectFloat, stackValue.width);
|
||||||
|
_stack.add(stackOffset);
|
||||||
|
_offset = newOffset;
|
||||||
|
if (cache) {
|
||||||
|
_indirectDoubleCache[value] = stackOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method starts a vector definition and needs to be followed by 0 to n add... value calls.
|
||||||
|
///
|
||||||
|
/// The vector definition needs to be finished with an [end] call.
|
||||||
|
/// It is also possible to add nested vector or map by calling [startVector] / [startMap].
|
||||||
|
void startVector() {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stackPointers.add(_StackPointer(_stack.length, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This method starts a map definition.
|
||||||
|
///
|
||||||
|
/// This method call needs to be followed by 0 to n [addKey] + add... value calls.
|
||||||
|
/// The map definition needs to be finished with an [end] call.
|
||||||
|
/// It is also possible to add nested vector or map by calling [startVector] / [startMap] after calling [addKey].
|
||||||
|
void startMap() {
|
||||||
|
_integrityCheckOnValueAddition();
|
||||||
|
_stackPointers.add(_StackPointer(_stack.length, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks that the addition of values to the last vector, or map have ended.
|
||||||
|
void end() {
|
||||||
|
final pointer = _stackPointers.removeLast();
|
||||||
|
if (pointer.isVector) {
|
||||||
|
_endVector(pointer);
|
||||||
|
} else {
|
||||||
|
_sortKeysAndEndMap(pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Finish building the FlatBuffer and return array of bytes.
|
||||||
|
///
|
||||||
|
/// Can be called multiple times, to get the array of bytes.
|
||||||
|
/// After the first call, adding values, or starting vectors / maps will result in an exception.
|
||||||
|
Uint8List finish() {
|
||||||
|
if (_finished == false) {
|
||||||
|
_finish();
|
||||||
|
}
|
||||||
|
return _buffer.buffer.asUint8List(0, _offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a FlatBuffer with current state without finishing the builder.
|
||||||
|
///
|
||||||
|
/// Creates an internal temporary copy of current builder and finishes the copy.
|
||||||
|
/// Use this method, when the state of a long lasting builder need to be persisted periodically.
|
||||||
|
ByteBuffer snapshot() {
|
||||||
|
final tmp = Builder(size: _offset + 200);
|
||||||
|
tmp._offset = _offset;
|
||||||
|
tmp._stack = List.from(_stack);
|
||||||
|
tmp._stackPointers = List.from(_stackPointers);
|
||||||
|
tmp._buffer.buffer.asUint8List().setAll(0, _buffer.buffer.asUint8List(0, _offset));
|
||||||
|
for (var i = 0; i < tmp._stackPointers.length; i++){
|
||||||
|
tmp.end();
|
||||||
|
}
|
||||||
|
final buffer = tmp.finish();
|
||||||
|
final bd = ByteData(buffer.lengthInBytes);
|
||||||
|
bd.buffer.asUint8List().setAll(0, buffer);
|
||||||
|
return bd.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _integrityCheckOnValueAddition() {
|
||||||
|
if (_finished) {
|
||||||
|
throw StateError('Adding values after finish is prohibited');
|
||||||
|
}
|
||||||
|
if (_stackPointers.isNotEmpty && _stackPointers.last.isVector == false) {
|
||||||
|
if (_stack.last.type != ValueType.Key) {
|
||||||
|
throw StateError('Adding value to a map before adding a key is prohibited');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _integrityCheckOnKeyAddition() {
|
||||||
|
if (_finished) {
|
||||||
|
throw StateError('Adding values after finish is prohibited');
|
||||||
|
}
|
||||||
|
if (_stackPointers.isEmpty || _stackPointers.last.isVector) {
|
||||||
|
throw StateError('Adding key before staring a map is prohibited');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _finish() {
|
||||||
|
if (_stack.length != 1) {
|
||||||
|
throw StateError('Stack has to be exactly 1, but is ${_stack.length}. You have to end all started vectors and maps, before calling [finish]');
|
||||||
|
}
|
||||||
|
final value = _stack[0];
|
||||||
|
final byteWidth = _align(value.elementWidth(_offset, 0));
|
||||||
|
_writeStackValue(value, byteWidth);
|
||||||
|
_writeInt(value.storedPackedType(), 1);
|
||||||
|
_writeInt(byteWidth, 1);
|
||||||
|
_finished = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_StackValue _createVector(int start, int vecLength, int step, [_StackValue keys]) {
|
||||||
|
var bitWidth = BitWidthUtil.width(vecLength);
|
||||||
|
var prefixElements = 1;
|
||||||
|
if (keys != null) {
|
||||||
|
var elemWidth = keys.elementWidth(_offset, 0);
|
||||||
|
if (elemWidth.index > bitWidth.index) {
|
||||||
|
bitWidth = elemWidth;
|
||||||
|
}
|
||||||
|
prefixElements += 2;
|
||||||
|
}
|
||||||
|
var vectorType = ValueType.Key;
|
||||||
|
var typed = keys == null;
|
||||||
|
for (var i = start; i < _stack.length; i += step) {
|
||||||
|
final elemWidth = _stack[i].elementWidth(_offset, i + prefixElements);
|
||||||
|
if (elemWidth.index > bitWidth.index) {
|
||||||
|
bitWidth = elemWidth;
|
||||||
|
}
|
||||||
|
if (i == start) {
|
||||||
|
vectorType = _stack[i].type;
|
||||||
|
typed &= ValueTypeUtils.isTypedVectorElement(vectorType);
|
||||||
|
} else {
|
||||||
|
if (vectorType != _stack[i].type) {
|
||||||
|
typed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final byteWidth = _align(bitWidth);
|
||||||
|
final fix = typed & ValueTypeUtils.isNumber(vectorType) && vecLength >= 2 && vecLength <= 4;
|
||||||
|
if (keys != null) {
|
||||||
|
_writeStackValue(keys, byteWidth);
|
||||||
|
_writeInt(1 << keys.width.index, byteWidth);
|
||||||
|
}
|
||||||
|
if (fix == false) {
|
||||||
|
_writeInt(vecLength, byteWidth);
|
||||||
|
}
|
||||||
|
final vecOffset = _offset;
|
||||||
|
for (var i = start; i < _stack.length; i += step) {
|
||||||
|
_writeStackValue(_stack[i], byteWidth);
|
||||||
|
}
|
||||||
|
if (typed == false) {
|
||||||
|
for (var i = start; i < _stack.length; i += step) {
|
||||||
|
_writeInt(_stack[i].storedPackedType(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keys != null) {
|
||||||
|
return _StackValue.WithOffset(vecOffset, ValueType.Map, bitWidth);
|
||||||
|
}
|
||||||
|
if (typed) {
|
||||||
|
final vType = ValueTypeUtils.toTypedVector(vectorType, fix ? vecLength : 0);
|
||||||
|
return _StackValue.WithOffset(vecOffset, vType, bitWidth);
|
||||||
|
}
|
||||||
|
return _StackValue.WithOffset(vecOffset, ValueType.Vector, bitWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _endVector(_StackPointer pointer) {
|
||||||
|
final vecLength = _stack.length - pointer.stackPosition;
|
||||||
|
final vec = _createVector(pointer.stackPosition, vecLength, 1);
|
||||||
|
_stack.removeRange(pointer.stackPosition, _stack.length);
|
||||||
|
_stack.add(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _sortKeysAndEndMap(_StackPointer pointer) {
|
||||||
|
if (((_stack.length - pointer.stackPosition) & 1) == 1) {
|
||||||
|
throw StateError('The stack needs to hold key value pairs (even number of elements). Check if you combined [addKey] with add... method calls properly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var sorted = true;
|
||||||
|
for (var i = pointer.stackPosition; i < _stack.length - 2; i += 2) {
|
||||||
|
if (_shouldFlip(_stack[i], _stack[i+2])) {
|
||||||
|
sorted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sorted == false) {
|
||||||
|
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
|
||||||
|
var flipIndex = i;
|
||||||
|
for (var j = i + 2; j < _stack.length; j += 2) {
|
||||||
|
if (_shouldFlip(_stack[flipIndex], _stack[j])) {
|
||||||
|
flipIndex = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (flipIndex != i) {
|
||||||
|
var k = _stack[flipIndex];
|
||||||
|
var v = _stack[flipIndex + 1];
|
||||||
|
_stack[flipIndex] = _stack[i];
|
||||||
|
_stack[flipIndex + 1] = _stack[i + 1];
|
||||||
|
_stack[i] = k;
|
||||||
|
_stack[i + 1] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_endMap(pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _endMap(_StackPointer pointer) {
|
||||||
|
final vecLength = (_stack.length - pointer.stackPosition) >> 1;
|
||||||
|
final offsets = <int>[];
|
||||||
|
for (var i = pointer.stackPosition; i < _stack.length; i += 2) {
|
||||||
|
offsets.add(_stack[i].offset);
|
||||||
|
}
|
||||||
|
final keysHash = _KeysHash(offsets);
|
||||||
|
var keysStackValue;
|
||||||
|
if (_keyVectorCache.containsKey(keysHash)) {
|
||||||
|
keysStackValue = _keyVectorCache[keysHash];
|
||||||
|
} else {
|
||||||
|
keysStackValue = _createVector(pointer.stackPosition, vecLength, 2);
|
||||||
|
_keyVectorCache[keysHash] = keysStackValue;
|
||||||
|
}
|
||||||
|
final vec = _createVector(pointer.stackPosition + 1, vecLength, 2, keysStackValue);
|
||||||
|
_stack.removeRange(pointer.stackPosition, _stack.length);
|
||||||
|
_stack.add(vec);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _shouldFlip(_StackValue v1, _StackValue v2) {
|
||||||
|
if (v1.type != ValueType.Key || v2.type != ValueType.Key) {
|
||||||
|
throw StateError('Stack values are not keys $v1 | $v2. Check if you combined [addKey] with add... method calls properly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
var c1, c2;
|
||||||
|
var index = 0;
|
||||||
|
do {
|
||||||
|
c1 = _buffer.getUint8(v1.offset + index);
|
||||||
|
c2 = _buffer.getUint8(v2.offset + index);
|
||||||
|
if (c2 < c1) return true;
|
||||||
|
if (c1 < c2) return false;
|
||||||
|
index += 1;
|
||||||
|
} while (c1 != 0 && c2 != 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _align(BitWidth width) {
|
||||||
|
final byteWidth = BitWidthUtil.toByteWidth(width);
|
||||||
|
_offset += BitWidthUtil.paddingSize(_offset, byteWidth);
|
||||||
|
return byteWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeStackValue(_StackValue value, int byteWidth) {
|
||||||
|
final newOffset = _newOffset(byteWidth);
|
||||||
|
if (value.isOffset) {
|
||||||
|
final relativeOffset = _offset - value.offset;
|
||||||
|
if (byteWidth == 8 || relativeOffset < (1 << (byteWidth * 8))) {
|
||||||
|
_writeInt(relativeOffset, byteWidth);
|
||||||
|
} else {
|
||||||
|
throw StateError('Unexpected size $byteWidth. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_pushBuffer(value.asU8List(BitWidthUtil.fromByteWidth(byteWidth)));
|
||||||
|
}
|
||||||
|
_offset = newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _writeInt(int value, int byteWidth) {
|
||||||
|
final newOffset = _newOffset(byteWidth);
|
||||||
|
_pushInt(value, BitWidthUtil.fromByteWidth(byteWidth));
|
||||||
|
_offset = newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _newOffset(int newValueSize) {
|
||||||
|
final newOffset = _offset + newValueSize;
|
||||||
|
var size = _buffer.lengthInBytes;
|
||||||
|
final prevSize = size;
|
||||||
|
while (size < newOffset) {
|
||||||
|
size <<= 1;
|
||||||
|
}
|
||||||
|
if (prevSize < size) {
|
||||||
|
final newBuf = ByteData(size);
|
||||||
|
newBuf.buffer
|
||||||
|
.asUint8List()
|
||||||
|
.setAll(0, _buffer.buffer.asUint8List());
|
||||||
|
}
|
||||||
|
return newOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pushInt(int value, BitWidth width) {
|
||||||
|
switch (width) {
|
||||||
|
|
||||||
|
case BitWidth.width8:
|
||||||
|
_buffer.setInt8(_offset, value);
|
||||||
|
break;
|
||||||
|
case BitWidth.width16:
|
||||||
|
_buffer.setInt16(_offset, value, Endian.little);
|
||||||
|
break;
|
||||||
|
case BitWidth.width32:
|
||||||
|
_buffer.setInt32(_offset, value, Endian.little);
|
||||||
|
break;
|
||||||
|
case BitWidth.width64:
|
||||||
|
_buffer.setInt64(_offset, value, Endian.little);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pushBuffer(List<int> value) {
|
||||||
|
_buffer.buffer.asUint8List().setAll(_offset, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StackValue {
|
||||||
|
Object _value;
|
||||||
|
int _offset;
|
||||||
|
ValueType _type;
|
||||||
|
BitWidth _width;
|
||||||
|
_StackValue.WithNull() {
|
||||||
|
_type = ValueType.Null;
|
||||||
|
_width = BitWidth.width8;
|
||||||
|
}
|
||||||
|
_StackValue.WithInt(int value) {
|
||||||
|
_type = value != null ? ValueType.Int : ValueType.Null;
|
||||||
|
_width = BitWidthUtil.width(value);
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
_StackValue.WithBool(bool value) {
|
||||||
|
_type = value != null ? ValueType.Bool : ValueType.Null;
|
||||||
|
_width = BitWidth.width8;
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
_StackValue.WithDouble(double value) {
|
||||||
|
_type = value != null ? ValueType.Float : ValueType.Null;
|
||||||
|
_width = BitWidthUtil.width(value);
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
_StackValue.WithOffset(int value, ValueType type, BitWidth width) {
|
||||||
|
_offset = value;
|
||||||
|
_type = type;
|
||||||
|
_width = width;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitWidth storedWidth({BitWidth width = BitWidth.width8}) {
|
||||||
|
return ValueTypeUtils.isInline(_type) ? BitWidthUtil.max(_width, width) : _width;
|
||||||
|
}
|
||||||
|
|
||||||
|
int storedPackedType({BitWidth width = BitWidth.width8}) {
|
||||||
|
return ValueTypeUtils.packedType(_type, storedWidth(width: width));
|
||||||
|
}
|
||||||
|
|
||||||
|
BitWidth elementWidth(int size, int index) {
|
||||||
|
if (ValueTypeUtils.isInline(_type)) return _width;
|
||||||
|
for(var i = 0; i < 4; i++) {
|
||||||
|
final width = 1 << i;
|
||||||
|
final offsetLoc = size + BitWidthUtil.paddingSize(size, width) + index * width;
|
||||||
|
final offset = offsetLoc - _offset;
|
||||||
|
final bitWidth = BitWidthUtil.width(offset);
|
||||||
|
if (1 << bitWidth.index == width) {
|
||||||
|
return bitWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw StateError('Element is of unknown. Size: $size at index: $index. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> asU8List(BitWidth width) {
|
||||||
|
if (ValueTypeUtils.isNumber(_type)) {
|
||||||
|
if (_type == ValueType.Float) {
|
||||||
|
if (width == BitWidth.width32) {
|
||||||
|
final result = ByteData(4);
|
||||||
|
result.setFloat32(0, _value, Endian.little);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
} else {
|
||||||
|
final result = ByteData(8);
|
||||||
|
result.setFloat64(0, _value, Endian.little);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch(width) {
|
||||||
|
case BitWidth.width8:
|
||||||
|
final result = ByteData(1);
|
||||||
|
result.setInt8(0, _value);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
case BitWidth.width16:
|
||||||
|
final result = ByteData(2);
|
||||||
|
result.setInt16(0, _value, Endian.little);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
case BitWidth.width32:
|
||||||
|
final result = ByteData(4);
|
||||||
|
result.setInt32(0, _value, Endian.little);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
case BitWidth.width64:
|
||||||
|
final result = ByteData(8);
|
||||||
|
result.setInt64(0, _value, Endian.little);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_type == ValueType.Null) {
|
||||||
|
final result = ByteData(1);
|
||||||
|
result.setInt8(0, 0);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
if (_type == ValueType.Bool) {
|
||||||
|
final result = ByteData(1);
|
||||||
|
result.setInt8(0, _value ? 1 : 0);
|
||||||
|
return result.buffer.asUint8List();
|
||||||
|
}
|
||||||
|
|
||||||
|
throw StateError('Unexpected type: $_type. This might be a bug. Please create an issue https://github.com/google/flatbuffers/issues/new');
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueType get type {
|
||||||
|
return _type;
|
||||||
|
}
|
||||||
|
|
||||||
|
BitWidth get width {
|
||||||
|
return _width;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isOffset {
|
||||||
|
return !ValueTypeUtils.isInline(_type);
|
||||||
|
}
|
||||||
|
int get offset => _offset;
|
||||||
|
|
||||||
|
bool get isFloat32 {
|
||||||
|
return _type == ValueType.Float && _width == BitWidth.width32;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StackPointer {
|
||||||
|
int stackPosition;
|
||||||
|
bool isVector;
|
||||||
|
_StackPointer(this.stackPosition, this.isVector);
|
||||||
|
}
|
||||||
|
|
||||||
|
class _KeysHash {
|
||||||
|
final List<int> keys;
|
||||||
|
|
||||||
|
const _KeysHash(this.keys);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
if (other is _KeysHash) {
|
||||||
|
if (keys.length != other.keys.length) return false;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
if (keys[i] != other.keys[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var result = 17;
|
||||||
|
for (var i = 0; i < keys.length; i++) {
|
||||||
|
result = result * 23 + keys[i];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
446
dart/lib/src/reference.dart
Normal file
446
dart/lib/src/reference.dart
Normal file
@@ -0,0 +1,446 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'types.dart';
|
||||||
|
|
||||||
|
/// Main class to read a value out of a FlexBuffer.
|
||||||
|
///
|
||||||
|
/// This class let you access values stored in the buffer in a lazy fashion.
|
||||||
|
class Reference {
|
||||||
|
final ByteData _buffer;
|
||||||
|
final int _offset;
|
||||||
|
final BitWidth _parentWidth;
|
||||||
|
final String _path;
|
||||||
|
int _byteWidth;
|
||||||
|
ValueType _valueType;
|
||||||
|
int _length;
|
||||||
|
|
||||||
|
Reference._(this._buffer, this._offset, this._parentWidth, int packedType, this._path) {
|
||||||
|
_byteWidth = 1 << (packedType & 3);
|
||||||
|
_valueType = ValueTypeUtils.fromInt(packedType >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use this method to access the root value of a FlexBuffer.
|
||||||
|
static Reference fromBuffer(ByteBuffer buffer) {
|
||||||
|
final len = buffer.lengthInBytes;
|
||||||
|
if (len < 3) {
|
||||||
|
throw UnsupportedError('Buffer needs to be bigger than 3');
|
||||||
|
}
|
||||||
|
final byteData = ByteData.view(buffer);
|
||||||
|
final byteWidth = byteData.getUint8(len - 1);
|
||||||
|
final packedType = byteData.getUint8(len - 2);
|
||||||
|
final offset = len - byteWidth - 2;
|
||||||
|
return Reference._(ByteData.view(buffer), offset, BitWidthUtil.fromByteWidth(byteWidth), packedType, "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the underlying value is null.
|
||||||
|
bool get isNull => _valueType == ValueType.Null;
|
||||||
|
/// Returns true if the underlying value can be represented as [num].
|
||||||
|
bool get isNum => ValueTypeUtils.isNumber(_valueType) || ValueTypeUtils.isIndirectNumber(_valueType);
|
||||||
|
/// Returns true if the underlying value was encoded as a float (direct or indirect).
|
||||||
|
bool get isDouble => _valueType == ValueType.Float || _valueType == ValueType.IndirectFloat;
|
||||||
|
/// Returns true if the underlying value was encoded as an int or uint (direct or indirect).
|
||||||
|
bool get isInt => isNum && !isDouble;
|
||||||
|
/// Returns true if the underlying value was encoded as a string or a key.
|
||||||
|
bool get isString => _valueType == ValueType.String || _valueType == ValueType.Key;
|
||||||
|
/// Returns true if the underlying value was encoded as a bool.
|
||||||
|
bool get isBool => _valueType == ValueType.Bool;
|
||||||
|
/// Returns true if the underlying value was encoded as a blob.
|
||||||
|
bool get isBlob => _valueType == ValueType.Blob;
|
||||||
|
/// Returns true if the underlying value points to a vector.
|
||||||
|
bool get isVector => ValueTypeUtils.isAVector(_valueType);
|
||||||
|
/// Returns true if the underlying value points to a map.
|
||||||
|
bool get isMap => _valueType == ValueType.Map;
|
||||||
|
|
||||||
|
/// If this [isBool], returns the bool value. Otherwise, returns null.
|
||||||
|
bool get boolValue {
|
||||||
|
if(_valueType == ValueType.Bool) {
|
||||||
|
return _readInt(_offset, _parentWidth) != 0;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns an [int], if the underlying value can be represented as an int.
|
||||||
|
///
|
||||||
|
/// Otherwise returns [null].
|
||||||
|
int get intValue {
|
||||||
|
if (_valueType == ValueType.Int) {
|
||||||
|
return _readInt(_offset, _parentWidth);
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.UInt) {
|
||||||
|
return _readUInt(_offset, _parentWidth);
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.IndirectInt) {
|
||||||
|
return _readInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.IndirectUInt) {
|
||||||
|
return _readUInt(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [double], if the underlying value [isDouble].
|
||||||
|
///
|
||||||
|
/// Otherwise returns [null].
|
||||||
|
double get doubleValue {
|
||||||
|
if (_valueType == ValueType.Float) {
|
||||||
|
return _readFloat(_offset, _parentWidth);
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.IndirectFloat) {
|
||||||
|
return _readFloat(_indirect, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [num], if the underlying value is numeric, be it int uint, or float (direct or indirect).
|
||||||
|
///
|
||||||
|
/// Otherwise returns [null].
|
||||||
|
num get numValue => doubleValue ?? intValue;
|
||||||
|
|
||||||
|
/// Returns [String] value or null otherwise.
|
||||||
|
///
|
||||||
|
/// This method performers a utf8 decoding, as FlexBuffers format stores strings in utf8 encoding.
|
||||||
|
String get stringValue {
|
||||||
|
if (_valueType == ValueType.String || _valueType == ValueType.Key) {
|
||||||
|
return utf8.decode(_buffer.buffer.asUint8List(_indirect, length));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns [Uint8List] value or null otherwise.
|
||||||
|
Uint8List get blobValue {
|
||||||
|
if (_valueType == ValueType.Blob) {
|
||||||
|
return _buffer.buffer.asUint8List(_indirect, length);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Can be used with an [int] or a [String] value for key.
|
||||||
|
/// If the underlying value in FlexBuffer is a vector, then use [int] for access.
|
||||||
|
/// If the underlying value in FlexBuffer is a map, then use [String] for access.
|
||||||
|
/// Returns [Reference] value. Throws an exception when [key] is not applicable.
|
||||||
|
Reference operator [](Object key) {
|
||||||
|
if (key is int && ValueTypeUtils.isAVector(_valueType)) {
|
||||||
|
final index = key;
|
||||||
|
if(index >= length || index < 0) {
|
||||||
|
throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType length: $length');
|
||||||
|
}
|
||||||
|
final elementOffset = _indirect + index * _byteWidth;
|
||||||
|
final reference = Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), 0, "$_path[$index]");
|
||||||
|
reference._byteWidth = 1;
|
||||||
|
if (ValueTypeUtils.isTypedVector(_valueType)) {
|
||||||
|
reference._valueType = ValueTypeUtils.typedVectorElementType(_valueType);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
if(ValueTypeUtils.isFixedTypedVector(_valueType)) {
|
||||||
|
reference._valueType = ValueTypeUtils.fixedTypedVectorElementType(_valueType);
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
final packedType = _buffer.getUint8(_indirect + length * _byteWidth + index);
|
||||||
|
return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path[$index]");
|
||||||
|
}
|
||||||
|
if (key is String && _valueType == ValueType.Map) {
|
||||||
|
final index = _keyIndex(key);
|
||||||
|
if (index != null) {
|
||||||
|
return _valueForIndexWithKey(index, key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw ArgumentError('Key: [$key] is not applicable on: $_path of: $_valueType');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterable if the underlying flexBuffer value is a vector.
|
||||||
|
/// Otherwise throws an exception.
|
||||||
|
Iterable<Reference> get vectorIterable {
|
||||||
|
if(isVector == false) {
|
||||||
|
throw UnsupportedError('Value is not a vector. It is: $_valueType');
|
||||||
|
}
|
||||||
|
return _VectorIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterable for keys if the underlying flexBuffer value is a map.
|
||||||
|
/// Otherwise throws an exception.
|
||||||
|
Iterable<String> get mapKeyIterable {
|
||||||
|
if(isMap == false) {
|
||||||
|
throw UnsupportedError('Value is not a map. It is: $_valueType');
|
||||||
|
}
|
||||||
|
return _MapKeyIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get an iterable for values if the underlying flexBuffer value is a map.
|
||||||
|
/// Otherwise throws an exception.
|
||||||
|
Iterable<Reference> get mapValueIterable {
|
||||||
|
if(isMap == false) {
|
||||||
|
throw UnsupportedError('Value is not a map. It is: $_valueType');
|
||||||
|
}
|
||||||
|
return _MapValueIterator(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the length of the the underlying FlexBuffer value.
|
||||||
|
/// If the underlying value is [null] the length is 0.
|
||||||
|
/// If the underlying value is a number, or a bool, the length is 1.
|
||||||
|
/// If the underlying value is a vector, or map, the length reflects number of elements / element pairs.
|
||||||
|
/// If the values is a string or a blob, the length reflects a number of bytes the value occupies (strings are encoded in utf8 format).
|
||||||
|
int get length {
|
||||||
|
if (_length != null) {
|
||||||
|
return _length;
|
||||||
|
}
|
||||||
|
// needs to be checked before more generic isAVector
|
||||||
|
if(ValueTypeUtils.isFixedTypedVector(_valueType)) {
|
||||||
|
_length = ValueTypeUtils.fixedTypedVectorElementSize(_valueType);
|
||||||
|
} else if(_valueType == ValueType.Blob || ValueTypeUtils.isAVector(_valueType) || _valueType == ValueType.Map){
|
||||||
|
_length = _readInt(_indirect - _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
} else if (_valueType == ValueType.Null) {
|
||||||
|
_length = 0;
|
||||||
|
} else if (_valueType == ValueType.String) {
|
||||||
|
final indirect = _indirect;
|
||||||
|
var size_byte_width = _byteWidth;
|
||||||
|
var size = _readInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
|
||||||
|
while (_buffer.getInt8(indirect + size) != 0) {
|
||||||
|
size_byte_width <<= 1;
|
||||||
|
size = _readInt(indirect - size_byte_width, BitWidthUtil.fromByteWidth(size_byte_width));
|
||||||
|
}
|
||||||
|
_length = size;
|
||||||
|
} else if (_valueType == ValueType.Key) {
|
||||||
|
final indirect = _indirect;
|
||||||
|
var size = 1;
|
||||||
|
while (_buffer.getInt8(indirect + size) != 0) {
|
||||||
|
size += 1;
|
||||||
|
}
|
||||||
|
_length = size;
|
||||||
|
} else {
|
||||||
|
_length = 1;
|
||||||
|
}
|
||||||
|
return _length;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Returns a minified JSON representation of the underlying FlexBuffer value.
|
||||||
|
///
|
||||||
|
/// This method involves materializing the entire object tree, which may be
|
||||||
|
/// expensive. It is more efficient to work with [Reference] and access only the needed data.
|
||||||
|
/// Blob values are represented as base64 encoded string.
|
||||||
|
String get json {
|
||||||
|
if(_valueType == ValueType.Bool) {
|
||||||
|
return boolValue ? 'true' : 'false';
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.Null) {
|
||||||
|
return 'null';
|
||||||
|
}
|
||||||
|
if(ValueTypeUtils.isNumber(_valueType)) {
|
||||||
|
return jsonEncode(numValue);
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.String) {
|
||||||
|
return jsonEncode(stringValue);
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.Blob) {
|
||||||
|
return jsonEncode(base64Encode(blobValue));
|
||||||
|
}
|
||||||
|
if (ValueTypeUtils.isAVector(_valueType)) {
|
||||||
|
final result = StringBuffer();
|
||||||
|
result.write('[');
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result.write(this[i].json);
|
||||||
|
if (i < length - 1) {
|
||||||
|
result.write(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.write(']');
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
if (_valueType == ValueType.Map) {
|
||||||
|
final result = StringBuffer();
|
||||||
|
result.write('{');
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result.write(jsonEncode(_keyForIndex(i)));
|
||||||
|
result.write(':');
|
||||||
|
result.write(_valueForIndex(i).json);
|
||||||
|
if (i < length - 1) {
|
||||||
|
result.write(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.write('}');
|
||||||
|
return result.toString();
|
||||||
|
}
|
||||||
|
throw UnsupportedError('Type: $_valueType is not supported for JSON conversion');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the indirect offset of the value.
|
||||||
|
///
|
||||||
|
/// To optimize for the more common case of being called only once, this
|
||||||
|
/// value is not cached. Callers that need to use it more than once should
|
||||||
|
/// cache the return value in a local variable.
|
||||||
|
int get _indirect {
|
||||||
|
final step = _readInt(_offset, _parentWidth);
|
||||||
|
return _offset - step;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _readInt(int offset, BitWidth width) {
|
||||||
|
_validateOffset(offset, width);
|
||||||
|
if (width == BitWidth.width8) {
|
||||||
|
return _buffer.getInt8(offset);
|
||||||
|
}
|
||||||
|
if (width == BitWidth.width16) {
|
||||||
|
return _buffer.getInt16(offset, Endian.little);
|
||||||
|
}
|
||||||
|
if (width == BitWidth.width32) {
|
||||||
|
return _buffer.getInt32(offset, Endian.little);
|
||||||
|
}
|
||||||
|
return _buffer.getInt64(offset, Endian.little);
|
||||||
|
}
|
||||||
|
|
||||||
|
int _readUInt(int offset, BitWidth width) {
|
||||||
|
_validateOffset(offset, width);
|
||||||
|
if (width == BitWidth.width8) {
|
||||||
|
return _buffer.getUint8(offset);
|
||||||
|
}
|
||||||
|
if (width == BitWidth.width16) {
|
||||||
|
return _buffer.getUint16(offset, Endian.little);
|
||||||
|
}
|
||||||
|
if (width == BitWidth.width32) {
|
||||||
|
return _buffer.getUint32(offset, Endian.little);
|
||||||
|
}
|
||||||
|
return _buffer.getUint64(offset, Endian.little);
|
||||||
|
}
|
||||||
|
|
||||||
|
double _readFloat(int offset, BitWidth width) {
|
||||||
|
_validateOffset(offset, width);
|
||||||
|
if (width.index < BitWidth.width32.index) {
|
||||||
|
throw StateError('Bad width: $width');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width == BitWidth.width32) {
|
||||||
|
return _buffer.getFloat32(offset, Endian.little);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _buffer.getFloat64(offset, Endian.little);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _validateOffset(int offset, BitWidth width) {
|
||||||
|
if (_offset < 0 || _buffer.lengthInBytes <= offset + width.index || offset & (BitWidthUtil.toByteWidth(width) - 1) != 0) {
|
||||||
|
throw StateError('Bad offset: $offset, width: $width');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int _keyIndex(String key) {
|
||||||
|
final input = utf8.encode(key);
|
||||||
|
final keysVectorOffset = _indirect - _byteWidth * 3;
|
||||||
|
final indirectOffset = keysVectorOffset - _readInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
final byteWidth = _readInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
var low = 0;
|
||||||
|
var high = length - 1;
|
||||||
|
while (low <= high) {
|
||||||
|
final mid = (high + low) >> 1;
|
||||||
|
final dif = _diffKeys(input, mid, indirectOffset, byteWidth);
|
||||||
|
if (dif == 0) return mid;
|
||||||
|
if (dif < 0) {
|
||||||
|
high = mid - 1;
|
||||||
|
} else {
|
||||||
|
low = mid + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
int _diffKeys(List<int> input, int index, int indirect_offset, int byteWidth) {
|
||||||
|
final keyOffset = indirect_offset + index * byteWidth;
|
||||||
|
final keyIndirectOffset = keyOffset - _readInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
|
||||||
|
for (var i = 0; i < input.length; i++) {
|
||||||
|
final dif = input[i] - _buffer.getUint8(keyIndirectOffset + i);
|
||||||
|
if (dif != 0) {
|
||||||
|
return dif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (_buffer.getUint8(keyIndirectOffset + input.length) == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference _valueForIndexWithKey(int index, String key) {
|
||||||
|
final indirect = _indirect;
|
||||||
|
final elementOffset = indirect + index * _byteWidth;
|
||||||
|
final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
|
||||||
|
return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/$key");
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference _valueForIndex(int index) {
|
||||||
|
final indirect = _indirect;
|
||||||
|
final elementOffset = indirect + index * _byteWidth;
|
||||||
|
final packedType = _buffer.getUint8(indirect + length * _byteWidth + index);
|
||||||
|
return Reference._(_buffer, elementOffset, BitWidthUtil.fromByteWidth(_byteWidth), packedType, "$_path/[$index]");
|
||||||
|
}
|
||||||
|
|
||||||
|
String _keyForIndex(int index) {
|
||||||
|
final keysVectorOffset = _indirect - _byteWidth * 3;
|
||||||
|
final indirectOffset = keysVectorOffset - _readInt(keysVectorOffset, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
final byteWidth = _readInt(keysVectorOffset + _byteWidth, BitWidthUtil.fromByteWidth(_byteWidth));
|
||||||
|
final keyOffset = indirectOffset + index * byteWidth;
|
||||||
|
final keyIndirectOffset = keyOffset - _readInt(keyOffset, BitWidthUtil.fromByteWidth(byteWidth));
|
||||||
|
var length = 0;
|
||||||
|
while (_buffer.getUint8(keyIndirectOffset + length) != 0) {
|
||||||
|
length += 1;
|
||||||
|
}
|
||||||
|
return utf8.decode(_buffer.buffer.asUint8List(keyIndirectOffset, length));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class _VectorIterator with IterableMixin<Reference> implements Iterator<Reference> {
|
||||||
|
final Reference _vector;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
_VectorIterator(this._vector) {
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Reference get current => _vector[index];
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool moveNext() {
|
||||||
|
index++;
|
||||||
|
return index < _vector.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterator<Reference> get iterator => this;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapKeyIterator with IterableMixin<String> implements Iterator<String> {
|
||||||
|
final Reference _map;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
_MapKeyIterator(this._map) {
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get current => _map._keyForIndex(index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool moveNext() {
|
||||||
|
index++;
|
||||||
|
return index < _map.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterator<String> get iterator => this;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MapValueIterator with IterableMixin<Reference> implements Iterator<Reference> {
|
||||||
|
final Reference _map;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
_MapValueIterator(this._map) {
|
||||||
|
index = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Reference get current => _map._valueForIndex(index);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool moveNext() {
|
||||||
|
index++;
|
||||||
|
return index < _map.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterator<Reference> get iterator => this;
|
||||||
|
}
|
||||||
156
dart/lib/src/types.dart
Normal file
156
dart/lib/src/types.dart
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
/// Represents the number of bits a value occupies.
|
||||||
|
enum BitWidth {
|
||||||
|
width8,
|
||||||
|
width16,
|
||||||
|
width32,
|
||||||
|
width64
|
||||||
|
}
|
||||||
|
|
||||||
|
class BitWidthUtil {
|
||||||
|
static int toByteWidth(BitWidth self) {
|
||||||
|
return 1 << self.index;
|
||||||
|
}
|
||||||
|
static BitWidth width(num value) {
|
||||||
|
if (value.toInt() == value) {
|
||||||
|
var v = value.toInt().abs();
|
||||||
|
if (v >> 7 == 0) return BitWidth.width8;
|
||||||
|
if (v >> 15 == 0) return BitWidth.width16;
|
||||||
|
if (v >> 31 == 0) return BitWidth.width32;
|
||||||
|
return BitWidth.width64;
|
||||||
|
}
|
||||||
|
return value == _toF32(value) ? BitWidth.width32 : BitWidth.width64;
|
||||||
|
}
|
||||||
|
static BitWidth fromByteWidth(int value) {
|
||||||
|
if (value == 1) {
|
||||||
|
return BitWidth.width8;
|
||||||
|
}
|
||||||
|
if (value == 2) {
|
||||||
|
return BitWidth.width16;
|
||||||
|
}
|
||||||
|
if (value == 4) {
|
||||||
|
return BitWidth.width32;
|
||||||
|
}
|
||||||
|
if (value == 8) {
|
||||||
|
return BitWidth.width64;
|
||||||
|
}
|
||||||
|
throw Exception('Unexpected value ${value}');
|
||||||
|
}
|
||||||
|
static int paddingSize(int bufSize, int scalarSize) {
|
||||||
|
return (~bufSize + 1) & (scalarSize - 1);
|
||||||
|
}
|
||||||
|
static double _toF32(double value) {
|
||||||
|
var bdata = ByteData(4);
|
||||||
|
bdata.setFloat32(0, value);
|
||||||
|
return bdata.getFloat32(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static BitWidth max(BitWidth self, BitWidth other) {
|
||||||
|
if (self.index < other.index) {
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents all internal FlexBuffer types.
|
||||||
|
enum ValueType {
|
||||||
|
Null, Int, UInt, Float,
|
||||||
|
Key, String, IndirectInt, IndirectUInt, IndirectFloat,
|
||||||
|
Map, Vector, VectorInt, VectorUInt, VectorFloat, VectorKey,
|
||||||
|
@Deprecated('VectorString is deprecated due to a flaw in the binary format (https://github.com/google/flatbuffers/issues/5627)')
|
||||||
|
VectorString,
|
||||||
|
VectorInt2, VectorUInt2, VectorFloat2,
|
||||||
|
VectorInt3, VectorUInt3, VectorFloat3,
|
||||||
|
VectorInt4, VectorUInt4, VectorFloat4,
|
||||||
|
Blob, Bool, VectorBool
|
||||||
|
}
|
||||||
|
|
||||||
|
class ValueTypeUtils {
|
||||||
|
static int toInt(ValueType self) {
|
||||||
|
if (self == ValueType.VectorBool) return 36;
|
||||||
|
return self.index;
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValueType fromInt(int value) {
|
||||||
|
if (value == 36) return ValueType.VectorBool;
|
||||||
|
return ValueType.values[value];
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isInline(ValueType self) {
|
||||||
|
return self == ValueType.Bool
|
||||||
|
|| toInt(self) <= toInt(ValueType.Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isNumber(ValueType self) {
|
||||||
|
return toInt(self) >= toInt(ValueType.Int)
|
||||||
|
&& toInt(self) <= toInt(ValueType.Float);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isIndirectNumber(ValueType self) {
|
||||||
|
return toInt(self) >= toInt(ValueType.IndirectInt)
|
||||||
|
&& toInt(self) <= toInt(ValueType.IndirectFloat);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isTypedVectorElement(ValueType self) {
|
||||||
|
return self == ValueType.Bool ||
|
||||||
|
(
|
||||||
|
toInt(self) >= toInt(ValueType.Int)
|
||||||
|
&& toInt(self) <= toInt(ValueType.String)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isTypedVector(ValueType self) {
|
||||||
|
return self == ValueType.VectorBool ||
|
||||||
|
(
|
||||||
|
toInt(self) >= toInt(ValueType.VectorInt)
|
||||||
|
&& toInt(self) <= toInt(ValueType.VectorString)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isFixedTypedVector(ValueType self) {
|
||||||
|
return (
|
||||||
|
toInt(self) >= toInt(ValueType.VectorInt2)
|
||||||
|
&& toInt(self) <= toInt(ValueType.VectorFloat4)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAVector(ValueType self) {
|
||||||
|
return (
|
||||||
|
isTypedVector(self) || isFixedTypedVector(self) || self == ValueType.Vector
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValueType toTypedVector(ValueType self, int length) {
|
||||||
|
if (length == 0) {
|
||||||
|
return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt));
|
||||||
|
}
|
||||||
|
if (length == 2) {
|
||||||
|
return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt2));
|
||||||
|
}
|
||||||
|
if (length == 3) {
|
||||||
|
return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt3));
|
||||||
|
}
|
||||||
|
if (length == 4) {
|
||||||
|
return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.Int) + toInt(ValueType.VectorInt4));
|
||||||
|
}
|
||||||
|
throw Exception('unexpected length ' + length.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValueType typedVectorElementType(ValueType self) {
|
||||||
|
return ValueTypeUtils.fromInt(toInt(self) - toInt(ValueType.VectorInt) + toInt(ValueType.Int));
|
||||||
|
}
|
||||||
|
|
||||||
|
static ValueType fixedTypedVectorElementType(ValueType self) {
|
||||||
|
return ValueTypeUtils.fromInt((toInt(self) - toInt(ValueType.VectorInt2)) % 3 + toInt(ValueType.Int));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int fixedTypedVectorElementSize(ValueType self) {
|
||||||
|
return (toInt(self) - toInt(ValueType.VectorInt2)) ~/ 3 + 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int packedType(ValueType self, BitWidth bitWidth) {
|
||||||
|
return bitWidth.index | (toInt(self) << 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
315
dart/test/flex_builder_test.dart
Normal file
315
dart/test/flex_builder_test.dart
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flat_buffers/flex_buffers.dart' show Builder;
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('build with single value', () {
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addNull();
|
||||||
|
expect(flx.finish(), [0, 0, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addBool(true);
|
||||||
|
expect(flx.finish(), [1, 104, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addBool(false);
|
||||||
|
expect(flx.finish(), [0, 104, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addInt(1);
|
||||||
|
expect(flx.finish(), [1, 4, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addInt(230);
|
||||||
|
expect(flx.finish(), [230, 0, 5, 2]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addInt(1025);
|
||||||
|
expect(flx.finish(), [1, 4, 5, 2]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addInt(-1025);
|
||||||
|
expect(flx.finish(), [255, 251, 5, 2]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addDouble(0.1);
|
||||||
|
expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 15, 8]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addDouble(0.5);
|
||||||
|
expect(flx.finish(), [0, 0, 0, 63, 14, 4]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addString('Maxim');
|
||||||
|
expect(flx.finish(), [5, 77, 97, 120, 105, 109, 0, 6, 20, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder();
|
||||||
|
flx.addString('hello 😱');
|
||||||
|
expect(flx.finish(), [10, 104, 101, 108, 108, 111, 32, 240, 159, 152, 177, 0, 11, 20, 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('build vector', (){
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addInt(1)
|
||||||
|
..addInt(2)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [1, 2, 2, 64, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addInt(-1)
|
||||||
|
..addInt(256)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [255, 255, 0, 1, 4, 65, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addInt(-45)
|
||||||
|
..addInt(256000)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addDouble(1.1)
|
||||||
|
..addDouble(-256)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [154, 153, 153, 153, 153, 153, 241, 63, 0, 0, 0, 0, 0, 0, 112, 192, 16, 75, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addInt(1)
|
||||||
|
..addInt(2)
|
||||||
|
..addInt(4)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [1, 2, 4, 3, 76, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addInt(-1)
|
||||||
|
..addInt(256)
|
||||||
|
..addInt(4)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [255, 255, 0, 1, 4, 0, 6, 77, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..startVector()
|
||||||
|
..addInt(61)
|
||||||
|
..end()
|
||||||
|
..addInt(64)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [1, 61, 2, 2, 64, 44, 4, 4, 40, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addString('foo')
|
||||||
|
..addString('bar')
|
||||||
|
..addString('baz')
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [3, 102, 111, 111, 0, 3, 98, 97, 114, 0, 3, 98, 97, 122, 0, 3, 15, 11, 7, 3, 60, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addString('foo')
|
||||||
|
..addString('bar')
|
||||||
|
..addString('baz')
|
||||||
|
..addString('foo')
|
||||||
|
..addString('bar')
|
||||||
|
..addString('baz')
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [3, 102, 111, 111, 0, 3, 98, 97, 114, 0, 3, 98, 97, 122, 0, 6, 15, 11, 7, 18, 14, 10, 6, 60, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addBool(true)
|
||||||
|
..addBool(false)
|
||||||
|
..addBool(true)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [3, 1, 0, 1, 3, 144, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addString('foo')
|
||||||
|
..addInt(1)
|
||||||
|
..addInt(-5)
|
||||||
|
..addDouble(1.3)
|
||||||
|
..addBool(true)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [
|
||||||
|
3, 102, 111, 111, 0, 0, 0, 0,
|
||||||
|
5, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
15, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
251, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
205, 204, 204, 204, 204, 204, 244, 63,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
20, 4, 4, 15, 104, 45, 43, 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('build map', ()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startMap()
|
||||||
|
..addKey('a')
|
||||||
|
..addInt(12)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startMap()
|
||||||
|
..addKey('a')
|
||||||
|
..addInt(12)
|
||||||
|
..addKey('')
|
||||||
|
..addInt(45)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [97, 0, 0, 2, 2, 5, 2, 1, 2, 45, 12, 4, 4, 4, 36, 1]);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..startMap()
|
||||||
|
..addKey('something')
|
||||||
|
..addInt(12)
|
||||||
|
..end()
|
||||||
|
..startMap()
|
||||||
|
..addKey('something')
|
||||||
|
..addInt(45)
|
||||||
|
..end()
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [115, 111, 109, 101, 116, 104, 105, 110, 103, 0,
|
||||||
|
1, 11, 1, 1, 1, 12, 4, 6, 1, 1, 45, 4, 2, 8, 4, 36, 36, 4, 40, 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('build blob', ()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
var flx = Builder()
|
||||||
|
..addBlob(Uint8List.fromList([1, 2, 3]).buffer)
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [3, 1, 2, 3, 3, 100, 1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('build from object', (){
|
||||||
|
expect(Builder.buildFromObject(Uint8List.fromList([1, 2, 3]).buffer).asUint8List(), [3, 1, 2, 3, 3, 100, 1]);
|
||||||
|
expect(Builder.buildFromObject(null).asUint8List(), [0, 0, 1]);
|
||||||
|
expect(Builder.buildFromObject(true).asUint8List(), [1, 104, 1]);
|
||||||
|
expect(Builder.buildFromObject(false).asUint8List(), [0, 104, 1]);
|
||||||
|
expect(Builder.buildFromObject(25).asUint8List(), [25, 4, 1]);
|
||||||
|
expect(Builder.buildFromObject(-250).asUint8List(), [6, 255, 5, 2]);
|
||||||
|
expect(Builder.buildFromObject(-2.50).asUint8List(), [0, 0, 32, 192, 14, 4]);
|
||||||
|
expect(Builder.buildFromObject('Maxim').asUint8List(), [5, 77, 97, 120, 105, 109, 0, 6, 20, 1]);
|
||||||
|
expect(Builder.buildFromObject([1, 3.3, 'max', true, null, false]).asUint8List(), [
|
||||||
|
3, 109, 97, 120, 0, 0, 0, 0,
|
||||||
|
6, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
102, 102, 102, 102, 102, 102, 10, 64,
|
||||||
|
31, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
4, 15, 20, 104, 0, 104, 54, 43, 1
|
||||||
|
]);
|
||||||
|
expect(Builder.buildFromObject([{'something':12}, {'something': 45}]).asUint8List(), [
|
||||||
|
115, 111, 109, 101, 116, 104, 105, 110, 103, 0,
|
||||||
|
1, 11, 1, 1, 1, 12, 4, 6, 1, 1, 45, 4, 2, 8, 4, 36, 36, 4, 40, 1
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add double indirectly', (){
|
||||||
|
var flx = Builder()
|
||||||
|
..addDoubleIndirectly(0.1)
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63, 8, 35, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add double indirectly to vector with cache', (){
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addDoubleIndirectly(0.1, cache: true)
|
||||||
|
..addDoubleIndirectly(0.1, cache: true)
|
||||||
|
..addDoubleIndirectly(0.1, cache: true)
|
||||||
|
..addDoubleIndirectly(0.1, cache: true)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [154, 153, 153, 153, 153, 153, 185, 63,
|
||||||
|
4, 9, 10, 11, 12, 35, 35, 35, 35, 8, 40, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add int indirectly', (){
|
||||||
|
var flx = Builder()
|
||||||
|
..addIntIndirectly(2345234523452345)
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [185, 115, 175, 118, 250, 84, 8, 0, 8, 27, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('add int indirectly to vector with cache', (){
|
||||||
|
var flx = Builder()
|
||||||
|
..startVector()
|
||||||
|
..addIntIndirectly(2345234523452345, cache: true)
|
||||||
|
..addIntIndirectly(2345234523452345, cache: true)
|
||||||
|
..addIntIndirectly(2345234523452345, cache: true)
|
||||||
|
..addIntIndirectly(2345234523452345, cache: true)
|
||||||
|
..end()
|
||||||
|
;
|
||||||
|
expect(flx.finish(), [185, 115, 175, 118, 250, 84, 8, 0,
|
||||||
|
4, 9, 10, 11, 12, 27, 27, 27, 27, 8, 40, 1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('snapshot', (){
|
||||||
|
var flx = Builder();
|
||||||
|
flx.startVector();
|
||||||
|
flx.addInt(12);
|
||||||
|
expect(flx.snapshot().asUint8List(), [1, 12, 1, 44, 1]);
|
||||||
|
flx.addInt(24);
|
||||||
|
expect(flx.snapshot().asUint8List(), [12, 24, 2, 64, 1]);
|
||||||
|
flx.addInt(45);
|
||||||
|
expect(flx.snapshot().asUint8List(), [12, 24, 45, 3, 76, 1]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
316
dart/test/flex_reader_test.dart
Normal file
316
dart/test/flex_reader_test.dart
Normal file
@@ -0,0 +1,316 @@
|
|||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flat_buffers/flex_buffers.dart' show Reference;
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('is null', () {
|
||||||
|
expect(Reference.fromBuffer(b([0, 0, 1])).isNull, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('bool value', () {
|
||||||
|
expect(Reference.fromBuffer(b([1, 104, 1])).boolValue, isTrue);
|
||||||
|
expect(Reference.fromBuffer(b([0, 104, 1])).boolValue, isFalse);
|
||||||
|
});
|
||||||
|
test('int value', () {
|
||||||
|
expect(Reference.fromBuffer(b([25, 4, 1])).intValue, 25);
|
||||||
|
expect(Reference.fromBuffer(b([231, 4, 1])).intValue, -25);
|
||||||
|
expect(Reference.fromBuffer(b([230, 8, 1])).intValue, 230);
|
||||||
|
expect(Reference.fromBuffer(b([230, 0, 5, 2])).intValue, 230);
|
||||||
|
expect(Reference.fromBuffer(b([1, 4, 5, 2])).intValue, 1025);
|
||||||
|
expect(Reference.fromBuffer(b([255, 251, 5, 2])).intValue, -1025);
|
||||||
|
expect(Reference.fromBuffer(b([1, 4, 9, 2])).intValue, 1025);
|
||||||
|
expect(Reference.fromBuffer(b([255, 255, 255, 127, 6, 4])).intValue,
|
||||||
|
2147483647);
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([0, 0, 0, 128, 6, 4])).intValue, -2147483648);
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([255, 255, 255, 255, 0, 0, 0, 0, 7, 8]))
|
||||||
|
.intValue,
|
||||||
|
4294967295);
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([255, 255, 255, 255, 255, 255, 255, 127, 7, 8]))
|
||||||
|
.intValue,
|
||||||
|
9223372036854775807);
|
||||||
|
expect(Reference.fromBuffer(b([0, 0, 0, 0, 0, 0, 0, 128, 7, 8])).intValue,
|
||||||
|
-9223372036854775808);
|
||||||
|
// Dart does not really support UInt64
|
||||||
|
// expect(FlxValue.fromBuffer(b([255, 255, 255, 255, 255, 255, 255, 255, 11, 8])).intValue, 18446744073709551615);
|
||||||
|
});
|
||||||
|
test('double value', () {
|
||||||
|
expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).doubleValue, 4.5);
|
||||||
|
expect(Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).doubleValue,
|
||||||
|
closeTo(.1, .001));
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8]))
|
||||||
|
.doubleValue,
|
||||||
|
.1);
|
||||||
|
});
|
||||||
|
test('num value', () {
|
||||||
|
expect(Reference.fromBuffer(b([0, 0, 144, 64, 14, 4])).numValue, 4.5);
|
||||||
|
expect(Reference.fromBuffer(b([205, 204, 204, 61, 14, 4])).numValue,
|
||||||
|
closeTo(.1, .001));
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([154, 153, 153, 153, 153, 153, 185, 63, 15, 8]))
|
||||||
|
.numValue,
|
||||||
|
.1);
|
||||||
|
expect(Reference.fromBuffer(b([255, 251, 5, 2])).numValue, -1025);
|
||||||
|
});
|
||||||
|
test('string value', () {
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([5, 77, 97, 120, 105, 109, 0, 6, 20, 1]))
|
||||||
|
.stringValue,
|
||||||
|
'Maxim');
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([
|
||||||
|
10, 104, 101, 108, 108, 111, 32, 240, 159, 152, 177, 0, 11, 20, 1
|
||||||
|
])).stringValue,
|
||||||
|
'hello 😱');
|
||||||
|
});
|
||||||
|
test('blob value', () {
|
||||||
|
expect(
|
||||||
|
Reference.fromBuffer(b([3, 1, 2, 3, 3, 100, 1])).blobValue, [1, 2, 3]);
|
||||||
|
});
|
||||||
|
test('bool vector', () {
|
||||||
|
var flx = Reference.fromBuffer(b([3, 1, 0, 1, 3, 144, 1]));
|
||||||
|
expect(flx[0].boolValue, true);
|
||||||
|
expect(flx[1].boolValue, false);
|
||||||
|
expect(flx[2].boolValue, true);
|
||||||
|
});
|
||||||
|
test('number vector', () {
|
||||||
|
testNumbers([3, 1, 2, 3, 3, 44, 1], [1, 2, 3]);
|
||||||
|
testNumbers([3, 255, 2, 3, 3, 44, 1], [-1, 2, 3]);
|
||||||
|
testNumbers([3, 0, 1, 0, 43, 2, 3, 0, 6, 45, 1], [1, 555, 3]);
|
||||||
|
testNumbers(
|
||||||
|
[3, 0, 0, 0, 1, 0, 0, 0, 204, 216, 0, 0, 3, 0, 0, 0, 12, 46, 1],
|
||||||
|
[1, 55500, 3]);
|
||||||
|
testNumbers([
|
||||||
|
3, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
172, 128, 94, 239, 12, 0, 0, 0,
|
||||||
|
3, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
24, 47, 1
|
||||||
|
], [1, 55555555500, 3
|
||||||
|
]);
|
||||||
|
testNumbers(
|
||||||
|
[3, 0, 0, 0, 0, 0, 192, 63, 0, 0, 32, 64, 0, 0, 96, 64, 12, 54, 1],
|
||||||
|
[1.5, 2.5, 3.5]);
|
||||||
|
testNumbers([
|
||||||
|
3, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
154, 153, 153, 153, 153, 153, 241, 63,
|
||||||
|
154, 153, 153, 153, 153, 153, 1, 64,
|
||||||
|
102, 102, 102, 102, 102, 102, 10, 64,
|
||||||
|
24, 55, 1
|
||||||
|
], [1.1, 2.2, 3.3
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('number vector, fixed type', () {
|
||||||
|
testNumbers([1, 2, 2, 64, 1], [1, 2]);
|
||||||
|
testNumbers([255, 255, 0, 1, 4, 65, 1], [-1, 256]);
|
||||||
|
testNumbers([211, 255, 255, 255, 0, 232, 3, 0, 8, 66, 1], [-45, 256000]);
|
||||||
|
testNumbers([
|
||||||
|
211, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 127,
|
||||||
|
16, 67, 1
|
||||||
|
], [
|
||||||
|
-45, 9223372036854775807
|
||||||
|
]);
|
||||||
|
|
||||||
|
testNumbers([1, 2, 2, 68, 1], [1, 2]);
|
||||||
|
testNumbers([1, 0, 0, 1, 4, 69, 1], [1, 256]);
|
||||||
|
testNumbers([45, 0, 0, 0, 0, 232, 3, 0, 8, 70, 1], [45, 256000]);
|
||||||
|
|
||||||
|
testNumbers([205, 204, 140, 63, 0, 0, 0, 192, 8, 74, 1], [1.1, -2]);
|
||||||
|
testNumbers([
|
||||||
|
154, 153, 153, 153, 153, 153, 241, 63,
|
||||||
|
0, 0, 0, 0, 0, 0, 112, 192,
|
||||||
|
16, 75, 1
|
||||||
|
], [
|
||||||
|
1.1, -256
|
||||||
|
]);
|
||||||
|
|
||||||
|
testNumbers([211, 255, 255, 255, 0, 232, 3, 0, 4, 0, 0, 0, 12, 78, 1],
|
||||||
|
[-45, 256000, 4]);
|
||||||
|
|
||||||
|
testNumbers([
|
||||||
|
211, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 127,
|
||||||
|
4, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
9, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
32, 91, 1
|
||||||
|
], [
|
||||||
|
-45, 9223372036854775807, 4, 9
|
||||||
|
]);
|
||||||
|
|
||||||
|
testNumbers([
|
||||||
|
45, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 127,
|
||||||
|
4, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
9, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
32, 95, 1
|
||||||
|
], [
|
||||||
|
45, 9223372036854775807, 4, 9
|
||||||
|
]);
|
||||||
|
|
||||||
|
testNumbers([
|
||||||
|
154, 153, 153, 153, 153, 153, 241, 63,
|
||||||
|
0, 0, 0, 0, 0, 0, 112, 64,
|
||||||
|
0, 0, 0, 0, 0, 0, 16, 64,
|
||||||
|
24, 87, 1
|
||||||
|
], [
|
||||||
|
1.1, 256, 4
|
||||||
|
]);
|
||||||
|
|
||||||
|
testNumbers([
|
||||||
|
154, 153, 153, 153, 153, 153, 241, 63,
|
||||||
|
0, 0, 0, 0, 0, 0, 112, 64,
|
||||||
|
0, 0, 0, 0, 0, 0, 16, 64,
|
||||||
|
0, 0, 0, 0, 0, 0, 34, 64,
|
||||||
|
32, 99, 1
|
||||||
|
], [
|
||||||
|
1.1, 256, 4, 9
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('string vector', () {
|
||||||
|
testStrings([
|
||||||
|
3, 102, 111, 111, 0,
|
||||||
|
3, 98, 97, 114, 0,
|
||||||
|
3, 98, 97, 122, 0,
|
||||||
|
3, 15, 11, 7,
|
||||||
|
3, 60, 1
|
||||||
|
], [
|
||||||
|
'foo', 'bar', 'baz'
|
||||||
|
]);
|
||||||
|
testStrings([
|
||||||
|
3, 102, 111, 111, 0,
|
||||||
|
3, 98, 97, 114, 0,
|
||||||
|
3, 98, 97, 122, 0,
|
||||||
|
6, 15, 11, 7, 18, 14, 10,
|
||||||
|
6, 60, 1
|
||||||
|
], [
|
||||||
|
'foo', 'bar', 'baz', 'foo', 'bar', 'baz'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test('mixed vector', () {
|
||||||
|
var flx = Reference.fromBuffer(b([
|
||||||
|
3, 102, 111, 111, 0, 0, 0, 0,
|
||||||
|
5, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
15, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
251, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
205, 204, 204, 204, 204, 204, 244, 63,
|
||||||
|
1, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
20, 4, 4, 15, 104, 45, 43, 1
|
||||||
|
]));
|
||||||
|
expect(flx.length, 5);
|
||||||
|
expect(flx[0].stringValue, 'foo');
|
||||||
|
expect(flx[1].numValue, 1);
|
||||||
|
expect(flx[2].numValue, -5);
|
||||||
|
expect(flx[3].numValue, 1.3);
|
||||||
|
expect(flx[4].boolValue, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('single value map', () {
|
||||||
|
var flx = Reference.fromBuffer(b([97, 0, 1, 3, 1, 1, 1, 12, 4, 2, 36, 1]));
|
||||||
|
expect(flx.length, 1);
|
||||||
|
expect(flx['a'].numValue, 12);
|
||||||
|
});
|
||||||
|
test('two value map', () {
|
||||||
|
var flx = Reference.fromBuffer(b([0, 97, 0, 2, 4, 4, 2, 1, 2, 45, 12, 4, 4, 4, 36, 1]));
|
||||||
|
expect(flx.length, 2);
|
||||||
|
expect(flx['a'].numValue, 12);
|
||||||
|
expect(flx[''].numValue, 45);
|
||||||
|
});
|
||||||
|
test('complex map', () {
|
||||||
|
var flx = complexMap();
|
||||||
|
expect(flx.length, 5);
|
||||||
|
expect(flx['age'].numValue, 35);
|
||||||
|
expect(flx['weight'].numValue, 72.5);
|
||||||
|
expect(flx['name'].stringValue, 'Maxim');
|
||||||
|
|
||||||
|
expect(flx['flags'].length, 4);
|
||||||
|
expect(flx['flags'][0].boolValue, true);
|
||||||
|
expect(flx['flags'][1].boolValue, false);
|
||||||
|
expect(flx['flags'][2].boolValue, true);
|
||||||
|
expect(flx['flags'][3].boolValue, true);
|
||||||
|
|
||||||
|
expect(flx['address'].length, 3);
|
||||||
|
expect(flx['address']['city'].stringValue, 'Bla');
|
||||||
|
expect(flx['address']['zip'].stringValue, '12345');
|
||||||
|
expect(flx['address']['countryCode'].stringValue, 'XX');
|
||||||
|
|
||||||
|
expect(() => flx['address']['country'].stringValue,
|
||||||
|
throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [country] is not applicable on: //address of: ValueType.Map')));
|
||||||
|
expect(() => flx['address']['countryCode'][0],
|
||||||
|
throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [0] is not applicable on: //address/countryCode of: ValueType.String')));
|
||||||
|
expect(() => flx[1],
|
||||||
|
throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [1] is not applicable on: / of: ValueType.Map')));
|
||||||
|
expect(() => flx['flags'][4],
|
||||||
|
throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [4] is not applicable on: //flags of: ValueType.VectorBool length: 4')));
|
||||||
|
expect(() => flx['flags'][-1],
|
||||||
|
throwsA(predicate((e) => e is ArgumentError && e.message == 'Key: [-1] is not applicable on: //flags of: ValueType.VectorBool length: 4')));
|
||||||
|
});
|
||||||
|
test('complex map to json', () {
|
||||||
|
var flx = complexMap();
|
||||||
|
expect(flx.json, '{"address":{"city":"Bla","countryCode":"XX","zip":"12345"},"age":35,"flags":[true,false,true,true],"name":"Maxim","weight":72.5}');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('complex map iterators', () {
|
||||||
|
var flx = complexMap();
|
||||||
|
expect(flx.mapKeyIterable.map((e) => e).toList(), ['address', 'age', 'flags', 'name', 'weight']);
|
||||||
|
expect(flx.mapValueIterable.map((e) => e.json).toList(), [flx['address'].json, flx['age'].json, flx['flags'].json, flx['name'].json, flx['weight'].json]);
|
||||||
|
expect(flx['flags'].vectorIterable.map((e) => e.boolValue).toList(), [true, false, true, true]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer b(List<int> values) {
|
||||||
|
var data = Uint8List.fromList(values);
|
||||||
|
return data.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void testNumbers(List<int> buffer, List<num> numbers) {
|
||||||
|
var flx = Reference.fromBuffer(b(buffer));
|
||||||
|
expect(flx.length, numbers.length);
|
||||||
|
for (var i = 0; i < flx.length; i++) {
|
||||||
|
expect(flx[i].numValue, closeTo(numbers[i], 0.001));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void testStrings(List<int> buffer, List<String> numbers) {
|
||||||
|
var flx = Reference.fromBuffer(b(buffer));
|
||||||
|
expect(flx.length, numbers.length);
|
||||||
|
for (var i = 0; i < flx.length; i++) {
|
||||||
|
expect(flx[i].stringValue, numbers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference complexMap(){
|
||||||
|
// {
|
||||||
|
// "age": 35,
|
||||||
|
// "flags": [True, False, True, True],
|
||||||
|
// "weight": 72.5,
|
||||||
|
// "name": "Maxim",
|
||||||
|
// "address": {
|
||||||
|
// "city": "Bla",
|
||||||
|
// "zip": "12345",
|
||||||
|
// "countryCode": "XX",
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
return Reference.fromBuffer(b([
|
||||||
|
97, 100, 100, 114, 101, 115, 115, 0,
|
||||||
|
99, 105, 116, 121, 0, 3, 66, 108, 97, 0,
|
||||||
|
99, 111, 117, 110, 116, 114, 121, 67, 111, 100, 101, 0,
|
||||||
|
2, 88, 88, 0,
|
||||||
|
122, 105, 112, 0,
|
||||||
|
5, 49, 50, 51, 52, 53, 0,
|
||||||
|
3, 38, 29, 14, 3, 1, 3, 38, 22, 15, 20, 20, 20,
|
||||||
|
97, 103, 101, 0,
|
||||||
|
102, 108, 97, 103, 115, 0,
|
||||||
|
4, 1, 0, 1, 1,
|
||||||
|
110, 97, 109, 101, 0,
|
||||||
|
5, 77, 97, 120, 105, 109, 0,
|
||||||
|
119, 101, 105, 103, 104, 116, 0,
|
||||||
|
5, 93, 36, 33, 23, 12, 0, 0, 7, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 60, 0, 0, 0, 35, 0, 0, 0, 51, 0, 0, 0, 45,
|
||||||
|
0, 0, 0, 0, 0, 145, 66, 36, 4, 144, 20, 14, 25, 38, 1
|
||||||
|
]));
|
||||||
|
}
|
||||||
137
dart/test/flex_types_test.dart
Normal file
137
dart/test/flex_types_test.dart
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import 'package:flat_buffers/src/types.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('is inline', () {
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.Bool), isTrue);
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.Int), isTrue);
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.UInt), isTrue);
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.Float), isTrue);
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.Null), isTrue);
|
||||||
|
expect(ValueTypeUtils.isInline(ValueType.String), isFalse);
|
||||||
|
});
|
||||||
|
test('is type vector element', () {
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Bool), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Int), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.UInt), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Float), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Key), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.String), isTrue);
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Null), isFalse);
|
||||||
|
expect(ValueTypeUtils.isTypedVectorElement(ValueType.Blob), isFalse);
|
||||||
|
});
|
||||||
|
test('is typed vector', () {
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorUInt), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorFloat), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorBool), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorKey), isTrue);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorString), isTrue);
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.Vector), isFalse);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.Map), isFalse);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.Bool), isFalse);
|
||||||
|
expect(ValueTypeUtils.isTypedVector(ValueType.VectorInt2), isFalse);
|
||||||
|
});
|
||||||
|
test('is fixed typed vector', () {
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt2), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt3), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt4), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt2), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt3), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorUInt4), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat2), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat3), isTrue);
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorFloat4), isTrue);
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.isFixedTypedVector(ValueType.VectorInt), isFalse);
|
||||||
|
});
|
||||||
|
test('to typed vector', () {
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Int,0), equals(ValueType.VectorInt));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.UInt,0), equals(ValueType.VectorUInt));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Bool,0), equals(ValueType.VectorBool));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Float,0), equals(ValueType.VectorFloat));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Key,0), equals(ValueType.VectorKey));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.String,0), equals(ValueType.VectorString));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Int,2), equals(ValueType.VectorInt2));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.UInt,2), equals(ValueType.VectorUInt2));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Float,2), equals(ValueType.VectorFloat2));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Int,3), equals(ValueType.VectorInt3));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.UInt,3), equals(ValueType.VectorUInt3));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Float,3), equals(ValueType.VectorFloat3));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Int,4), equals(ValueType.VectorInt4));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.UInt,4), equals(ValueType.VectorUInt4));
|
||||||
|
expect(ValueTypeUtils.toTypedVector(ValueType.Float,4), equals(ValueType.VectorFloat4));
|
||||||
|
});
|
||||||
|
test('typed vector element type', () {
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorInt), equals(ValueType.Int));
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorUInt), equals(ValueType.UInt));
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorFloat), equals(ValueType.Float));
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorString), equals(ValueType.String));
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorKey), equals(ValueType.Key));
|
||||||
|
expect(ValueTypeUtils.typedVectorElementType(ValueType.VectorBool), equals(ValueType.Bool));
|
||||||
|
});
|
||||||
|
test('fixed typed vector element type', () {
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt2), equals(ValueType.Int));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt3), equals(ValueType.Int));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorInt4), equals(ValueType.Int));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt2), equals(ValueType.UInt));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt3), equals(ValueType.UInt));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorUInt4), equals(ValueType.UInt));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat2), equals(ValueType.Float));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat3), equals(ValueType.Float));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementType(ValueType.VectorFloat4), equals(ValueType.Float));
|
||||||
|
});
|
||||||
|
test('fixed typed vector element size', () {
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt2), equals(2));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt3), equals(3));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorInt4), equals(4));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt2), equals(2));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt3), equals(3));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorUInt4), equals(4));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat2), equals(2));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat3), equals(3));
|
||||||
|
expect(ValueTypeUtils.fixedTypedVectorElementSize(ValueType.VectorFloat4), equals(4));
|
||||||
|
});
|
||||||
|
test('packed type', () {
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width8), equals(0));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width16), equals(1));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width32), equals(2));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Null, BitWidth.width64), equals(3));
|
||||||
|
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width8), equals(4));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width16), equals(5));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width32), equals(6));
|
||||||
|
expect(ValueTypeUtils.packedType(ValueType.Int, BitWidth.width64), equals(7));
|
||||||
|
});
|
||||||
|
test('bit width', () {
|
||||||
|
expect(BitWidthUtil.width(0), BitWidth.width8);
|
||||||
|
expect(BitWidthUtil.width(-20), BitWidth.width8);
|
||||||
|
expect(BitWidthUtil.width(127), BitWidth.width8);
|
||||||
|
expect(BitWidthUtil.width(128), BitWidth.width16);
|
||||||
|
expect(BitWidthUtil.width(128123), BitWidth.width32);
|
||||||
|
expect(BitWidthUtil.width(12812324534), BitWidth.width64);
|
||||||
|
expect(BitWidthUtil.width(-127), BitWidth.width8);
|
||||||
|
expect(BitWidthUtil.width(-128), BitWidth.width16);
|
||||||
|
expect(BitWidthUtil.width(-12812324534), BitWidth.width64);
|
||||||
|
expect(BitWidthUtil.width(-0.1), BitWidth.width64);
|
||||||
|
expect(BitWidthUtil.width(0.25), BitWidth.width32);
|
||||||
|
});
|
||||||
|
test('padding size', () {
|
||||||
|
expect(BitWidthUtil.paddingSize(10, 8), 6);
|
||||||
|
expect(BitWidthUtil.paddingSize(10, 4), 2);
|
||||||
|
expect(BitWidthUtil.paddingSize(15, 4), 1);
|
||||||
|
expect(BitWidthUtil.paddingSize(15, 2), 1);
|
||||||
|
expect(BitWidthUtil.paddingSize(15, 1), 0);
|
||||||
|
expect(BitWidthUtil.paddingSize(16, 8), 0);
|
||||||
|
expect(BitWidthUtil.paddingSize(17, 8), 7);
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user