Boot.hx 26 KB

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