Browse Source

haxe.io.Bytes serialization support

Nicolas Cannasse 17 years ago
parent
commit
a36be2529c
9 changed files with 265 additions and 27 deletions
  1. 2 0
      doc/CHANGES.txt
  2. 1 1
      genswf9.ml
  3. 34 17
      std/haxe/Serializer.hx
  4. 56 9
      std/haxe/Unserializer.hx
  5. 23 0
      tests/unit/MyClass.hx
  6. 8 0
      tests/unit/MyEnum.hx
  7. 10 0
      tests/unit/Test.hx
  8. 128 0
      tests/unit/TestSerialize.hx
  9. 3 0
      tests/unit/unit.hxp

+ 2 - 0
doc/CHANGES.txt

@@ -30,9 +30,11 @@ TODO inlining : substitute class+function type parameters in order to have fully
 	added haxe.Int32
 	removed neko.Int32
 	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
 	fixed big bug in js/flash8 debug stack handling
 	complete rewrite of haxe.remoting package
+	haxe.io.Bytes serialization support
 
 2008-04-05: 1.19
 	fixed flash9 Array.toString

+ 1 - 1
genswf9.ml

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

+ 34 - 17
std/haxe/Serializer.hx

@@ -39,6 +39,8 @@ class Serializer {
 	**/
 	public static var USE_ENUM_INDEX = false;
 
+	static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:";
+
 	var buf : StringBuf;
 	var cache : Array<Dynamic>;
 	var shash : Hash<Int>;
@@ -78,7 +80,7 @@ class Serializer {
 		p : +Inf
 		q : inthash
 		r : reference
-		s :
+		s : bytes (base64)
 		t : true
 		u : array nulls
 		v : date
@@ -241,24 +243,39 @@ class Serializer {
 					serialize(v.get(k));
 				}
 				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(s);
-			#end
+				buf.add(chars);
 			default:
 				cache.pop();
 				buf.add("c");

+ 56 - 9
std/haxe/Unserializer.hx

@@ -34,6 +34,23 @@ class Unserializer {
 
 	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 pos : Int;
  	var length : Int;
@@ -167,6 +184,7 @@ class Unserializer {
  		case 112: // p
  			return Math.POSITIVE_INFINITY;
  		case 97: // a
+			var buf = buf;
  			var a = new Array<Dynamic>();
  			cache.push(a);
  			while( true ) {
@@ -228,12 +246,14 @@ class Unserializer {
 			return unserializeEnum(edecl,tag);
 		case 108: // l
 			var l = new List();
+			var buf = buf;
 			while( buf.charCodeAt(pos) != 104 /*h*/ )
 				l.add(unserialize());
 			pos++;
 			return l;
 		case 98: // b
 			var h = new Hash();
+			var buf = buf;
 			while( buf.charCodeAt(pos) != 104 /*h*/ ) {
 				var s = unserialize();
 				h.set(s,unserialize());
@@ -242,6 +262,7 @@ class Unserializer {
 			return h;
 		case 113: // q
 			var h = new IntHash();
+			var buf = buf;
 			var c = buf.charCodeAt(pos++);
 			while( c == 58 ) { /*:*/
 				var i = readDigits();
@@ -255,21 +276,47 @@ class Unserializer {
 			var d = Date.fromString(buf.substr(pos,19));
 			pos += 19;
 			return d;
-		// DEPRECATED
  		case 115: // s
  			var len = readDigits();
+			var buf = buf;
  			if( buf.charAt(pos++) != ":" || length - pos < len )
-				throw "Invalid string length";
+				throw "Invalid bytes length";
 			#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
- 			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
-			scache.push(s);
-			return s;
+			pos += len;
+			cache.push(bytes);
+			return bytes;
  		default:
  		}
  		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);
 	}
 
+	function t( v, ?pos ) {
+		eq(v,true,pos);
+	}
+
+	function f( v, ?pos ) {
+		eq(v,false,pos);
+	}
+
 	function exc( f : Void -> Void, ?pos : haxe.PosInfos ) {
 		count++;
 		try {
@@ -144,6 +152,7 @@ class Test {
 			new TestBytes(),
 			new TestInt32(),
 			new TestIO(),
+			new TestSerialize(),
 			new TestRemoting(),
 		];
 		var current = null;
@@ -160,6 +169,7 @@ class Test {
 			asyncWaits.remove(null);
 			checkDone();
 		} catch( e : Dynamic ) {
+			asyncWaits.remove(null);
 			reportInfos = null;
 			var msg = "???";
 			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="RemotingServer" mode="neko" out="remoting.n" class="unit.RemotingServer" lib="" cmd="" main="True" debug="False">-cp ..</output>
   <files path="/">
+    <file path="MyClass.hx" />
+    <file path="MyEnum.hx" />
     <file path="RemotingApi.hx" />
     <file path="RemotingServer.hx" />
     <file path="Test.hx" />
@@ -12,5 +14,6 @@
     <file path="TestInt32.hx" />
     <file path="TestIO.hx" />
     <file path="TestRemoting.hx" />
+    <file path="TestSerialize.hx" />
   </files>
 </haxe>