Boot.hx 27 KB

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