| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021 | /* * 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 'php\\NativeArray':				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)@:keepprivate 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);	}}
 |