Boot.hx 27 KB

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