blob: f840a01ad71272214fca89849a21e08b9642231a [file] [log] [blame]
// TODO: Add the following necessary types and extensions to `dart:js_interop`
import 'dart:collection';
import 'dart:js_interop';
/// A JS [Iterator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator)
/// There is no definition of the Iterator protocol in JS as it is **hidden**,
/// and implementations inherit from its prototype
///
/// This can be used as the base type used for implementing iterators used in
/// Arrays, Maps, etc
/// which can then be translated to iterator interfaces and more used in
/// `dart:core`, `dart:async` and `dart:typed_data`
///
/// The thing is: JavaScript handles iterators and their usage differently from
/// how Dart does
///
/// When doing `for of` loops in JS, you use the `Iterator` itself, while in
/// Dart, you use an `Iterable` (or `Stream` for `await for of`)
///
/// Implementations converting an `Iterator` to both a Dart `Iterator` and
/// `Iterable` are available
@JS('Iterator')
extension type JSIterator<T extends JSAny>._(JSObject _) implements JSObject {
external factory JSIterator.from(JSIterator object);
external JSIteratorResult<T> next([T value]);
// MDN says the rest are optional, but they are here for completeness.
// I've seen that `return` is useful when converting to Dart types, but most
// likely not `throw`
@JS('return')
external JSIteratorResult<T> return$([T value]);
}
extension type JSIteratorResult<T extends JSAny>._(JSObject _)
implements JSObject {
external JSIteratorResult({bool done, T value});
external bool get done;
external T get value;
}
extension ToDartIterableIterator<T extends JSAny> on JSIterator<T> {
Iterator<T> get toDart => JSIteratorRep<T>._(this);
// TODO: Upgrade to SDK 3.8 to use `Iterable.withIterator`
Iterable<T> get toDartIterable => iterableFromIterator(toDart);
}
Iterable<T> iterableFromIterator<T>(Iterator<T> iterator) sync* {
while (iterator.moveNext()) {
yield iterator.current;
}
}
class JSIteratorRep<T extends JSAny> implements Iterator<T> {
JSIterator<T> iterator;
bool isDone = false;
T? _currentValue;
JSIteratorRep._(this.iterator);
@override
T get current {
if (isDone) {
throw Exception('Stream Iterator Done');
} else if (_currentValue == null) {
throw Exception('No value');
} else {
return _currentValue!;
}
}
@override
bool moveNext() {
final value = iterator.next();
if (value.done) {
_currentValue = null;
isDone = true;
} else {
_currentValue = value.value;
}
return !value.done;
}
}
/// A JS [Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
///
@JS('Map')
extension type JSMap<K extends JSAny, V extends JSAny>._(JSObject _)
implements JSIterator {
external JSMap([JSArray<JSArray<JSAny>> iterable]);
external int get size;
external bool delete(K key);
external bool has(K key);
external V? get(K key);
external JSMap<K, V> set(K key, V value);
external void clear();
external JSIterator<K> keys();
external JSIterator<V> values();
}
extension JSMapToMap<K extends JSAny, V extends JSAny> on JSMap<K, V> {
Map<K, V> get toDart => JSMapRep._(this);
}
// TODO: Should we make use of a `DelegatingMap`:
// https://pub.dev/documentation/collection/latest/collection/DelegatingMap-class.html
class JSMapRep<K extends JSAny, V extends JSAny> extends MapBase<K, V> {
final JSMap<K, V> _map;
JSMapRep._(this._map);
@override
V? operator [](covariant K? key) => key == null ? null : _map.get(key);
@override
void operator []=(K key, V value) => _map.set(key, value);
@override
void clear() => _map.clear();
@override
Iterable<K> get keys => _map.keys().toDartIterable;
@override
Iterable<V> get values => _map.values().toDartIterable;
@override
V? remove(covariant K? key) {
if (key == null) {
return null;
} else {
final value = _map.get(key);
_map.delete(key);
return value;
}
}
}