Browse Source

[js] Add compiler switch to generate enums as objects instead of arrays (#6350)

* add -D js_enums_as_objects switch

* add $hxEnums, change __enum__ to String

* whitespace

* resolve genjs.ml conflict

* add -D js-enums-as-objects to tests
ousado 8 years ago
parent
commit
5717b22f7a

+ 2 - 0
src/context/common.ml

@@ -499,6 +499,7 @@ module Define = struct
 		| JsEs
 		| JsEs
 		| JsUnflatten
 		| JsUnflatten
 		| JsSourceMap
 		| JsSourceMap
+		| JsEnumsAsObjects
 		| SourceMap
 		| SourceMap
 		| KeepOldOutput
 		| KeepOldOutput
 		| LoopUnrollMaxCost
 		| LoopUnrollMaxCost
@@ -603,6 +604,7 @@ module Define = struct
 		| JqueryVer -> "jquery_ver",("The jQuery version supported by js.jquery.*. The version is encoded as an interger. e.g. 1.11.3 is encoded as 11103",[Platform Js])
 		| JqueryVer -> "jquery_ver",("The jQuery version supported by js.jquery.*. The version is encoded as an interger. e.g. 1.11.3 is encoded as 11103",[Platform Js])
 		| JsClassic -> "js_classic",("Don't use a function wrapper and strict mode in JS output",[Platform Js])
 		| JsClassic -> "js_classic",("Don't use a function wrapper and strict mode in JS output",[Platform Js])
 		| JsEs -> "js_es",("Generate JS compilant with given ES standard version (default 5)",[Platform Js; HasParam "version number"])
 		| JsEs -> "js_es",("Generate JS compilant with given ES standard version (default 5)",[Platform Js; HasParam "version number"])
+		| JsEnumsAsObjects -> "js_enums_as_objects",("Generate enum representation as object instead of as array",[Platform Js])
 		| JsUnflatten -> "js_unflatten",("Generate nested objects for packages and types",[Platform Js])
 		| JsUnflatten -> "js_unflatten",("Generate nested objects for packages and types",[Platform Js])
 		| JsSourceMap -> "js_source_map",("Generate JavaScript source map even in non-debug mode",[Platform Js])
 		| JsSourceMap -> "js_source_map",("Generate JavaScript source map even in non-debug mode",[Platform Js])
 		| SourceMap -> "source_map",("Generate source map for compiled files (Currently supported for php7 only)",[Platform Php])
 		| SourceMap -> "source_map",("Generate source map for compiled files (Currently supported for php7 only)",[Platform Php])

+ 32 - 6
src/generators/genjs.ml

@@ -494,10 +494,17 @@ and gen_expr ctx e =
 			print ctx ",$bind($_,$_%s))" (if Meta.has Meta.SelfCall f.cf_meta then "" else (field f.cf_name)))
 			print ctx ",$bind($_,$_%s))" (if Meta.has Meta.SelfCall f.cf_meta then "" else (field f.cf_name)))
 	| TEnumIndex x ->
 	| TEnumIndex x ->
 		gen_value ctx x;
 		gen_value ctx x;
+		if Common.defined ctx.com Define.JsEnumsAsObjects then
+		print ctx "._hx_index"
+		else
 		print ctx "[1]"
 		print ctx "[1]"
-	| TEnumParameter (x,_,i) ->
+	| TEnumParameter (x,f,i) ->
 		gen_value ctx x;
 		gen_value ctx x;
-		print ctx "[%i]" (i + 2)
+		if Common.defined ctx.com Define.JsEnumsAsObjects then
+			let fname = (match f.ef_type with TFun((args,_)) -> let fname,_,_ = List.nth args i in  fname | _ -> assert false ) in
+			print ctx ".%s" (fname)
+		else
+			print ctx "[%i]" (i + 2)
 	| TField (_, FStatic ({cl_path = [],""},f)) ->
 	| TField (_, FStatic ({cl_path = [],""},f)) ->
 		spr ctx f.cf_name;
 		spr ctx f.cf_name;
 	| TField (x, (FInstance(_,_,f) | FStatic(_,f) | FAnon(f))) when Meta.has Meta.SelfCall f.cf_meta ->
 	| TField (x, (FInstance(_,_,f) | FStatic(_,f) | FAnon(f))) when Meta.has Meta.SelfCall f.cf_meta ->
@@ -1148,26 +1155,44 @@ let generate_enum ctx e =
 	if has_feature ctx "js.Boot.isEnum" then print ctx " __ename__ : %s," (if has_feature ctx "Type.getEnumName" then "[" ^ String.concat "," ename ^ "]" else "true");
 	if has_feature ctx "js.Boot.isEnum" then print ctx " __ename__ : %s," (if has_feature ctx "Type.getEnumName" then "[" ^ String.concat "," ename ^ "]" else "true");
 	print ctx " __constructs__ : [%s] }" (String.concat "," (List.map (fun s -> Printf.sprintf "\"%s\"" s) e.e_names));
 	print ctx " __constructs__ : [%s] }" (String.concat "," (List.map (fun s -> Printf.sprintf "\"%s\"" s) e.e_names));
 	ctx.separator <- true;
 	ctx.separator <- true;
+	let as_objects = Common.defined ctx.com Define.JsEnumsAsObjects in
+	if as_objects then begin
+		newline ctx;
+		print ctx "$hxEnums[\"%s\"] = %s" p p
+	end;
 	newline ctx;
 	newline ctx;
 	List.iter (fun n ->
 	List.iter (fun n ->
 		let f = PMap.find n e.e_constrs in
 		let f = PMap.find n e.e_constrs in
 		print ctx "%s%s = " p (field f.ef_name);
 		print ctx "%s%s = " p (field f.ef_name);
 		(match f.ef_type with
 		(match f.ef_type with
 		| TFun (args,_) ->
 		| TFun (args,_) ->
-			let sargs = String.concat "," (List.map (fun (n,_,_) -> ident n) args) in
-			print ctx "function(%s) { var $x = [\"%s\",%d,%s]; $x.__enum__ = %s;" sargs f.ef_name f.ef_index sargs p;
+			let sargs = String.concat "," (List.map (fun (n,_,_) -> ident n) args) in begin
+			if as_objects then
+				let sfields = String.concat "," (List.map (fun (n,_,_) -> (ident n) ^ ":" ^ (ident n) ) args) in
+				print ctx "function(%s) { var $x = {_hx_index:%d,%s,__enum__:\"%s\"};" sargs f.ef_index sfields p;
+			else
+				print ctx "function(%s) { var $x = [\"%s\",%d,%s]; $x.__enum__ = %s;" sargs f.ef_name f.ef_index sargs p;
+			end;
 			if has_feature ctx "has_enum" then
 			if has_feature ctx "has_enum" then
 				spr ctx " $x.toString = $estr;";
 				spr ctx " $x.toString = $estr;";
 			spr ctx " return $x; }";
 			spr ctx " return $x; }";
+			if as_objects then begin
+				let sparams = String.concat "," (List.map (fun (n,_,_) -> "\"" ^ (ident n) ^ "\"" ) args) in
+				newline ctx;
+				print ctx "%s%s.__params__ = [%s];" p (field f.ef_name) sparams
+			end;
 			ctx.separator <- true;
 			ctx.separator <- true;
 		| _ ->
 		| _ ->
-			print ctx "[\"%s\",%d]" f.ef_name f.ef_index;
+			if as_objects then
+				print ctx "{_hx_index:%d};" f.ef_index
+			else
+				print ctx "[\"%s\",%d]" f.ef_name f.ef_index;
 			newline ctx;
 			newline ctx;
 			if has_feature ctx "has_enum" then begin
 			if has_feature ctx "has_enum" then begin
 				print ctx "%s%s.toString = $estr" p (field f.ef_name);
 				print ctx "%s%s.toString = $estr" p (field f.ef_name);
 				newline ctx;
 				newline ctx;
 			end;
 			end;
-			print ctx "%s%s.__enum__ = %s" p (field f.ef_name) p;
+			print ctx "%s%s.__enum__ = %s" p (field f.ef_name) (if as_objects then "\"" ^ p ^"\"" else p);
 		);
 		);
 		newline ctx
 		newline ctx
 	) e.e_names;
 	) e.e_names;
@@ -1441,6 +1466,7 @@ let generate com =
 	let vars = if has_feature ctx "has_enum"
 	let vars = if has_feature ctx "has_enum"
 		then ("$estr = function() { return " ^ (ctx.type_accessor (TClassDecl { null_class with cl_path = ["js"],"Boot" })) ^ ".__string_rec(this,''); }") :: vars
 		then ("$estr = function() { return " ^ (ctx.type_accessor (TClassDecl { null_class with cl_path = ["js"],"Boot" })) ^ ".__string_rec(this,''); }") :: vars
 		else vars in
 		else vars in
+	let vars = if Common.defined com Define.JsEnumsAsObjects then "$hxEnums = {}" :: vars else vars in
 	(match List.rev vars with
 	(match List.rev vars with
 	| [] -> ()
 	| [] -> ()
 	| vl ->
 	| vl ->

+ 1 - 1
src/optimization/analyzerTypes.ml

@@ -524,4 +524,4 @@ type analyzer_context = {
 	mutable loop_stack : int list;
 	mutable loop_stack : int list;
 	mutable debug_exprs : (string * texpr) list;
 	mutable debug_exprs : (string * texpr) list;
 	mutable name_stack : string list;
 	mutable name_stack : string list;
-}
+}

+ 10 - 0
std/haxe/Serializer.hx

@@ -501,6 +501,16 @@ class Serializer {
 				buf.add("0");
 				buf.add("0");
 			}
 			}
 
 
+			#elseif js_enums_as_objects
+			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
 			#else
 			if( useEnumIndex ) {
 			if( useEnumIndex ) {
 				buf.add(":");
 				buf.add(":");

+ 21 - 1
std/js/Boot.hx

@@ -74,7 +74,22 @@ class Boot {
 				t = "object";
 				t = "object";
 			switch( t ) {
 			switch( t ) {
 			case "object":
 			case "object":
+				#if js_enums_as_objects
+				if (o.__enum__) {
+					var e = $hxEnums[o.__enum__];
+					var n = e.__constructs__[o._hx_index];
+					var con = e[n];
+					if (con.__params__) {
+						s += "\t";
+						return n + "(" +
+							[for (p in (con.__params__:Array<String>)) __string_rec(o[p],s)].join(",") + ")";
+					} else {
+						return n;
+					}
+				}
+				#end
 				if( __js__("o instanceof Array") ) {
 				if( __js__("o instanceof Array") ) {
+					#if !js_enums_as_objects
 					if( o.__enum__ ) {
 					if( o.__enum__ ) {
 						if( o.length == 2 )
 						if( o.length == 2 )
 							return o[0];
 							return o[0];
@@ -88,6 +103,7 @@ class Boot {
 						}
 						}
 						return str + ")";
 						return str + ")";
 					}
 					}
+					#end
 					var l = o.length;
 					var l = o.length;
 					var i;
 					var i;
 					var str = "[";
 					var str = "[";
@@ -185,7 +201,11 @@ class Boot {
 			// do not use isClass/isEnum here
 			// do not use isClass/isEnum here
 			untyped __feature__("Class.*",if( cl == Class && o.__name__ != null ) return true);
 			untyped __feature__("Class.*",if( cl == Class && o.__name__ != null ) return true);
 			untyped __feature__("Enum.*",if( cl == Enum && o.__ename__ != null ) return true);
 			untyped __feature__("Enum.*",if( cl == Enum && o.__ename__ != null ) return true);
-			return o.__enum__ == cl;
+			#if !js_enums_as_objects
+			return untyped o.__enum__ == cl;
+			#else
+			return (untyped $hxEnums[o.__enum__]) == cl;
+			#end
 		}
 		}
 	}
 	}
 
 

+ 35 - 1
std/js/_std/Type.hx

@@ -41,7 +41,11 @@ enum ValueType {
 	public static function getEnum( o : EnumValue ) : Enum<Dynamic> untyped {
 	public static function getEnum( o : EnumValue ) : Enum<Dynamic> untyped {
 		if( o == null )
 		if( o == null )
 			return null;
 			return null;
+		#if !js_enums_as_objects
 		return o.__enum__;
 		return o.__enum__;
+		#else
+		return $hxEnums[o.__enum__];
+		#end
 	}
 	}
 
 
 	public static function getSuperClass( c : Class<Dynamic> ) : Class<Dynamic> untyped {
 	public static function getSuperClass( c : Class<Dynamic> ) : Class<Dynamic> untyped {
@@ -182,8 +186,13 @@ enum ValueType {
 			if( v == null )
 			if( v == null )
 				return TNull;
 				return TNull;
 			var e = v.__enum__;
 			var e = v.__enum__;
-			if( e != null )
+			if( e != null ){
+				#if !js_enums_as_objects
 				return TEnum(e);
 				return TEnum(e);
+				#else
+				return TEnum(untyped $hxEnums[e]);
+				#end
+			}
 			var c = js.Boot.getClass(v);
 			var c = js.Boot.getClass(v);
 			if( c != null )
 			if( c != null )
 				return TClass(c);
 				return TClass(c);
@@ -203,14 +212,25 @@ enum ValueType {
 		if( a == b )
 		if( a == b )
 			return true;
 			return true;
 		try {
 		try {
+			#if !js_enums_as_objects
 			if( a[0] != b[0] )
 			if( a[0] != b[0] )
 				return false;
 				return false;
 			for( i in 2...a.length )
 			for( i in 2...a.length )
 				if( !enumEq(a[i],b[i]) )
 				if( !enumEq(a[i],b[i]) )
 					return false;
 					return false;
+			#else
+			if (a._hx_index != b._hx_index)
+				return false;
+			for (f in Reflect.fields(a)){
+				if ( !enumEq(a[f],b[f]) ){
+					return false;
+				}
+			}
+			#end
 			var e = a.__enum__;
 			var e = a.__enum__;
 			if( e != b.__enum__ || e == null )
 			if( e != b.__enum__ || e == null )
 				return false;
 				return false;
+
 		} catch( e : Dynamic ) {
 		} catch( e : Dynamic ) {
 			return false;
 			return false;
 		}
 		}
@@ -218,15 +238,29 @@ enum ValueType {
 	}
 	}
 
 
 	public inline static function enumConstructor( e : EnumValue ) : String {
 	public inline static function enumConstructor( e : EnumValue ) : String {
+		#if !js_enums_as_objects
 		return untyped e[0];
 		return untyped e[0];
+		#else
+		return untyped $hxEnums[e.__enum__].__constructs__[e._hx_index];
+		#end
 	}
 	}
 
 
 	public inline static function enumParameters( e : EnumValue ) : Array<Dynamic> {
 	public inline static function enumParameters( e : EnumValue ) : Array<Dynamic> {
+		#if !js_enums_as_objects
 		return untyped e.slice(2);
 		return untyped e.slice(2);
+		#else
+		var n = enumConstructor(e);
+		var params:Array<String> = untyped __js__("$hxEnums[{0}.__enum__][{1}].__params__",e,n);
+		return params != null ? [for (p in params) untyped e[p]] : [];
+		#end
 	}
 	}
 
 
 	public inline static function enumIndex( e : EnumValue ) : Int {
 	public inline static function enumIndex( e : EnumValue ) : Int {
+		#if js_enums_as_objects
+		return untyped e._hx_index;
+		#else
 		return untyped e[1];
 		return untyped e[1];
+		#end
 	}
 	}
 
 
 	public inline static function allEnums<T>( e : Enum<T> ) : Array<T> {
 	public inline static function allEnums<T>( e : Enum<T> ) : Array<T> {

+ 2 - 1
tests/RunCi.hx

@@ -908,8 +908,9 @@ class RunCi {
 							for (es3 in       [[], ["-D", "js-es=3"]])
 							for (es3 in       [[], ["-D", "js-es=3"]])
 							for (unflatten in [[], ["-D", "js-unflatten"]])
 							for (unflatten in [[], ["-D", "js-unflatten"]])
 							for (classic in   [[], ["-D", "js-classic"]])
 							for (classic in   [[], ["-D", "js-classic"]])
+							for (enums_as_objects in [[], ["-D", "js-enums-as-objects"]])
 							{
 							{
-								var extras = args.concat(es3).concat(unflatten).concat(classic);
+								var extras = args.concat(es3).concat(unflatten).concat(classic).concat(enums_as_objects);
 
 
 								runCommand("haxe", ["compile-js.hxml"].concat(extras));
 								runCommand("haxe", ["compile-js.hxml"].concat(extras));