Boot.hx 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012
  1. /*
  2. * Copyright (C)2005-2019 Haxe Foundation
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a
  5. * copy of this software and associated documentation files (the "Software"),
  6. * to deal in the Software without restriction, including without limitation
  7. * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  8. * and/or sell copies of the Software, and to permit persons to whom the
  9. * Software is furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  20. * DEALINGS IN THE SOFTWARE.
  21. */
  22. package php;
  23. import haxe.PosInfos;
  24. import haxe.iterators.StringIterator;
  25. import haxe.iterators.StringKeyValueIterator;
  26. import haxe.extern.EitherType;
  27. using php.Global;
  28. /**
  29. Various Haxe->PHP compatibility utilities.
  30. You should not use this class directly.
  31. **/
  32. @:keep
  33. @:dox(hide)
  34. class Boot {
  35. /** List of Haxe classes registered by their PHP class names */
  36. @:protected static var aliases = new NativeAssocArray<String>();
  37. /** Cache of HxClass instances */
  38. @:protected static var classes = new NativeAssocArray<HxClass>();
  39. /** List of getters (for Reflect) */
  40. @:protected static var getters = new NativeAssocArray<NativeAssocArray<Bool>>();
  41. /** List of setters (for Reflect) */
  42. @:protected static var setters = new NativeAssocArray<NativeAssocArray<Bool>>();
  43. /** Metadata storage */
  44. @:protected static var meta = new NativeAssocArray<{}>();
  45. /** Cache for closures created of static methods */
  46. @:protected static var staticClosures = new NativeAssocArray<NativeAssocArray<HxClosure>>();
  47. /**
  48. Initialization stuff.
  49. This method is called once before invoking any Haxe-generated user code.
  50. **/
  51. static function __init__() {
  52. Global.mb_internal_encoding('UTF-8');
  53. if (!Global.defined('HAXE_CUSTOM_ERROR_HANDLER') || !Const.HAXE_CUSTOM_ERROR_HANDLER) {
  54. var previousLevel = Global.error_reporting(Const.E_ALL);
  55. var previousHandler = Global.set_error_handler(
  56. function (errno:Int, errstr:String, errfile:String, errline:Int) {
  57. if (Global.error_reporting() & errno == 0) {
  58. return false;
  59. }
  60. /*
  61. * Division by zero should not throw
  62. * @see https://github.com/HaxeFoundation/haxe/issues/7034#issuecomment-394264544
  63. */
  64. if(errno == Const.E_WARNING && errstr == 'Division by zero') {
  65. return true;
  66. }
  67. throw new ErrorException(errstr, 0, errno, errfile, errline);
  68. }
  69. );
  70. //Already had user-defined handler. Return it.
  71. if (previousHandler != null) {
  72. Global.error_reporting(previousLevel);
  73. Global.set_error_handler(previousHandler);
  74. }
  75. }
  76. }
  77. /**
  78. Returns root namespace based on a value of `-D php-prefix=value` compiler flag.
  79. Returns empty string if no `-D php-prefix=value` provided.
  80. **/
  81. public static function getPrefix() : String {
  82. return Syntax.code('self::PHP_PREFIX');
  83. }
  84. /**
  85. Register list of getters to be able to call getters using reflection
  86. **/
  87. public static function registerGetters( phpClassName:String, list:NativeAssocArray<Bool> ) : Void {
  88. getters[phpClassName] = list;
  89. }
  90. /**
  91. Register list of setters to be able to call getters using reflection
  92. **/
  93. public static function registerSetters( phpClassName:String, list:NativeAssocArray<Bool> ) : Void {
  94. setters[phpClassName] = list;
  95. }
  96. /**
  97. Check if specified property has getter
  98. **/
  99. public static function hasGetter( phpClassName:String, property:String ) : Bool {
  100. ensureLoaded(phpClassName);
  101. var has = false;
  102. var phpClassName:haxe.extern.EitherType<Bool,String> = phpClassName;
  103. do {
  104. has = Global.isset(getters[phpClassName][property]);
  105. phpClassName = Global.get_parent_class(phpClassName);
  106. } while (!has && phpClassName != false && Global.class_exists(phpClassName));
  107. return has;
  108. }
  109. /**
  110. Check if specified property has setter
  111. **/
  112. public static function hasSetter( phpClassName:String, property:String ) : Bool {
  113. ensureLoaded(phpClassName);
  114. var has = false;
  115. var phpClassName:haxe.extern.EitherType<Bool,String> = phpClassName;
  116. do {
  117. has = Global.isset(setters[phpClassName][property]);
  118. phpClassName = Global.get_parent_class(phpClassName);
  119. } while (!has && phpClassName != false && Global.class_exists(phpClassName));
  120. return has;
  121. }
  122. /**
  123. Save metadata for specified class
  124. **/
  125. public static function registerMeta( phpClassName:String, data:Dynamic ) : Void {
  126. meta[phpClassName] = data;
  127. }
  128. /**
  129. Retrieve metadata for specified class
  130. **/
  131. public static function getMeta( phpClassName:String ) : Null<Dynamic> {
  132. ensureLoaded(phpClassName);
  133. return Global.isset(meta[phpClassName]) ? meta[phpClassName] : null;
  134. }
  135. /**
  136. Associate PHP class name with Haxe class name
  137. **/
  138. public static function registerClass( phpClassName:String, haxeClassName:String ) : Void {
  139. aliases[phpClassName] = haxeClassName;
  140. }
  141. /**
  142. Returns a list of currently loaded haxe-generated classes.
  143. **/
  144. public static function getRegisteredClasses():Array<Class<Dynamic>> {
  145. var result = [];
  146. Syntax.foreach(aliases, function(phpName, haxeName) {
  147. result.push(cast getClass(phpName));
  148. });
  149. return result;
  150. }
  151. /**
  152. Returns a list of phpName=>haxeName for currently loaded haxe-generated classes.
  153. **/
  154. public static function getRegisteredAliases():NativeAssocArray<String> {
  155. return aliases;
  156. }
  157. /**
  158. Get Class<T> instance for PHP fully qualified class name (E.g. '\some\pack\MyClass')
  159. It's always the same instance for the same `phpClassName`
  160. **/
  161. public static function getClass( phpClassName:String ) : HxClass {
  162. if (phpClassName.charAt(0) == '\\') {
  163. phpClassName = phpClassName.substr(1);
  164. }
  165. if (!Global.isset(classes[phpClassName])) {
  166. classes[phpClassName] = new HxClass(phpClassName);
  167. }
  168. return classes[phpClassName];
  169. }
  170. /**
  171. Returns Class<HxAnon>
  172. **/
  173. public static inline function getHxAnon() : HxClass {
  174. return cast HxAnon;
  175. }
  176. /**
  177. Check if provided value is an anonymous object
  178. **/
  179. public static inline function isAnon(v:Any) : Bool {
  180. return Std.is(v, HxAnon);
  181. }
  182. /**
  183. Returns Class<HxClass>
  184. **/
  185. public static inline function getHxClass() : HxClass {
  186. return cast HxClass;
  187. }
  188. /**
  189. Returns either Haxe class name for specified `phpClassName` or (if no such Haxe class registered) `phpClassName`.
  190. **/
  191. public static function getClassName( phpClassName:String ) : String {
  192. var hxClass = getClass(phpClassName);
  193. var name = getHaxeName(hxClass);
  194. return (name == null ? hxClass.phpClassName : name);
  195. }
  196. /**
  197. Returns original Haxe fully qualified class name for this type (if exists)
  198. **/
  199. public static function getHaxeName( hxClass:HxClass) : Null<String> {
  200. switch (hxClass.phpClassName) {
  201. case 'Int': return 'Int';
  202. case 'String': return 'String';
  203. case 'Bool': return 'Bool';
  204. case 'Float': return 'Float';
  205. case 'Class': return 'Class';
  206. case 'Enum': return 'Enum';
  207. case 'Dynamic': return 'Dynamic';
  208. case _:
  209. }
  210. inline function exists() return Global.isset(aliases[hxClass.phpClassName]);
  211. if (exists()) {
  212. return aliases[hxClass.phpClassName];
  213. } else if (Global.class_exists(hxClass.phpClassName) && exists()) {
  214. return aliases[hxClass.phpClassName];
  215. } else if (Global.interface_exists(hxClass.phpClassName) && exists()) {
  216. return aliases[hxClass.phpClassName];
  217. }
  218. return null;
  219. }
  220. /**
  221. Find corresponding PHP class name.
  222. Returns `null` if specified class does not exist.
  223. **/
  224. public static function getPhpName( haxeName:String ) : Null<String> {
  225. var prefix = getPrefix();
  226. var phpParts = (Global.strlen(prefix) == 0 ? [] : [prefix]);
  227. var haxeParts = haxeName.split('.');
  228. for (part in haxeParts) {
  229. switch (part.toLowerCase()) {
  230. case "__halt_compiler" | "abstract" | "and" | "array" | "as" | "break" | "callable" | "case" | "catch" | "class"
  231. | "clone" | "const" | "continue" | "declare" | "default" | "die" | "do" | "echo" | "else" | "elseif" | "empty"
  232. | "enddeclare" | "endfor" | "endforeach" | "endif" | "endswitch" | "endwhile" | "eval" | "exit" | "extends"
  233. | "final" | "finally" | "for" | "foreach" | "function" | "global" | "goto" | "if" | "implements" | "include"
  234. | "include_once" | "instanceof" | "insteadof" | "interface" | "isset" | "list" | "namespace" | "new" | "or"
  235. | "print" | "private" | "protected" | "public" | "require" | "require_once" | "return" | "static" | "switch"
  236. | "throw" | "trait" | "try" | "unset" | "use" | "var" | "while" | "xor" | "yield" | "__class__" | "__dir__"
  237. | "__file__" | "__function__" | "__line__" | "__method__" | "__trait__" | "__namespace__" | "int" | "float"
  238. | "bool" | "string" | "true" | "false" | "null" | "parent" | "void" | "iterable" | "object":
  239. part += '_hx';
  240. case _:
  241. }
  242. phpParts.push(part);
  243. }
  244. return phpParts.join('\\');
  245. }
  246. /**
  247. Unsafe cast to HxClosure
  248. **/
  249. public static inline function castClosure(value:Dynamic) : HxClosure {
  250. return value;
  251. }
  252. /**
  253. Unsafe cast to HxClass
  254. **/
  255. public static inline function castClass(cls:Class<Dynamic>) : HxClass {
  256. return cast cls;
  257. }
  258. /**
  259. Returns `Class<T>` for `HxClosure`
  260. **/
  261. public static inline function closureHxClass() : HxClass {
  262. return cast HxClosure;
  263. }
  264. /**
  265. Implementation for `cast(value, Class<Dynamic>)`
  266. @throws HxException if `value` cannot be casted to this type
  267. **/
  268. public static function typedCast( hxClass:HxClass, value:Dynamic ) : Dynamic {
  269. if (value == null) return null;
  270. switch (hxClass.phpClassName) {
  271. case 'Int':
  272. if (Boot.isNumber(value)) {
  273. return Global.intval(value);
  274. }
  275. case 'Float':
  276. if (Boot.isNumber(value)) {
  277. return value.floatval();
  278. }
  279. case 'Bool':
  280. if (value.is_bool()) {
  281. return value;
  282. }
  283. case 'String':
  284. if (value.is_string()) {
  285. return value;
  286. }
  287. case 'php\\NativeArray':
  288. if (value.is_array()) {
  289. return value;
  290. }
  291. case _:
  292. if (value.is_object() && Std.is(value, cast hxClass)) {
  293. return value;
  294. }
  295. }
  296. throw 'Cannot cast ' + Std.string(value) + ' to ' + getClassName(hxClass.phpClassName);
  297. }
  298. /**
  299. Returns string representation of `value`
  300. **/
  301. public static function stringify( value : Dynamic, maxRecursion:Int = 10 ) : String {
  302. if(maxRecursion <= 0) {
  303. return '<...>';
  304. }
  305. if (value == null) {
  306. return 'null';
  307. }
  308. if (value.is_string()) {
  309. return value;
  310. }
  311. if (value.is_int() || value.is_float()) {
  312. return Syntax.string(value);
  313. }
  314. if (value.is_bool()) {
  315. return value ? 'true' : 'false';
  316. }
  317. if (value.is_array()) {
  318. var strings = Syntax.arrayDecl();
  319. Syntax.foreach(value, function(key:Dynamic, item:Dynamic) {
  320. strings.push(Syntax.string(key) + ' => ' + stringify(item, maxRecursion - 1));
  321. });
  322. return '[' + Global.implode(', ', strings) + ']';
  323. }
  324. if (value.is_object()) {
  325. if(Std.is(value, Array)) {
  326. return inline stringifyNativeIndexedArray(value.arr, maxRecursion - 1);
  327. }
  328. if (value.method_exists('toString')) {
  329. return value.toString();
  330. }
  331. if (value.method_exists('__toString')) {
  332. return value.__toString();
  333. }
  334. if (Std.is(value, StdClass)) {
  335. if (Global.isset(Syntax.field(value, 'toString')) && value.toString.is_callable()) {
  336. return value.toString();
  337. }
  338. var result = new NativeIndexedArray<String>();
  339. var data = Global.get_object_vars(value);
  340. for (key in data.array_keys()) {
  341. result.array_push('$key : ' + stringify(data[key], maxRecursion - 1));
  342. }
  343. return '{ ' + Global.implode(', ', result) + ' }';
  344. }
  345. if (isFunction(value)) {
  346. return '<function>';
  347. }
  348. if (Std.is(value, HxClass)) {
  349. return '[class ' + getClassName((value:HxClass).phpClassName) + ']';
  350. } else {
  351. return '[object ' + getClassName(Global.get_class(value)) + ']';
  352. }
  353. }
  354. throw "Unable to stringify value";
  355. }
  356. static public function stringifyNativeIndexedArray<T>( arr : NativeIndexedArray<T>, maxRecursion : Int = 10 ) : String {
  357. var strings = Syntax.arrayDecl();
  358. Syntax.foreach(arr, function(index:Int, value:T) {
  359. strings[index] = Boot.stringify(value, maxRecursion - 1);
  360. });
  361. return '[' + Global.implode(',', strings) + ']';
  362. }
  363. static public inline function isNumber( value:Dynamic ) {
  364. return value.is_int() || value.is_float();
  365. }
  366. /**
  367. Check if specified values are equal
  368. **/
  369. public static function equal( left:Dynamic, right:Dynamic ) : Bool {
  370. if (isNumber(left) && isNumber(right)) {
  371. return Syntax.equal(left, right);
  372. }
  373. if (Std.is(left, HxClosure) && Std.is(right, HxClosure)) {
  374. return (left:HxClosure).equals(right);
  375. }
  376. return Syntax.strictEqual(left, right);
  377. }
  378. /**
  379. Concat `left` and `right` if both are strings or string and null.
  380. Otherwise return sum of `left` and `right`.
  381. **/
  382. public static function addOrConcat( left:Dynamic, right:Dynamic ) : Dynamic {
  383. if (left.is_string() || right.is_string()) {
  384. return (left:String) + (right:String);
  385. }
  386. return Syntax.add(left, right);
  387. }
  388. /**
  389. `Std.is()` implementation
  390. **/
  391. public static function is( value:Dynamic, type:HxClass ) : Bool {
  392. if (type == null) return false;
  393. var phpType = type.phpClassName;
  394. #if php_prefix
  395. var prefix = getPrefix();
  396. if(Global.substr(phpType, 0, Global.strlen(prefix) + 1) == '$prefix\\') {
  397. phpType = Global.substr(phpType, Global.strlen(prefix) + 1);
  398. }
  399. #end
  400. switch (phpType) {
  401. case 'Dynamic':
  402. return value != null;
  403. case 'Int':
  404. return (
  405. value.is_int()
  406. || (
  407. value.is_float()
  408. && Syntax.equal(Syntax.int(value), value)
  409. && !Global.is_nan(value)
  410. )
  411. )
  412. && Global.abs(value) <= 2147483648;
  413. case 'Float':
  414. return value.is_float() || value.is_int();
  415. case 'Bool':
  416. return value.is_bool();
  417. case 'String':
  418. return value.is_string();
  419. case 'php\\NativeArray', 'php\\_NativeArray\\NativeArray_Impl_':
  420. return value.is_array();
  421. case 'Enum' | 'Class':
  422. if (Std.is(value, HxClass)) {
  423. var valuePhpClass = (cast value:HxClass).phpClassName;
  424. var enumPhpClass = (cast HxEnum:HxClass).phpClassName;
  425. var isEnumType = Global.is_subclass_of(valuePhpClass, enumPhpClass);
  426. return (phpType == 'Enum' ? isEnumType : !isEnumType);
  427. }
  428. case _:
  429. if (value.is_object()) {
  430. var type:Class<Dynamic> = cast type;
  431. return Syntax.instanceof(value, type);
  432. }
  433. }
  434. return false;
  435. }
  436. /**
  437. Check if `value` is a `Class<T>`
  438. **/
  439. public static inline function isClass(value:Dynamic) : Bool {
  440. return Std.is(value, HxClass);
  441. }
  442. /**
  443. Check if `value` is an enum constructor instance
  444. **/
  445. public static inline function isEnumValue(value:Dynamic) : Bool {
  446. return Std.is(value, HxEnum);
  447. }
  448. /**
  449. Check if `value` is a function
  450. **/
  451. public static inline function isFunction(value:Dynamic) : Bool {
  452. return Std.is(value, Closure) || Std.is(value, HxClosure);
  453. }
  454. /**
  455. Check if `value` is an instance of `HxClosure`
  456. **/
  457. public static inline function isHxClosure(value:Dynamic) : Bool {
  458. return Std.is(value, HxClosure);
  459. }
  460. /**
  461. Performs `left >>> right` operation
  462. **/
  463. public static function shiftRightUnsigned( left:Int, right:Int ) : Int {
  464. if (right == 0) {
  465. return left;
  466. } else if (left >= 0) {
  467. return (left >> right) & ~( (1 << (8 * Const.PHP_INT_SIZE - 1)) >> (right - 1) );
  468. } else {
  469. return (left >> right) & (0x7fffffff >> (right - 1));
  470. }
  471. }
  472. /**
  473. Helper method to avoid "Cannot use temporary expression in write context" error for expressions like this:
  474. ```haxe
  475. (new MyClass()).fieldName = 'value';
  476. ```
  477. **/
  478. static public function deref( value:Dynamic ) : Dynamic {
  479. return value;
  480. }
  481. /**
  482. Create Haxe-compatible anonymous structure of `data` associative array
  483. **/
  484. static public inline function createAnon( data:NativeArray ) : Dynamic {
  485. return new HxAnon(data);
  486. }
  487. /**
  488. Make sure specified class is loaded
  489. **/
  490. static public inline function ensureLoaded( phpClassName:String ) : Bool {
  491. return Global.class_exists(phpClassName) || Global.interface_exists(phpClassName);
  492. }
  493. /**
  494. Get `field` of a dynamic `value` in a safe manner (avoid exceptions on trying to get a method)
  495. **/
  496. static public function dynamicField( value:Dynamic, field:String ) : Dynamic {
  497. if(Global.method_exists(value, field)) {
  498. return closure(value, field);
  499. }
  500. if(Global.is_string(value)) {
  501. value = @:privateAccess new HxDynamicStr(value);
  502. }
  503. return Syntax.field(value, field);
  504. }
  505. public static function dynamicString( str:String ) : HxDynamicStr {
  506. return @:privateAccess new HxDynamicStr(str);
  507. }
  508. /**
  509. Creates Haxe-compatible closure of an instance method.
  510. @param obj - any object
  511. **/
  512. public static function getInstanceClosure(obj:{?__hx_closureCache:NativeAssocArray<HxClosure>}, methodName:String) {
  513. var result = Syntax.coalesce(obj.__hx_closureCache[methodName], null);
  514. if(result != null) {
  515. return result;
  516. }
  517. result = new HxClosure(obj, methodName);
  518. if(!Global.property_exists(obj, '__hx_closureCache')) {
  519. obj.__hx_closureCache = new NativeAssocArray();
  520. }
  521. obj.__hx_closureCache[methodName] = result;
  522. return result;
  523. }
  524. /**
  525. Creates Haxe-compatible closure of a static method.
  526. **/
  527. public static function getStaticClosure(phpClassName:String, methodName:String) {
  528. var result = Syntax.coalesce(staticClosures[phpClassName][methodName], null);
  529. if(result != null) {
  530. return result;
  531. }
  532. result = new HxClosure(phpClassName, methodName);
  533. if(!Global.array_key_exists(phpClassName, staticClosures)) {
  534. staticClosures[phpClassName] = new NativeAssocArray();
  535. }
  536. staticClosures[phpClassName][methodName] = result;
  537. return result;
  538. }
  539. /**
  540. Creates Haxe-compatible closure.
  541. @param type `this` for instance methods; full php class name for static methods
  542. @param func Method name
  543. **/
  544. public static inline function closure( target:Dynamic, func:String ) : HxClosure {
  545. return target.is_string() ? getStaticClosure(target, func) : getInstanceClosure(target, func);
  546. }
  547. /**
  548. Get UTF-8 code of che first character in `s` without any checks
  549. **/
  550. static public inline function unsafeOrd(s:NativeString):Int {
  551. var code = Global.ord(s[0]);
  552. if(code < 0xC0) {
  553. return code;
  554. } else if(code < 0xE0) {
  555. return ((code - 0xC0) << 6) + Global.ord(s[1]) - 0x80;
  556. } else if(code < 0xF0) {
  557. return ((code - 0xE0) << 12) + ((Global.ord(s[1]) - 0x80) << 6) + Global.ord(s[2]) - 0x80;
  558. } else {
  559. return ((code - 0xF0) << 18) + ((Global.ord(s[1]) - 0x80) << 12) + ((Global.ord(s[2]) - 0x80) << 6) + Global.ord(s[3]) - 0x80;
  560. }
  561. }
  562. }
  563. /**
  564. Class<T> implementation for Haxe->PHP internals.
  565. **/
  566. @:keep
  567. @:dox(hide)
  568. private class HxClass {
  569. public var phpClassName (default,null) : String;
  570. public function new( phpClassName:String ) : Void {
  571. this.phpClassName = phpClassName;
  572. }
  573. /**
  574. Magic method to call static methods of this class, when `HxClass` instance is in a `Dynamic` variable.
  575. **/
  576. @:phpMagic
  577. function __call( method:String, args:NativeArray ) : Dynamic {
  578. var callback = (phpClassName == 'String' ? Syntax.nativeClassName(HxString) : phpClassName) + '::' + method;
  579. return Global.call_user_func_array(callback, args);
  580. }
  581. /**
  582. Magic method to get static vars of this class, when `HxClass` instance is in a `Dynamic` variable.
  583. **/
  584. @:phpMagic
  585. function __get( property:String ) : Dynamic {
  586. if (Global.defined('$phpClassName::$property')) {
  587. return Global.constant('$phpClassName::$property');
  588. } else if (Boot.hasGetter(phpClassName, property)) {
  589. return Syntax.staticCall(phpClassName, 'get_$property');
  590. } else if(phpClassName.method_exists(property)) {
  591. return new HxClosure(phpClassName, property);
  592. } else {
  593. return Syntax.getStaticField(phpClassName, property);
  594. }
  595. }
  596. /**
  597. Magic method to set static vars of this class, when `HxClass` instance is in a `Dynamic` variable.
  598. **/
  599. @:phpMagic
  600. function __set( property:String, value:Dynamic ) : Void {
  601. if (Boot.hasSetter(phpClassName, property)) {
  602. Syntax.staticCall(phpClassName, 'set_$property', value);
  603. } else {
  604. Syntax.setStaticField(phpClassName, property, value);
  605. }
  606. }
  607. }
  608. /**
  609. Base class for enum types
  610. **/
  611. @:keep
  612. @:dox(hide)
  613. private class HxEnum {
  614. var tag : String;
  615. var index : Int;
  616. var params : NativeArray;
  617. public function new( tag:String, index:Int, arguments:NativeArray = null ) : Void {
  618. this.tag = tag;
  619. this.index = index;
  620. params = (arguments == null ? new NativeArray() : arguments);
  621. }
  622. /**
  623. Get string representation of this `Class`
  624. **/
  625. public function toString() : String {
  626. return __toString();
  627. }
  628. /**
  629. PHP magic method to get string representation of this `Class`
  630. **/
  631. @:phpMagic
  632. public function __toString() : String {
  633. var result = tag;
  634. if (Global.count(params) > 0) {
  635. var strings = Global.array_map(function (item) return Boot.stringify(item), params);
  636. result += '(' + Global.implode(',', strings) + ')';
  637. }
  638. return result;
  639. }
  640. }
  641. /**
  642. `String` implementation
  643. **/
  644. @:keep
  645. @:dox(hide)
  646. private class HxString {
  647. public static function toUpperCase( str:String ) : String {
  648. return Global.mb_strtoupper(str);
  649. }
  650. public static function toLowerCase( str:String ) : String {
  651. return Global.mb_strtolower(str);
  652. }
  653. public static function charAt( str:String, index:Int) : String {
  654. return index < 0 ? '' : Global.mb_substr(str, index, 1);
  655. }
  656. public static function charCodeAt( str:String, index:Int) : Null<Int> {
  657. if(index < 0 || str == '') {
  658. return null;
  659. }
  660. if(index == 0) {
  661. return Boot.unsafeOrd(str);
  662. }
  663. var char = Global.mb_substr(str, index, 1);
  664. return char == '' ? null : Boot.unsafeOrd(char);
  665. }
  666. public static function iterator( str:String ):StringIterator {
  667. return new StringIterator(str);
  668. }
  669. public static function keyValueIterator( str:String ):StringKeyValueIterator {
  670. return new StringKeyValueIterator(str);
  671. }
  672. public static function indexOf( str:String, search:String, startIndex:Int = null ) : Int {
  673. if (startIndex == null) {
  674. startIndex = 0;
  675. } else {
  676. var length = str.length;
  677. if (startIndex < 0) {
  678. startIndex += length;
  679. if(startIndex < 0) {
  680. startIndex = 0;
  681. }
  682. }
  683. if(startIndex >= length && search != '') {
  684. return -1;
  685. }
  686. }
  687. var index:EitherType<Int,Bool> = if(search == '') {
  688. var length = str.length;
  689. startIndex > length ? length : startIndex;
  690. } else{
  691. Global.mb_strpos(str, search, startIndex);
  692. }
  693. return (index == false ? -1 : index);
  694. }
  695. public static function lastIndexOf( str:String, search:String, startIndex:Int = null ) : Int {
  696. var start = startIndex;
  697. if(start == null) {
  698. start = 0;
  699. } else {
  700. start = start - str.length;
  701. if(start > 0) {
  702. start = 0;
  703. }
  704. }
  705. var index:EitherType<Int,Bool> = if(search == '') {
  706. var length = str.length;
  707. startIndex == null || startIndex > length ? length : startIndex;
  708. } else {
  709. Global.mb_strrpos(str, search, start);
  710. }
  711. if (index == false) {
  712. return -1;
  713. } else {
  714. return index;
  715. }
  716. }
  717. public static function split( str:String, delimiter:String ) : Array<String> {
  718. var arr:NativeArray = if(delimiter == '') {
  719. Global.preg_split('//u', str, -1, Const.PREG_SPLIT_NO_EMPTY);
  720. } else {
  721. delimiter = Global.preg_quote(delimiter, '/');
  722. Global.preg_split('/$delimiter/', str);
  723. }
  724. return @:privateAccess Array.wrap(arr);
  725. }
  726. public static function substr( str:String, pos:Int, ?len:Int ) : String {
  727. return Global.mb_substr(str, pos, len);
  728. }
  729. public static function substring( str:String, startIndex:Int, ?endIndex:Int ) : String {
  730. if (endIndex == null) {
  731. if(startIndex < 0) {
  732. startIndex = 0;
  733. }
  734. return Global.mb_substr(str, startIndex);
  735. }
  736. if (endIndex < 0) {
  737. endIndex = 0;
  738. }
  739. if (startIndex < 0) {
  740. startIndex = 0;
  741. }
  742. if (startIndex > endIndex) {
  743. var tmp = endIndex;
  744. endIndex = startIndex;
  745. startIndex = tmp;
  746. }
  747. return Global.mb_substr(str, startIndex, endIndex - startIndex);
  748. }
  749. public static function toString( str:String ) : String {
  750. return str;
  751. }
  752. public static function fromCharCode( code:Int ) : String {
  753. return Global.mb_chr(code);
  754. }
  755. }
  756. /**
  757. For Dynamic access which looks like String.
  758. Instances of this class should not be saved anywhere.
  759. Instead it should be used to immediately invoke a String field right after instance creation one time only.
  760. **/
  761. @:dox(hide)
  762. @:keep
  763. private class HxDynamicStr extends HxClosure {
  764. static var hxString : String = (cast HxString:HxClass).phpClassName;
  765. /**
  766. Returns HxDynamicStr instance if `value` is a string.
  767. Otherwise returns `value` as-is.
  768. **/
  769. static function wrap( value:Dynamic ) : Dynamic {
  770. if (value.is_string()) {
  771. return new HxDynamicStr(value);
  772. } else {
  773. return value;
  774. }
  775. }
  776. static inline function invoke( str:String, method:String, args:NativeArray ) : Dynamic {
  777. Global.array_unshift(args, str);
  778. return Global.call_user_func_array(hxString + '::' + method, args);
  779. }
  780. function new( str:String ) {
  781. super(str, null);
  782. }
  783. @:phpMagic
  784. function __get( field:String ) : Dynamic {
  785. switch (field) {
  786. case 'length':
  787. return (target:String).length;
  788. case _:
  789. func = field;
  790. return this;
  791. }
  792. }
  793. @:phpMagic
  794. function __call( method:String, args:NativeArray ) : Dynamic {
  795. return invoke(target, method, args);
  796. }
  797. /**
  798. @see http://php.net/manual/en/language.oop5.magic.php#object.invoke
  799. **/
  800. @:phpMagic
  801. override public function __invoke() {
  802. return invoke(target, func, Global.func_get_args());
  803. }
  804. /**
  805. Generates callable value for PHP
  806. **/
  807. override public function getCallback(eThis:Dynamic = null) : NativeIndexedArray<Dynamic> {
  808. if (eThis == null) {
  809. return Syntax.arrayDecl((this:Dynamic), func);
  810. }
  811. return Syntax.arrayDecl((new HxDynamicStr(eThis):Dynamic), func);
  812. }
  813. /**
  814. Invoke this closure with `newThis` instead of `this`
  815. **/
  816. override public function callWith( newThis:Dynamic, args:NativeArray ) : Dynamic {
  817. if (newThis == null) {
  818. newThis = target;
  819. }
  820. return invoke(newThis, func, args);
  821. }
  822. }
  823. /**
  824. Anonymous objects implementation
  825. **/
  826. @:keep
  827. @:dox(hide)
  828. private class HxAnon extends StdClass {
  829. public function new( fields:NativeArray = null ) {
  830. super();
  831. if (fields != null) {
  832. Syntax.foreach(fields, function(name, value) Syntax.setField(this, name, value));
  833. }
  834. }
  835. @:phpMagic
  836. function __get( name:String ) {
  837. return null;
  838. }
  839. @:phpMagic
  840. function __call( name:String, args:NativeArray ) : Dynamic {
  841. return Syntax.code("($this->{0})(...{1})", name, args);
  842. }
  843. }
  844. /**
  845. Closures implementation
  846. **/
  847. @:keep
  848. @:dox(hide)
  849. private class HxClosure {
  850. /** `this` for instance methods; php class name for static methods */
  851. var target : Dynamic;
  852. /** Method name for methods */
  853. var func : String;
  854. /** A callable value, which can be invoked by PHP */
  855. var callable : Any;
  856. public function new( target:Dynamic, func:String ) : Void {
  857. this.target = target;
  858. this.func = func;
  859. //Force runtime error if trying to create a closure of an instance which happen to be `null`
  860. if (target.is_null()) {
  861. throw "Unable to create closure on `null`";
  862. }
  863. callable = Std.is(target, HxAnon) ? Syntax.field(target, func) : Syntax.arrayDecl(target, func);
  864. }
  865. /**
  866. @see http://php.net/manual/en/language.oop5.magic.php#object.invoke
  867. **/
  868. @:phpMagic
  869. public function __invoke() {
  870. return Global.call_user_func_array(callable, Global.func_get_args());
  871. }
  872. /**
  873. Generates callable value for PHP
  874. **/
  875. public function getCallback(eThis:Dynamic = null) : NativeIndexedArray<Dynamic> {
  876. if (eThis == null) {
  877. eThis = target;
  878. }
  879. if (Std.is(eThis, HxAnon)) {
  880. return Syntax.field(eThis, func);
  881. }
  882. return Syntax.arrayDecl(eThis, func);
  883. }
  884. /**
  885. Check if this is the same closure
  886. **/
  887. public function equals( closure:HxClosure ) : Bool {
  888. return (target == closure.target && func == closure.func);
  889. }
  890. /**
  891. Invoke this closure with `newThis` instead of `this`
  892. **/
  893. public function callWith( newThis:Dynamic, args:NativeArray ) : Dynamic {
  894. return Global.call_user_func_array(getCallback(newThis), args);
  895. }
  896. }
  897. /**
  898. Special exception which is used to wrap non-throwable values
  899. **/
  900. @:keep
  901. @:dox(hide)
  902. private class HxException extends Exception {
  903. var e : Dynamic;
  904. public function new( e:Dynamic ) : Void {
  905. this.e = e;
  906. super(Boot.stringify(e));
  907. }
  908. }