Переглянути джерело

Merge pull request #3743 from HaxeFoundation/new_stringmap

New stringmap
Simon Krajewski 10 роки тому
батько
коміт
68553eb688

+ 145 - 15
std/flash/_std/haxe/ds/StringMap.hx

@@ -23,44 +23,95 @@ package haxe.ds;
 
 @:coreApi class StringMap<T> implements haxe.Constraints.IMap<String,T> {
 
-	private var h :flash.utils.Dictionary;
+	private var h : Dynamic;
+	private var rh : Dynamic;
+	static var reserved = { };
 
 	public function new() : Void {
-		h = new flash.utils.Dictionary();
+		h = {};
 	}
 
-	public function set( key : String, value : T ) : Void {
-		untyped h["$"+key] = value;
+	inline function isReserved(key:String) : Bool {
+		return untyped __in__(key,reserved);
 	}
 
-	public function get( key : String ) : Null<T> {
-		return untyped h["$"+key];
+	public inline function set( key : String, value : T ) : Void {
+		if( isReserved(key) )
+			setReserved(key, value);
+		else
+			untyped h[key] = value;
+	}
+
+	public inline function get( key : String ) : Null<T> {
+		if( isReserved(key) )
+			return getReserved(key);
+		return untyped h[key];
 	}
 
 	public inline function exists( key : String ) : Bool {
-		return untyped __in__("$"+key,h);
+		if( isReserved(key) )
+			return existsReserved(key);
+		return untyped __in__(key,h);
+	}
+
+	function setReserved( key : String, value : T ) : Void {
+		if( rh == null ) rh = {};
+		untyped rh["$"+key] = value;
+	}
+
+	function getReserved( key : String ) : Null<T> {
+		return rh == null ? null : untyped rh["$"+key];
+	}
+
+	function existsReserved( key : String ) : Bool {
+		if( rh == null ) return false;
+		return untyped __in__("$"+key,rh);
 	}
 
 	public function remove( key : String ) : Bool {
-		key = "$"+key;
-		if( untyped !__in__(key,h) ) return false;
-		untyped __delete__(h,key);
-		return true;
+		if( isReserved(key) ) {
+			key = "$" + key;
+			if( rh == null || !untyped __in__(key,rh) ) return false;
+			untyped __delete__(rh,key);
+			return true;
+		} else {
+			if( !untyped __in__(key,h) )
+				return false;
+			untyped __delete__(h,key);
+			return true;
+		}
 	}
 
+	#if as3
+
+	// unoptimized version
+
 	public function keys() : Iterator<String> {
-		return untyped (__hkeys__(h)).iterator();
+		var out : Array<String> = untyped __keys__(h);
+		if( rh != null ) out = out.concat(untyped __hkeys__(rh));
+		return out.iterator();
 	}
 
 	public function iterator() : Iterator<T> {
 		return untyped {
-			ref : h,
-			it : __keys__(h).iterator(),
+			it : keys(),
 			hasNext : function() { return __this__.it.hasNext(); },
-			next : function() { var i : Dynamic = __this__.it.next(); return __this__.ref[i]; }
+			next : function() { return get(__this__.it.next()); }
 		};
 	}
 
+	#else
+
+	public inline function keys() : Iterator<String> {
+		return new StringMapKeysIterator(h, rh);
+	}
+
+	public inline function iterator() : Iterator<T> {
+		return new StringMapValuesIterator<T>(h, rh);
+	}
+
+	#end
+
 	public function toString() : String {
 		var s = new StringBuf();
 		s.add("{");
@@ -77,3 +128,82 @@ package haxe.ds;
 	}
 
 }
+
+#if !as3
+
+// this version uses __has_next__/__forin__ special SWF opcodes for iteration with no allocation
+
+@:allow(haxe.ds.StringMap)
+private class StringMapKeysIterator {
+	var h:Dynamic;
+	var rh:Dynamic;
+	var index : Int;
+	var nextIndex : Int;
+	var isReserved : Bool;
+
+	inline function new(h:Dynamic, rh:Dynamic):Void {
+		this.h = h;
+		this.rh = rh;
+		this.index = 0;
+		this.nextIndex = 0;
+		isReserved = false;
+	}
+
+	public inline function hasNext():Bool {
+		var h = h, index = index; // tmp vars required for __has_next
+		var n = untyped __has_next__(h, index);
+		if( !n && rh != null ) {
+			h = this.h = rh;
+			index = this.index = 0;
+			rh = null;
+			isReserved = true;
+			n = untyped __has_next__(h, index);
+		}
+		this.nextIndex = index; // store next index
+		return n;
+	}
+
+	public inline function next():String {
+		var r : String = untyped __forin__(h, nextIndex);
+		index = nextIndex;
+		if( isReserved ) r = r.substr(1);
+		return r;
+	}
+
+}
+
+@:allow(haxe.ds.StringMap)
+private class StringMapValuesIterator<T> {
+	var h:Dynamic;
+	var rh:Dynamic;
+	var index : Int;
+	var nextIndex : Int;
+
+	inline function new(h:Dynamic, rh:Dynamic):Void {
+		this.h = h;
+		this.rh = rh;
+		this.index = 0;
+		this.nextIndex = 0;
+	}
+
+	public inline function hasNext():Bool {
+		var h = h, index = index; // tmp vars required for __has_next
+		var n = untyped __has_next__(h, index);
+		if( !n && rh != null ) {
+			h = this.h = rh;
+			index = this.index = 0;
+			rh = null;
+			n = untyped __has_next__(h, index);
+		}
+		this.nextIndex = index; // store next index
+		return n;
+	}
+
+	public inline function next():T {
+		var r = untyped __foreach__(h, nextIndex);
+		index = nextIndex;
+		return r;
+	}
+
+}
+#end

+ 84 - 22
std/js/_std/haxe/ds/StringMap.hx

@@ -21,66 +21,128 @@
  */
 package haxe.ds;
 
+private class StringMapIterator<T> {
+	var map : StringMap<T>;
+	var keys : Array<String>;
+	var index : Int;
+	var count : Int;
+	public inline function new(map:StringMap<T>, keys:Array<String>) {
+		this.map = map;
+		this.keys = keys;
+		this.index = 0;
+		this.count = keys.length;
+	}
+	public inline function hasNext() {
+		return index < count;
+	}
+	public inline function next() {
+		return map.get(keys[index++]);
+	}
+}
+
 @:coreApi class StringMap<T> implements haxe.Constraints.IMap<String,T> {
 
 	private var h : Dynamic;
+	private var rh : Dynamic;
 
 	public inline function new() : Void {
 		h = {};
 	}
 
+	inline function isReserved(key:String) : Bool {
+		return untyped __js__("__map_reserved")[key] != null;
+	}
+
 	public inline function set( key : String, value : T ) : Void {
-		untyped h["$"+key] = value;
+		if( isReserved(key) )
+			setReserved(key, value);
+		else
+			h[cast key] = value;
 	}
 
 	public inline function get( key : String ) : Null<T> {
-		return untyped h["$"+key];
+		if( isReserved(key) )
+			return getReserved(key);
+		return h[cast key];
 	}
 
 	public inline function exists( key : String ) : Bool {
-		return untyped h.hasOwnProperty("$"+key);
+		if( isReserved(key) )
+			return existsReserved(key);
+		return h.hasOwnProperty(key);
+	}
+
+	function setReserved( key : String, value : T ) : Void {
+		if( rh == null ) rh = {};
+		rh[cast "$"+key] = value;
+	}
+
+	function getReserved( key : String ) : Null<T> {
+		return rh == null ? null : rh[cast "$"+key];
+	}
+
+	function existsReserved( key : String ) : Bool {
+		if( rh == null ) return false;
+		return untyped rh.hasOwnProperty("$"+key);
 	}
 
 	public function remove( key : String ) : Bool {
-		key = "$"+key;
-		if( untyped !h.hasOwnProperty(key) ) return false;
-		untyped __js__("delete")(h[key]);
-		return true;
+		if( isReserved(key) ) {
+			key = "$" + key;
+			if( rh == null || !rh.hasOwnProperty(key) ) return false;
+			untyped __js__("delete")(rh[key]);
+			return true;
+		} else {
+			if( !h.hasOwnProperty(key) )
+				return false;
+			untyped __js__("delete")(h[key]);
+			return true;
+		}
 	}
 
 	public function keys() : Iterator<String> {
-		var a = [];
+		return arrayKeys().iterator();
+	}
+	
+	function arrayKeys() : Array<String> {
+		var out = [];
 		untyped {
 			__js__("for( var key in this.h ) {");
 				if( h.hasOwnProperty(key) )
-					a.push(key.substr(1));
+					out.push(key);
 			__js__("}");
 		}
-		return a.iterator();
+		if( rh != null ) untyped {
+			__js__("for( var key in this.rh ) {");
+				if( key.charCodeAt(0) == "$".code )
+					out.push(key.substr(1));
+			__js__("}");
+		}
+		return out;
 	}
 
-	public function iterator() : Iterator<T> {
-		return untyped {
-			ref : h,
-			it : keys(),
-			hasNext : function() { return __this__.it.hasNext(); },
-			next : function() { var i = __this__.it.next(); return __this__.ref["$"+i]; }
-		};
+	public inline function iterator() : Iterator<T> {
+		return new StringMapIterator(this, arrayKeys());
 	}
 
 	public function toString() : String {
 		var s = new StringBuf();
 		s.add("{");
-		var it = keys();
-		for( i in it ) {
-			s.add(i);
+		var keys = arrayKeys();
+		for( i in 0...keys.length ) {
+			var k = keys[i];
+			s.add(k);
 			s.add(" => ");
-			s.add(Std.string(get(i)));
-			if( it.hasNext() )
+			s.add(Std.string(get(k)));
+			if( i < keys.length )
 				s.add(", ");
 		}
 		s.add("}");
 		return s.toString();
 	}
 
+	static function __init__() : Void {
+		untyped __js__("var __map_reserved = {}");
+	}
+
 }

+ 74 - 0
tests/unit/issues/Issue3462.hx

@@ -0,0 +1,74 @@
+package unit.issues;
+import unit.Test;
+import haxe.ds.StringMap;
+
+class Issue3462 extends Test
+{
+	function test()
+	{
+		var d:StringMap<Int> = new StringMap<Int>();
+		var r:Dynamic;
+		
+		var s:String = "";
+		var keys = ["__proto__", "__definegetter__", "__definesetter__", "__lookupgetter__", "__lookupsetter__", "__noSuchMethod__", "__count__", "__parent__", "eval", "toSource", "unwatch", "watch", "constructor", "sillyTest", "0", "1", "prototype", "hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable", "setPropertyIsEnumerable", "toLocaleString", "toString", "valueOf", "toJSON", "get", "set"];
+		for (i in 0...keys.length) {
+			var k = keys[i];
+			eq(d.exists(k), false);
+			eq(d.get(k),null);
+			d.set(k, i);
+			eq(d.exists(k), true);
+			eq(d.get(k), i);
+			eq( d.remove(k), true );
+			eq( d.remove(k), false );
+			eq( d.exists(k), false );
+			eq( d.get(k), null );
+			d.set(k, i); // set again
+		}
+		
+		var keyCount:Int = 0;
+		for (k in d.keys()) {
+			f( keys.indexOf(k) == -1 ); // key missing from keys iterator
+			keyCount++;
+		}
+		f(keyCount != keys.length); // keys missing
+		var keyCount:Int = 0;
+		for (v in d) {
+			f(v < 0 || v >= keys.length); // bad value iterated
+			keyCount++;
+		}
+		f(keyCount != keys.length); // values missing
+		for (i in 0...keys.length) {
+			var k = keys[i];
+			r = d.get(k);
+			f(r != i); // Value not correct after setting
+			r = d.remove(k);
+			f(r != true); // Value unable to be removed
+			r = d.exists(k);
+			f(r == true); // Entry should not exist after removal
+		}
+		keyCount = 0;
+		for (k in d.keys()) {			
+		}
+		f( keyCount != 0 ); // keys remaining after removal
+		
+		// test fast iterators against normal keys
+		d = new StringMap();
+		keys = ["1", "2", "3"];
+		for (i in 0...keys.length) {
+			var k = keys[i];
+			d.set(k, i);
+		}
+		var keyCount:Int = 0;
+		for (k in d.keys()) {
+			f( keys.indexOf(k) == -1 ); // key missing from keys iterator
+			keyCount++;
+		}
+		f( keyCount != keys.length ); // keys missing
+		var keyCount:Int = 0;
+		for (v in d) {
+			f(v < 0 || v >= keys.length); // bad value iterated
+			keyCount++;
+		}
+		f(keyCount != keys.length); // values missing
+	}
+}