1021 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			1021 lines
		
	
	
		
			27 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 php; | ||
|  | 
 | ||
|  | import haxe.PosInfos; | ||
|  | import haxe.extern.EitherType; | ||
|  | 
 | ||
|  | using php.Global; | ||
|  | 
 | ||
|  | /** | ||
|  | 	Various Haxe->PHP compatibility utilities. | ||
|  | 	You should not use this class directly. | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | class Boot { | ||
|  | 	/** List of Haxe classes registered by their PHP class names  */ | ||
|  | 	@:protected static var aliases = new NativeAssocArray<String>(); | ||
|  | 
 | ||
|  | 	/** Cache of HxClass instances */ | ||
|  | 	@:protected static var classes = new NativeAssocArray<HxClass>(); | ||
|  | 
 | ||
|  | 	/** List of getters (for Reflect) */ | ||
|  | 	@:protected static var getters = new NativeAssocArray<NativeAssocArray<Bool>>(); | ||
|  | 
 | ||
|  | 	/** List of setters (for Reflect) */ | ||
|  | 	@:protected static var setters = new NativeAssocArray<NativeAssocArray<Bool>>(); | ||
|  | 
 | ||
|  | 	/** Metadata storage */ | ||
|  | 	@:protected static var meta = new NativeAssocArray<{}>(); | ||
|  | 
 | ||
|  | 	/** Cache for closures created of static methods */ | ||
|  | 	@:protected static var staticClosures = new NativeAssocArray<NativeAssocArray<HxClosure>>(); | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Initialization stuff. | ||
|  | 		This method is called once before invoking any Haxe-generated user code. | ||
|  | 	**/ | ||
|  | 	static function __init__() { | ||
|  | 		Global.mb_internal_encoding('UTF-8'); | ||
|  | 		if (!Global.defined('HAXE_CUSTOM_ERROR_HANDLER') || !Const.HAXE_CUSTOM_ERROR_HANDLER) { | ||
|  | 			var previousLevel = Global.error_reporting(Const.E_ALL & ~Const.E_DEPRECATED); | ||
|  | 			var previousHandler = Global.set_error_handler(function(errno:Int, errstr:String, errfile:String, errline:Int) { | ||
|  | 				if (Global.error_reporting() & errno == 0) { | ||
|  | 					return false; | ||
|  | 				} | ||
|  | 				/* | ||
|  | 				 * Division by zero should not throw | ||
|  | 				 * @see https://github.com/HaxeFoundation/haxe/issues/7034#issuecomment-394264544 | ||
|  | 				 */ | ||
|  | 				if (errno == Const.E_WARNING && errstr == 'Division by zero') { | ||
|  | 					return true; | ||
|  | 				} | ||
|  | 				throw new ErrorException(errstr, 0, errno, errfile, errline); | ||
|  | 			}); | ||
|  | 			// Already had user-defined handler. Return it. | ||
|  | 			if (previousHandler != null) { | ||
|  | 				Global.error_reporting(previousLevel); | ||
|  | 				Global.set_error_handler(previousHandler); | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns root namespace based on a value of `-D php-prefix=value` compiler flag. | ||
|  | 		Returns empty string if no `-D php-prefix=value` provided. | ||
|  | 	**/ | ||
|  | 	public static function getPrefix():String { | ||
|  | 		return Syntax.code('self::PHP_PREFIX'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Register list of getters to be able to call getters using reflection | ||
|  | 	**/ | ||
|  | 	public static function registerGetters(phpClassName:String, list:NativeAssocArray<Bool>):Void { | ||
|  | 		getters[phpClassName] = list; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Register list of setters to be able to call getters using reflection | ||
|  | 	**/ | ||
|  | 	public static function registerSetters(phpClassName:String, list:NativeAssocArray<Bool>):Void { | ||
|  | 		setters[phpClassName] = list; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if specified property has getter | ||
|  | 	**/ | ||
|  | 	public static function hasGetter(phpClassName:String, property:String):Bool { | ||
|  | 		if(!ensureLoaded(phpClassName)) | ||
|  | 			return false; | ||
|  | 
 | ||
|  | 		var has = false; | ||
|  | 		var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName; | ||
|  | 		do { | ||
|  | 			has = Global.isset(getters[phpClassName][property]); | ||
|  | 			phpClassName = Global.get_parent_class(phpClassName); | ||
|  | 		} while (!has && phpClassName != false && Global.class_exists(phpClassName)); | ||
|  | 
 | ||
|  | 		return has; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if specified property has setter | ||
|  | 	**/ | ||
|  | 	public static function hasSetter(phpClassName:String, property:String):Bool { | ||
|  | 		if(!ensureLoaded(phpClassName)) | ||
|  | 			return false; | ||
|  | 
 | ||
|  | 		var has = false; | ||
|  | 		var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName; | ||
|  | 		do { | ||
|  | 			has = Global.isset(setters[phpClassName][property]); | ||
|  | 			phpClassName = Global.get_parent_class(phpClassName); | ||
|  | 		} while (!has && phpClassName != false && Global.class_exists(phpClassName)); | ||
|  | 
 | ||
|  | 		return has; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Save metadata for specified class | ||
|  | 	**/ | ||
|  | 	public static function registerMeta(phpClassName:String, data:Dynamic):Void { | ||
|  | 		meta[phpClassName] = data; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Retrieve metadata for specified class | ||
|  | 	**/ | ||
|  | 	public static function getMeta(phpClassName:String):Null<Dynamic> { | ||
|  | 		if(!ensureLoaded(phpClassName)) return null; | ||
|  | 		return Global.isset(meta[phpClassName]) ? meta[phpClassName] : null; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Associate PHP class name with Haxe class name | ||
|  | 	**/ | ||
|  | 	public static function registerClass(phpClassName:String, haxeClassName:String):Void { | ||
|  | 		aliases[phpClassName] = haxeClassName; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns a list of currently loaded haxe-generated classes. | ||
|  | 	**/ | ||
|  | 	public static function getRegisteredClasses():Array<Class<Dynamic>> { | ||
|  | 		var result = []; | ||
|  | 		Syntax.foreach(aliases, function(phpName, haxeName) { | ||
|  | 			result.push(cast getClass(phpName)); | ||
|  | 		}); | ||
|  | 		return result; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns a list of phpName=>haxeName for currently loaded haxe-generated classes. | ||
|  | 	**/ | ||
|  | 	public static function getRegisteredAliases():NativeAssocArray<String> { | ||
|  | 		return aliases; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Get Class<T> instance for PHP fully qualified class name (E.g. '\some\pack\MyClass') | ||
|  | 		It's always the same instance for the same `phpClassName`
 | ||
|  | 	**/ | ||
|  | 	public static function getClass(phpClassName:String):HxClass { | ||
|  | 		if (phpClassName.charAt(0) == '\\') { | ||
|  | 			phpClassName = phpClassName.substr(1); | ||
|  | 		} | ||
|  | 		if (!Global.isset(classes[phpClassName])) { | ||
|  | 			classes[phpClassName] = new HxClass(phpClassName); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return classes[phpClassName]; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns Class<HxAnon> | ||
|  | 	**/ | ||
|  | 	public static inline function getHxAnon():HxClass { | ||
|  | 		return cast HxAnon; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if provided value is an anonymous object | ||
|  | 	**/ | ||
|  | 	public static inline function isAnon(v:Any):Bool { | ||
|  | 		return Std.isOfType(v, HxAnon); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns Class<HxClass> | ||
|  | 	**/ | ||
|  | 	public static inline function getHxClass():HxClass { | ||
|  | 		return cast HxClass; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns either Haxe class name for specified `phpClassName` or (if no such Haxe class registered) `phpClassName`. | ||
|  | 	**/ | ||
|  | 	public static function getClassName(phpClassName:String):String { | ||
|  | 		var hxClass = getClass(phpClassName); | ||
|  | 		var name = getHaxeName(hxClass); | ||
|  | 		return (name == null ? hxClass.phpClassName : name); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns original Haxe fully qualified class name for this type (if exists) | ||
|  | 	**/ | ||
|  | 	public static function getHaxeName(hxClass:HxClass):Null<String> { | ||
|  | 		switch (hxClass.phpClassName) { | ||
|  | 			case 'Int': | ||
|  | 				return 'Int'; | ||
|  | 			case 'String': | ||
|  | 				return 'String'; | ||
|  | 			case 'Bool': | ||
|  | 				return 'Bool'; | ||
|  | 			case 'Float': | ||
|  | 				return 'Float'; | ||
|  | 			case 'Class': | ||
|  | 				return 'Class'; | ||
|  | 			case 'Enum': | ||
|  | 				return 'Enum'; | ||
|  | 			case 'Dynamic': | ||
|  | 				return 'Dynamic'; | ||
|  | 			case _: | ||
|  | 		} | ||
|  | 
 | ||
|  | 		inline function exists() | ||
|  | 			return Global.isset(aliases[hxClass.phpClassName]); | ||
|  | 
 | ||
|  | 		if (exists()) { | ||
|  | 			return aliases[hxClass.phpClassName]; | ||
|  | 		} else if (Global.class_exists(hxClass.phpClassName) && exists()) { | ||
|  | 			return aliases[hxClass.phpClassName]; | ||
|  | 		} else if (Global.interface_exists(hxClass.phpClassName) && exists()) { | ||
|  | 			return aliases[hxClass.phpClassName]; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return null; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Find corresponding PHP class name. | ||
|  | 		Returns `null` if specified class does not exist. | ||
|  | 	**/ | ||
|  | 	public static function getPhpName(haxeName:String):Null<String> { | ||
|  | 		var prefix = getPrefix(); | ||
|  | 		var phpParts = (Global.strlen(prefix) == 0 ? [] : [prefix]); | ||
|  | 
 | ||
|  | 		var haxeParts = haxeName.split('.'); | ||
|  | 		for (part in haxeParts) { | ||
|  | 			if (isPhpKeyword(part)) { | ||
|  | 				part += '_hx'; | ||
|  | 			} | ||
|  | 			phpParts.push(part); | ||
|  | 		} | ||
|  | 
 | ||
|  | 		return phpParts.join('\\'); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if the value of `str` is a reserved keyword in PHP | ||
|  | 		@see https://www.php.net/manual/en/reserved.keywords.php | ||
|  | 	**/ | ||
|  | 	@:pure(false) | ||
|  | 	static public function isPhpKeyword(str:String):Bool { | ||
|  | 		//The body of this method is generated by the compiler | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Unsafe cast to HxClosure | ||
|  | 	**/ | ||
|  | 	public static inline function castClosure(value:Dynamic):HxClosure { | ||
|  | 		return value; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Unsafe cast to HxClass | ||
|  | 	**/ | ||
|  | 	public static inline function castClass(cls:Class<Dynamic>):HxClass { | ||
|  | 		return cast cls; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Unsafe cast to HxEnum | ||
|  | 	**/ | ||
|  | 	public static inline function castEnumValue(enm:EnumValue):HxEnum { | ||
|  | 		return cast enm; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns `Class<T>` for `HxClosure` | ||
|  | 	**/ | ||
|  | 	public static inline function closureHxClass():HxClass { | ||
|  | 		return cast HxClosure; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Implementation for `cast(value, Class<Dynamic>)` | ||
|  | 		@throws haxe.ValueError if `value` cannot be casted to this type | ||
|  | 	**/ | ||
|  | 	public static function typedCast(hxClass:HxClass, value:Dynamic):Dynamic { | ||
|  | 		if (value == null) | ||
|  | 			return null; | ||
|  | 		switch (hxClass.phpClassName) { | ||
|  | 			case 'Int': | ||
|  | 				if (Boot.isNumber(value)) { | ||
|  | 					return Global.intval(value); | ||
|  | 				} | ||
|  | 			case 'Float': | ||
|  | 				if (Boot.isNumber(value)) { | ||
|  | 					return value.floatval(); | ||
|  | 				} | ||
|  | 			case 'Bool': | ||
|  | 				if (value.is_bool()) { | ||
|  | 					return value; | ||
|  | 				} | ||
|  | 			case 'String': | ||
|  | 				if (value.is_string()) { | ||
|  | 					return value; | ||
|  | 				} | ||
|  | 			case 'array': | ||
|  | 				if (value.is_array()) { | ||
|  | 					return value; | ||
|  | 				} | ||
|  | 			case _: | ||
|  | 				if (value.is_object() && Std.isOfType(value, cast hxClass)) { | ||
|  | 					return value; | ||
|  | 				} | ||
|  | 		} | ||
|  | 		throw 'Cannot cast ' + Std.string(value) + ' to ' + getClassName(hxClass.phpClassName); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns string representation of `value` | ||
|  | 	**/ | ||
|  | 	public static function stringify(value:Dynamic, maxRecursion:Int = 10):String { | ||
|  | 		if (maxRecursion <= 0) { | ||
|  | 			return '<...>'; | ||
|  | 		} | ||
|  | 		if (value == null) { | ||
|  | 			return 'null'; | ||
|  | 		} | ||
|  | 		if (value.is_string()) { | ||
|  | 			return value; | ||
|  | 		} | ||
|  | 		if (value.is_int() || value.is_float()) { | ||
|  | 			return Syntax.string(value); | ||
|  | 		} | ||
|  | 		if (value.is_bool()) { | ||
|  | 			return value ? 'true' : 'false'; | ||
|  | 		} | ||
|  | 		if (value.is_array()) { | ||
|  | 			var strings = Syntax.arrayDecl(); | ||
|  | 			Syntax.foreach(value, function(key:Dynamic, item:Dynamic) { | ||
|  | 				strings.push(Syntax.string(key) + ' => ' + stringify(item, maxRecursion - 1)); | ||
|  | 			}); | ||
|  | 			return '[' + Global.implode(', ', strings) + ']'; | ||
|  | 		} | ||
|  | 		if (value.is_object()) { | ||
|  | 			if (Std.isOfType(value, Array)) { | ||
|  | 				return inline stringifyNativeIndexedArray(value.arr, maxRecursion - 1); | ||
|  | 			} | ||
|  | 			if (Std.isOfType(value, HxEnum)) { | ||
|  | 				var e:HxEnum = value; | ||
|  | 				var result = e.tag; | ||
|  | 				if (Global.count(e.params) > 0) { | ||
|  | 					var strings = Global.array_map(function(item) return Boot.stringify(item, maxRecursion - 1), e.params); | ||
|  | 					result += '(' + Global.implode(',', strings) + ')'; | ||
|  | 				} | ||
|  | 				return result; | ||
|  | 			} | ||
|  | 			if (value.method_exists('toString')) { | ||
|  | 				return value.toString(); | ||
|  | 			} | ||
|  | 			if (value.method_exists('__toString')) { | ||
|  | 				return value.__toString(); | ||
|  | 			} | ||
|  | 			if (Std.isOfType(value, StdClass)) { | ||
|  | 				if (Global.isset(Syntax.field(value, 'toString')) && value.toString.is_callable()) { | ||
|  | 					return value.toString(); | ||
|  | 				} | ||
|  | 				var result = new NativeIndexedArray<String>(); | ||
|  | 				var data = Global.get_object_vars(value); | ||
|  | 				for (key in data.array_keys()) { | ||
|  | 					result.array_push('$key : ' + stringify(data[key], maxRecursion - 1)); | ||
|  | 				} | ||
|  | 				return '{ ' + Global.implode(', ', result) + ' }'; | ||
|  | 			} | ||
|  | 			if (isFunction(value)) { | ||
|  | 				return '<function>'; | ||
|  | 			} | ||
|  | 			if (Std.isOfType(value, HxClass)) { | ||
|  | 				return '[class ' + getClassName((value : HxClass).phpClassName) + ']'; | ||
|  | 			} else { | ||
|  | 				return '[object ' + getClassName(Global.get_class(value)) + ']'; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		throw "Unable to stringify value"; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static public function stringifyNativeIndexedArray<T>(arr:NativeIndexedArray<T>, maxRecursion:Int = 10):String { | ||
|  | 		var strings = Syntax.arrayDecl(); | ||
|  | 		Syntax.foreach(arr, function(index:Int, value:T) { | ||
|  | 			strings[index] = Boot.stringify(value, maxRecursion - 1); | ||
|  | 		}); | ||
|  | 		return '[' + Global.implode(',', strings) + ']'; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static public inline function isNumber(value:Dynamic) { | ||
|  | 		return value.is_int() || value.is_float(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if specified values are equal | ||
|  | 	**/ | ||
|  | 	public static function equal(left:Dynamic, right:Dynamic):Bool { | ||
|  | 		if (isNumber(left) && isNumber(right)) { | ||
|  | 			return Syntax.equal(left, right); | ||
|  | 		} | ||
|  | 		if (Std.isOfType(left, HxClosure) && Std.isOfType(right, HxClosure)) { | ||
|  | 			return (left : HxClosure).equals(right); | ||
|  | 		} | ||
|  | 		return Syntax.strictEqual(left, right); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Concat `left` and `right` if both are strings or string and null. | ||
|  | 		Otherwise return sum of `left` and `right`. | ||
|  | 	**/ | ||
|  | 	public static function addOrConcat(left:Dynamic, right:Dynamic):Dynamic { | ||
|  | 		if (left.is_string() || right.is_string()) { | ||
|  | 			return (left : String) + (right : String); | ||
|  | 		} | ||
|  | 		return Syntax.add(left, right); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	@:deprecated('php.Boot.is() is deprecated. Use php.Boot.isOfType() instead') | ||
|  | 	public static inline function is(value:Dynamic, type:HxClass):Bool { | ||
|  | 		return isOfType(value, type); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		`Std.isOfType()` implementation | ||
|  | 	**/ | ||
|  | 	public static function isOfType(value:Dynamic, type:HxClass):Bool { | ||
|  | 		if (type == null) | ||
|  | 			return false; | ||
|  | 
 | ||
|  | 		var phpType = type.phpClassName; | ||
|  | 		#if php_prefix | ||
|  | 		var prefix = getPrefix(); | ||
|  | 		if (Global.substr(phpType, 0, Global.strlen(prefix) + 1) == '$prefix\\') { | ||
|  | 			phpType = Global.substr(phpType, Global.strlen(prefix) + 1); | ||
|  | 		} | ||
|  | 		#end | ||
|  | 
 | ||
|  | 		switch (phpType) { | ||
|  | 			case 'Dynamic': | ||
|  | 				return value != null; | ||
|  | 			case 'Int': | ||
|  | 				return (value.is_int() || (value.is_float() && Syntax.equal(Syntax.int(value), value) && !Global.is_nan(value))) | ||
|  | 					&& Global.abs(value) <= 2147483648; | ||
|  | 			case 'Float': | ||
|  | 				return value.is_float() || value.is_int(); | ||
|  | 			case 'Bool': | ||
|  | 				return value.is_bool(); | ||
|  | 			case 'String': | ||
|  | 				return value.is_string(); | ||
|  | 			case 'php\\NativeArray', 'php\\_NativeArray\\NativeArray_Impl_': | ||
|  | 				return value.is_array(); | ||
|  | 			case 'Enum' | 'Class': | ||
|  | 				if (Std.isOfType(value, HxClass)) { | ||
|  | 					var valuePhpClass = (cast value : HxClass).phpClassName; | ||
|  | 					var enumPhpClass = (cast HxEnum : HxClass).phpClassName; | ||
|  | 					var isEnumType = Global.is_subclass_of(valuePhpClass, enumPhpClass); | ||
|  | 					return (phpType == 'Enum' ? isEnumType : !isEnumType); | ||
|  | 				} | ||
|  | 			case _: | ||
|  | 				if (value.is_object()) { | ||
|  | 					var type:Class<Dynamic> = cast type; | ||
|  | 					return Syntax.instanceof(value, type); | ||
|  | 				} | ||
|  | 		} | ||
|  | 		return false; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if `value` is a `Class<T>` | ||
|  | 	**/ | ||
|  | 	public static inline function isClass(value:Dynamic):Bool { | ||
|  | 		return Std.isOfType(value, HxClass); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if `value` is an enum constructor instance | ||
|  | 	**/ | ||
|  | 	public static inline function isEnumValue(value:Dynamic):Bool { | ||
|  | 		return Std.isOfType(value, HxEnum); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if `value` is a function | ||
|  | 	**/ | ||
|  | 	public static inline function isFunction(value:Dynamic):Bool { | ||
|  | 		return Std.isOfType(value, Closure) || Std.isOfType(value, HxClosure); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if `value` is an instance of `HxClosure` | ||
|  | 	**/ | ||
|  | 	public static inline function isHxClosure(value:Dynamic):Bool { | ||
|  | 		return Std.isOfType(value, HxClosure); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Performs `left >>> right` operation | ||
|  | 	**/ | ||
|  | 	public static function shiftRightUnsigned(left:Int, right:Int):Int { | ||
|  | 		if (right == 0) { | ||
|  | 			return left; | ||
|  | 		} else if (left >= 0) { | ||
|  | 			return (left >> right) & ~((1 << (8 * Const.PHP_INT_SIZE - 1)) >> (right - 1)); | ||
|  | 		} else { | ||
|  | 			return (left >> right) & (0x7fffffff >> (right - 1)); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Helper method to avoid "Cannot use temporary expression in write context" error for expressions like this: | ||
|  | 		```haxe | ||
|  | 		(new MyClass()).fieldName = 'value'; | ||
|  | 		``` | ||
|  | 	**/ | ||
|  | 	static public function deref(value:Dynamic):Dynamic { | ||
|  | 		return value; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Create Haxe-compatible anonymous structure of `data` associative array | ||
|  | 	**/ | ||
|  | 	static public function createAnon(data:NativeArray):Dynamic { | ||
|  | 		var o = new HxAnon(); | ||
|  | 		Syntax.foreach(data, (field:String, value:Any) -> Syntax.setField(o, field, value)); | ||
|  | 		return o; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Make sure specified class is loaded | ||
|  | 	**/ | ||
|  | 	static public inline function ensureLoaded(phpClassName:String):Bool { | ||
|  | 		return Global.class_exists(phpClassName) || Global.interface_exists(phpClassName); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Get `field` of a dynamic `value` in a safe manner (avoid exceptions on trying to get a method) | ||
|  | 	**/ | ||
|  | 	static public function dynamicField(value:Dynamic, field:String):Dynamic { | ||
|  | 		if (Global.method_exists(value, field)) { | ||
|  | 			return closure(value, field); | ||
|  | 		} | ||
|  | 		if (Global.is_string(value)) { | ||
|  | 			value = @:privateAccess new HxDynamicStr(value); | ||
|  | 		} | ||
|  | 		return Syntax.field(value, field); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function dynamicString(str:String):HxDynamicStr { | ||
|  | 		return @:privateAccess new HxDynamicStr(str); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Creates Haxe-compatible closure of an instance method. | ||
|  | 		@param obj - any object | ||
|  | 	**/ | ||
|  | 	public static function getInstanceClosure(obj:{?__hx_closureCache:NativeAssocArray<HxClosure>}, methodName:String):Null<HxClosure> { | ||
|  | 		var result = Syntax.coalesce(obj.__hx_closureCache[methodName], null); | ||
|  | 		if (result != null) { | ||
|  | 			return result; | ||
|  | 		} | ||
|  | 		if(!Global.method_exists(obj, methodName) && !Global.isset(Syntax.field(obj, methodName))) { | ||
|  | 			return null; | ||
|  | 		} | ||
|  | 		result = new HxClosure(obj, methodName); | ||
|  | 		if (!Global.property_exists(obj, '__hx_closureCache')) { | ||
|  | 			obj.__hx_closureCache = new NativeAssocArray(); | ||
|  | 		} | ||
|  | 		obj.__hx_closureCache[methodName] = result; | ||
|  | 		return result; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Creates Haxe-compatible closure of a static method. | ||
|  | 	**/ | ||
|  | 	public static function getStaticClosure(phpClassName:String, methodName:String) { | ||
|  | 		var result = Syntax.coalesce(staticClosures[phpClassName][methodName], null); | ||
|  | 		if (result != null) { | ||
|  | 			return result; | ||
|  | 		} | ||
|  | 		result = new HxClosure(phpClassName, methodName); | ||
|  | 		if (!Global.array_key_exists(phpClassName, staticClosures)) { | ||
|  | 			staticClosures[phpClassName] = new NativeAssocArray(); | ||
|  | 		} | ||
|  | 		staticClosures[phpClassName][methodName] = result; | ||
|  | 		return result; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Creates Haxe-compatible closure. | ||
|  | 		@param type `this` for instance methods; full php class name for static methods | ||
|  | 		@param func Method name | ||
|  | 	**/ | ||
|  | 	public static inline function closure(target:Dynamic, func:String):HxClosure { | ||
|  | 		return target.is_string() ? getStaticClosure(target, func) : getInstanceClosure(target, func); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Get UTF-8 code of the first character in `s` without any checks | ||
|  | 	**/ | ||
|  | 	static public inline function unsafeOrd(s:NativeString):Int { | ||
|  | 		var code = Global.ord(s[0]); | ||
|  | 		if (code < 0xC0) { | ||
|  | 			return code; | ||
|  | 		} else if (code < 0xE0) { | ||
|  | 			return ((code - 0xC0) << 6) + Global.ord(s[1]) - 0x80; | ||
|  | 		} else if (code < 0xF0) { | ||
|  | 			return ((code - 0xE0) << 12) + ((Global.ord(s[1]) - 0x80) << 6) + Global.ord(s[2]) - 0x80; | ||
|  | 		} else { | ||
|  | 			return ((code - 0xF0) << 18) + ((Global.ord(s[1]) - 0x80) << 12) + ((Global.ord(s[2]) - 0x80) << 6) + Global.ord(s[3]) - 0x80; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static public function divByZero(value:Float):Float { | ||
|  | 		return value == 0 ? Const.NAN : (value < 0 ? -Const.INF : Const.INF); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	Class<T> implementation for Haxe->PHP internals. | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | private class HxClass { | ||
|  | 	public var phpClassName(default, null):String; | ||
|  | 
 | ||
|  | 	public function new(phpClassName:String):Void { | ||
|  | 		this.phpClassName = phpClassName; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Magic method to call static methods of this class, when `HxClass` instance is in a `Dynamic` variable. | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	function __call(method:String, args:NativeArray):Dynamic { | ||
|  | 		var callback = (phpClassName == 'String' ? Syntax.nativeClassName(HxString) : phpClassName) + '::' + method; | ||
|  | 		return Global.call_user_func_array(callback, args); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Magic method to get static vars of this class, when `HxClass` instance is in a `Dynamic` variable. | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	function __get(property:String):Dynamic { | ||
|  | 		if (Global.defined('$phpClassName::$property')) { | ||
|  | 			return Global.constant('$phpClassName::$property'); | ||
|  | 		} else if (Boot.hasGetter(phpClassName, property)) { | ||
|  | 			return Syntax.staticCall(phpClassName, 'get_$property'); | ||
|  | 		} else if (phpClassName.method_exists(property)) { | ||
|  | 			return Boot.getStaticClosure(phpClassName, property); | ||
|  | 		} else { | ||
|  | 			return Syntax.getStaticField(phpClassName, property); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Magic method to set static vars of this class, when `HxClass` instance is in a `Dynamic` variable. | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	function __set(property:String, value:Dynamic):Void { | ||
|  | 		if (Boot.hasSetter(phpClassName, property)) { | ||
|  | 			Syntax.staticCall(phpClassName, 'set_$property', value); | ||
|  | 		} else { | ||
|  | 			Syntax.setStaticField(phpClassName, property, value); | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	Base class for enum types | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | @:allow(php.Boot.stringify) | ||
|  | @:allow(Type) | ||
|  | private class HxEnum { | ||
|  | 	final tag:String; | ||
|  | 	final index:Int; | ||
|  | 	final params:NativeArray; | ||
|  | 
 | ||
|  | 	public function new(tag:String, index:Int, arguments:NativeArray = null):Void { | ||
|  | 		this.tag = tag; | ||
|  | 		this.index = index; | ||
|  | 		params = (arguments == null ? new NativeArray() : arguments); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Get string representation of this `Class` | ||
|  | 	**/ | ||
|  | 	public function toString():String { | ||
|  | 		return __toString(); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		PHP magic method to get string representation of this `Class` | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	public function __toString():String { | ||
|  | 		return Boot.stringify(this); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	extern public static function __hx__list():Array<String>; | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	`String` implementation | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | private class HxString { | ||
|  | 	public static function toUpperCase(str:String):String { | ||
|  | 		return Global.mb_strtoupper(str); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function toLowerCase(str:String):String { | ||
|  | 		return Global.mb_strtolower(str); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function charAt(str:String, index:Int):String { | ||
|  | 		return index < 0 ? '' : Global.mb_substr(str, index, 1); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function charCodeAt(str:String, index:Int):Null<Int> { | ||
|  | 		if (index < 0 || str == '') { | ||
|  | 			return null; | ||
|  | 		} | ||
|  | 		if (index == 0) { | ||
|  | 			return Boot.unsafeOrd(str); | ||
|  | 		} | ||
|  | 		var char = Global.mb_substr(str, index, 1); | ||
|  | 		return char == '' ? null : Boot.unsafeOrd(char); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function indexOf(str:String, search:String, startIndex:Int = null):Int { | ||
|  | 		if (startIndex == null) { | ||
|  | 			startIndex = 0; | ||
|  | 		} else { | ||
|  | 			var length = str.length; | ||
|  | 			if (startIndex < 0) { | ||
|  | 				startIndex += length; | ||
|  | 				if (startIndex < 0) { | ||
|  | 					startIndex = 0; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			if (startIndex >= length && search != '') { | ||
|  | 				return -1; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		var index:EitherType<Int, Bool> = if (search == '') { | ||
|  | 			var length = str.length; | ||
|  | 			startIndex > length ? length : startIndex; | ||
|  | 		} else { | ||
|  | 			Global.mb_strpos(str, search, startIndex); | ||
|  | 		} | ||
|  | 		return (index == false ? -1 : index); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function lastIndexOf(str:String, search:String, startIndex:Int = null):Int { | ||
|  | 		var start = startIndex; | ||
|  | 		if (start == null) { | ||
|  | 			start = 0; | ||
|  | 		} else { | ||
|  | 			var length = str.length; | ||
|  | 			if (start >= 0) { | ||
|  | 				start = start - length; | ||
|  | 				if (start > 0) { | ||
|  | 					start = 0; | ||
|  | 				} | ||
|  | 			} else if (start < -length) { | ||
|  | 				start = -length; | ||
|  | 			} | ||
|  | 		} | ||
|  | 		var index:EitherType<Int, Bool> = if (search == '') { | ||
|  | 			var length = str.length; | ||
|  | 			startIndex == null || startIndex > length ? length : startIndex; | ||
|  | 		} else { | ||
|  | 			Global.mb_strrpos(str, search, start); | ||
|  | 		} | ||
|  | 		if (index == false) { | ||
|  | 			return -1; | ||
|  | 		} else { | ||
|  | 			return index; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function split(str:String, delimiter:String):Array<String> { | ||
|  | 		var arr:NativeArray = if (delimiter == '') { | ||
|  | 			Global.preg_split('//u', str, -1, Const.PREG_SPLIT_NO_EMPTY); | ||
|  | 		} else { | ||
|  | 			delimiter = Global.preg_quote(delimiter, '/'); | ||
|  | 			Global.preg_split('/$delimiter/', str); | ||
|  | 		} | ||
|  | 		return @:privateAccess Array.wrap(arr); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function substr(str:String, pos:Int, ?len:Int):String { | ||
|  | 		return Global.mb_substr(str, pos, len); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function substring(str:String, startIndex:Int, ?endIndex:Int):String { | ||
|  | 		if (endIndex == null) { | ||
|  | 			if (startIndex < 0) { | ||
|  | 				startIndex = 0; | ||
|  | 			} | ||
|  | 			return Global.mb_substr(str, startIndex); | ||
|  | 		} | ||
|  | 		if (endIndex < 0) { | ||
|  | 			endIndex = 0; | ||
|  | 		} | ||
|  | 		if (startIndex < 0) { | ||
|  | 			startIndex = 0; | ||
|  | 		} | ||
|  | 		if (startIndex > endIndex) { | ||
|  | 			var tmp = endIndex; | ||
|  | 			endIndex = startIndex; | ||
|  | 			startIndex = tmp; | ||
|  | 		} | ||
|  | 		return Global.mb_substr(str, startIndex, endIndex - startIndex); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function toString(str:String):String { | ||
|  | 		return str; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function fromCharCode(code:Int):String { | ||
|  | 		return Global.mb_chr(code); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	For Dynamic access which looks like String. | ||
|  | 	Instances of this class should not be saved anywhere. | ||
|  | 	Instead it should be used to immediately invoke a String field right after instance creation one time only. | ||
|  | **/ | ||
|  | @:dox(hide) | ||
|  | @:keep | ||
|  | private class HxDynamicStr extends HxClosure { | ||
|  | 	static var hxString:String = (cast HxString : HxClass).phpClassName; | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Returns HxDynamicStr instance if `value` is a string. | ||
|  | 		Otherwise returns `value` as-is. | ||
|  | 	**/ | ||
|  | 	static function wrap(value:Dynamic):Dynamic { | ||
|  | 		if (value.is_string()) { | ||
|  | 			return new HxDynamicStr(value); | ||
|  | 		} else { | ||
|  | 			return value; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static inline function invoke(str:String, method:String, args:NativeArray):Dynamic { | ||
|  | 		Global.array_unshift(args, str); | ||
|  | 		return Global.call_user_func_array(hxString + '::' + method, args); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	function new(str:String) { | ||
|  | 		super(str, null); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	@:phpMagic | ||
|  | 	function __get(field:String):Dynamic { | ||
|  | 		switch (field) { | ||
|  | 			case 'length': | ||
|  | 				return (target : String).length; | ||
|  | 			case _: | ||
|  | 				func = field; | ||
|  | 				return this; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	@:phpMagic | ||
|  | 	function __call(method:String, args:NativeArray):Dynamic { | ||
|  | 		return invoke(target, method, args); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		@see http://php.net/manual/en/language.oop5.magic.php#object.invoke | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	override public function __invoke() { | ||
|  | 		return invoke(target, func, Global.func_get_args()); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Generates callable value for PHP | ||
|  | 	**/ | ||
|  | 	override public function getCallback(eThis:Dynamic = null):NativeIndexedArray<Dynamic> { | ||
|  | 		if (eThis == null) { | ||
|  | 			return Syntax.arrayDecl((this : Dynamic), func); | ||
|  | 		} | ||
|  | 		return Syntax.arrayDecl((new HxDynamicStr(eThis) : Dynamic), func); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Invoke this closure with `newThis` instead of `this` | ||
|  | 	**/ | ||
|  | 	override public function callWith(newThis:Dynamic, args:NativeArray):Dynamic { | ||
|  | 		if (newThis == null) { | ||
|  | 			newThis = target; | ||
|  | 		} | ||
|  | 		return invoke(newThis, func, args); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	Anonymous objects implementation | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | private class HxAnon extends StdClass { | ||
|  | 	@:phpMagic | ||
|  | 	function __get(name:String) { | ||
|  | 		return null; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	@:phpMagic | ||
|  | 	function __call(name:String, args:NativeArray):Dynamic { | ||
|  | 		return Syntax.code("($this->{0})(...{1})", name, args); | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | /** | ||
|  | 	Closures implementation | ||
|  | **/ | ||
|  | @:keep | ||
|  | @:dox(hide) | ||
|  | private class HxClosure { | ||
|  | 	/** `this` for instance methods; php class name for static methods */ | ||
|  | 	var target:Dynamic; | ||
|  | 
 | ||
|  | 	/** Method name for methods */ | ||
|  | 	var func:String; | ||
|  | 
 | ||
|  | 	/** A callable value, which can be invoked by PHP */ | ||
|  | 	var callable:Any; | ||
|  | 
 | ||
|  | 	public function new(target:Dynamic, func:String):Void { | ||
|  | 		this.target = target; | ||
|  | 		this.func = func; | ||
|  | 		// Force runtime error if trying to create a closure of an instance which happen to be `null` | ||
|  | 		if (target.is_null()) { | ||
|  | 			throw "Unable to create closure on `null`"; | ||
|  | 		} | ||
|  | 		callable = Std.isOfType(target, HxAnon) ? Syntax.field(target, func) : Syntax.arrayDecl(target, func); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		@see http://php.net/manual/en/language.oop5.magic.php#object.invoke | ||
|  | 	**/ | ||
|  | 	@:phpMagic | ||
|  | 	public function __invoke() { | ||
|  | 		return Global.call_user_func_array(callable, Global.func_get_args()); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Generates callable value for PHP | ||
|  | 	**/ | ||
|  | 	public function getCallback(eThis:Dynamic = null):NativeIndexedArray<Dynamic> { | ||
|  | 		if (eThis == null) { | ||
|  | 			eThis = target; | ||
|  | 		} | ||
|  | 		if (Std.isOfType(eThis, HxAnon)) { | ||
|  | 			return Syntax.field(eThis, func); | ||
|  | 		} | ||
|  | 		return Syntax.arrayDecl(eThis, func); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Check if this is the same closure | ||
|  | 	**/ | ||
|  | 	public function equals(closure:HxClosure):Bool { | ||
|  | 		return (target == closure.target && func == closure.func); | ||
|  | 	} | ||
|  | 
 | ||
|  | 	/** | ||
|  | 		Invoke this closure with `newThis` instead of `this` | ||
|  | 	**/ | ||
|  | 	public function callWith(newThis:Dynamic, args:NativeArray):Dynamic { | ||
|  | 		return Global.call_user_func_array(getCallback(newThis), args); | ||
|  | 	} | ||
|  | } |