Browse Source

haxe.io.Bytes serialization support

Nicolas Cannasse 17 years ago
parent
commit
a36be2529c

+ 2 - 0
doc/CHANGES.txt

@@ -30,9 +30,11 @@ TODO inlining : substitute class+function type parameters in order to have fully
 	added haxe.Int32
 	added haxe.Int32
 	removed neko.Int32
 	removed neko.Int32
 	removed neko.io.Input/Output/Eof/Error/Logger/Multiple/StringInput/StringOutput
 	removed neko.io.Input/Output/Eof/Error/Logger/Multiple/StringInput/StringOutput
+	removed neko.net.RemotingServer
 	changed neko apis to use haxe.io and Bytes instead of String buffers
 	changed neko apis to use haxe.io and Bytes instead of String buffers
 	fixed big bug in js/flash8 debug stack handling
 	fixed big bug in js/flash8 debug stack handling
 	complete rewrite of haxe.remoting package
 	complete rewrite of haxe.remoting package
+	haxe.io.Bytes serialization support
 
 
 2008-04-05: 1.19
 2008-04-05: 1.19
 	fixed flash9 Array.toString
 	fixed flash9 Array.toString

+ 1 - 1
genswf9.ml

@@ -267,7 +267,7 @@ let property p t =
 		(match p with
 		(match p with
 		| "length" (* Int in AS3/haXe *) -> ident p, None, false
 		| "length" (* Int in AS3/haXe *) -> ident p, None, false
 		| "charCodeAt" (* use haXe version *) -> ident p, None, true
 		| "charCodeAt" (* use haXe version *) -> ident p, None, true
-		| "__charCodeAt" -> as3 "charCodeAt", Some KInt, false
+		| "cca" -> as3 "charCodeAt", Some KInt, false
 		| _ -> as3 p, None, false);
 		| _ -> as3 p, None, false);
 	| TAnon a ->
 	| TAnon a ->
 		(match !(a.a_status) with
 		(match !(a.a_status) with

+ 34 - 17
std/haxe/Serializer.hx

@@ -39,6 +39,8 @@ class Serializer {
 	**/
 	**/
 	public static var USE_ENUM_INDEX = false;
 	public static var USE_ENUM_INDEX = false;
 
 
+	static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
+
 	var buf : StringBuf;
 	var buf : StringBuf;
 	var cache : Array<Dynamic>;
 	var cache : Array<Dynamic>;
 	var shash : Hash<Int>;
 	var shash : Hash<Int>;
@@ -78,7 +80,7 @@ class Serializer {
 		p : +Inf
 		p : +Inf
 		q : inthash
 		q : inthash
 		r : reference
 		r : reference
-		s :
+		s : bytes (base64)
 		t : true
 		t : true
 		u : array nulls
 		u : array nulls
 		v : date
 		v : date
@@ -241,24 +243,39 @@ class Serializer {
 					serialize(v.get(k));
 					serialize(v.get(k));
 				}
 				}
 				buf.add("h");
 				buf.add("h");
-			#if flash9
-			case cast flash.utils.ByteArray:
-				buf.add("y");
-				var s = "";
-				var b : flash.utils.ByteArray = v;
-				var CHARS = ["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"];
-				for( p in 0...b.length ) {
-					var c = b[p];
-					// 0-9a-zA-Z
-					if( (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) )
-						s += String.fromCharCode(c);
-					else
-						s += "%"+CHARS[c>>4]+CHARS[c&15];
+			case cast haxe.io.Bytes:
+				var v : haxe.io.Bytes = v;
+				#if neko
+				var chars = StringTools.baseEncode(new String(cast v.getData()),BASE64);
+				#else
+				var i = 0;
+				var max = v.length - 2;
+				var chars = "";
+				var b64 = BASE64;
+				while( i < max ) {
+					var b1 = v.get(i++);
+					var b2 = v.get(i++);
+					var b3 = v.get(i++);
+					chars += b64.charAt(b1 >> 2)
+						+ b64.charAt(((b1 << 4) | (b2 >> 4)) & 63)
+						+ b64.charAt(((b2 << 2) | (b3 >> 6)) & 63)
+						+ b64.charAt(b3 & 63);
+				}
+				if( i == max ) {
+					var b1 = v.get(i++);
+					var b2 = v.get(i++);
+					chars += b64.charAt(b1 >> 2)
+						+ b64.charAt(((b1 << 4) | (b2 >> 4)) & 63)
+						+ b64.charAt((b2 << 2) & 63);
+				} else if( i == max + 1 ) {
+					var b1 = v.get(i++);
+					chars += b64.charAt(b1 >> 2) + b64.charAt((b1 << 4) & 63);
 				}
 				}
-				buf.add(s.length);
+				#end
+				buf.add("s");
+				buf.add(chars.length);
 				buf.add(":");
 				buf.add(":");
-				buf.add(s);
-			#end
+				buf.add(chars);
 			default:
 			default:
 				cache.pop();
 				cache.pop();
 				buf.add("c");
 				buf.add("c");

+ 56 - 9
std/haxe/Unserializer.hx

@@ -34,6 +34,23 @@ class Unserializer {
 
 
 	public static var DEFAULT_RESOLVER : TypeResolver = Type;
 	public static var DEFAULT_RESOLVER : TypeResolver = Type;
 
 
+	static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
+	static var CODES = null;
+
+	static function initCodes() {
+		var codes =
+			#if flash9
+				new flash.utils.ByteArray();
+			#elseif neko
+				untyped __dollar__amake(BASE64.length);
+			#else
+				new Array();
+			#end
+		for( i in 0...BASE64.length )
+			codes[untyped BASE64.cca(i)] = i;
+		return codes;
+	}
+
  	var buf : String;
  	var buf : String;
  	var pos : Int;
  	var pos : Int;
  	var length : Int;
  	var length : Int;
@@ -167,6 +184,7 @@ class Unserializer {
  		case 112: // p
  		case 112: // p
  			return Math.POSITIVE_INFINITY;
  			return Math.POSITIVE_INFINITY;
  		case 97: // a
  		case 97: // a
+			var buf = buf;
  			var a = new Array<Dynamic>();
  			var a = new Array<Dynamic>();
  			cache.push(a);
  			cache.push(a);
  			while( true ) {
  			while( true ) {
@@ -228,12 +246,14 @@ class Unserializer {
 			return unserializeEnum(edecl,tag);
 			return unserializeEnum(edecl,tag);
 		case 108: // l
 		case 108: // l
 			var l = new List();
 			var l = new List();
+			var buf = buf;
 			while( buf.charCodeAt(pos) != 104 /*h*/ )
 			while( buf.charCodeAt(pos) != 104 /*h*/ )
 				l.add(unserialize());
 				l.add(unserialize());
 			pos++;
 			pos++;
 			return l;
 			return l;
 		case 98: // b
 		case 98: // b
 			var h = new Hash();
 			var h = new Hash();
+			var buf = buf;
 			while( buf.charCodeAt(pos) != 104 /*h*/ ) {
 			while( buf.charCodeAt(pos) != 104 /*h*/ ) {
 				var s = unserialize();
 				var s = unserialize();
 				h.set(s,unserialize());
 				h.set(s,unserialize());
@@ -242,6 +262,7 @@ class Unserializer {
 			return h;
 			return h;
 		case 113: // q
 		case 113: // q
 			var h = new IntHash();
 			var h = new IntHash();
+			var buf = buf;
 			var c = buf.charCodeAt(pos++);
 			var c = buf.charCodeAt(pos++);
 			while( c == 58 ) { /*:*/
 			while( c == 58 ) { /*:*/
 				var i = readDigits();
 				var i = readDigits();
@@ -255,21 +276,47 @@ class Unserializer {
 			var d = Date.fromString(buf.substr(pos,19));
 			var d = Date.fromString(buf.substr(pos,19));
 			pos += 19;
 			pos += 19;
 			return d;
 			return d;
-		// DEPRECATED
  		case 115: // s
  		case 115: // s
  			var len = readDigits();
  			var len = readDigits();
+			var buf = buf;
  			if( buf.charAt(pos++) != ":" || length - pos < len )
  			if( buf.charAt(pos++) != ":" || length - pos < len )
-				throw "Invalid string length";
+				throw "Invalid bytes length";
 			#if neko
 			#if neko
-			var s = neko.Utf8.sub(buf,pos-upos,len);
-			pos += s.length;
-			upos += s.length - len;
+			var str =  StringTools.baseDecode(buf.substr(pos,len),BASE64);
+			var bytes = untyped new haxe.io.Bytes(str.length,str.__s);
 			#else
 			#else
- 			var s = buf.substr(pos,len);
- 			pos += len;
+			var codes = CODES;
+			if( codes == null ) {
+				codes = initCodes();
+				CODES = codes;
+			}
+			var b = new haxe.io.BytesBuffer();
+			var i = pos;
+			var rest = len & 3;
+			var max = i + (len - rest);
+			while( i < max ) {
+				var c1 = codes[untyped buf.cca(i++)];
+				var c2 = codes[untyped buf.cca(i++)];
+				b.addByte((c1 << 2) | (c2 >> 4));
+				var c3 = codes[untyped buf.cca(i++)];
+				b.addByte(((c2 << 4) | (c3 >> 2)) #if !flash9 & 0xFF #end );
+				var c4 = codes[untyped buf.cca(i++)];
+				b.addByte(((c3 << 6) | c4) #if !flash9 & 0xFF #end );
+			}
+			if( rest >= 2 ) {
+				var c1 = codes[untyped buf.cca(i++)];
+				var c2 = codes[untyped buf.cca(i++)];
+				b.addByte((c1 << 2) | (c2 >> 4));
+				if( rest == 3 ) {
+					var c3 = codes[untyped buf.cca(i++)];
+					b.addByte(((c2 << 4) | (c3 >> 2)) #if !flash9 & 0xFF #end );
+				}
+			}
+			var bytes = b.getBytes();
  			#end
  			#end
-			scache.push(s);
-			return s;
+			pos += len;
+			cache.push(bytes);
+			return bytes;
  		default:
  		default:
  		}
  		}
  		pos--;
  		pos--;

+ 23 - 0
tests/unit/MyClass.hx

@@ -0,0 +1,23 @@
+package unit;
+
+class MyClass {
+
+	var val : Int;
+
+	public var ref : MyClass;
+	public var intValue : Int;
+	public var stringValue : String;
+
+	public function new(v) {
+		val = v;
+	}
+
+	public function get() {
+		return val;
+	}
+
+	public function set(v) {
+		val = v;
+	}
+
+}

+ 8 - 0
tests/unit/MyEnum.hx

@@ -0,0 +1,8 @@
+package unit;
+
+enum MyEnum {
+	A;
+	B;
+	C( a : Int, b : String );
+	D( e : MyEnum );
+}

+ 10 - 0
tests/unit/Test.hx

@@ -10,6 +10,14 @@ class Test {
 		if( v != v2 ) report(v+" should be "+v2,pos);
 		if( v != v2 ) report(v+" should be "+v2,pos);
 	}
 	}
 
 
+	function t( v, ?pos ) {
+		eq(v,true,pos);
+	}
+
+	function f( v, ?pos ) {
+		eq(v,false,pos);
+	}
+
 	function exc( f : Void -> Void, ?pos : haxe.PosInfos ) {
 	function exc( f : Void -> Void, ?pos : haxe.PosInfos ) {
 		count++;
 		count++;
 		try {
 		try {
@@ -144,6 +152,7 @@ class Test {
 			new TestBytes(),
 			new TestBytes(),
 			new TestInt32(),
 			new TestInt32(),
 			new TestIO(),
 			new TestIO(),
+			new TestSerialize(),
 			new TestRemoting(),
 			new TestRemoting(),
 		];
 		];
 		var current = null;
 		var current = null;
@@ -160,6 +169,7 @@ class Test {
 			asyncWaits.remove(null);
 			asyncWaits.remove(null);
 			checkDone();
 			checkDone();
 		} catch( e : Dynamic ) {
 		} catch( e : Dynamic ) {
+			asyncWaits.remove(null);
 			reportInfos = null;
 			reportInfos = null;
 			var msg = "???";
 			var msg = "???";
 			var stack = haxe.Stack.toString(haxe.Stack.exceptionStack());
 			var stack = haxe.Stack.toString(haxe.Stack.exceptionStack());

+ 128 - 0
tests/unit/TestSerialize.hx

@@ -0,0 +1,128 @@
+package unit;
+
+class TestSerialize extends Test {
+
+	function id<T>( v : T ) : T {
+		return haxe.Unserializer.run(haxe.Serializer.run(v));
+	}
+
+	function test() {
+		// basic types
+		for( v in [null,true,false,0,1,1506,-0xABCDEF,12.3,-1e10,Math.POSITIVE_INFINITY,Math.NEGATIVE_INFINITY,"hello","éé","\r\n","\n","   ",""] )
+			eq( id(v), v );
+		t( Math.isNaN(id(Math.NaN)) );
+
+		// array/list
+		doTestCollection([]);
+		doTestCollection([1,2,4,5]);
+		doTestCollection([1,2,null,null,null,null,null,4,5]);
+
+		// date
+		var d = Date.now();
+		var d2 = id(d);
+		t( Std.is(d2,Date) );
+		eq( d2.toString(), d.toString() );
+
+		// object
+		var o = { x : "a", y : -1.56, z : "hello" };
+		var o2 = id(o);
+		eq(o.x,o2.x);
+		eq(o.y,o2.y);
+		eq(o.z,o2.z);
+
+		// class instance
+		var c = new MyClass(999);
+		c.intValue = 33;
+		c.stringValue = "Hello";
+		var c2 = id(c);
+		t( Std.is(c2,MyClass) );
+		f( c == c2 );
+		eq( c2.intValue, c.intValue );
+		eq( c2.stringValue, c.stringValue );
+		eq( c2.get(), 999 );
+
+		// enums
+		haxe.Serializer.USE_ENUM_INDEX = false;
+		doTestEnums();
+		haxe.Serializer.USE_ENUM_INDEX = true;
+		doTestEnums();
+
+		// hash
+		var h = new Hash();
+		h.set("keya",2);
+		h.set("kéyb",-465);
+		var h2 = id(h);
+		t( Std.is(h2,Hash) );
+		eq( h2.get("keya"), 2 );
+		eq( h2.get("kéyb"), -465 );
+		eq( Lambda.count(h2), 2 );
+
+		// inthash
+		var h = new IntHash();
+		h.set(55,2);
+		h.set(-101,-465);
+		var h2 = id(h);
+		t( Std.is(h2,IntHash) );
+		eq( h2.get(55), 2 );
+		eq( h2.get(-101), -465 );
+		eq( Lambda.count(h2), 2 );
+
+		// bytes
+		doTestBytes(haxe.io.Bytes.alloc(0));
+		doTestBytes(haxe.io.Bytes.ofString("A"));
+		doTestBytes(haxe.io.Bytes.ofString("AB"));
+		doTestBytes(haxe.io.Bytes.ofString("ABC"));
+		doTestBytes(haxe.io.Bytes.ofString("ABCD"));
+		doTestBytes(haxe.io.Bytes.ofString("héllé"));
+		var b = haxe.io.Bytes.alloc(100);
+		for( i in 0...b.length )
+			b.set(i,i%10);
+		doTestBytes(b);
+
+		// recursivity
+		c.ref = c;
+		haxe.Serializer.USE_CACHE = true;
+		var c2 = id(c);
+		haxe.Serializer.USE_CACHE = false;
+		eq( c2.ref, c2 );
+
+	}
+
+	function doTestEnums() {
+		eq( id(MyEnum.A), MyEnum.A );
+		eq( id(MyEnum.B), MyEnum.B );
+		var c = MyEnum.C(0,"hello");
+		t( Type.enumEq( id(c), c ) );
+		t( Type.enumEq( id(MyEnum.D(MyEnum.D(c))), MyEnum.D(MyEnum.D(c)) ) );
+		t( Std.is(id(c),MyEnum) );
+		t(switch( id(c) ) {
+			case C(_,_): true;
+			default: false;
+		});
+	}
+
+	function doTestCollection( a : Array<Dynamic> ) {
+		var a2 = id(a);
+		eq( a2.length, a.length );
+		for( i in 0...a.length )
+			eq( a2[i], a[i] );
+		var l = Lambda.list(a);
+		var l2 = id(l);
+		t( Std.is(l2,List) );
+		eq( l2.length, l.length );
+		var it = l.iterator();
+		for( x in l2 )
+			eq( x, it.next() );
+		f( it.hasNext() );
+	}
+
+	function doTestBytes( b : haxe.io.Bytes ) {
+		var b2 = id(b);
+		t( Std.is(b2,haxe.io.Bytes) );
+		eq( b2.length, b.length );
+		for( i in 0...b.length )
+			eq( b2.get(i), b.get(i) );
+		infos(null);
+	}
+
+}

+ 3 - 0
tests/unit/unit.hxp

@@ -5,6 +5,8 @@
   <output name="Neko" mode="neko" out="unit.n" class="unit.Test" lib="" cmd="" main="True" debug="True">-cp ..</output>
   <output name="Neko" mode="neko" out="unit.n" class="unit.Test" lib="" cmd="" main="True" debug="True">-cp ..</output>
   <output name="RemotingServer" mode="neko" out="remoting.n" class="unit.RemotingServer" lib="" cmd="" main="True" debug="False">-cp ..</output>
   <output name="RemotingServer" mode="neko" out="remoting.n" class="unit.RemotingServer" lib="" cmd="" main="True" debug="False">-cp ..</output>
   <files path="/">
   <files path="/">
+    <file path="MyClass.hx" />
+    <file path="MyEnum.hx" />
     <file path="RemotingApi.hx" />
     <file path="RemotingApi.hx" />
     <file path="RemotingServer.hx" />
     <file path="RemotingServer.hx" />
     <file path="Test.hx" />
     <file path="Test.hx" />
@@ -12,5 +14,6 @@
     <file path="TestInt32.hx" />
     <file path="TestInt32.hx" />
     <file path="TestIO.hx" />
     <file path="TestIO.hx" />
     <file path="TestRemoting.hx" />
     <file path="TestRemoting.hx" />
+    <file path="TestSerialize.hx" />
   </files>
   </files>
 </haxe>
 </haxe>