Chromium Code Reviews| Index: runtime/lib/convert_patch.dart |
| diff --git a/runtime/lib/convert_patch.dart b/runtime/lib/convert_patch.dart |
| index 93da46140cb3fac8b3477af430d4d278bd61a3d0..c1d924b89aa9d094a242dc1f33cf607addcb6d0b 100644 |
| --- a/runtime/lib/convert_patch.dart |
| +++ b/runtime/lib/convert_patch.dart |
| @@ -3,6 +3,8 @@ |
| // BSD-style license that can be found in the LICENSE file. |
| import "dart:typed_data"; |
| +import "dart:collection" show HashMap, LinkedHashMap, Maps; |
| +import "dart:_internal" show SubListIterable, makeListFixedLength; |
| // JSON conversion. |
| @@ -58,10 +60,19 @@ class _BuildJsonListener extends _JsonListener { |
| String key; |
| /** The most recently read value. */ |
| var value; |
| + /** Cache for reusable hidden classes of objects. Start out in multi-mode. */ |
| + static _JsonTransitionMap staticCache = |
| + new _JsonMultiTransitionMap(const _JsonHiddenClass.empty(), |
| + new HashMap()); |
| + // Counts how many transitions have been added to the cache. |
| + // Cache is cleared when reaching the max size. |
| + static int staticCacheSize = 0; |
| + static const int MAX_STATIC_CACHE_SIZE = 512; |
| /** Pushes the currently active container (and key, if a [Map]). */ |
| void pushContainer() { |
| - if (currentContainer is Map) stack.add(key); |
| + if (key != null) |
| + if (currentContainer is _JsonObjectBuilder) stack.add(key); |
| stack.add(currentContainer); |
| } |
| @@ -69,7 +80,7 @@ class _BuildJsonListener extends _JsonListener { |
| void popContainer() { |
| value = currentContainer; |
| currentContainer = stack.removeLast(); |
| - if (currentContainer is Map) key = stack.removeLast(); |
| + if (currentContainer is _JsonObjectBuilder) key = stack.removeLast(); |
| } |
| void handleString(String value) { this.value = value; } |
| @@ -79,7 +90,7 @@ class _BuildJsonListener extends _JsonListener { |
| void beginObject() { |
| pushContainer(); |
| - currentContainer = {}; |
| + currentContainer = new _JsonObjectBuilder(staticCache); |
| } |
| void propertyName() { |
| @@ -88,12 +99,15 @@ class _BuildJsonListener extends _JsonListener { |
| } |
| void propertyValue() { |
| - Map map = currentContainer; |
| - map[key] = value; |
| + _JsonObjectBuilder builder = currentContainer; |
| + builder.add(key, value); |
| key = value = null; |
| } |
| void endObject() { |
| + _JsonObjectBuilder builder = currentContainer; |
| + currentContainer = builder.toMap(); |
| + staticCacheSize += builder.transitionsAdded; |
| popContainer(); |
| } |
| @@ -115,6 +129,11 @@ class _BuildJsonListener extends _JsonListener { |
| /** Read out the final result of parsing a JSON string. */ |
| get result { |
| assert(currentContainer == null); |
| + if (staticCacheSize > MAX_STATIC_CACHE_SIZE) { |
| + _JsonMultiTransitionMap cache = staticCache; |
| + cache.mapping.clear(); |
| + staticCacheSize = 0; |
| + } |
| return value; |
| } |
| } |
| @@ -144,7 +163,7 @@ class _JsonParser { |
| // |
| // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON |
| // and strings also in OBJECT_EMPTY, OBJECT_COMMA. |
| - // VALUE STRING : , } ] Transitions to |
| + // VALUE STRING : , } ] f to |
| // EMPTY X X -> END |
| // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop |
| // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop |
| @@ -391,21 +410,26 @@ class _JsonParser { |
| // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"' |
| // Initial position is right after first '"'. |
| int start = position; |
| - int char; |
| - do { |
| - if (position == source.length) { |
| - fail(start - 1, "Unterminated string"); |
| - } |
| - char = source.codeUnitAt(position); |
| - if (char == QUOTE) { |
| - listener.handleString(source.substring(start, position)); |
| - return position + 1; |
| - } |
| - if (char < SPACE) { |
| - fail(position, "Control character in string"); |
| + while (position < source.length) { |
| + int char = source.codeUnitAt(position); |
| + if (char <= BACKSLASH) { // BACKSLASH is larger than QUOTE. |
| + if (char == BACKSLASH) { |
| + return parseStringWithEscapes(start, position); |
| + } |
| + if (char == QUOTE) { |
| + listener.handleString(source.substring(start, position)); |
| + return position + 1; |
| + } |
| + if (char < SPACE) { |
| + fail(position, "Control character in string"); |
| + } |
| } |
| position++; |
| - } while (char != BACKSLASH); |
| + } |
| + fail(start - 1, "Unterminated string"); |
| + } |
| + |
| + int parseStringWithEscapes(string, position) { |
| // Backslash escape detected. Collect character codes for rest of string. |
| int firstEscape = position - 1; |
| List<int> chars = <int>[]; |
| @@ -472,74 +496,84 @@ class _JsonParser { |
| } |
| } |
| - int _handleLiteral(start, position, isDouble) { |
| - String literal = source.substring(start, position); |
| - // This correctly creates -0 for doubles. |
| - num value = (isDouble ? double.parse(literal) : int.parse(literal)); |
| - listener.handleNumber(value); |
| - return position; |
| - } |
| - |
| int parseNumber(int char, int position) { |
| // Format: |
| // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)? |
| int start = position; |
| int length = source.length; |
| + int intValue = 0; // Collect int value while parsing. |
| + int intSign = 1; |
| bool isDouble = false; |
| - if (char == MINUS) { |
| - position++; |
| - if (position == length) fail(position, "Missing expected digit"); |
| - char = source.codeUnitAt(position); |
| - } |
| - if (char < CHAR_0 || char > CHAR_9) { |
| - fail(position, "Missing expected digit"); |
| - } |
| - if (char == CHAR_0) { |
| - position++; |
| - if (position == length) return _handleLiteral(start, position, false); |
| - char = source.codeUnitAt(position); |
| - if (CHAR_0 <= char && char <= CHAR_9) { |
| - fail(position); |
| - } |
| - } else { |
| - do { |
| + // Break this block when the end of the number literal is reached. |
| + // At that time, position points to the next character, and isDouble |
| + // is set if the literal contains a decimal point or an exponential. |
| + parsing: { |
| + if (char == MINUS) { |
| + intSign = -1; |
| position++; |
| - if (position == length) return _handleLiteral(start, position, false); |
| + if (position == length) fail(position, "Missing expected digit"); |
| char = source.codeUnitAt(position); |
| - } while (CHAR_0 <= char && char <= CHAR_9); |
| - } |
| - if (char == DECIMALPOINT) { |
| - isDouble = true; |
| - position++; |
| - if (position == length) fail(position, "Missing expected digit"); |
| - char = source.codeUnitAt(position); |
| - if (char < CHAR_0 || char > CHAR_9) fail(position); |
| - do { |
| + } |
| + if (char < CHAR_0 || char > CHAR_9) { |
| + fail(position, "Missing expected digit"); |
| + } |
| + if (char == CHAR_0) { |
| position++; |
| - if (position == length) return _handleLiteral(start, position, true); |
| + if (position == length) break parsing; |
| char = source.codeUnitAt(position); |
| - } while (CHAR_0 <= char && char <= CHAR_9); |
| - } |
| - if (char == CHAR_e || char == CHAR_E) { |
| - isDouble = true; |
| - position++; |
| - if (position == length) fail(position, "Missing expected digit"); |
| - char = source.codeUnitAt(position); |
| - if (char == PLUS || char == MINUS) { |
| + if (CHAR_0 <= char && char <= CHAR_9) { |
| + fail(position); |
| + } |
| + } else { |
| + do { |
| + intValue = intValue * 10 + (char - CHAR_0); |
| + position++; |
| + if (position == length) break parsing; |
| + char = source.codeUnitAt(position); |
| + } while (CHAR_0 <= char && char <= CHAR_9); |
| + } |
| + if (char == DECIMALPOINT) { |
| + isDouble = true; |
| position++; |
| if (position == length) fail(position, "Missing expected digit"); |
| char = source.codeUnitAt(position); |
| + if (char < CHAR_0 || char > CHAR_9) fail(position); |
| + do { |
| + position++; |
| + if (position == length) break parsing; |
| + char = source.codeUnitAt(position); |
| + } while (CHAR_0 <= char && char <= CHAR_9); |
| } |
| - if (char < CHAR_0 || char > CHAR_9) { |
| - fail(position, "Missing expected digit"); |
| - } |
| - do { |
| + if (char == CHAR_e || char == CHAR_E) { |
| + isDouble = true; |
| position++; |
| - if (position == length) return _handleLiteral(start, position, true); |
| + if (position == length) fail(position, "Missing expected digit"); |
| char = source.codeUnitAt(position); |
| - } while (CHAR_0 <= char && char <= CHAR_9); |
| + if (char == PLUS || char == MINUS) { |
| + position++; |
| + if (position == length) fail(position, "Missing expected digit"); |
| + char = source.codeUnitAt(position); |
| + } |
| + if (char < CHAR_0 || char > CHAR_9) { |
| + fail(position, "Missing expected digit"); |
| + } |
| + do { |
| + position++; |
| + if (position == length) break parsing; |
| + char = source.codeUnitAt(position); |
| + } while (CHAR_0 <= char && char <= CHAR_9); |
| + } |
| } |
| - return _handleLiteral(start, position, isDouble); |
| + if (!isDouble) { |
| + listener.handleNumber(intSign * intValue); |
| + return position; |
| + } |
| + // Consider whether we can have an int/double.parse that works on part of |
| + // a string, to avoid creating the substring. |
| + String literal = source.substring(start, position); |
| + // This correctly creates -0.0 for doubles. |
| + listener.handleNumber(double.parse(literal)); |
| + return position; |
| } |
| void fail(int position, [String message]) { |
| @@ -557,8 +591,409 @@ class _JsonParser { |
| } |
| } |
| +/* |
| + * JSON Map |
| + * |
| + * A map with hidden class structure. |
| + * |
| + * When building maps, don't use a linked hashmap directly. |
| + * Instead use a "hidden class" map that keeps the hash structure |
| + * in a separate sharable structure representation, and only the |
| + * data in the actual map. |
| + * Basically, use a map of string->index, and a list of values, |
| + * and share the map between all objects with the same structure. |
| + * |
| + * JSON maps are expected to preserve order, so the hidden classes |
| + * maintain the order of the keys. |
| + * |
| + * The maps will be a delegating map that points to the hidden class |
| + * (itself a "map") except that all modifying operations makes the |
| + * hidden class replace itself with a linked hash map. |
| + */ |
| + |
| +/** |
| + * A transition cache that shows transitions from one hidden class |
| + * to another. |
| + */ |
| +class _JsonTransitionMap { |
| + _JsonHiddenClass get hiddenClass; |
| + /** See if there is a transition from this class with [key] as key. */ |
| + _JsonTransitionMap lookup(String key); |
| + /** Add a new transition from this class to a new one. */ |
| + _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap); |
| + /** Update the transition map that is linked by a given key. */ |
| + void update(String key, _JsonTransitionMap map); |
| +} |
| + |
| +class _JsonLeafTransitionMap implements _JsonTransitionMap { |
| + final _JsonHiddenClass hiddenClass; |
| + _JsonLeafTransitionMap(this.hiddenClass); |
| + _JsonTransitionMap lookup(String key) => null; |
| + _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { |
| + return new _JsonSingletonTransitionMap(hiddenClass, key, targetMap); |
| + } |
| + void update(String key, _JsonTransitionMap map) { |
| + assert(false); // Must not be called. |
| + } |
| +} |
| + |
| +class _JsonSingletonTransitionMap implements _JsonTransitionMap { |
| + final _JsonHiddenClass hiddenClass; |
| + final String key; |
| + _JsonTransitionMap next; |
| + _JsonSingletonTransitionMap(this.hiddenClass, this.key, this.next); |
| + |
| + _JsonTransitionMap lookup(String key) { |
| + if (this.key == key) return next; |
| + return null; |
| + } |
| + |
| + _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { |
| + Map mapping = new HashMap(); |
| + mapping[this.key] = next; |
| + mapping[key] = targetMap; |
| + return new _JsonMultiTransitionMap(hiddenClass, mapping); |
| + } |
| + |
| + void update(String key, _JsonTransitionMap map) { |
| + assert(this.key == key); |
| + next = map; |
| + } |
| +} |
| + |
| +class _JsonMultiTransitionMap implements _JsonTransitionMap { |
| + final _JsonHiddenClass hiddenClass; |
| + final Map mapping; |
| + _JsonMultiTransitionMap(this.hiddenClass, this.mapping); |
| + _JsonTransitionMap lookup(String key) => mapping[key]; |
| + _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { |
| + assert(!mapping.containsKey(key)); |
| + mapping[key] = targetMap; |
| + return this; |
| + } |
| + void update(String key, _JsonTransitionMap map) { |
| + assert(mapping.containsKey(key)); |
| + mapping[key] = map; |
| + } |
| +} |
| + |
| +/** |
| + * A JSON Object builder that keeps a hidden class for keys and a list of |
| + * values. |
| + * |
| + * When the object is complete, it can be extracted as a `Map` using `toMap`. |
| + * |
| + */ |
| +class _JsonObjectBuilder { |
| + int transitionsAdded = 0; |
| + _JsonTransitionMap parentMap; |
| + String previousKey; |
| + _JsonTransitionMap currentMap; |
| + |
| + final List values = []; |
| + |
| + _JsonObjectBuilder(this.currentMap); |
| + |
| + Object toMap() { |
| + return currentMap.hiddenClass.asMap(values); |
| + } |
| + |
| + /** |
| + * Add a property to the object being built. |
| + * |
| + * If the key is already in the object, its value is just overwritten. |
| + * Otherwise the hidden class is transitioned to one with the new key |
| + * and the result is added at the end. |
| + */ |
| + void add(String key, var value) { |
| + int index = currentMap.hiddenClass.lookup(key); |
| + if (index >= 0) { |
| + values[index] = value; |
| + } else { |
| + _JsonTransitionMap nextMap = currentMap.lookup(key); |
| + if (nextMap == null) { |
| + _JsonHiddenClass nextClass = currentMap.hiddenClass.addKey(key); |
| + nextMap = new _JsonLeafTransitionMap(nextClass); |
| + currentMap = currentMap.addAlternative(key, nextMap); |
| + if (parentMap != null) { |
| + parentMap.update(previousKey, currentMap); |
| + } |
| + transitionsAdded++; |
| + } |
| + parentMap = currentMap; |
| + previousKey = key; |
| + currentMap = nextMap; |
| + |
| + values.add(value); |
| + } |
| + } |
| +} |
| + |
| +/** |
| + * A "hidden class" is a mapping from string key to integer index. |
| + * |
| + * A map using a class will have a list of values for each index in the |
| + * hidden class. |
| + */ |
| +abstract class _JsonHiddenClass { |
| + const _JsonHiddenClass(); |
| + const factory _JsonHiddenClass.empty() = _JsonEmptyHiddenClass; |
| + int lookup(String key); |
| + Map toMap(List values) { |
| + Map map = new LinkedHashMap<String, dynamic>(); |
| + forEach((k, v) { map[k] = v; }); |
| + } |
| + Iterable<String> get keyIterable; |
| + void forEach(List values, void action(String key, var value)); |
| + int get length; |
| + |
| + _JsonHiddenClass addKey(String key); |
| + |
| + Map<String, dynamic> asMap(List values) { |
| + return new _JsonHiddenClassMap(this, values).wrapper; |
| + //return new _JsonHiddenClassMap(this, makeListFixedLength(values)).wrapper; |
|
sra1
2014/02/27 04:45:22
Delete comment.
Lasse Reichstein Nielsen
2014/02/27 09:10:38
Acl, yes. It was an attempt to save a little extra
|
| + } |
| +} |
| + |
| +class _JsonEmptyHiddenClass extends _JsonHiddenClass { |
| + const _JsonEmptyHiddenClass(); |
| + int lookup(String key) => -1; |
| + Map toMap(List values) => new LinkedHashMap<String, dynamic>(); |
| + Iterable<String> get keyIterable => new Iterable<String>.generate(0, null); |
| + void forEach(List values, void action(String key, var value)) {} |
| + int get length => 0; |
| + _JsonHiddenClass addKey(String key) { |
| + return new _JsonSmallHiddenClass(<String>[key], 1); |
| + } |
| +} |
| + |
| +/** |
| + * A hidden class for a JSON object that maps keys to value indices. |
| + * |
| + * This is intended for small objects. Looking up a key is done using |
| + * linear search. |
| + */ |
| +class _JsonSmallHiddenClass extends _JsonHiddenClass { |
| + final List keys; |
| + final int length; // `keys` may contain more elements than length. |
| + _JsonSmallHiddenClass(this.keys, this.length); |
| + int lookup(String key) { |
| + for (int i = 0; i < length; i++) { |
| + if (keys[i] == key) return i; |
| + } |
| + return -1; |
| + } |
| + |
| + Iterable<String> get keyIterable => |
| + new SubListIterable<String>(keys, 0, length); |
| + |
| + void forEach(List values, void action(String key, var value)) { |
| + for (int i = 0; i < length; i++) { |
| + action(keys[i], values[i]); |
| + } |
| + } |
| + |
| + _JsonHiddenClass addKey(String key) { |
| + const int MAX_SMALL_CLASS = 4; |
| + if (length == MAX_SMALL_CLASS) { |
| + Map map = new LinkedHashMap<String,int>(); |
| + for (int i = 0; i < length; i++) map[keys[i]] = i; |
| + map[key] = length; |
| + return new _JsonMediumHiddenClass(map, length + 1); |
| + } |
| + // TODO(lrn): Add an implementation for larger key lists that doesn't use |
| + // linear search. Switch to using that implementation here if length is |
| + // above a threshold. |
| + var newKeys; |
| + if (keys.length > length) { |
| + newKeys = keys.sublist(0, length); |
| + } else { |
| + newKeys = keys; |
| + } |
| + newKeys.add(key); |
| + return new _JsonSmallHiddenClass(newKeys, length + 1); |
| + } |
| +} |
| + |
| +/** |
| + * A hidden class that uses a [LinkedHashMap] to store the key-to-index mapping. |
| + * |
| + * This introduces the same overhead as a normal map, so if the hidden class |
| + * is only used once, it's just an overhead. |
| + */ |
| +class _JsonMediumHiddenClass extends _JsonHiddenClass { |
| + final LinkedHashMap<String, int> keys; |
| + final int length; // `keys` may contain more elements than length. |
| + _JsonMediumHiddenClass(this.keys, this.length); |
| + |
| + int lookup(String key) { |
| + int index = keys[key]; |
| + if (index == null || index >= length) return -1; |
| + return index; |
| + } |
| + |
| + Iterable<String> get keyIterable => keys.keys.take(length); |
| + |
| + void forEach(List values, void action(String key, var value)) { |
| + int i = 0; |
| + assert(length != 0); |
| + for (String key in keys.keys) { |
| + action(key, values[i]); |
| + i++; |
| + if (i == length) break; |
| + } |
| + } |
| + |
| + _JsonHiddenClass addKey(String key) { |
| + // TODO(lrn): Add an implementation for larger key lists that doesn't use |
| + // linear search. Switch to using that implementation here if length is |
| + // above a threshold. |
| + var newKeys; |
| + if (keys.length > length) { |
| + newKeys = new HashMap<String,int>(); |
| + keys.forEach((String key, int value) { |
| + if (value < length) newKeys[key] = value; |
| + }); |
| + } else { |
| + newKeys = keys; |
| + } |
| + newKeys[key] = length; |
| + return new _JsonMediumHiddenClass(newKeys, length + 1); |
| + } |
| +} |
| + |
| + |
| +/** |
| + * A map based on a hidden class. |
| + * |
| + * The hidden class translates string keys to integer indices, and the |
| + * values are stored at those indices in [values]. |
| + * The idea is that the hidden class can be shared between multiple similar |
| + * objects, reducing the memory overhead of the map created by decoding a |
| + * JSON Object. This only works when there are more than one object with |
| + * the same structure. |
| + * |
| + * |
| + * This object is hidden behind the [_JsonMapWrapper]. |
| + * |
| + * Any attempt to write to the map will make it convert itself to a |
| + * [LinkedHashMap] with the same values, and make the wrapper delegate to that |
| + * map instead. |
| + */ |
| +class _JsonHiddenClassMap implements Map { |
| + final _JsonHiddenClass hiddenClass; |
| + final List mapValues; |
| + _JsonMapWrapper wrapper; |
| + |
| + _JsonHiddenClassMap(this.hiddenClass, this.mapValues) { |
| + wrapper = new _JsonMapWrapper(this); |
| + } |
| + |
| + Map convertToMap() { |
| + Map map = hiddenClass.toMap(mapValues); |
| + wapper._delegate = map; |
| + return map; |
| + } |
| + |
| + bool containsValue(Object value) { |
| + for (int i = 0; i < mapValues.length; i++) { |
| + if (mapValues[i] == value) return true; |
| + } |
| + return false; |
| + } |
| + |
| + bool containsKey(Object key) => hiddenClass.lookup(key) >= 0; |
| + |
| + operator [](Object key) { |
| + int index = hiddenClass.lookup(key); |
| + if (index < 0) return null; |
| + return mapValues[index]; |
| + } |
| + |
| + void operator []=(String key, var value) { |
| + convertToMap()[key] = value; |
| + } |
| + |
| + putIfAbsent(String key, ifAbsent()) { |
| + return convertToMap().putIfAbsent(key, ifAbsent); |
| + } |
| + |
| + void addAll(Map<String, dynamic> other) { |
| + convertToMap().addAll(other); |
| + } |
| + |
| + remove(Object key) { |
| + convertToMap().remove(key); |
| + } |
| + |
| + void clear() { wrapper._delegate = new LinkedHashMap<String, dynamic>(); } |
| + |
| + void forEach(void f(String key, var value)) { |
| + hiddenClass.forEach(mapValues, f); |
| + } |
| + |
| + Iterable<String> get keys => hiddenClass.keyIterable; |
| + |
| + Iterable get valueIterator => |
| + new SubListIterable(mapValues, 0, values.length); |
|
sra1
2014/02/27 04:45:22
If we modify the map while iterating the keys or v
Lasse Reichstein Nielsen
2014/02/27 09:10:38
Ack.
Concurrent modification. Why don't we just di
|
| + |
| + int get length => mapValues.length; |
| + |
| + bool get isEmpty => mapValues.length == 0; |
| + |
| + bool get isNotEmpty => mapValues.length != 0; |
| + |
| + String toString() => Maps.mapToString(this); |
| +} |
| + |
| +/** |
| + * Delegating map wrapper. |
| + * |
| + * Used to have a "copy on write" map implementation optimized for reading, |
| + * which converts itself to a [LinkedHashMap] on any write operation by |
| + * creating the hash map and writing it to [_delegate]. |
| + * |
| + * This is the only object that the JSON decoder's user sees. |
|
sra1
2014/02/26 21:56:06
Interesting trick.
Can you think of a way to make
Lasse Reichstein Nielsen
2014/02/27 09:10:38
I am considering adding a public static setter fun
|
| + */ |
| +class _JsonMapWrapper implements Map<String, dynamic> { |
|
sra1
2014/02/26 21:56:06
The original map was created with
currentContaine
Lasse Reichstein Nielsen
2014/02/27 09:10:38
It wasn't the intent, because I hadn't noticed tha
|
| + Map _delegate; |
| + |
| + _JsonMapWrapper(this._delegate); |
| + |
| + bool containsValue(Object value) => _delegate.containsValue(value); |
| + |
| + bool containsKey(Object key) => _delegate.containsKey(key); |
| + |
| + operator [](Object key) => _delegate[key]; |
| + |
| + void operator []=(String key, var value) { _delegate[key] = value; } |
| + |
| + putIfAbsent(String key, ifAbsent()) => _delegate.putIfAbsent(key, ifAbsent); |
| + |
| + void addAll(Map<String, dynamic> other) => _delegate.addAll(other); |
| + |
| + remove(Object key) => _delegate.remove(key); |
| + |
| + void clear() { _delegate.clear(); } |
|
sra1
2014/02/26 21:56:06
FYI you can use => for these too.
I guess in poorl
Lasse Reichstein Nielsen
2014/02/27 09:10:38
I prefre (strongly) to not use "=>" for void funct
|
| + |
| + void forEach(void f(String key, var value)) { _delegate.forEach(f); } |
| + |
| + Iterable<String> get keys => _delegate.keys; |
| + |
| + Iterable get values => _delegate.values; |
| + |
| + int get length => _delegate.length; |
| + |
| + bool get isEmpty => _delegate.isEmpty; |
| + |
| + bool get isNotEmpty => _delegate.isNotEmpty; |
| + |
| + String toString() => _delegate.toString(); |
| +} |
| + |
| // UTF-8 conversion. |
| patch class _Utf8Encoder { |
| /* patch */ static List<int> _createBuffer(int size) => new Uint8List(size); |
| } |
| + |