592 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			592 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | /* | ||
|  |  * Copyright (C)2005-2019 Haxe Foundation | ||
|  |  * | ||
|  |  * Permission is hereby granted, free of charge, to any person obtaining a | ||
|  |  * copy of this software and associated documentation files (the "Software"), | ||
|  |  * to deal in the Software without restriction, including without limitation | ||
|  |  * the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||
|  |  * and/or sell copies of the Software, and to permit persons to whom the | ||
|  |  * Software is furnished to do so, subject to the following conditions: | ||
|  |  * | ||
|  |  * The above copyright notice and this permission notice shall be included in | ||
|  |  * all copies or substantial portions of the Software. | ||
|  |  * | ||
|  |  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
|  |  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
|  |  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
|  |  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
|  |  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||
|  |  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
|  |  * DEALINGS IN THE SOFTWARE. | ||
|  |  */ | ||
|  | 
 | ||
|  | package haxe; | ||
|  | 
 | ||
|  | import haxe.ds.List; | ||
|  | 
 | ||
|  | /** | ||
|  | 	The Serializer class can be used to encode values and objects into a `String`, | ||
|  | 	from which the `Unserializer` class can recreate the original representation. | ||
|  | 
 | ||
|  | 	This class can be used in two ways: | ||
|  | 
 | ||
|  | 	- create a `new Serializer()` instance, call its `serialize()` method with | ||
|  | 		any argument and finally retrieve the String representation from | ||
|  | 		`toString()` | ||
|  | 	- call `Serializer.run()` to obtain the serialized representation of a | ||
|  | 		single argument | ||
|  | 
 | ||
|  | 	Serialization is guaranteed to work for all haxe-defined classes, but may | ||
|  | 	or may not work for instances of external/native classes. | ||
|  | 
 | ||
|  | 	The specification of the serialization format can be found here: | ||
|  | 	<https://haxe.org/manual/std-serialization-format.html> | ||
|  | **/ | ||
|  | class Serializer { | ||
|  | 	/** | ||
|  | 		If the values you are serializing can contain circular references or | ||
|  | 		objects repetitions, you should set `USE_CACHE` to true to prevent | ||
|  | 		infinite loops. | ||
|  | 
 | ||
|  | 		This may also reduce the size of serialization Strings at the expense of | ||
|  | 		performance. | ||
|  | 
 | ||
|  | 		This value can be changed for individual instances of `Serializer` by | ||
|  | 		setting their `useCache` field. | ||
|  | 	**/ | ||
|  | 	public static var USE_CACHE = false; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Use constructor indexes for enums instead of names. | ||
|  | 
 | ||
|  | 		This may reduce the size of serialization Strings, but makes them less | ||
|  | 		suited for long-term storage: If constructors are removed or added from | ||
|  | 		the enum, the indices may no longer match. | ||
|  | 
 | ||
|  | 		This value can be changed for individual instances of `Serializer` by | ||
|  | 		setting their `useEnumIndex` field. | ||
|  | 	**/ | ||
|  | 	public static var USE_ENUM_INDEX = false; | ||
|  | 
 | ||
|  | 	static var BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789%:"; | ||
|  | 	static var BASE64_CODES = null; | ||
|  | 
 | ||
|  | 	var buf:StringBuf; | ||
|  | 	var cache:Array<Dynamic>; | ||
|  | 	var shash:haxe.ds.StringMap<Int>; | ||
|  | 	var scount:Int; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		The individual cache setting for `this` Serializer instance. | ||
|  | 
 | ||
|  | 		See `USE_CACHE` for a complete description. | ||
|  | 	**/ | ||
|  | 	public var useCache:Bool; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		The individual enum index setting for `this` Serializer instance. | ||
|  | 
 | ||
|  | 		See `USE_ENUM_INDEX` for a complete description. | ||
|  | 	**/ | ||
|  | 	public var useEnumIndex:Bool; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Creates a new Serializer instance. | ||
|  | 
 | ||
|  | 		Subsequent calls to `this.serialize` will append values to the | ||
|  | 		internal buffer of this String. Once complete, the contents can be | ||
|  | 		retrieved through a call to `this.toString`. | ||
|  | 
 | ||
|  | 		Each `Serializer` instance maintains its own cache if `this.useCache` is | ||
|  | 		`true`. | ||
|  | 	**/ | ||
|  | 	public function new() { | ||
|  | 		buf = new StringBuf(); | ||
|  | 		cache = new Array(); | ||
|  | 		useCache = USE_CACHE; | ||
|  | 		useEnumIndex = USE_ENUM_INDEX; | ||
|  | 		shash = new haxe.ds.StringMap(); | ||
|  | 		scount = 0; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Return the String representation of `this` Serializer. | ||
|  | 
 | ||
|  | 		The exact format specification can be found here: | ||
|  | 		https://haxe.org/manual/serialization/format | ||
|  | 	**/ | ||
|  | 	public function toString() { | ||
|  | 		return buf.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/* prefixes : | ||
|  | 		a : array | ||
|  | 		b : hash | ||
|  | 		c : class | ||
|  | 		d : Float | ||
|  | 		e : reserved (float exp) | ||
|  | 		f : false | ||
|  | 		g : object end | ||
|  | 		h : array/list/hash end | ||
|  | 		i : Int | ||
|  | 		j : enum (by index) | ||
|  | 		k : NaN | ||
|  | 		l : list | ||
|  | 		m : -Inf | ||
|  | 		n : null | ||
|  | 		o : object | ||
|  | 		p : +Inf | ||
|  | 		q : haxe.ds.IntMap | ||
|  | 		r : reference | ||
|  | 		s : bytes (base64) | ||
|  | 		t : true | ||
|  | 		u : array nulls | ||
|  | 		v : date | ||
|  | 		w : enum | ||
|  | 		x : exception | ||
|  | 		y : urlencoded string | ||
|  | 		z : zero | ||
|  | 		A : Class<Dynamic> | ||
|  | 		B : Enum<Dynamic> | ||
|  | 		M : haxe.ds.ObjectMap | ||
|  | 		C : custom | ||
|  | 	 */ | ||
|  | 	function serializeString(s:String) { | ||
|  | 		var x = shash.get(s); | ||
|  | 		if (x != null) { | ||
|  | 			buf.add("R"); | ||
|  | 			buf.add(x); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		shash.set(s, scount++); | ||
|  | 		#if old_serialize | ||
|  | 		// no more support for -D old_serialize due to 'j' reuse | ||
|  | 		#if error | ||
|  | 		#end | ||
|  | 		#end | ||
|  | 		buf.add("y"); | ||
|  | 		s = StringTools.urlEncode(s); | ||
|  | 		buf.add(s.length); | ||
|  | 		buf.add(":"); | ||
|  | 		buf.add(s); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function serializeRef(v:Dynamic) { | ||
|  | 		#if js | ||
|  | 		var vt = js.Syntax.typeof(v); | ||
|  | 		#end | ||
|  | 		for (i in 0...cache.length) { | ||
|  | 			#if js | ||
|  | 			var ci = cache[i]; | ||
|  | 			if (js.Syntax.typeof(ci) == vt && ci == v) { | ||
|  | 			#else | ||
|  | 			if (cache[i] == v) { | ||
|  | 			#end | ||
|  | 				buf.add("r"); | ||
|  | 				buf.add(i); | ||
|  | 				return true; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		cache.push(v); | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	#if flash | ||
|  | 	// only the instance variables | ||
|  | 
 | ||
|  | 	function serializeClassFields(v:Dynamic, c:Dynamic) { | ||
|  | 		var xml:flash.xml.XML = untyped __global__["flash.utils.describeType"](c); | ||
|  | 		var vars = xml.factory[0].child("variable"); | ||
|  | 		for (i in 0...vars.length()) { | ||
|  | 			var f = vars[i].attribute("name").toString(); | ||
|  | 			if (!v.hasOwnProperty(f)) | ||
|  | 				continue; | ||
|  | 			serializeString(f); | ||
|  | 			serialize(Reflect.field(v, f)); | ||
|  | 		} | ||
|  | 		buf.add("g"); | ||
|  | 	} | ||
|  | 	#end | ||
|  | 
 | ||
|  | 	function serializeFields(v:{}) { | ||
|  | 		for (f in Reflect.fields(v)) { | ||
|  | 			serializeString(f); | ||
|  | 			serialize(Reflect.field(v, f)); | ||
|  | 		} | ||
|  | 		buf.add("g"); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	Serializes `v`. | ||
|  | 
 | ||
|  | 	All haxe-defined values and objects with the exception of functions can | ||
|  | 	be serialized. Serialization of external/native objects is not | ||
|  | 	guaranteed to work. | ||
|  | 
 | ||
|  | 	The values of `this.useCache` and `this.useEnumIndex` may affect | ||
|  | 	serialization output. | ||
|  | **/ | ||
|  | 	public function serialize(v:Dynamic) { | ||
|  | 		switch (Type.typeof(v)) { | ||
|  | 			case TNull: | ||
|  | 				buf.add("n"); | ||
|  | 			case TInt: | ||
|  | 				var v:Int = v; | ||
|  | 				if (v == 0) { | ||
|  | 					buf.add("z"); | ||
|  | 					return; | ||
|  | 				} | ||
|  | 				buf.add("i"); | ||
|  | 				buf.add(v); | ||
|  | 			case TFloat: | ||
|  | 				var v:Float = v; | ||
|  | 				if (Math.isNaN(v)) | ||
|  | 					buf.add("k"); | ||
|  | 				else if (!Math.isFinite(v)) | ||
|  | 					buf.add(if (v < 0) "m" else "p"); | ||
|  | 				else { | ||
|  | 					buf.add("d"); | ||
|  | 					buf.add(v); | ||
|  | 				} | ||
|  | 			case TBool: | ||
|  | 				buf.add(if (v) "t" else "f"); | ||
|  | 			case TClass(c): | ||
|  | 				if (#if neko untyped c.__is_String #else c == String #end) { | ||
|  | 					serializeString(v); | ||
|  | 					return; | ||
|  | 				} | ||
|  | 				if (useCache && serializeRef(v)) | ||
|  | 					return; | ||
|  | 				switch (#if (neko || cs || python) Type.getClassName(c) #else c #end) { | ||
|  | 					case #if (neko || cs || python) "Array" #else cast Array #end: | ||
|  | 						var ucount = 0; | ||
|  | 						buf.add("a"); | ||
|  | 						#if (flash || python || hl) | ||
|  | 						var v:Array<Dynamic> = v; | ||
|  | 						#end | ||
|  | 						var l = #if (neko || flash || php || cs || java || python || hl || lua || eval) v.length #elseif cpp v.__length() #else __getField(v, | ||
|  | 							"length") #end; | ||
|  | 						for (i in 0...l) { | ||
|  | 							if (v[i] == null) | ||
|  | 								ucount++; | ||
|  | 							else { | ||
|  | 								if (ucount > 0) { | ||
|  | 									if (ucount == 1) | ||
|  | 										buf.add("n"); | ||
|  | 									else { | ||
|  | 										buf.add("u"); | ||
|  | 										buf.add(ucount); | ||
|  | 									} | ||
|  | 									ucount = 0; | ||
|  | 								} | ||
|  | 								serialize(v[i]); | ||
|  | 							} | ||
|  | 						} | ||
|  | 						if (ucount > 0) { | ||
|  | 							if (ucount == 1) | ||
|  | 								buf.add("n"); | ||
|  | 							else { | ||
|  | 								buf.add("u"); | ||
|  | 								buf.add(ucount); | ||
|  | 							} | ||
|  | 						} | ||
|  | 						buf.add("h"); | ||
|  | 					case #if (neko || cs || python) "haxe.ds.List" #else cast List #end: | ||
|  | 						buf.add("l"); | ||
|  | 						var v:List<Dynamic> = v; | ||
|  | 						for (i in v) | ||
|  | 							serialize(i); | ||
|  | 						buf.add("h"); | ||
|  | 					case #if (neko || cs || python) "Date" #else cast Date #end: | ||
|  | 						var d:Date = v; | ||
|  | 						buf.add("v"); | ||
|  | 						buf.add(d.getTime()); | ||
|  | 					case #if (neko || cs || python) "haxe.ds.StringMap" #else cast haxe.ds.StringMap #end: | ||
|  | 						buf.add("b"); | ||
|  | 						var v:haxe.ds.StringMap<Dynamic> = v; | ||
|  | 						for (k in v.keys()) { | ||
|  | 							serializeString(k); | ||
|  | 							serialize(v.get(k)); | ||
|  | 						} | ||
|  | 						buf.add("h"); | ||
|  | 					case #if (neko || cs || python) "haxe.ds.IntMap" #else cast haxe.ds.IntMap #end: | ||
|  | 						buf.add("q"); | ||
|  | 						var v:haxe.ds.IntMap<Dynamic> = v; | ||
|  | 						for (k in v.keys()) { | ||
|  | 							buf.add(":"); | ||
|  | 							buf.add(k); | ||
|  | 							serialize(v.get(k)); | ||
|  | 						} | ||
|  | 						buf.add("h"); | ||
|  | 					case #if (neko || cs || python) "haxe.ds.ObjectMap" #else cast haxe.ds.ObjectMap #end: | ||
|  | 						buf.add("M"); | ||
|  | 						var v:haxe.ds.ObjectMap<Dynamic, Dynamic> = v; | ||
|  | 						for (k in v.keys()) { | ||
|  | 							#if (js || neko) | ||
|  | 							var id = Reflect.field(k, "__id__"); | ||
|  | 							Reflect.deleteField(k, "__id__"); | ||
|  | 							serialize(k); | ||
|  | 							Reflect.setField(k, "__id__", id); | ||
|  | 							#else | ||
|  | 							serialize(k); | ||
|  | 							#end | ||
|  | 							serialize(v.get(k)); | ||
|  | 						} | ||
|  | 						buf.add("h"); | ||
|  | 					case #if (neko || cs || python) "haxe.io.Bytes" #else cast haxe.io.Bytes #end: | ||
|  | 						var v:haxe.io.Bytes = v; | ||
|  | 						#if neko | ||
|  | 						var chars = new String(base_encode(v.getData(), untyped BASE64.__s)); | ||
|  | 						buf.add("s"); | ||
|  | 						buf.add(chars.length); | ||
|  | 						buf.add(":"); | ||
|  | 						buf.add(chars); | ||
|  | 						#elseif php | ||
|  | 						var chars = new String(php.Global.base64_encode(v.getData())); | ||
|  | 						chars = php.Global.strtr(chars, '+/', '%:'); | ||
|  | 						buf.add("s"); | ||
|  | 						buf.add(chars.length); | ||
|  | 						buf.add(":"); | ||
|  | 						buf.add(chars); | ||
|  | 						#else | ||
|  | 						buf.add("s"); | ||
|  | 						buf.add(Math.ceil((v.length * 8) / 6)); | ||
|  | 						buf.add(":"); | ||
|  | 
 | ||
|  | 						var i = 0; | ||
|  | 						var max = v.length - 2; | ||
|  | 						var b64 = BASE64_CODES; | ||
|  | 						if (b64 == null) { | ||
|  | 							b64 = new haxe.ds.Vector(BASE64.length); | ||
|  | 							for (i in 0...BASE64.length) | ||
|  | 								b64[i] = BASE64.charCodeAt(i); | ||
|  | 							BASE64_CODES = b64; | ||
|  | 						} | ||
|  | 						while (i < max) { | ||
|  | 							var b1 = v.get(i++); | ||
|  | 							var b2 = v.get(i++); | ||
|  | 							var b3 = v.get(i++); | ||
|  | 
 | ||
|  | 							buf.addChar(b64[b1 >> 2]); | ||
|  | 							buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); | ||
|  | 							buf.addChar(b64[((b2 << 2) | (b3 >> 6)) & 63]); | ||
|  | 							buf.addChar(b64[b3 & 63]); | ||
|  | 						} | ||
|  | 						if (i == max) { | ||
|  | 							var b1 = v.get(i++); | ||
|  | 							var b2 = v.get(i++); | ||
|  | 							buf.addChar(b64[b1 >> 2]); | ||
|  | 							buf.addChar(b64[((b1 << 4) | (b2 >> 4)) & 63]); | ||
|  | 							buf.addChar(b64[(b2 << 2) & 63]); | ||
|  | 						} else if (i == max + 1) { | ||
|  | 							var b1 = v.get(i++); | ||
|  | 							buf.addChar(b64[b1 >> 2]); | ||
|  | 							buf.addChar(b64[(b1 << 4) & 63]); | ||
|  | 						} | ||
|  | 						#end | ||
|  | 					default: | ||
|  | 						if (useCache) cache.pop(); | ||
|  | 						if (#if flash try | ||
|  | 							v.hxSerialize != null | ||
|  | 						catch (e:Dynamic) | ||
|  | 							false #elseif (cs || java || python) Reflect.hasField(v, | ||
|  | 								"hxSerialize") #elseif php php.Global.method_exists(v, 'hxSerialize') #else v.hxSerialize != null #end) { | ||
|  | 							buf.add("C"); | ||
|  | 							serializeString(Type.getClassName(c)); | ||
|  | 							if (useCache) | ||
|  | 								cache.push(v); | ||
|  | 							v.hxSerialize(this); | ||
|  | 							buf.add("g"); | ||
|  | 						} else { | ||
|  | 							buf.add("c"); | ||
|  | 							serializeString(Type.getClassName(c)); | ||
|  | 							if (useCache) | ||
|  | 								cache.push(v); | ||
|  | 							#if flash | ||
|  | 							serializeClassFields(v, c); | ||
|  | 							#else | ||
|  | 							serializeFields(v); | ||
|  | 							#end | ||
|  | 						} | ||
|  | 				} | ||
|  | 			case TObject: | ||
|  | 				if (Std.isOfType(v, Class)) { | ||
|  | 					var className = Type.getClassName(v); | ||
|  | 					#if (flash || cpp) | ||
|  | 					// Currently, Enum and Class are the same for flash and cpp. | ||
|  | 					//  use resolveEnum to test if it is actually an enum | ||
|  | 					if (Type.resolveEnum(className) != null) | ||
|  | 						buf.add("B") | ||
|  | 					else | ||
|  | 					#end | ||
|  | 					buf.add("A"); | ||
|  | 					serializeString(className); | ||
|  | 				} else if (Std.isOfType(v, Enum)) { | ||
|  | 					buf.add("B"); | ||
|  | 					serializeString(Type.getEnumName(v)); | ||
|  | 				} else { | ||
|  | 					if (useCache && serializeRef(v)) | ||
|  | 						return; | ||
|  | 					buf.add("o"); | ||
|  | 					serializeFields(v); | ||
|  | 				} | ||
|  | 			case TEnum(e): | ||
|  | 				if (useCache) { | ||
|  | 					if (serializeRef(v)) | ||
|  | 						return; | ||
|  | 					cache.pop(); | ||
|  | 				} | ||
|  | 				buf.add(useEnumIndex ? "j" : "w"); | ||
|  | 				serializeString(Type.getEnumName(e)); | ||
|  | 				#if neko | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(v.index); | ||
|  | 				} else | ||
|  | 					serializeString(new String(v.tag)); | ||
|  | 				buf.add(":"); | ||
|  | 				if (v.args == null) | ||
|  | 					buf.add(0); | ||
|  | 				else { | ||
|  | 					var l:Int = untyped __dollar__asize(v.args); | ||
|  | 					buf.add(l); | ||
|  | 					for (i in 0...l) | ||
|  | 						serialize(v.args[i]); | ||
|  | 				} | ||
|  | 				#elseif flash | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					var i:Int = v.index; | ||
|  | 					buf.add(i); | ||
|  | 				} else | ||
|  | 					serializeString(v.tag); | ||
|  | 				buf.add(":"); | ||
|  | 				var pl:Array<Dynamic> = v.params; | ||
|  | 				if (pl == null) | ||
|  | 					buf.add(0); | ||
|  | 				else { | ||
|  | 					buf.add(pl.length); | ||
|  | 					for (p in pl) | ||
|  | 						serialize(p); | ||
|  | 				} | ||
|  | 				#elseif cpp | ||
|  | 				var enumBase:cpp.EnumBase = v; | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(enumBase.getIndex()); | ||
|  | 				} else | ||
|  | 					serializeString(enumBase.getTag()); | ||
|  | 				buf.add(":"); | ||
|  | 				var len = enumBase.getParamCount(); | ||
|  | 				buf.add(len); | ||
|  | 				for (p in 0...len) | ||
|  | 					serialize(enumBase.getParamI(p)); | ||
|  | 				#elseif php | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(v.index); | ||
|  | 				} else | ||
|  | 					serializeString(v.tag); | ||
|  | 				buf.add(":"); | ||
|  | 				var l:Int = php.Syntax.code("count({0})", v.params); | ||
|  | 				if (l == 0 || v.params == null) | ||
|  | 					buf.add(0); | ||
|  | 				else { | ||
|  | 					buf.add(l); | ||
|  | 					for (i in 0...l) { | ||
|  | 						#if php | ||
|  | 						serialize(v.params[i]); | ||
|  | 						#end | ||
|  | 					} | ||
|  | 				} | ||
|  | 				#elseif (java || cs || python || hl || eval) | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(Type.enumIndex(v)); | ||
|  | 				} else | ||
|  | 					serializeString(Type.enumConstructor(v)); | ||
|  | 				buf.add(":"); | ||
|  | 				var arr:Array<Dynamic> = Type.enumParameters(v); | ||
|  | 				if (arr != null) { | ||
|  | 					buf.add(arr.length); | ||
|  | 					for (v in arr) | ||
|  | 						serialize(v); | ||
|  | 				} else { | ||
|  | 					buf.add("0"); | ||
|  | 				} | ||
|  | 				#elseif (js && !js_enums_as_arrays) | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(v._hx_index); | ||
|  | 				} else | ||
|  | 					serializeString(Type.enumConstructor(v)); | ||
|  | 				buf.add(":"); | ||
|  | 				var params = Type.enumParameters(v); | ||
|  | 				buf.add(params.length); | ||
|  | 				for (p in params) | ||
|  | 					serialize(p); | ||
|  | 				#else | ||
|  | 				if (useEnumIndex) { | ||
|  | 					buf.add(":"); | ||
|  | 					buf.add(v[1]); | ||
|  | 				} else | ||
|  | 					serializeString(v[0]); | ||
|  | 				buf.add(":"); | ||
|  | 				var l = __getField(v, "length"); | ||
|  | 				buf.add(l - 2); | ||
|  | 				for (i in 2...l) | ||
|  | 					serialize(v[i]); | ||
|  | 				#end | ||
|  | 				if (useCache) | ||
|  | 					cache.push(v); | ||
|  | 			case TFunction: | ||
|  | 				throw "Cannot serialize function"; | ||
|  | 			default: | ||
|  | 				#if neko | ||
|  | 				if (untyped (__i32__kind != null && __dollar__iskind(v, __i32__kind))) { | ||
|  | 					buf.add("i"); | ||
|  | 					buf.add(v); | ||
|  | 					return; | ||
|  | 				} | ||
|  | 				#end | ||
|  | 				throw "Cannot serialize " + Std.string(v); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	extern inline function __getField(o:Dynamic, f:String):Dynamic | ||
|  | 		return o[cast f]; | ||
|  | 
 | ||
|  | 	public function serializeException(e:Dynamic) { | ||
|  | 		buf.add("x"); | ||
|  | 		#if flash | ||
|  | 		if (untyped __is__(e, __global__["Error"])) { | ||
|  | 			var e:flash.errors.Error = e; | ||
|  | 			var s = e.getStackTrace(); | ||
|  | 			if (s == null) | ||
|  | 				serialize(e.message); | ||
|  | 			else | ||
|  | 				serialize(s); | ||
|  | 			return; | ||
|  | 		} | ||
|  | 		#end | ||
|  | 		serialize(e); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 	Serializes `v` and returns the String representation. | ||
|  | 
 | ||
|  | 	This is a convenience function for creating a new instance of | ||
|  | 	Serializer, serialize `v` into it and obtain the result through a call | ||
|  | 	to `toString()`. | ||
|  | **/ | ||
|  | 	public static function run(v:Dynamic) { | ||
|  | 		var s = new Serializer(); | ||
|  | 		s.serialize(v); | ||
|  | 		return s.toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	#if neko | ||
|  | 	static var base_encode = neko.Lib.load("std", "base_encode", 2); | ||
|  | 	#end | ||
|  | } |