Boot.hx 27 KB

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