Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add JsonBufferBuilder. #51

Merged
merged 4 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions pkgs/dart_model/lib/src/json_buffer/closed_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of 'json_buffer_builder.dart';

/// Methods for writing and reading "closed maps".
///
/// A "closed map" is a `Map<String, Object?>` in a byte buffer that has all
/// keys and values known at the time of writing.
extension ClosedMaps on JsonBufferBuilder {
// Layout: [length, ...entries]
//
// Each entry is: [key (pointer to string), value type, value].
//
// Values are stored with `_writeSmallValueOrPointer`.
static const _keySize = _pointerSize;
static const _valueSize = _typeSize + _pointerSize;
static const _entrySize = _keySize + _valueSize;

/// Adds a `Map` to the buffer, returns the [_Pointer] to it.
///
/// The `Map` should be small and already evaluated, making it fast to
/// iterate. For large maps see "growable map" methods.
_Pointer _addClosedMap(Map<String, Object?> map) {
_explanations?.push('addClosedMap $map');

final length = map.length;
final pointer = _reserve(_lengthSize + length * _entrySize);

_writeLength(pointer, length);

var entryPointer = pointer + _lengthSize;
for (final entry in map.entries) {
final key = entry.key;
final value = entry.value;
_writePointer(entryPointer, _pointerToString(key));
_writeAny(entryPointer + _keySize, value);
entryPointer += _entrySize;
}

_explanations?.pop();
return pointer;
}

/// Returns the [_ClosedMap] at [pointer].
Map<String, Object?> _readClosedMap(_Pointer pointer) {
return _ClosedMap(this, pointer);
}
}

class _ClosedMap
with MapMixin<String, Object?>, _EntryMapMixin<String, Object?> {
final JsonBufferBuilder _buffer;
final _Pointer _pointer;
@override
final int length;

_ClosedMap(this._buffer, this._pointer)
: length = _buffer._readLength(_pointer);

@override
Object? operator [](Object? key) {
final iterator = entries.iterator as _ClosedMapEntryIterator;
while (iterator.moveNext()) {
if (iterator.current.key == key) return iterator.current.value;
}
return null;
}

@override
late final Iterable<String> keys = _IteratorFunctionIterable(
() => _ClosedMapKeyIterator(_buffer, _pointer, length),
length: length);

@override
late final Iterable<Object?> values = _IteratorFunctionIterable(
() => _ClosedMapValueIterator(_buffer, _pointer, length),
length: length);

@override
late final Iterable<MapEntry<String, Object?>> entries =
_IteratorFunctionIterable(
() => _ClosedMapEntryIterator(_buffer, _pointer, length),
length: length);

@override
void operator []=(String key, Object? value) {
throw UnsupportedError(
'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}

@override
Object? remove(Object? key) {
throw UnsupportedError(
'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}

@override
void clear() {
throw UnsupportedError(
'This JsonBufferBuilder map is read-only, see "createGrowableMap".');
}
}

/// `Iterator` that reads a "closed map" in a [JsonBufferBuilder].
abstract class _ClosedMapIterator<T> implements Iterator<T> {
final JsonBufferBuilder _buffer;
final _Pointer _last;

_Pointer _pointer;

_ClosedMapIterator(this._buffer, _Pointer pointer, int length)
: _last = pointer + _lengthSize + length * ClosedMaps._entrySize,
// Subtract because `moveNext` is called before reading.
_pointer = pointer + _lengthSize - ClosedMaps._entrySize;

@override
T get current;

String get _currentKey => _buffer._readString(_buffer._readPointer(_pointer));
Object? get _currentValue => _buffer._readAny(_pointer + _pointerSize);

@override
bool moveNext() {
if (_pointer == _last) return false;
if (_pointer > _last) throw StateError('Moved past _last!');
_pointer += ClosedMaps._entrySize;
return _pointer != _last;
}
}

class _ClosedMapKeyIterator extends _ClosedMapIterator<String> {
_ClosedMapKeyIterator(super._buffer, super.pointer, super.length);

@override
String get current => _currentKey;
}

class _ClosedMapValueIterator extends _ClosedMapIterator<Object?> {
_ClosedMapValueIterator(super._buffer, super.pointer, super.length);

@override
Object? get current => _currentValue;
}

class _ClosedMapEntryIterator
extends _ClosedMapIterator<MapEntry<String, Object?>> {
_ClosedMapEntryIterator(super._buffer, super.pointer, super.length);

@override
MapEntry<String, Object?> get current => MapEntry(_currentKey, _currentValue);
}
58 changes: 58 additions & 0 deletions pkgs/dart_model/lib/src/json_buffer/explanations.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

part of 'json_buffer_builder.dart';

/// Tracks why each byte in a [JsonBufferBuilder] was written, and does
/// additional checks.
class _Explanations {
final Map<int, String> _explanationsByPointer = {};
final List<String> _explanationsStack = [];

static const _allowOverwriteTag = ' [allowOverwrite]';

/// Log what is being written.
///
/// At the start of a method writing to the buffer, "push" details of the
/// write. Then, call "explain" on every write to the buffer. The details
/// will be logged. Finally, call "pop" before returning.
void push(String explanation) {
if (explanation.length > 40) {
explanation = '${explanation.substring(0, 37)}...';
}
_explanationsStack.add(explanation);
}

/// Call at the end of a write method to "pop" from the explanations stack.
void pop() {
_explanationsStack.removeLast();
jakemac53 marked this conversation as resolved.
Show resolved Hide resolved
}

/// Associate the current explanation stack with [pointer].
///
/// Set [allowOverwrite] if multiple writes to this location are allowed.
/// Otherwise, this method throws on multiple writes.
void explain(_Pointer pointer, {bool allowOverwrite = false}) {
if (_explanationsStack.isNotEmpty) {
final explanation = _explanationsStack.join(', ') +
(allowOverwrite ? _allowOverwriteTag : '');
final oldExplanation = _explanationsByPointer[pointer];
if (oldExplanation != null) {
if (!allowOverwrite || !oldExplanation.contains(_allowOverwriteTag)) {
throw StateError('Second write to $pointer!\n'
' Old explanation: ${_explanationsByPointer[pointer]}\n'
' New explanation: $explanation');
}
}
_explanationsByPointer[pointer] = explanation;
}
}

/// Associate the current explanation stack with all bytes [from] until [to].
void explainRange(_Pointer from, _Pointer to) {
for (var pointer = from; pointer != to; ++pointer) {
explain(pointer);
}
}
}
Loading