Boot.hx 25 KB

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