From d8096b2f885b0c0b97a2bd7ec4b031f02563fbcc Mon Sep 17 00:00:00 2001 From: Immanuel Scheerer Date: Mon, 12 Sep 2022 13:16:45 +0200 Subject: [PATCH] Closes #17: Prettifier is configured to format PHP and check format in the same way, it does for JS. --- .prettierignore | 3 + package.json | 7 +- php/classes/Array.php | 884 ++++++++++++++++---------------- php/classes/Boolean.php | 89 ++-- php/classes/Buffer.php | 424 ++++++++-------- php/classes/Date.php | 542 ++++++++++---------- php/classes/Error.php | 117 +++-- php/classes/Exception.php | 276 +++++----- php/classes/Function.php | 551 ++++++++++---------- php/classes/Global.php | 272 ++++++---- php/classes/Number.php | 233 +++++---- php/classes/ObjectClass.php | 848 +++++++++++++++++-------------- php/classes/RegExp.php | 308 +++++------ php/classes/String.php | 613 +++++++++++----------- php/globals/JSON.php | 312 +++++++----- php/globals/Math.php | 244 ++++----- php/globals/console.php | 91 ++-- php/globals/globals.php | 303 ++++++----- php/globals/process.php | 32 +- php/helpers/Debug.php | 807 ++++++++++++++++++++--------- php/helpers/Module.php | 139 ++--- php/helpers/Test.php | 40 +- php/helpers/helpers.php | 487 ++++++++++-------- php/helpers/operators.php | 218 ++++---- php/modules/fs.php | 988 +++++++++++++++++++----------------- php/modules/http.php | 168 +++--- runtime.php | 48 +- tests.php | 432 ++++++++-------- yarn.lock | 52 +- 29 files changed, 5274 insertions(+), 4254 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..17b7b08 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +vendor +node_modules +utils.js \ No newline at end of file diff --git a/package.json b/package.json index c81bbb0..f78aa03 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,8 @@ "js2php": "./js2php.js" }, "scripts": { - "format": "prettier --write \"**/*.js\"", - "format:check": "prettier --check \"**/*.js\"", + "format": "prettier --write \"**/*.php\" \"**/*.js\" ", + "format:check": "prettier --check \"**/*.php\" \"**/*.js\"", "test:unit": "node js2php --test", "test": "npm run format:check && npm run test:unit" }, @@ -17,7 +17,8 @@ "yargs": "^1.3.2" }, "devDependencies": { - "prettier": "^2.4.1" + "@prettier/plugin-php": "^0.19.0", + "prettier": "^2.7.1" }, "repository": "github:sstur/js2php", "keywords": [ diff --git a/php/classes/Array.php b/php/classes/Array.php index 8d314f7..b50abed 100644 --- a/php/classes/Array.php +++ b/php/classes/Array.php @@ -1,480 +1,514 @@ proto = self::$protoObject; - //set a non-enumerable, non-configurable property descriptor for length - $this->setProp('length', null, true, false, false); - if (func_num_args() > 0) { - $this->init(func_get_args()); - } else { - $this->length = 0; + function __construct() + { + parent::__construct(); + $this->proto = self::$protoObject; + //set a non-enumerable, non-configurable property descriptor for length + $this->setProp('length', null, true, false, false); + if (func_num_args() > 0) { + $this->init(func_get_args()); + } else { + $this->length = 0; + } } - } - function init($arr) { - $len = 0; - foreach ($arr as $i => $item) { - if ($item !== Arr::$empty) { - $this->set($i, $item); - } - $len += 1; + function init($arr) + { + $len = 0; + foreach ($arr as $i => $item) { + if ($item !== Arr::$empty) { + $this->set($i, $item); + } + $len += 1; + } + $this->length = $len; } - $this->length = $len; - } - function push($value) { - $i = $this->length; - foreach (func_get_args() as $value) { - $this->set($i, $value); - $i += 1; + function push($value) + { + $i = $this->length; + foreach (func_get_args() as $value) { + $this->set($i, $value); + $i += 1; + } + //we don't need to return a float here because this is an internal method + return $this->length = $i; } - //we don't need to return a float here because this is an internal method - return ($this->length = $i); - } - - function shift() { - $el = $this->get(0); - $this->shiftElementsBackward(1); - return $el; - } - function unshift($value) { - $num = func_num_args(); - $this->shiftElementsForward($num); - //add new element(s) - foreach (func_get_args() as $i => $value) { - $this->set($i, $value); + function shift() + { + $el = $this->get(0); + $this->shiftElementsBackward(1); + return $el; } - //we don't need to return a float here because this is an internal method - return $this->length; - } - function shiftElementsBackward($num, $startIndex = null) { - if ($startIndex === null) { - $startIndex = $num; - } - $len = $this->length; - for ($pos = $startIndex; $pos < $len; $pos++) { - $newPos = $pos - $num; - if (array_key_exists($pos, $this->data)) { - $this->data[$newPos] = $this->data[$pos]; - } else if (array_key_exists($newPos, $this->data)) { - unset($this->data[$newPos]); - } - if (array_key_exists($pos, $this->dscr)) { - $this->dscr[$newPos] = $this->dscr[$pos]; - } else if (array_key_exists($newPos, $this->dscr)) { - unset($this->dscr[$newPos]); - } - } - //remove what was previously at the end - for ($pos = $len - $num; $pos < $len; $pos++) { - unset($this->data[$pos]); - unset($this->dscr[$pos]); + function unshift($value) + { + $num = func_num_args(); + $this->shiftElementsForward($num); + //add new element(s) + foreach (func_get_args() as $i => $value) { + $this->set($i, $value); + } + //we don't need to return a float here because this is an internal method + return $this->length; } - $this->length = $len - $num; - } - function shiftElementsForward($num, $startIndex = 0) { - $pos = $this->length; - while (($pos--) > $startIndex) { - $newPos = $pos + $num; - if (array_key_exists($pos, $this->data)) { - $this->data[$newPos] = $this->data[$pos]; - unset($this->data[$pos]); - } else if (array_key_exists($newPos, $this->data)) { - unset($this->data[$newPos]); - } - if (array_key_exists($pos, $this->dscr)) { - $this->dscr[$newPos] = $this->dscr[$pos]; - unset($this->dscr[$pos]); - } else if (array_key_exists($newPos, $this->dscr)) { - unset($this->dscr[$newPos]); - } + function shiftElementsBackward($num, $startIndex = null) + { + if ($startIndex === null) { + $startIndex = $num; + } + $len = $this->length; + for ($pos = $startIndex; $pos < $len; $pos++) { + $newPos = $pos - $num; + if (array_key_exists($pos, $this->data)) { + $this->data[$newPos] = $this->data[$pos]; + } elseif (array_key_exists($newPos, $this->data)) { + unset($this->data[$newPos]); + } + if (array_key_exists($pos, $this->dscr)) { + $this->dscr[$newPos] = $this->dscr[$pos]; + } elseif (array_key_exists($newPos, $this->dscr)) { + unset($this->dscr[$newPos]); + } + } + //remove what was previously at the end + for ($pos = $len - $num; $pos < $len; $pos++) { + unset($this->data[$pos]); + unset($this->dscr[$pos]); + } + $this->length = $len - $num; } - $this->length += $num; - } - - static function checkInt($s) { - if (is_int($s) && $s >= 0) return $s; - $s = to_string($s); - return ((string)(int)$s === $s) ? (int)$s : null; - } - function set($key, $value) { - $i = self::checkInt($key); - if ($i !== null && $i >= $this->length) { - $this->length = $i + 1; + function shiftElementsForward($num, $startIndex = 0) + { + $pos = $this->length; + while ($pos-- > $startIndex) { + $newPos = $pos + $num; + if (array_key_exists($pos, $this->data)) { + $this->data[$newPos] = $this->data[$pos]; + unset($this->data[$pos]); + } elseif (array_key_exists($newPos, $this->data)) { + unset($this->data[$newPos]); + } + if (array_key_exists($pos, $this->dscr)) { + $this->dscr[$newPos] = $this->dscr[$pos]; + unset($this->dscr[$pos]); + } elseif (array_key_exists($newPos, $this->dscr)) { + unset($this->dscr[$newPos]); + } + } + $this->length += $num; } - return parent::set($key, $value); - } - function get_length() { - return (float)$this->length; - } + static function checkInt($s) + { + if (is_int($s) && $s >= 0) { + return $s; + } + $s = to_string($s); + return (string) (int) $s === $s ? (int) $s : null; + } - function set_length($len) { - $len = self::checkInt($len); - if ($len === null) { - throw new Ex(Err::create('Invalid array length')); + function set($key, $value) + { + $i = self::checkInt($key); + if ($i !== null && $i >= $this->length) { + $this->length = $i + 1; + } + return parent::set($key, $value); } - //when setting the length smaller than before, we need to delete elements - $oldLen = $this->length; - if ($oldLen > $len) { - for ($i = $len; $i < $oldLen; $i++) { - //todo: directly access $this->data and $this->dscr? - $this->remove($i); - } + + function get_length() + { + return (float) $this->length; } - $this->length = $len; - return (float)$len; - } - function toArray() { - $results = array(); - $len = $this->length; - for ($i = 0; $i < $len; $i++) { - $results[] = $this->get($i); + function set_length($len) + { + $len = self::checkInt($len); + if ($len === null) { + throw new Ex(Err::create('Invalid array length')); + } + //when setting the length smaller than before, we need to delete elements + $oldLen = $this->length; + if ($oldLen > $len) { + for ($i = $len; $i < $oldLen; $i++) { + //todo: directly access $this->data and $this->dscr? + $this->remove($i); + } + } + $this->length = $len; + return (float) $len; } - return $results; - } - static function fromArray($nativeArray) { - $arr = new Arr(); - $arr->init($nativeArray); - return $arr; - } + function toArray() + { + $results = []; + $len = $this->length; + for ($i = 0; $i < $len; $i++) { + $results[] = $this->get($i); + } + return $results; + } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Array = new Func(function($value = null) { - $arr = new Arr(); - $len = func_num_args(); - if ($len === 1 && is_int_or_float($value)) { - $arr->length = (int)$value; - } else if ($len > 0) { - $arr->init(func_get_args()); - } - return $arr; - }); - $Array->set('prototype', Arr::$protoObject); - $Array->setMethods(Arr::$classMethods, true, false, true); - return $Array; - } -} + static function fromArray($nativeArray) + { + $arr = new Arr(); + $arr->init($nativeArray); + return $arr; + } -Arr::$classMethods = array( - 'isArray' => function($arr) { - return ($arr instanceof Arr); + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Array = new Func(function ($value = null) { + $arr = new Arr(); + $len = func_num_args(); + if ($len === 1 && is_int_or_float($value)) { + $arr->length = (int) $value; + } elseif ($len > 0) { + $arr->init(func_get_args()); + } + return $arr; + }); + $Array->set('prototype', Arr::$protoObject); + $Array->setMethods(Arr::$classMethods, true, false, true); + return $Array; } -); +} -Arr::$protoMethods = array( - 'push' => function($value) { - $self = Func::getContext(); - //for this we have a low-level method - $length = call_user_func_array(array($self, 'push'), func_get_args()); - return (float)$length; +Arr::$classMethods = [ + 'isArray' => function ($arr) { + return $arr instanceof Arr; }, - 'pop' => function() { - $self = Func::getContext(); - $i = $self->length - 1; - $result = $self->get($i); - $self->remove($i); - $self->length = $i; - return $result; +]; + +Arr::$protoMethods = [ + 'push' => function ($value) { + $self = Func::getContext(); + //for this we have a low-level method + $length = call_user_func_array([$self, 'push'], func_get_args()); + return (float) $length; }, - 'unshift' => function($value) { - $self = Func::getContext(); - //for this we have a low-level method - $length = call_user_func_array(array($self, 'unshift'), func_get_args()); - return (float)$length; + 'pop' => function () { + $self = Func::getContext(); + $i = $self->length - 1; + $result = $self->get($i); + $self->remove($i); + $self->length = $i; + return $result; }, - 'shift' => function() { - $self = Func::getContext(); - //for this we have a low-level method - return $self->shift(); + 'unshift' => function ($value) { + $self = Func::getContext(); + //for this we have a low-level method + $length = call_user_func_array([$self, 'unshift'], func_get_args()); + return (float) $length; }, - 'join' => function($str = ',') { - $results = array(); - $self = Func::getContext(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - $value = $self->get($i); - $results[] = ($value === null || $value === ObjectClass::$null) ? '' : to_string($value); - } - return join(to_string($str), $results); + 'shift' => function () { + $self = Func::getContext(); + //for this we have a low-level method + return $self->shift(); }, - 'indexOf' => function($value) { - $self = Func::getContext(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->get($i) === $value) return (float)$i; - } - return -1.0; + 'join' => function ($str = ',') { + $results = []; + $self = Func::getContext(); + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + $value = $self->get($i); + $results[] = + $value === null || $value === ObjectClass::$null + ? '' + : to_string($value); + } + return join(to_string($str), $results); }, - 'lastIndexOf' => function($value) { - $self = Func::getContext(); - $i = $self->length; - while ($i--) { - if ($self->get($i) === $value) return (float)$i; - } - return -1.0; + 'indexOf' => function ($value) { + $self = Func::getContext(); + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->get($i) === $value) { + return (float) $i; + } + } + return -1.0; }, - 'slice' => function($start = 0, $end = null) { - $self = Func::getContext(); - $len = $self->length; - if ($len === 0) { - return new Arr(); - } - $start = (int)$start; - if ($start < 0) { - $start = $len + $start; - if ($start < 0) $start = 0; - } - if ($start >= $len) { - return new Arr(); - } - $end = ($end === null) ? $len : (int)$end; - if ($end < 0) { - $end = $len + $end; - } - if ($end < $start) { - $end = $start; - } - if ($end > $len) { - $end = $len; - } - $result = new Arr(); - for ($i = $start; $i < $end; $i++) { - $value = $self->get($i); - $result->push($value); - } - return $result; + 'lastIndexOf' => function ($value) { + $self = Func::getContext(); + $i = $self->length; + while ($i--) { + if ($self->get($i) === $value) { + return (float) $i; + } + } + return -1.0; }, - 'forEach' => function($fn, $context = null) { - $self = Func::getContext(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $fn->call($context, $self->get($i), (float)$i, $self); - } - } + 'slice' => function ($start = 0, $end = null) { + $self = Func::getContext(); + $len = $self->length; + if ($len === 0) { + return new Arr(); + } + $start = (int) $start; + if ($start < 0) { + $start = $len + $start; + if ($start < 0) { + $start = 0; + } + } + if ($start >= $len) { + return new Arr(); + } + $end = $end === null ? $len : (int) $end; + if ($end < 0) { + $end = $len + $end; + } + if ($end < $start) { + $end = $start; + } + if ($end > $len) { + $end = $len; + } + $result = new Arr(); + for ($i = $start; $i < $end; $i++) { + $value = $self->get($i); + $result->push($value); + } + return $result; }, - 'map' => function($fn, $context = null) { - $self = Func::getContext(); - $results = new Arr(); - $len = $results->length = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $result = $fn->call($context, $self->get($i), (float)$i, $self); - $results->set($i, $result); - } - } - return $results; + 'forEach' => function ($fn, $context = null) { + $self = Func::getContext(); + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->hasOwnProperty($i)) { + $fn->call($context, $self->get($i), (float) $i, $self); + } + } }, - 'filter' => function($fn, $context = null) { - $self = Func::getContext(); - $results = new Arr(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $item = $self->get($i); - $result = $fn->call($context, $item, (float)$i, $self); - if (is($result)) { - $results->push($item); - } - } - } - return $results; + 'map' => function ($fn, $context = null) { + $self = Func::getContext(); + $results = new Arr(); + $len = $results->length = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->hasOwnProperty($i)) { + $result = $fn->call( + $context, + $self->get($i), + (float) $i, + $self + ); + $results->set($i, $result); + } + } + return $results; }, - 'sort' => function($fn = null) { - $self = Func::getContext(); - if ($fn instanceof Func) { - $results = $self->toArray(); - $comparator = function($a, $b) use (&$fn) { - return $fn->call(null, $a, $b); - }; - uasort($results, $comparator); - } else { - $results = array(); + 'filter' => function ($fn, $context = null) { + $self = Func::getContext(); + $results = new Arr(); $len = $self->length; for ($i = 0; $i < $len; $i++) { - $results[$i] = to_string($self->get($i)); - } - asort($results, SORT_STRING); - } - //todo: descriptors - $i = 0; - $temp = array(); - foreach ($results as $index => $str) { - $temp[$i] = $self->data[$index]; - $i += 1; - } - foreach ($temp as $i => $prop) { - $self->data[$i] = $prop; - } - return $self; + if ($self->hasOwnProperty($i)) { + $item = $self->get($i); + $result = $fn->call($context, $item, (float) $i, $self); + if (is($result)) { + $results->push($item); + } + } + } + return $results; }, - 'concat' => function() { - $self = Func::getContext(); - $items = $self->toArray(); - foreach (func_get_args() as $item) { - if ($item instanceof Arr) { - foreach ($item->toArray() as $subitem) { - $items[] = $subitem; - } + 'sort' => function ($fn = null) { + $self = Func::getContext(); + if ($fn instanceof Func) { + $results = $self->toArray(); + $comparator = function ($a, $b) use (&$fn) { + return $fn->call(null, $a, $b); + }; + uasort($results, $comparator); } else { - $items[] = $item; + $results = []; + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + $results[$i] = to_string($self->get($i)); + } + asort($results, SORT_STRING); + } + //todo: descriptors + $i = 0; + $temp = []; + foreach ($results as $index => $str) { + $temp[$i] = $self->data[$index]; + $i += 1; } - } - $arr = new Arr(); - $arr->init($items); - return $arr; + foreach ($temp as $i => $prop) { + $self->data[$i] = $prop; + } + return $self; }, - 'splice' => function($offset, $deleteCount) { - $offset = (int)$offset; - $deleteCount = (int)$deleteCount; - $elements = array_slice(func_get_args(), 2); - $self = Func::getContext(); - $data = &$self->data; - unset($data['length']); - //array is considered simple if: - // - array has no holes - // - array has no non-numeric keys - // - array has no property descriptors - $isSimpleArray = false; - $len = $self->length; - if (count($data) === $len) { - $isSimpleArray = true; + 'concat' => function () { + $self = Func::getContext(); + $items = $self->toArray(); + foreach (func_get_args() as $item) { + if ($item instanceof Arr) { + foreach ($item->toArray() as $subitem) { + $items[] = $subitem; + } + } else { + $items[] = $item; + } + } + $arr = new Arr(); + $arr->init($items); + return $arr; + }, + 'splice' => function ($offset, $deleteCount) { + $offset = (int) $offset; + $deleteCount = (int) $deleteCount; + $elements = array_slice(func_get_args(), 2); + $self = Func::getContext(); + $data = &$self->data; + unset($data['length']); + //array is considered simple if: + // - array has no holes + // - array has no non-numeric keys + // - array has no property descriptors + $isSimpleArray = false; + $len = $self->length; + if (count($data) === $len) { + $isSimpleArray = true; + for ($i = 0; $i < $len; $i++) { + if ( + !array_key_exists($i, $data) || + array_key_exists($i, $self->dscr) + ) { + $isSimpleArray = false; + break; + } + } + } + if ($isSimpleArray) { + array_splice($data, $offset, $deleteCount, $elements); + $newLength = count($data); + } else { + $addCount = count($elements); + $countChange = $addCount - $deleteCount; + $nextOffset = $offset + $deleteCount; + if ($countChange < 0) { + $self->shiftElementsBackward(abs($countChange), $nextOffset); + } elseif ($countChange > 0) { + $self->shiftElementsForward($countChange, $nextOffset); + } + foreach ($elements as $i => $el) { + $data[$offset + $i] = $el; + unset($self->dscr[$offset + $i]); + } + $newLength = $len + $countChange; + } + $data['length'] = null; + $self->length = $newLength; + }, + 'reverse' => function () { + $self = Func::getContext(); + $data = &$self->data; + $newData = []; + $dscr = &$self->dscr; + $newDscr = []; + $len = $self->length; for ($i = 0; $i < $len; $i++) { - if (!array_key_exists($i, $data) || array_key_exists($i, $self->dscr)) { - $isSimpleArray = false; - break; - } - } - } - if ($isSimpleArray) { - array_splice($data, $offset, $deleteCount, $elements); - $newLength = count($data); - } else { - $addCount = count($elements); - $countChange = $addCount - $deleteCount; - $nextOffset = $offset + $deleteCount; - if ($countChange < 0) { - $self->shiftElementsBackward(abs($countChange), $nextOffset); - } else if ($countChange > 0) { - $self->shiftElementsForward($countChange, $nextOffset); - } - foreach ($elements as $i => $el) { - $data[$offset + $i] = $el; - unset($self->dscr[$offset + $i]); - } - $newLength = $len + $countChange; - } - $data['length'] = null; - $self->length = $newLength; + if (array_key_exists($i, $data)) { + $newData[$len - $i - 1] = $data[$i]; + unset($data[$i]); + } + if (array_key_exists($i, $dscr)) { + $newDscr[$len - $i - 1] = $dscr[$i]; + } + } + //copy named (remaining) keys + foreach ($data as $name => $value) { + $newData[$name] = $value; + if (array_key_exists($name, $dscr)) { + $newDscr[$name] = $dscr[$name]; + } + } + $self->data = &$newData; + $self->dscr = &$newDscr; }, - 'reverse' => function() { - $self = Func::getContext(); - $data = &$self->data; - $newData = array(); - $dscr = &$self->dscr; - $newDscr = array(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if (array_key_exists($i, $data)) { - $newData[$len - $i - 1] = $data[$i]; - unset($data[$i]); - } - if (array_key_exists($i, $dscr)) { - $newDscr[$len - $i - 1] = $dscr[$i]; - } - } - //copy named (remaining) keys - foreach ($data as $name => $value) { - $newData[$name] = $value; - if (array_key_exists($name, $dscr)) { - $newDscr[$name] = $dscr[$name]; - } - } - $self->data = &$newData; - $self->dscr = &$newDscr; + 'some' => function ($fn, $context = null) { + $self = Func::getContext(); + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->hasOwnProperty($i)) { + $item = $self->get($i); + $result = $fn->call($context, $item, (float) $i, $self); + if (is($result)) { + return true; + } + } + } + return false; }, - 'some' => function($fn, $context = null) { - $self = Func::getContext(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $item = $self->get($i); - $result = $fn->call($context, $item, (float)$i, $self); - if (is($result)) { - return true; - } - } - } - return false; + 'every' => function ($fn, $context = null) { + $self = Func::getContext(); + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->hasOwnProperty($i)) { + $item = $self->get($i); + $result = $fn->call($context, $item, (float) $i, $self); + if (!is($result)) { + return false; + } + } + } + return true; }, - 'every' => function($fn, $context = null) { - $self = Func::getContext(); - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $item = $self->get($i); - $result = $fn->call($context, $item, (float)$i, $self); - if (!is($result)) { - return false; - } - } - } - return true; + 'reduce' => function ($fn, $initValue = null) { + $self = Func::getContext(); + $value = $initValue; + $len = $self->length; + for ($i = 0; $i < $len; $i++) { + if ($self->hasOwnProperty($i)) { + $item = $self->get($i); + $value = $fn->call(null, $value, $item, (float) $i, $self); + } + } + return $value; }, - 'reduce' => function($fn, $initValue = null) { - $self = Func::getContext(); - $value = $initValue; - $len = $self->length; - for ($i = 0; $i < $len; $i++) { - if ($self->hasOwnProperty($i)) { - $item = $self->get($i); - $value = $fn->call(null, $value, $item, (float)$i, $self); - } - } - return $value; + 'reduceRight' => function ($fn, $initValue = null) { + $self = Func::getContext(); + $value = $initValue; + $len = $self->length; + for ($i = $len - 1; $i >= 0; $i--) { + if ($self->hasOwnProperty($i)) { + $item = $self->get($i); + $value = $fn->call(null, $value, $item, (float) $i, $self); + } + } + return $value; }, - 'reduceRight' => function($fn, $initValue = null) { - $self = Func::getContext(); - $value = $initValue; - $len = $self->length; - for ($i = $len - 1; $i >= 0; $i--) { - if ($self->hasOwnProperty($i)) { - $item = $self->get($i); - $value = $fn->call(null, $value, $item, (float)$i, $self); - } - } - return $value; + 'toString' => function () { + $self = Func::getContext(); + return $self->callMethod('join'); }, - 'toString' => function() { - $self = Func::getContext(); - return $self->callMethod('join'); + 'toLocaleString' => function () { + $self = Func::getContext(); + return $self->callMethod('join'); }, - 'toLocaleString' => function() { - $self = Func::getContext(); - return $self->callMethod('join'); - } -); +]; Arr::$protoObject = new ObjectClass(); Arr::$protoObject->setMethods(Arr::$protoMethods, true, false, true); diff --git a/php/classes/Boolean.php b/php/classes/Boolean.php index e1042fb..f103553 100644 --- a/php/classes/Boolean.php +++ b/php/classes/Boolean.php @@ -1,55 +1,58 @@ proto = self::$protoObject; - if (func_num_args() === 1) { - $this->value = $value; + function __construct($value = null) + { + parent::__construct(); + $this->proto = self::$protoObject; + if (func_num_args() === 1) { + $this->value = $value; + } } - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Boolean = new Func(function($value = false) { - $self = Func::getContext(); - if ($self instanceof Bln) { - $self->value = $value ? true : false; - return $self; - } else { - return $value ? true : false; - } - }); - $Boolean->instantiate = function() { - return new Bln(); - }; - $Boolean->set('prototype', Bln::$protoObject); - $Boolean->setMethods(Bln::$classMethods, true, false, true); - return $Boolean; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Boolean = new Func(function ($value = false) { + $self = Func::getContext(); + if ($self instanceof Bln) { + $self->value = $value ? true : false; + return $self; + } else { + return $value ? true : false; + } + }); + $Boolean->instantiate = function () { + return new Bln(); + }; + $Boolean->set('prototype', Bln::$protoObject); + $Boolean->setMethods(Bln::$classMethods, true, false, true); + return $Boolean; + } } -Bln::$classMethods = array(); +Bln::$classMethods = []; -Bln::$protoMethods = array( - 'valueOf' => function() { - $self = Func::getContext(); - return $self->value; +Bln::$protoMethods = [ + 'valueOf' => function () { + $self = Func::getContext(); + return $self->value; }, - 'toString' => function() { - $self = Func::getContext(); - return to_string($self->value); - } -); + 'toString' => function () { + $self = Func::getContext(); + return to_string($self->value); + }, +]; Bln::$protoObject = new ObjectClass(); Bln::$protoObject->setMethods(Bln::$protoMethods, true, false, true); diff --git a/php/classes/Buffer.php b/php/classes/Buffer.php index a50d95b..0f546d2 100644 --- a/php/classes/Buffer.php +++ b/php/classes/Buffer.php @@ -1,231 +1,241 @@ proto = self::$protoObject; - if (func_num_args() > 0) { - $this->init(func_get_args()); + function __construct() + { + parent::__construct(); + $this->proto = self::$protoObject; + if (func_num_args() > 0) { + $this->init(func_get_args()); + } } - } - function init($args) { - global $Buffer; - list($subject, $encoding, $offset) = array_pad($args, 3, null); - $type = gettype($subject); - if ($type === 'integer' || $type === 'double') { - $this->raw = str_repeat("\0", (int)$subject); - } else if ($type === 'string') { - $encoding = ($encoding === null) ? 'utf8' : to_string($encoding); - if ($encoding === 'hex') { - //note: this is a workaround for when hex2bin() is not available (v5.3) - //todo: ensure the hex string length is not odd - $this->raw = pack('H*', $subject); - } else if ($encoding === 'base64') { - $this->raw = base64_decode($subject); - } else { - $this->raw = $subject; - } - } else if (_instanceof($subject, $Buffer)) { - $this->raw = $subject->raw; - } else if ($subject instanceof Arr) { - $this->raw = $util['arrToRaw']($subject); - } else { - throw new Ex(Err::create('Invalid parameters to construct Buffer')); + function init($args) + { + global $Buffer; + list($subject, $encoding, $offset) = array_pad($args, 3, null); + $type = gettype($subject); + if ($type === 'integer' || $type === 'double') { + $this->raw = str_repeat("\0", (int) $subject); + } elseif ($type === 'string') { + $encoding = $encoding === null ? 'utf8' : to_string($encoding); + if ($encoding === 'hex') { + //note: this is a workaround for when hex2bin() is not available (v5.3) + //todo: ensure the hex string length is not odd + $this->raw = pack('H*', $subject); + } elseif ($encoding === 'base64') { + $this->raw = base64_decode($subject); + } else { + $this->raw = $subject; + } + } elseif (_instanceof($subject, $Buffer)) { + $this->raw = $subject->raw; + } elseif ($subject instanceof Arr) { + $this->raw = $util['arrToRaw']($subject); + } else { + throw new Ex(Err::create('Invalid parameters to construct Buffer')); + } + $len = strlen($this->raw); + //save an integer copy of length for performance + $this->length = $len; + $this->set('length', (float) $len); } - $len = strlen($this->raw); - //save an integer copy of length for performance - $this->length = $len; - $this->set('length', (float)$len); - } - function toJSON($max = null) { - $raw = $this->raw; - if ($max !== null && $max < strlen($raw)) { - return ''; - } else { - return ''; + function toJSON($max = null) + { + $raw = $this->raw; + if ($max !== null && $max < strlen($raw)) { + return ''; + } else { + return ''; + } } - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Buffer = new Func('Buffer', function() { - $self = new Buffer(); - $self->init(func_get_args()); - return $self; - }); - $Buffer->set('prototype', Buffer::$protoObject); - $Buffer->setMethods(Buffer::$classMethods, true, false, true); - return $Buffer; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Buffer = new Func('Buffer', function () { + $self = new Buffer(); + $self->init(func_get_args()); + return $self; + }); + $Buffer->set('prototype', Buffer::$protoObject); + $Buffer->setMethods(Buffer::$classMethods, true, false, true); + return $Buffer; + } } -Buffer::$classMethods = array( - 'isBuffer' => function($obj) { - global $Buffer; - return _instanceof($obj, $Buffer); +Buffer::$classMethods = [ + 'isBuffer' => function ($obj) { + global $Buffer; + return _instanceof($obj, $Buffer); + }, + 'concat' => function (/*Arr*/ $list, $totalLength = null) { + if (!($list instanceof Arr)) { + throw new Ex(Err::create('Usage: Buffer.concat(array, [length])')); + } + $rawList = []; + $length = 0; + foreach ($list->toArray() as $buffer) { + if (!($buffer instanceof Buffer)) { + throw new Ex( + Err::create('Usage: Buffer.concat(array, [length])') + ); + } + $rawList[] = $buffer->raw; + $length += $buffer->length; + } + $newRaw = join('', $rawList); + if ($totalLength !== null) { + $totalLength = (int) $totalLength; + if ($totalLength > $length) { + $newRaw .= str_repeat("\0", $totalLength - $length); + } elseif ($totalLength < $length) { + $newRaw = substr($newRaw, 0, $totalLength); + } + $length = $totalLength; + } + $newBuffer = new Buffer(); + $newBuffer->raw = $newRaw; + $newBuffer->length = $length; + $newBuffer->set('length', (float) $length); + return $newBuffer; }, - 'concat' => function(/*Arr*/ $list, $totalLength = null) { - if (!($list instanceof Arr)) { - throw new Ex(Err::create('Usage: Buffer.concat(array, [length])')); - } - $rawList = array(); - $length = 0; - foreach ($list->toArray() as $buffer) { - if (!($buffer instanceof Buffer)) { - throw new Ex(Err::create('Usage: Buffer.concat(array, [length])')); - } - $rawList[] = $buffer->raw; - $length += $buffer->length; - } - $newRaw = join('', $rawList); - if ($totalLength !== null) { - $totalLength = (int)$totalLength; - if ($totalLength > $length) { - $newRaw .= str_repeat("\0", $totalLength - $length); - } else if ($totalLength < $length) { - $newRaw = substr($newRaw, 0, $totalLength); - } - $length = $totalLength; - } - $newBuffer = new Buffer(); - $newBuffer->raw = $newRaw; - $newBuffer->length = $length; - $newBuffer->set('length', (float)$length); - return $newBuffer; + 'byteLength' => function ($string, $enc = null) { + $b = new Buffer($string, $enc); + return $b->length; }, - 'byteLength' => function($string, $enc = null) { - $b = new Buffer($string, $enc); - return $b->length; - } -); +]; -Buffer::$protoMethods = array( - 'get' => function($index) { - $self = Func::getContext(); - $i = (int)$index; - if ($i < 0 || $i >= $self->length) { - throw new Ex(Err::create('offset is out of bounds')); - } - return (float)ord($self->raw[$i]); +Buffer::$protoMethods = [ + 'get' => function ($index) { + $self = Func::getContext(); + $i = (int) $index; + if ($i < 0 || $i >= $self->length) { + throw new Ex(Err::create('offset is out of bounds')); + } + return (float) ord($self->raw[$i]); }, - 'set' => function($index, $byte) { - $self = Func::getContext(); - $i = (int)$index; - if ($i < 0 || $i >= $self->length) { - throw new Ex(Err::create('offset is out of bounds')); - } - $old = $self->raw; - $self->raw = substr($old, 0, $i) . chr($byte) . substr($old, $i + 1); - return $self->raw; + 'set' => function ($index, $byte) { + $self = Func::getContext(); + $i = (int) $index; + if ($i < 0 || $i >= $self->length) { + throw new Ex(Err::create('offset is out of bounds')); + } + $old = $self->raw; + $self->raw = substr($old, 0, $i) . chr($byte) . substr($old, $i + 1); + return $self->raw; }, - //todo: bounds check; see node[test/parallel/test-buffer.js] ~L243 `assert.throws` - 'write' => function($data /*, $offset = null, $len = null, $enc = null*/) { - $self = Func::getContext(); - //all optional args - $args = array_slice(func_get_args(), 1); - //the number of optional args - $count = func_num_args() - 1; - $offset = null; $len = null; $enc = null; - if ($count > 0) { - //allow write(data, enc), write(data, enc, offset), write(data, enc, offset, length) - if (is_string($args[0])) { - $enc = array_shift($args); - $offset = array_shift($args); - $len = array_shift($args); - //allow write(data, offset), write(data, offset, enc), write(data, offset, len), write(data, offset, enc, len) - } else if (is_int_or_float($args[0])) { - $offset = array_shift($args); - $enc = array_pop($args); - $len = array_pop($args); - //swap len/enc if necessary - if (is_int_or_float($enc)) { - list($len, $enc) = array($enc, $len); - } - } - } - if (!($data instanceof Buffer)) { - $data = new Buffer($data, $enc); - } - $new = $data->raw; - if ($len !== null) { - $newLen = (int)$len; - $new = substr($new, 0, $newLen); - } else { - $newLen = $data->length; - } - $offset = (int)$offset; - $old = $self->raw; - $oldLen = $self->length; - if ($offset + $newLen > strlen($old)) { - $newLen = $oldLen - $offset; - } - $pre = ($offset === 0) ? '' : substr($old, 0, $offset); - $self->raw = $pre . $new . substr($old, $offset + $newLen); + //todo: bounds check; see node[test/parallel/test-buffer.js] ~L243 `assert.throws` + 'write' => function ($data /*, $offset = null, $len = null, $enc = null*/) { + $self = Func::getContext(); + //all optional args + $args = array_slice(func_get_args(), 1); + //the number of optional args + $count = func_num_args() - 1; + $offset = null; + $len = null; + $enc = null; + if ($count > 0) { + //allow write(data, enc), write(data, enc, offset), write(data, enc, offset, length) + if (is_string($args[0])) { + $enc = array_shift($args); + $offset = array_shift($args); + $len = array_shift($args); + //allow write(data, offset), write(data, offset, enc), write(data, offset, len), write(data, offset, enc, len) + } elseif (is_int_or_float($args[0])) { + $offset = array_shift($args); + $enc = array_pop($args); + $len = array_pop($args); + //swap len/enc if necessary + if (is_int_or_float($enc)) { + list($len, $enc) = [$enc, $len]; + } + } + } + if (!($data instanceof Buffer)) { + $data = new Buffer($data, $enc); + } + $new = $data->raw; + if ($len !== null) { + $newLen = (int) $len; + $new = substr($new, 0, $newLen); + } else { + $newLen = $data->length; + } + $offset = (int) $offset; + $old = $self->raw; + $oldLen = $self->length; + if ($offset + $newLen > strlen($old)) { + $newLen = $oldLen - $offset; + } + $pre = $offset === 0 ? '' : substr($old, 0, $offset); + $self->raw = $pre . $new . substr($old, $offset + $newLen); }, - 'slice' => function($start, $end = null) { - $self = Func::getContext(); - $len = $self->length; - if ($len === 0) { - return new Buffer(0); - } - $start = (int)$start; - if ($start < 0) { - $start = $len + $start; - if ($start < 0) $start = 0; - } - if ($start >= $len) { - return new Buffer(0); - } - $end = ($end === null) ? $len : (int)$end; - if ($end < 0) { - $end = $len + $end; - } - if ($end < $start) { - $end = $start; - } - if ($end > $len) { - $end = $len; - } - $new = substr($self->raw, $start, $end - $start); - return new Buffer($new, 'binary'); + 'slice' => function ($start, $end = null) { + $self = Func::getContext(); + $len = $self->length; + if ($len === 0) { + return new Buffer(0); + } + $start = (int) $start; + if ($start < 0) { + $start = $len + $start; + if ($start < 0) { + $start = 0; + } + } + if ($start >= $len) { + return new Buffer(0); + } + $end = $end === null ? $len : (int) $end; + if ($end < 0) { + $end = $len + $end; + } + if ($end < $start) { + $end = $start; + } + if ($end > $len) { + $end = $len; + } + $new = substr($self->raw, $start, $end - $start); + return new Buffer($new, 'binary'); + }, + 'toString' => function ($enc = 'utf8', $start = null, $end = null) { + $self = Func::getContext(); + $raw = $self->raw; + if (func_num_args() > 1) { + $raw = substr($raw, $start, $end - $start + 1); + } + if ($enc === 'hex') { + return bin2hex($raw); + } + if ($enc === 'base64') { + return base64_encode($raw); + } + return $raw; }, - 'toString' => function($enc = 'utf8', $start = null, $end = null) { - $self = Func::getContext(); - $raw = $self->raw; - if (func_num_args() > 1) { - $raw = substr($raw, $start, $end - $start + 1); - } - if ($enc === 'hex') { - return bin2hex($raw); - } - if ($enc === 'base64') { - return base64_encode($raw); - } - return $raw; + 'toJSON' => function () { + $self = Func::getContext(); + return $self->toJSON(); }, - 'toJSON' => function() { - $self = Func::getContext(); - return $self->toJSON(); + 'inspect' => function () { + $self = Func::getContext(); + return $self->toJSON(Buffer::$SHOW_MAX); }, - 'inspect' => function() { - $self = Func::getContext(); - return $self->toJSON(Buffer::$SHOW_MAX); - } -); +]; Buffer::$protoObject = new ObjectClass(); Buffer::$protoObject->setMethods(Buffer::$protoMethods, true, false, true); diff --git a/php/classes/Date.php b/php/classes/Date.php index 36c4177..f410d35 100644 --- a/php/classes/Date.php +++ b/php/classes/Date.php @@ -1,323 +1,353 @@ proto = Date::$protoObject; - if (func_num_args() > 0) { - $this->init(func_get_args()); + function __construct() + { + parent::__construct(); + $this->proto = Date::$protoObject; + if (func_num_args() > 0) { + $this->init(func_get_args()); + } } - } - function init($arr) { - $len = count($arr); - if ($len === 1 && is_string($arr[0])) { - $this->_initFromString($arr[0]); - } else { - $this->_initFromParts($arr); + function init($arr) + { + $len = count($arr); + if ($len === 1 && is_string($arr[0])) { + $this->_initFromString($arr[0]); + } else { + $this->_initFromParts($arr); + } } - } - function _initFromString($str) { - $a = Date::parse($str); - if (!$a) { - $a = array( - 'year' => 1970, - 'month' => 1, - 'day' => 1, - 'hour' => 0, - 'minute' => 0, - 'second' => 0, - 'ms' => 0, - 'isLocal' => false - ); - } - if ($a['isLocal']) { - $arr = array($a['year'], $a['month'] - 1, $a['day'], $a['hour'], $a['minute'], $a['second'], $a['ms']); - $this->_initFromParts($arr); - } else { - //create a UTC date first, get it's unix timestamp, then create a local date - $date = Date::create('UTC'); - $date->setDate($a['year'], $a['month'], $a['day']); - $date->setTime($a['hour'], $a['minute'], $a['second']); - $ms = $date->getTimestamp() * 1000 + $a['ms']; - $this->date = Date::fromMilliseconds($ms); - $this->value = $ms; + function _initFromString($str) + { + $a = Date::parse($str); + if (!$a) { + $a = [ + 'year' => 1970, + 'month' => 1, + 'day' => 1, + 'hour' => 0, + 'minute' => 0, + 'second' => 0, + 'ms' => 0, + 'isLocal' => false, + ]; + } + if ($a['isLocal']) { + $arr = [ + $a['year'], + $a['month'] - 1, + $a['day'], + $a['hour'], + $a['minute'], + $a['second'], + $a['ms'], + ]; + $this->_initFromParts($arr); + } else { + //create a UTC date first, get it's unix timestamp, then create a local date + $date = Date::create('UTC'); + $date->setDate($a['year'], $a['month'], $a['day']); + $date->setTime($a['hour'], $a['minute'], $a['second']); + $ms = $date->getTimestamp() * 1000 + $a['ms']; + $this->date = Date::fromMilliseconds($ms); + $this->value = $ms; + } } - } - function _initFromParts($arr, $tz = null) { - $len = count($arr); - if ($len > 1) { - //allow 0 - 7 parts; default for each part is 0 - $arr = array_pad($arr, 7, 0); - $date = Date::create($tz); - $date->setDate($arr[0], $arr[1] + 1, $arr[2]); - $date->setTime($arr[3], $arr[4], $arr[5]); - $this->date = $date; - $this->value = (int)($date->getTimestamp() * 1000 + $arr[6]); - } else { - $ms = ($len === 1) ? (int)$arr[0] : (int)Date::now(); - $this->date = Date::fromMilliseconds($ms); - $this->value = $ms; + function _initFromParts($arr, $tz = null) + { + $len = count($arr); + if ($len > 1) { + //allow 0 - 7 parts; default for each part is 0 + $arr = array_pad($arr, 7, 0); + $date = Date::create($tz); + $date->setDate($arr[0], $arr[1] + 1, $arr[2]); + $date->setTime($arr[3], $arr[4], $arr[5]); + $this->date = $date; + $this->value = (int) ($date->getTimestamp() * 1000 + $arr[6]); + } else { + $ms = $len === 1 ? (int) $arr[0] : (int) Date::now(); + $this->date = Date::fromMilliseconds($ms); + $this->value = $ms; + } } - } - function toJSON() { - $date = Date::fromMilliseconds($this->value, 'UTC'); - $str = $date->format('Y-m-d\TH:i:s'); - $ms = $this->value % 1000; - if ($ms < 0) $ms = 1000 + $ms; - if ($ms < 0) $ms = 0; - return $str . '.' . substr('00' . $ms, -3) . 'Z'; - } - - static function now() { - return floor(microtime(true) * 1000); - } + function toJSON() + { + $date = Date::fromMilliseconds($this->value, 'UTC'); + $str = $date->format('Y-m-d\TH:i:s'); + $ms = $this->value % 1000; + if ($ms < 0) { + $ms = 1000 + $ms; + } + if ($ms < 0) { + $ms = 0; + } + return $str . '.' . substr('00' . $ms, -3) . 'Z'; + } - //create a native DateTime object with current time (optional timezone) - static function create($tz = null) { - if ($tz === null) { - return new DateTime('now', new DateTimeZone(Date::$LOCAL_TZ)); - } else { - return new DateTime('now', new DateTimeZone($tz)); + static function now() + { + return floor(microtime(true) * 1000); } - } - //create a native DateTime object from milliseconds (optional timezone) - static function fromMilliseconds($ms, $tz = null) { - $date = Date::create($tz); - $seconds = floor($ms / 1000); - $date->setTimestamp($seconds); - return $date; - } + //create a native DateTime object with current time (optional timezone) + static function create($tz = null) + { + if ($tz === null) { + return new DateTime('now', new DateTimeZone(Date::$LOCAL_TZ)); + } else { + return new DateTime('now', new DateTimeZone($tz)); + } + } - static function parse($str) { - $str = to_string($str); - $a = date_parse($str); - if ($a['error_count'] > 0 || $a['warning_count'] > 0) { - return null; + //create a native DateTime object from milliseconds (optional timezone) + static function fromMilliseconds($ms, $tz = null) + { + $date = Date::create($tz); + $seconds = floor($ms / 1000); + $date->setTimestamp($seconds); + return $date; } - $hasTime = ($a['hour'] !== false || $a['minute'] !== false || $a['second'] !== false); - $tz = array_key_exists('tz_abbr', $a) ? $a['tz_abbr'] : null; - if ($tz === 'Z' || $tz === 'GMT') { - $tz = 'UTC'; + + static function parse($str) + { + $str = to_string($str); + $a = date_parse($str); + if ($a['error_count'] > 0 || $a['warning_count'] > 0) { + return null; + } + $hasTime = + $a['hour'] !== false || + $a['minute'] !== false || + $a['second'] !== false; + $tz = array_key_exists('tz_abbr', $a) ? $a['tz_abbr'] : null; + if ($tz === 'Z' || $tz === 'GMT') { + $tz = 'UTC'; + } + //todo: this should not depend on whether there is time present, but if it "looks like" ISO-8601 + $isLocal = $tz === null && $hasTime === true; + return [ + 'year' => $a['year'] ?: 1970, + 'month' => $a['month'] ?: 1, + 'day' => $a['day'] ?: 1, + 'hour' => $a['hour'] ?: 0, + 'minute' => $a['minute'] ?: 0, + 'second' => $a['second'] ?: 0, + 'ms' => (int) ($a['fraction'] * 1000), + 'timezone' => $tz, + 'isLocal' => $isLocal, + ]; } - //todo: this should not depend on whether there is time present, but if it "looks like" ISO-8601 - $isLocal = ($tz === null && $hasTime === true); - return array( - 'year' => $a['year'] ?: 1970, - 'month' => $a['month'] ?: 1, - 'day' => $a['day'] ?: 1, - 'hour' => $a['hour'] ?: 0, - 'minute' => $a['minute'] ?: 0, - 'second' => $a['second'] ?: 0, - 'ms' => (int)($a['fraction'] * 1000), - 'timezone' => $tz, - 'isLocal' => $isLocal - ); - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Date = new Func(function() { - $date = new Date(); - $date->init(func_get_args()); - return $date; - }); - $Date->set('prototype', Date::$protoObject); - $Date->setMethods(Date::$classMethods, true, false, true); - return $Date; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Date = new Func(function () { + $date = new Date(); + $date->init(func_get_args()); + return $date; + }); + $Date->set('prototype', Date::$protoObject); + $Date->setMethods(Date::$classMethods, true, false, true); + return $Date; + } } -Date::$classMethods = array( - 'now' => function() { - return Date::now(); - }, - 'parse' => function($str) { - $date = new Date($str); - return (float)$date->value; - }, - 'UTC' => function() { - $date = new Date(); - $date->_initFromParts(func_get_args(), 'UTC'); - return (float)$date->value; - } -); +Date::$classMethods = [ + 'now' => function () { + return Date::now(); + }, + 'parse' => function ($str) { + $date = new Date($str); + return (float) $date->value; + }, + 'UTC' => function () { + $date = new Date(); + $date->_initFromParts(func_get_args(), 'UTC'); + return (float) $date->value; + }, +]; -Date::$protoMethods = array( - 'valueOf' => function() { - $self = Func::getContext(); - return (float)$self->value; +Date::$protoMethods = [ + 'valueOf' => function () { + $self = Func::getContext(); + return (float) $self->value; + }, + 'toString' => function () { + $self = Func::getContext(); + //Sat Aug 09 2014 12:00:00 GMT+0000 (UTC) + return str_replace( + '~', + 'GMT', + $self->date->format('D M d Y H:i:s ~O (T)') + ); }, - 'toString' => function() { - $self = Func::getContext(); - //Sat Aug 09 2014 12:00:00 GMT+0000 (UTC) - return str_replace('~', 'GMT', $self->date->format('D M d Y H:i:s ~O (T)')); + //this will give us en-US locale formatted + 'toLocaleString' => function () { + $self = Func::getContext(); + //12/28/2014, 1:37:30 AM + return str_replace('~', 'GMT', $self->date->format('n/j/Y, g:i:s A')); }, - //this will give us en-US locale formatted - 'toLocaleString' => function() { - $self = Func::getContext(); - //12/28/2014, 1:37:30 AM - return str_replace('~', 'GMT', $self->date->format('n/j/Y, g:i:s A')); + 'toJSON' => function () { + $self = Func::getContext(); + //2014-08-09T12:00:00.000Z + return $self->toJSON(); }, - 'toJSON' => function() { - $self = Func::getContext(); - //2014-08-09T12:00:00.000Z - return $self->toJSON(); + //identical to `toJSON` above + 'toISOString' => function () { + $self = Func::getContext(); + //2014-08-09T12:00:00.000Z + return $self->toJSON(); }, - //identical to `toJSON` above - 'toISOString' => function() { - $self = Func::getContext(); - //2014-08-09T12:00:00.000Z - return $self->toJSON(); + 'toUTCString' => function () { + $self = Func::getContext(); + $date = Date::fromMilliseconds($self->value, 'UTC'); + //Sun, 07 Dec 2014 01:10:08 GMT + return $date->format('D, d M Y H:i:s') . ' GMT'; }, - 'toUTCString' => function() { - $self = Func::getContext(); - $date = Date::fromMilliseconds($self->value, 'UTC'); - //Sun, 07 Dec 2014 01:10:08 GMT - return $date->format('D, d M Y H:i:s') . ' GMT'; + //identical to `toUTCString` above + 'toGMTString' => function () { + $self = Func::getContext(); + $date = Date::fromMilliseconds($self->value, 'UTC'); + //Sun, 07 Dec 2014 01:10:08 GMT + return $date->format('D, d M Y H:i:s') . ' GMT'; }, - //identical to `toUTCString` above - 'toGMTString' => function() { - $self = Func::getContext(); - $date = Date::fromMilliseconds($self->value, 'UTC'); - //Sun, 07 Dec 2014 01:10:08 GMT - return $date->format('D, d M Y H:i:s') . ' GMT'; + 'toDateString' => function () { + throw new Ex(Err::create('date.toDateString not implemented')); }, - 'toDateString' => function() { - throw new Ex(Err::create('date.toDateString not implemented')); + 'toLocaleDateString' => function () { + throw new Ex(Err::create('date.toLocaleDateString not implemented')); }, - 'toLocaleDateString' => function() { - throw new Ex(Err::create('date.toLocaleDateString not implemented')); + 'toTimeString' => function () { + throw new Ex(Err::create('date.toTimeString not implemented')); }, - 'toTimeString' => function() { - throw new Ex(Err::create('date.toTimeString not implemented')); + 'toLocaleTimeString' => function () { + throw new Ex(Err::create('date.toLocaleTimeString not implemented')); }, - 'toLocaleTimeString' => function() { - throw new Ex(Err::create('date.toLocaleTimeString not implemented')); + 'getDate' => function () { + throw new Ex(Err::create('date.getDate not implemented')); }, - 'getDate' => function() { - throw new Ex(Err::create('date.getDate not implemented')); + 'getDay' => function () { + throw new Ex(Err::create('date.getDay not implemented')); }, - 'getDay' => function() { - throw new Ex(Err::create('date.getDay not implemented')); + 'getFullYear' => function () { + throw new Ex(Err::create('date.getFullYear not implemented')); }, - 'getFullYear' => function() { - throw new Ex(Err::create('date.getFullYear not implemented')); + 'getHours' => function () { + throw new Ex(Err::create('date.getHours not implemented')); }, - 'getHours' => function() { - throw new Ex(Err::create('date.getHours not implemented')); + 'getMilliseconds' => function () { + throw new Ex(Err::create('date.getMilliseconds not implemented')); }, - 'getMilliseconds' => function() { - throw new Ex(Err::create('date.getMilliseconds not implemented')); + 'getMinutes' => function () { + throw new Ex(Err::create('date.getMinutes not implemented')); }, - 'getMinutes' => function() { - throw new Ex(Err::create('date.getMinutes not implemented')); + 'getMonth' => function () { + throw new Ex(Err::create('date.getMonth not implemented')); }, - 'getMonth' => function() { - throw new Ex(Err::create('date.getMonth not implemented')); + 'getSeconds' => function () { + throw new Ex(Err::create('date.getSeconds not implemented')); }, - 'getSeconds' => function() { - throw new Ex(Err::create('date.getSeconds not implemented')); + 'getUTCDate' => function () { + throw new Ex(Err::create('date.getUTCDate not implemented')); }, - 'getUTCDate' => function() { - throw new Ex(Err::create('date.getUTCDate not implemented')); + 'getUTCDay' => function () { + throw new Ex(Err::create('date.getUTCDay not implemented')); }, - 'getUTCDay' => function() { - throw new Ex(Err::create('date.getUTCDay not implemented')); + 'getUTCFullYear' => function () { + throw new Ex(Err::create('date.getUTCFullYear not implemented')); }, - 'getUTCFullYear' => function() { - throw new Ex(Err::create('date.getUTCFullYear not implemented')); + 'getUTCHours' => function () { + throw new Ex(Err::create('date.getUTCHours not implemented')); }, - 'getUTCHours' => function() { - throw new Ex(Err::create('date.getUTCHours not implemented')); + 'getUTCMilliseconds' => function () { + throw new Ex(Err::create('date.getUTCMilliseconds not implemented')); }, - 'getUTCMilliseconds' => function() { - throw new Ex(Err::create('date.getUTCMilliseconds not implemented')); + 'getUTCMinutes' => function () { + throw new Ex(Err::create('date.getUTCMinutes not implemented')); }, - 'getUTCMinutes' => function() { - throw new Ex(Err::create('date.getUTCMinutes not implemented')); + 'getUTCMonth' => function () { + throw new Ex(Err::create('date.getUTCMonth not implemented')); }, - 'getUTCMonth' => function() { - throw new Ex(Err::create('date.getUTCMonth not implemented')); + 'getUTCSeconds' => function () { + throw new Ex(Err::create('date.getUTCSeconds not implemented')); }, - 'getUTCSeconds' => function() { - throw new Ex(Err::create('date.getUTCSeconds not implemented')); + 'setDate' => function () { + throw new Ex(Err::create('date.setDate not implemented')); }, - 'setDate' => function() { - throw new Ex(Err::create('date.setDate not implemented')); + 'setFullYear' => function () { + throw new Ex(Err::create('date.setFullYear not implemented')); }, - 'setFullYear' => function() { - throw new Ex(Err::create('date.setFullYear not implemented')); + 'setHours' => function () { + throw new Ex(Err::create('date.setHours not implemented')); }, - 'setHours' => function() { - throw new Ex(Err::create('date.setHours not implemented')); + 'setMilliseconds' => function () { + throw new Ex(Err::create('date.setMilliseconds not implemented')); }, - 'setMilliseconds' => function() { - throw new Ex(Err::create('date.setMilliseconds not implemented')); + 'setMinutes' => function () { + throw new Ex(Err::create('date.setMinutes not implemented')); }, - 'setMinutes' => function() { - throw new Ex(Err::create('date.setMinutes not implemented')); + 'setMonth' => function () { + throw new Ex(Err::create('date.setMonth not implemented')); }, - 'setMonth' => function() { - throw new Ex(Err::create('date.setMonth not implemented')); + 'setSeconds' => function () { + throw new Ex(Err::create('date.setSeconds not implemented')); }, - 'setSeconds' => function() { - throw new Ex(Err::create('date.setSeconds not implemented')); + 'setUTCDate' => function () { + throw new Ex(Err::create('date.setUTCDate not implemented')); }, - 'setUTCDate' => function() { - throw new Ex(Err::create('date.setUTCDate not implemented')); + 'setUTCFullYear' => function () { + throw new Ex(Err::create('date.setUTCFullYear not implemented')); }, - 'setUTCFullYear' => function() { - throw new Ex(Err::create('date.setUTCFullYear not implemented')); + 'setUTCHours' => function () { + throw new Ex(Err::create('date.setUTCHours not implemented')); }, - 'setUTCHours' => function() { - throw new Ex(Err::create('date.setUTCHours not implemented')); + 'setUTCMilliseconds' => function () { + throw new Ex(Err::create('date.setUTCMilliseconds not implemented')); }, - 'setUTCMilliseconds' => function() { - throw new Ex(Err::create('date.setUTCMilliseconds not implemented')); + 'setUTCMinutes' => function () { + throw new Ex(Err::create('date.setUTCMinutes not implemented')); }, - 'setUTCMinutes' => function() { - throw new Ex(Err::create('date.setUTCMinutes not implemented')); + 'setUTCMonth' => function () { + throw new Ex(Err::create('date.setUTCMonth not implemented')); }, - 'setUTCMonth' => function() { - throw new Ex(Err::create('date.setUTCMonth not implemented')); + 'setUTCSeconds' => function () { + throw new Ex(Err::create('date.setUTCSeconds not implemented')); }, - 'setUTCSeconds' => function() { - throw new Ex(Err::create('date.setUTCSeconds not implemented')); + 'getTimezoneOffset' => function () { + throw new Ex(Err::create('date.getTimezoneOffset not implemented')); }, - 'getTimezoneOffset' => function() { - throw new Ex(Err::create('date.getTimezoneOffset not implemented')); + 'getTime' => function () { + throw new Ex(Err::create('date.getTime not implemented')); }, - 'getTime' => function() { - throw new Ex(Err::create('date.getTime not implemented')); + 'getYear' => function () { + throw new Ex(Err::create('date.getYear not implemented')); }, - 'getYear' => function() { - throw new Ex(Err::create('date.getYear not implemented')); + 'setTime' => function () { + throw new Ex(Err::create('date.setTime not implemented')); }, - 'setTime' => function() { - throw new Ex(Err::create('date.setTime not implemented')); + 'setYear' => function () { + throw new Ex(Err::create('date.setYear not implemented')); }, - 'setYear' => function() { - throw new Ex(Err::create('date.setYear not implemented')); - } -); +]; Date::$protoObject = new ObjectClass(); Date::$protoObject->setMethods(Date::$protoMethods, true, false, true); @@ -325,8 +355,8 @@ static function getGlobalConstructor() { //get the local timezone by looking for environment variable; fallback to constant and then UTC Date::$LOCAL_TZ = getenv('LOCAL_TZ'); if (Date::$LOCAL_TZ === false && defined('LOCAL_TZ')) { - Date::$LOCAL_TZ = constant('LOCAL_TZ'); + Date::$LOCAL_TZ = constant('LOCAL_TZ'); } if (Date::$LOCAL_TZ === false) { - Date::$LOCAL_TZ = 'UTC'; + Date::$LOCAL_TZ = 'UTC'; } diff --git a/php/classes/Error.php b/php/classes/Error.php index 4625a3b..a4890d4 100644 --- a/php/classes/Error.php +++ b/php/classes/Error.php @@ -1,76 +1,85 @@ proto = self::$protoObject; - if (func_num_args() === 1) { - $this->set('message', to_string($str)); + function __construct($str = null) + { + parent::__construct(); + $this->proto = self::$protoObject; + if (func_num_args() === 1) { + $this->set('message', to_string($str)); + } } - } - public function getMessage() { - $message = $this->get('message'); - return $this->className . ($message ? ': ' . $message : ''); - } + public function getMessage() + { + $message = $this->get('message'); + return $this->className . ($message ? ': ' . $message : ''); + } - //this is used in class/helper code only - static function create($str, $framesToPop = 0) { - $error = new Err($str); - $stack = debug_backtrace(); - while ($framesToPop--) { - array_shift($stack); + //this is used in class/helper code only + static function create($str, $framesToPop = 0) + { + $error = new Err($str); + $stack = debug_backtrace(); + while ($framesToPop--) { + array_shift($stack); + } + $error->stack = $stack; + return $error; } - $error->stack = $stack; - return $error; - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Error = new Func(function($str = null) { - $error = new Err($str); - $error->stack = debug_backtrace(); - return $error; - }); - $Error->set('prototype', Err::$protoObject); - $Error->setMethods(Err::$classMethods, true, false, true); - return $Error; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Error = new Func(function ($str = null) { + $error = new Err($str); + $error->stack = debug_backtrace(); + return $error; + }); + $Error->set('prototype', Err::$protoObject); + $Error->setMethods(Err::$classMethods, true, false, true); + return $Error; + } } -class RangeErr extends Err { - public $className = "RangeError"; +class RangeErr extends Err +{ + public $className = 'RangeError'; } -class ReferenceErr extends Err { - public $className = "ReferenceError"; +class ReferenceErr extends Err +{ + public $className = 'ReferenceError'; } -class SyntaxErr extends Err { - public $className = "SyntaxError"; +class SyntaxErr extends Err +{ + public $className = 'SyntaxError'; } -class TypeErr extends Err { - public $className = "TypeError"; +class TypeErr extends Err +{ + public $className = 'TypeError'; } -Err::$classMethods = array(); +Err::$classMethods = []; -Err::$protoMethods = array( - 'toString' => function() { - $self = Func::getContext(); - return $self->get('message'); - } -); +Err::$protoMethods = [ + 'toString' => function () { + $self = Func::getContext(); + return $self->get('message'); + }, +]; Err::$protoObject = new ObjectClass(); Err::$protoObject->setMethods(Err::$protoMethods, true, false, true); diff --git a/php/classes/Exception.php b/php/classes/Exception.php index ec3c2aa..76ca930 100644 --- a/php/classes/Exception.php +++ b/php/classes/Exception.php @@ -1,150 +1,166 @@ getMessage(); - } else { - $message = to_string($value); - } - parent::__construct($message); - $this->value = $value; - } - - /** - * PHP Errors (or warning/notices) will usually output something to the - * console and then return some unexpected value like false. Here we cause it - * to throw instead. - */ - static function handleError($level, $message, $file, $line, $context = null) { - if ($level === E_NOTICE) { - return false; + function __construct($value) + { + if ($value instanceof Err) { + $message = $value->getMessage(); + } else { + $message = to_string($value); + } + parent::__construct($message); + $this->value = $value; } - $err = Err::create($message, 1); - $err->set('level', $level); - throw new Ex($err); - } - /** - * Since we use a custom exception handler we need to render our own stack trace - * @param Exception $ex - */ - static function handleException($ex) { - global $console; - $stack = null; - $output = array(); - if ($ex instanceof Ex) { - $value = $ex->value; - if ($value instanceof Err) { - $stack = $value->stack; - $frame = array_shift($stack); - if (isset($frame['file'])) { - $output[] = $frame['file'] . "(" . $frame['line'] . ")\n"; + /** + * PHP Errors (or warning/notices) will usually output something to the + * console and then return some unexpected value like false. Here we cause it + * to throw instead. + */ + static function handleError($level, $message, $file, $line, $context = null) + { + if ($level === E_NOTICE) { + return false; } - $output[] = $value->getMessage() . "\n"; - } - } - if ($stack === null) { - $output[] = $ex->getFile() . "(" . $ex->getLine() . ")\n"; - $output[] = $ex->getMessage() . "\n"; - $stack = $ex->getTrace(); - } - $output[] = self::renderStack($stack) . "\n"; - $output[] = "----\n"; - $output = implode('', $output); - foreach(self::$errorOutputHandlers as $fn) { - $fn($output); + $err = Err::create($message, 1); + $err->set('level', $level); + throw new Ex($err); } - //an exception could be thrown before we have initialized $console - if (isset($console) && ($console instanceof ObjectClass)) { - $console->callMethod('log', $output); - } else { - echo $output; - } - exit(1); - } - static function renderStack($stack) { - $lines = array(); - foreach ($stack as $frame) { - $args = isset($frame['args']) ? $frame['args'] : array(); - $list = array(); - foreach ($args as $arg) { - if (is_string($arg)) { - $list[] = self::stringify($arg); - } else if (is_array($arg)) { - $list[] = 'array()'; - } else if (is_null($arg)) { - $list[] = 'null'; - } else if (is_bool($arg)) { - $list[] = ($arg) ? 'true' : 'false'; - } else if (is_object($arg)) { - $class = get_class($arg); - if ($arg instanceof ObjectClass) { - $constructor = $arg->get('constructor'); - if ($constructor instanceof Func && $constructor->name) { - $class .= '[' . $constructor->name . ']'; + /** + * Since we use a custom exception handler we need to render our own stack trace + * @param Exception $ex + */ + static function handleException($ex) + { + global $console; + $stack = null; + $output = []; + if ($ex instanceof Ex) { + $value = $ex->value; + if ($value instanceof Err) { + $stack = $value->stack; + $frame = array_shift($stack); + if (isset($frame['file'])) { + $output[] = $frame['file'] . '(' . $frame['line'] . ")\n"; + } + $output[] = $value->getMessage() . "\n"; } - } - $list[] = $class; - } else if (is_resource($arg)) { - $list[] = get_resource_type($arg); + } + if ($stack === null) { + $output[] = $ex->getFile() . '(' . $ex->getLine() . ")\n"; + $output[] = $ex->getMessage() . "\n"; + $stack = $ex->getTrace(); + } + $output[] = self::renderStack($stack) . "\n"; + $output[] = "----\n"; + $output = implode('', $output); + foreach (self::$errorOutputHandlers as $fn) { + $fn($output); + } + //an exception could be thrown before we have initialized $console + if (isset($console) && $console instanceof ObjectClass) { + $console->callMethod('log', $output); } else { - $list[] = $arg; + echo $output; } - } - $function = $frame['function']; - /* todo: use Func::$callStack to resolve anonymous functions */ - //if ($function === '{closure}') { - // $name = $wrapper ? $wrapper->name : ''; - // $function = $name ? $name : ''; - //} - if ($function === '{closure}') { - $function = ''; - } - if (isset($frame['class'])) { - $function = $frame['class'] . '->' . $function; - } - $line = ' at '; - if (isset($frame['file'])) { - $line .= $frame['file'] . '(' . $frame['line'] . ') '; - } - $line .= $function . '(' . join(', ', $list) . ') '; - array_push($lines, $line); + exit(1); } - return join("\n", $lines); - } - static function stringify($str) { - $len = strlen($str); - if ($len === 0) { - return "''"; - } - //replace leading extended characters - $str = preg_replace('/^[^\x20-\x7E]+/', '', $str, 1); - $trimAt = null; - $hasExtendedChars = preg_match('/[^\x20-\x7E]/', $str, $matches, PREG_OFFSET_CAPTURE); - if ($hasExtendedChars === 1) { - $trimAt = $matches[0][1]; - } - if ($len > self::MAX_STR_LEN) { - $trimAt = ($trimAt === null) ? self::MAX_STR_LEN : min($trimAt, self::MAX_STR_LEN); - } - if ($trimAt !== null) { - return "'" . substr($str, 0, $trimAt) . "...'($len)"; - } else { - return "'" . $str . "'"; + static function renderStack($stack) + { + $lines = []; + foreach ($stack as $frame) { + $args = isset($frame['args']) ? $frame['args'] : []; + $list = []; + foreach ($args as $arg) { + if (is_string($arg)) { + $list[] = self::stringify($arg); + } elseif (is_array($arg)) { + $list[] = 'array()'; + } elseif (is_null($arg)) { + $list[] = 'null'; + } elseif (is_bool($arg)) { + $list[] = $arg ? 'true' : 'false'; + } elseif (is_object($arg)) { + $class = get_class($arg); + if ($arg instanceof ObjectClass) { + $constructor = $arg->get('constructor'); + if ( + $constructor instanceof Func && + $constructor->name + ) { + $class .= '[' . $constructor->name . ']'; + } + } + $list[] = $class; + } elseif (is_resource($arg)) { + $list[] = get_resource_type($arg); + } else { + $list[] = $arg; + } + } + $function = $frame['function']; + /* todo: use Func::$callStack to resolve anonymous functions */ + //if ($function === '{closure}') { + // $name = $wrapper ? $wrapper->name : ''; + // $function = $name ? $name : ''; + //} + if ($function === '{closure}') { + $function = ''; + } + if (isset($frame['class'])) { + $function = $frame['class'] . '->' . $function; + } + $line = ' at '; + if (isset($frame['file'])) { + $line .= $frame['file'] . '(' . $frame['line'] . ') '; + } + $line .= $function . '(' . join(', ', $list) . ') '; + array_push($lines, $line); + } + return join("\n", $lines); } - } + static function stringify($str) + { + $len = strlen($str); + if ($len === 0) { + return "''"; + } + //replace leading extended characters + $str = preg_replace('/^[^\x20-\x7E]+/', '', $str, 1); + $trimAt = null; + $hasExtendedChars = preg_match( + '/[^\x20-\x7E]/', + $str, + $matches, + PREG_OFFSET_CAPTURE + ); + if ($hasExtendedChars === 1) { + $trimAt = $matches[0][1]; + } + if ($len > self::MAX_STR_LEN) { + $trimAt = + $trimAt === null + ? self::MAX_STR_LEN + : min($trimAt, self::MAX_STR_LEN); + } + if ($trimAt !== null) { + return "'" . substr($str, 0, $trimAt) . "...'($len)"; + } else { + return "'" . $str . "'"; + } + } } if (function_exists('set_error_handler')) { - set_error_handler(array('Ex', 'handleError')); + set_error_handler(['Ex', 'handleError']); } if (function_exists('set_exception_handler')) { - set_exception_handler(array('Ex', 'handleException')); + set_exception_handler(['Ex', 'handleException']); } diff --git a/php/classes/Function.php b/php/classes/Function.php index 9f1cf34..2c8ce01 100644 --- a/php/classes/Function.php +++ b/php/classes/Function.php @@ -1,278 +1,317 @@ proto = self::$protoObject; - $args = func_get_args(); - if (gettype($args[0]) === 'string') { - $this->name = array_shift($args); +class Func extends ObjectClass +{ + public $name = ''; + public $className = 'Function'; + + /* @var callable */ + public $fn = null; + /* @var null|array */ + public $meta = null; + /* @var boolean */ + public $strict = false; + + /* @var null|int */ + public $callStackPosition = null; + /* @var null|array */ + public $args = null; + /* @var null|array */ + public $boundArgs = null; + /* @var null|int */ + public $context = null; + /* @var mixed */ + public $boundContext = null; + /* @var null|Args */ + public $arguments = null; + + /** + * Instantiate is an optional method that can be specified if calling `new` on + * this function should instantiate a different `this` than `new ObjectClass()` + * @var callable + */ + public $instantiate = null; + + static $protoObject = null; + static $classMethods = null; + static $protoMethods = null; + + static $callStack = []; + static $callStackLength = 0; + + function __construct() + { + parent::__construct(); + $this->proto = self::$protoObject; + $args = func_get_args(); + if (gettype($args[0]) === 'string') { + $this->name = array_shift($args); + } + $this->fn = array_shift($args); + $this->meta = isset($args[0]) ? $args[0] : []; + $this->strict = isset($this->meta['strict']); + $prototype = new ObjectClass(); + $prototype->setProp('constructor', $this, true, false, true); + $this->setProp('prototype', $prototype, true, false, true); + $this->setProp('name', $this->name, false, false, false); + } + + function construct() + { + if ($this->instantiate !== null) { + $obj = call_user_func($this->instantiate); + } else { + $obj = new ObjectClass(); + $obj->proto = $this->get('prototype'); + } + $result = $this->apply($obj, func_get_args()); + return is_primitive($result) ? $obj : $result; + } + + function call($context = null) + { + return $this->apply($context, array_slice(func_get_args(), 1)); + } + + function apply($context, $args) + { + if ($this->boundContext !== null) { + $context = $this->boundContext; + if ($this->boundArgs) { + $args = array_merge($this->boundArgs, $args); + } + } + $this->args = $args; + if (!$this->strict) { + if ($context === null || $context === ObjectClass::$null) { + $context = ObjectClass::$global; + } elseif (!($context instanceof ObjectClass)) { + //primitives (boolean, number, string) should be wrapped in object + $context = objectify($context); + } + } + $oldStackPosition = $this->callStackPosition; + $oldArguments = $this->arguments; + $oldContext = $this->context; + $this->context = $context; + $this->callStackPosition = self::$callStackLength; + //add ourself to the call stack, execute, then remove + self::$callStack[self::$callStackLength++] = $this; + $result = call_user_func_array($this->fn, $args); + self::$callStack[--self::$callStackLength] = null; + $this->callStackPosition = $oldStackPosition; + $this->arguments = $oldArguments; + $this->context = $oldContext; + return $result; + } + + function get_arguments() + { + $arguments = $this->arguments; + if ($arguments === null && $this->callStackPosition !== null) { + $arguments = $this->arguments = Args::create($this); + } + return $arguments; } - $this->fn = array_shift($args); - $this->meta = isset($args[0]) ? $args[0] : array(); - $this->strict = isset($this->meta['strict']); - $prototype = new ObjectClass(); - $prototype->setProp('constructor', $this, true, false, true); - $this->setProp('prototype', $prototype, true, false, true); - $this->setProp('name', $this->name, false, false, false); - } - - function construct() { - if ($this->instantiate !== null) { - $obj = call_user_func($this->instantiate); - } else { - $obj = new ObjectClass(); - $obj->proto = $this->get('prototype'); + + function set_arguments($value) + { + return $value; } - $result = $this->apply($obj, func_get_args()); - return is_primitive($result) ? $obj : $result; - } - - function call($context = null) { - return $this->apply($context, array_slice(func_get_args(), 1)); - } - - function apply($context, $args) { - if ($this->boundContext !== null) { - $context = $this->boundContext; - if ($this->boundArgs) { - $args = array_merge($this->boundArgs, $args); - } + + function get_caller() + { + $stackPosition = $this->callStackPosition; + if ($stackPosition !== null && $stackPosition > 0) { + return self::$callStack[$stackPosition - 1]; + } else { + return null; + } } - $this->args = $args; - if (!$this->strict) { - if ($context === null || $context === ObjectClass::$null) { - $context = ObjectClass::$global; - } else if (!($context instanceof ObjectClass)) { - //primitives (boolean, number, string) should be wrapped in object - $context = objectify($context); - } + + function set_caller($value) + { + return $value; } - $oldStackPosition = $this->callStackPosition; - $oldArguments = $this->arguments; - $oldContext = $this->context; - $this->context = $context; - $this->callStackPosition = self::$callStackLength; - //add ourself to the call stack, execute, then remove - self::$callStack[self::$callStackLength++] = $this; - $result = call_user_func_array($this->fn, $args); - self::$callStack[--self::$callStackLength] = null; - $this->callStackPosition = $oldStackPosition; - $this->arguments = $oldArguments; - $this->context = $oldContext; - return $result; - } - - function get_arguments() { - $arguments = $this->arguments; - if ($arguments === null && $this->callStackPosition !== null) { - $arguments = $this->arguments = Args::create($this); + + function get_length() + { + $reflection = new ReflectionObject($this->fn); + $method = $reflection->getMethod('__invoke'); + $arity = $method->getNumberOfParameters(); + if ($this->boundArgs) { + $boundArgsLength = count($this->boundArgs); + $arity = $boundArgsLength >= $arity ? 0 : $arity - $boundArgsLength; + } + return (float) $arity; } - return $arguments; - } - - function set_arguments($value) { - return $value; - } - - function get_caller() { - $stackPosition = $this->callStackPosition; - if ($stackPosition !== null && $stackPosition > 0) { - return self::$callStack[$stackPosition - 1]; - } else { - return null; + + function set_length($value) + { + return $value; } - } - - function set_caller($value) { - return $value; - } - - function get_length() { - $reflection = new ReflectionObject($this->fn); - $method = $reflection->getMethod('__invoke'); - $arity = $method->getNumberOfParameters(); - if ($this->boundArgs) { - $boundArgsLength = count($this->boundArgs); - $arity = ($boundArgsLength >= $arity) ? 0 : $arity - $boundArgsLength; + + //functions are treated as undefined in JSON.stringify + function toJSON() + { + return null; + } + + //get the function that is currently running (top of the call stack) + static function getCurrent() + { + return self::$callStack[self::$callStackLength - 1]; + } + + //get the `this` context for the currently running function + static function getContext() + { + $func = self::$callStack[self::$callStackLength - 1]; + return $func->context; + } + + //get the `arguments` object for the currently running function + static function getArguments() + { + $func = self::$callStack[self::$callStackLength - 1]; + return $func->get_arguments(); + } + + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Function = new Func(function ($fn) { + throw new Ex(Err::create('Cannot construct function at runtime.')); + }); + $Function->set('prototype', Func::$protoObject); + $Function->setMethods(Func::$classMethods, true, false, true); + return $Function; } - return (float)$arity; - } - - function set_length($value) { - return $value; - } - - //functions are treated as undefined in JSON.stringify - function toJSON() { - return null; - } - - //get the function that is currently running (top of the call stack) - static function getCurrent() { - return self::$callStack[self::$callStackLength - 1]; - } - - //get the `this` context for the currently running function - static function getContext() { - $func = self::$callStack[self::$callStackLength - 1]; - return $func->context; - } - - //get the `arguments` object for the currently running function - static function getArguments() { - $func = self::$callStack[self::$callStackLength - 1]; - return $func->get_arguments(); - } - - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Function = new Func(function($fn) { - throw new Ex(Err::create('Cannot construct function at runtime.')); - }); - $Function->set('prototype', Func::$protoObject); - $Function->setMethods(Func::$classMethods, true, false, true); - return $Function; - } } -class Args extends ObjectClass { - /* @var array */ - public $args = null; - /* @var int */ - public $length = 0; - /* @var Func */ - public $callee = null; - - static $protoObject = null; - static $classMethods = null; - static $protoMethods = null; - - function toArray() { - return array_slice($this->args, 0); - } - - function get_callee() { - return $this->callee; - } - - function set_callee($value) { - return $value; - } - - function get_caller() { - return $this->callee->get_caller(); - } - - function set_caller($value) { - return $value; - } - - function get_length() { - return (float)$this->length; - } - - function set_length($value) { - return $value; - } - - static function create($callee) { - $self = new Args(); - foreach ($callee->args as $i => $arg) { - $self->set($i, $arg); - $self->length += 1; +class Args extends ObjectClass +{ + /* @var array */ + public $args = null; + /* @var int */ + public $length = 0; + /* @var Func */ + public $callee = null; + + static $protoObject = null; + static $classMethods = null; + static $protoMethods = null; + + function toArray() + { + return array_slice($this->args, 0); + } + + function get_callee() + { + return $this->callee; + } + + function set_callee($value) + { + return $value; + } + + function get_caller() + { + return $this->callee->get_caller(); + } + + function set_caller($value) + { + return $value; + } + + function get_length() + { + return (float) $this->length; + } + + function set_length($value) + { + return $value; + } + + static function create($callee) + { + $self = new Args(); + foreach ($callee->args as $i => $arg) { + $self->set($i, $arg); + $self->length += 1; + } + $self->args = $callee->args; + $self->callee = $callee; + return $self; } - $self->args = $callee->args; - $self->callee = $callee; - return $self; - } } -Func::$classMethods = array(); - -Func::$protoMethods = array( - 'bind' => function($context) { - $self = Func::getContext(); - $fn = new Func($self->name, $self->fn, $self->meta); - $fn->boundContext = $context; - $args = array_slice(func_get_args(), 1); - if (!empty($args)) { - $fn->boundArgs = $args; - } - return $fn; +Func::$classMethods = []; + +Func::$protoMethods = [ + 'bind' => function ($context) { + $self = Func::getContext(); + $fn = new Func($self->name, $self->fn, $self->meta); + $fn->boundContext = $context; + $args = array_slice(func_get_args(), 1); + if (!empty($args)) { + $fn->boundArgs = $args; + } + return $fn; }, - 'call' => function() { - $self = Func::getContext(); - $args = func_get_args(); - return $self->apply($args[0], array_slice($args, 1)); + 'call' => function () { + $self = Func::getContext(); + $args = func_get_args(); + return $self->apply($args[0], array_slice($args, 1)); }, - 'apply' => function($context, $args = null) { - $self = Func::getContext(); - if ($args === null) { - $args = array(); - } else - if ($args instanceof Args || $args instanceof Arr) { - $args = $args->toArray(); - } else { - throw new Ex(Err::create('Function.prototype.apply: Arguments list has wrong type')); - } - return $self->apply($context, $args); + 'apply' => function ($context, $args = null) { + $self = Func::getContext(); + if ($args === null) { + $args = []; + } elseif ($args instanceof Args || $args instanceof Arr) { + $args = $args->toArray(); + } else { + throw new Ex( + Err::create( + 'Function.prototype.apply: Arguments list has wrong type' + ) + ); + } + return $self->apply($context, $args); }, - 'toString' => function() { - $self = Func::getContext(); - $source = array_key_exists('source_', $GLOBALS) ? $GLOBALS['source_'] : null; - if ($source) { - $meta = $self->meta; - if (isset($meta['id']) && isset($source[$meta['id']])) { - $source = $source[$meta['id']]; - return substr($source, $meta['start'], $meta['end'] - $meta['start'] + 1); + 'toString' => function () { + $self = Func::getContext(); + $source = array_key_exists('source_', $GLOBALS) + ? $GLOBALS['source_'] + : null; + if ($source) { + $meta = $self->meta; + if (isset($meta['id']) && isset($source[$meta['id']])) { + $source = $source[$meta['id']]; + return substr( + $source, + $meta['start'], + $meta['end'] - $meta['start'] + 1 + ); + } } - } - return 'function ' . $self->name . '() { [native code] }'; - } -); + return 'function ' . $self->name . '() { [native code] }'; + }, +]; Func::$protoObject = new ObjectClass(); Func::$protoObject->setMethods(Func::$protoMethods, true, false, true); //set the methods on Object.prototype before we proceed -ObjectClass::$protoObject->setMethods(ObjectClass::$protoMethods, true, false, true); +ObjectClass::$protoObject->setMethods( + ObjectClass::$protoMethods, + true, + false, + true +); diff --git a/php/classes/Global.php b/php/classes/Global.php index c87cfe4..7a88c89 100644 --- a/php/classes/Global.php +++ b/php/classes/Global.php @@ -1,128 +1,190 @@ 1, 'Boolean' => 1, 'Buffer' => 1, 'Date' => 1, 'Error' => 1, 'RangeError' => 1, 'ReferenceError' => 1, 'SyntaxError' => 1, 'TypeError' => 1, 'Function' => 1, 'Infinity' => 1, 'JSON' => 1, 'Math' => 1, 'NaN' => 1, 'Number' => 1, 'Object' => 1, 'RegExp' => 1, 'String' => 1, 'console' => 1, 'decodeURI' => 1, 'decodeURIComponent' => 1, 'encodeURI' => 1, 'encodeURIComponent' => 1, 'escape' => 1, 'eval' => 1, 'isFinite' => 1, 'isNaN' => 1, 'parseFloat' => 1, 'parseInt' => 1, 'undefined' => 1, 'unescape' => 1); - //copy of GLOBALS that we have deleted (if any) - static $OLD_GLOBALS = null; - //a list of PHP's superglobals (cannot be accessed via set/get/remove) - static $SUPER_GLOBALS = array('GLOBALS' => 1, '_SERVER' => 1, '_GET' => 1, '_POST' => 1, '_FILES' => 1, '_COOKIE' => 1, '_SESSION' => 1, '_REQUEST' => 1, '_ENV' => 1); - - static $protoObject = null; - static $classMethods = null; - - function set($key, $value) { - if (array_key_exists($key, self::$immutable)) { - return $value; +class GlobalObject extends ObjectClass +{ + public $className = 'global'; + + //disallow mutating pre-defined globals + static $immutable = [ + 'Array' => 1, + 'Boolean' => 1, + 'Buffer' => 1, + 'Date' => 1, + 'Error' => 1, + 'RangeError' => 1, + 'ReferenceError' => 1, + 'SyntaxError' => 1, + 'TypeError' => 1, + 'Function' => 1, + 'Infinity' => 1, + 'JSON' => 1, + 'Math' => 1, + 'NaN' => 1, + 'Number' => 1, + 'Object' => 1, + 'RegExp' => 1, + 'String' => 1, + 'console' => 1, + 'decodeURI' => 1, + 'decodeURIComponent' => 1, + 'encodeURI' => 1, + 'encodeURIComponent' => 1, + 'escape' => 1, + 'eval' => 1, + 'isFinite' => 1, + 'isNaN' => 1, + 'parseFloat' => 1, + 'parseInt' => 1, + 'undefined' => 1, + 'unescape' => 1, + ]; + //copy of GLOBALS that we have deleted (if any) + static $OLD_GLOBALS = null; + //a list of PHP's superglobals (cannot be accessed via set/get/remove) + static $SUPER_GLOBALS = [ + 'GLOBALS' => 1, + '_SERVER' => 1, + '_GET' => 1, + '_POST' => 1, + '_FILES' => 1, + '_COOKIE' => 1, + '_SESSION' => 1, + '_REQUEST' => 1, + '_ENV' => 1, + ]; + + static $protoObject = null; + static $classMethods = null; + + function set($key, $value) + { + if (array_key_exists($key, self::$immutable)) { + return $value; + } + $key = self::encodeVar($key); + return $GLOBALS[$key] = $value; } - $key = self::encodeVar($key); - return ($GLOBALS[$key] = $value); - } - function get($key) { - $key = self::encodeVar($key); - $value = array_key_exists($key, $GLOBALS) ? $GLOBALS[$key] : null; - return $value; - } - - function remove($key) { - if (array_key_exists($key, self::$immutable)) { - return false; - } - $key = self::encodeVar($key); - if (array_key_exists($key, $GLOBALS)) { - unset($GLOBALS[$key]); + function get($key) + { + $key = self::encodeVar($key); + $value = array_key_exists($key, $GLOBALS) ? $GLOBALS[$key] : null; + return $value; } - return true; - } - - //determine if a valid value exists at the given key (don't walk proto) - function hasOwnProperty($key) { - $key = self::encodeVar($key); - return array_key_exists($key, $GLOBALS); - } - //determine if a valid value exists at the given key (walk proto) - function hasProperty($key) { - $key = self::encodeVar($key); - if (array_key_exists($key, $GLOBALS)) { - return true; + function remove($key) + { + if (array_key_exists($key, self::$immutable)) { + return false; + } + $key = self::encodeVar($key); + if (array_key_exists($key, $GLOBALS)) { + unset($GLOBALS[$key]); + } + return true; } - $proto = $this->proto; - if ($proto instanceof ObjectClass) { - return $proto->hasProperty($key); - } - return false; - } - //produce the list of keys (all globals are enumerable) - function getOwnKeys($onlyEnumerable) { - $arr = array(); - foreach ($GLOBALS as $key => $value) { - if (!array_key_exists($key, self::$SUPER_GLOBALS)) { - $arr[] = self::decodeVar($key); - } + //determine if a valid value exists at the given key (don't walk proto) + function hasOwnProperty($key) + { + $key = self::encodeVar($key); + return array_key_exists($key, $GLOBALS); } - return $arr; - } - //produce the list of keys (walk proto) - function getKeys(&$arr = array()) { - foreach ($GLOBALS as $key => $value) { - if (!array_key_exists($key, self::$SUPER_GLOBALS)) { - $arr[] = self::decodeVar($key); - } + //determine if a valid value exists at the given key (walk proto) + function hasProperty($key) + { + $key = self::encodeVar($key); + if (array_key_exists($key, $GLOBALS)) { + return true; + } + $proto = $this->proto; + if ($proto instanceof ObjectClass) { + return $proto->hasProperty($key); + } + return false; } - $proto = $this->proto; - if ($proto instanceof ObjectClass) { - $proto->getKeys($arr); + + //produce the list of keys (all globals are enumerable) + function getOwnKeys($onlyEnumerable) + { + $arr = []; + foreach ($GLOBALS as $key => $value) { + if (!array_key_exists($key, self::$SUPER_GLOBALS)) { + $arr[] = self::decodeVar($key); + } + } + return $arr; } - return $arr; - } - static function encodeVar($str) { - if (array_key_exists($str, self::$SUPER_GLOBALS)) { - return $str . '_'; + //produce the list of keys (walk proto) + function getKeys(&$arr = []) + { + foreach ($GLOBALS as $key => $value) { + if (!array_key_exists($key, self::$SUPER_GLOBALS)) { + $arr[] = self::decodeVar($key); + } + } + $proto = $this->proto; + if ($proto instanceof ObjectClass) { + $proto->getKeys($arr); + } + return $arr; } - $str = preg_replace('/_$/', '__', $str); - $str = preg_replace_callback('/[^a-zA-Z0-9_]/', 'self::encodeChar', $str); - return $str; - } - static function encodeChar($matches) { - return '«' . bin2hex($matches[0]) . '»'; - } + static function encodeVar($str) + { + if (array_key_exists($str, self::$SUPER_GLOBALS)) { + return $str . '_'; + } + $str = preg_replace('/_$/', '__', $str); + $str = preg_replace_callback( + '/[^a-zA-Z0-9_]/', + 'self::encodeChar', + $str + ); + return $str; + } - static function decodeVar($str) { - $len = strlen($str); - if ($str[$len - 1] === '_') { - $name = substr($str, 0, $len - 1); - if (array_key_exists($name, self::$SUPER_GLOBALS)) { - return $name; - } + static function encodeChar($matches) + { + return '«' . bin2hex($matches[0]) . '»'; } - $str = preg_replace('/__$/', '_', $str); - $str = preg_replace_callback('/«([a-z0-9]+)»/', 'self::decodeChar', $str); - return $str; - } - static function decodeChar($matches) { - //note: this is a workaround for when hex2bin() is not available (v5.3) - //todo: ensure the hex string length is not odd - return pack('H*', $matches[1]); - } + static function decodeVar($str) + { + $len = strlen($str); + if ($str[$len - 1] === '_') { + $name = substr($str, 0, $len - 1); + if (array_key_exists($name, self::$SUPER_GLOBALS)) { + return $name; + } + } + $str = preg_replace('/__$/', '_', $str); + $str = preg_replace_callback( + '/«([a-z0-9]+)»/', + 'self::decodeChar', + $str + ); + return $str; + } - static function unsetGlobals() { - self::$OLD_GLOBALS = array(); - foreach ($GLOBALS as $key => $value) { - if (!array_key_exists($key, self::$SUPER_GLOBALS)) { - self::$OLD_GLOBALS[$key] = $value; - unset($GLOBALS[$key]); - } + static function decodeChar($matches) + { + //note: this is a workaround for when hex2bin() is not available (v5.3) + //todo: ensure the hex string length is not odd + return pack('H*', $matches[1]); } - } + static function unsetGlobals() + { + self::$OLD_GLOBALS = []; + foreach ($GLOBALS as $key => $value) { + if (!array_key_exists($key, self::$SUPER_GLOBALS)) { + self::$OLD_GLOBALS[$key] = $value; + unset($GLOBALS[$key]); + } + } + } } GlobalObject::unsetGlobals(); diff --git a/php/classes/Number.php b/php/classes/Number.php index 2f6015d..07014a0 100644 --- a/php/classes/Number.php +++ b/php/classes/Number.php @@ -1,131 +1,140 @@ proto = self::$protoObject; - if (func_num_args() === 1) { - $this->value = (float)$value; + function __construct($value = null) + { + parent::__construct(); + $this->proto = self::$protoObject; + if (func_num_args() === 1) { + $this->value = (float) $value; + } } - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Number = new Func(function($value = 0) { - $self = Func::getContext(); - if ($self instanceof Number) { - $self->value = to_number($value); - return $self; - } else { - return to_number($value); - } - }); - $Number->instantiate = function() { - return new Number(); - }; - $Number->set('prototype', Number::$protoObject); - $Number->setMethods(Number::$classMethods, true, false, true); - //constants - $Number->set('NaN', NAN); - $Number->set('MAX_VALUE', 1.8e308); - $Number->set('MIN_VALUE', -1.8e308); - $Number->set('NEGATIVE_INFINITY', -INF); - $Number->set('POSITIVE_INFINITY', INF); - return $Number; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Number = new Func(function ($value = 0) { + $self = Func::getContext(); + if ($self instanceof Number) { + $self->value = to_number($value); + return $self; + } else { + return to_number($value); + } + }); + $Number->instantiate = function () { + return new Number(); + }; + $Number->set('prototype', Number::$protoObject); + $Number->setMethods(Number::$classMethods, true, false, true); + //constants + $Number->set('NaN', NAN); + $Number->set('MAX_VALUE', 1.8e308); + $Number->set('MIN_VALUE', -1.8e308); + $Number->set('NEGATIVE_INFINITY', -INF); + $Number->set('POSITIVE_INFINITY', INF); + return $Number; + } } -Number::$classMethods = array( - 'isFinite' => function($value) { - $value = to_number($value); - return !($value === INF || $value === -INF || is_nan($value)); +Number::$classMethods = [ + 'isFinite' => function ($value) { + $value = to_number($value); + return !($value === INF || $value === -INF || is_nan($value)); }, - 'parseInt' => function($value, $radix = null) { - $value = to_string($value); - $value = preg_replace('/^[\\t\\x0B\\f \\xA0\\r\\n]+/', '', $value); - $sign = ($value[0] === '-') ? -1 : 1; - $value = preg_replace('/^[+-]/', '', $value); - if ($radix === null && strtolower(substr($value, 0, 2)) === '0x') { - $radix = 16; - } - if ($radix === null) { - $radix = 10; - } else { - $radix = to_number($radix); - if (is_nan($radix) || $radix < 2 || $radix > 36) { - return NAN; + 'parseInt' => function ($value, $radix = null) { + $value = to_string($value); + $value = preg_replace('/^[\\t\\x0B\\f \\xA0\\r\\n]+/', '', $value); + $sign = $value[0] === '-' ? -1 : 1; + $value = preg_replace('/^[+-]/', '', $value); + if ($radix === null && strtolower(substr($value, 0, 2)) === '0x') { + $radix = 16; } - } - if ($radix === 10) { - return preg_match('/^[0-9]/', $value) ? (float)(intval($value) * $sign) : NAN; - } elseif ($radix === 16) { - $value = preg_replace('/^0x/i', '', $value); - return preg_match('/^[0-9a-f]/i', $value) ? (float)(hexdec($value) * $sign) : NAN; - } elseif ($radix === 8) { - return preg_match('/^[0-7]/', $value) ? (float)(octdec($value) * $sign) : NAN; - } - $value = strtoupper($value); - $len = strlen($value); - $numValidChars = 0; - for ($i = 0; $i < $len; $i++) { - $n = ord($value[$i]); - if ($n >= 48 && $n <= 57) { - $n = $n - 48; - } elseif ($n >= 65 && $n <= 90) { - $n = $n - 55; + if ($radix === null) { + $radix = 10; } else { - $n = 36; + $radix = to_number($radix); + if (is_nan($radix) || $radix < 2 || $radix > 36) { + return NAN; + } } - if ($n < $radix) { - $numValidChars += 1; - } else { - break; + if ($radix === 10) { + return preg_match('/^[0-9]/', $value) + ? (float) (intval($value) * $sign) + : NAN; + } elseif ($radix === 16) { + $value = preg_replace('/^0x/i', '', $value); + return preg_match('/^[0-9a-f]/i', $value) + ? (float) (hexdec($value) * $sign) + : NAN; + } elseif ($radix === 8) { + return preg_match('/^[0-7]/', $value) + ? (float) (octdec($value) * $sign) + : NAN; } - } - if ($numValidChars > 0) { - $value = substr($value, 0, $numValidChars); - return floatval(base_convert($value, $radix, 10)); - } - return NAN; + $value = strtoupper($value); + $len = strlen($value); + $numValidChars = 0; + for ($i = 0; $i < $len; $i++) { + $n = ord($value[$i]); + if ($n >= 48 && $n <= 57) { + $n = $n - 48; + } elseif ($n >= 65 && $n <= 90) { + $n = $n - 55; + } else { + $n = 36; + } + if ($n < $radix) { + $numValidChars += 1; + } else { + break; + } + } + if ($numValidChars > 0) { + $value = substr($value, 0, $numValidChars); + return floatval(base_convert($value, $radix, 10)); + } + return NAN; }, - 'parseFloat' => function($value) { - $value = to_string($value); - $value = preg_replace('/^[\\t\\x0B\\f \\xA0\\r\\n]+/', '', $value); - $sign = ($value[0] === '-') ? -1 : 1; - $value = preg_replace('/^[+-]/', '', $value); - if (preg_match('/^(\d+\.\d*|\.\d+|\d+)e([+-]?[0-9]+)/i', $value, $m)) { - return (float)($sign * $m[1] * pow(10, $m[2])); - } - if (preg_match('/^(\d+\.\d*|\.\d+|\d+)/i', $value, $m)) { - return (float)($m[0] * $sign); - } - return NAN; + 'parseFloat' => function ($value) { + $value = to_string($value); + $value = preg_replace('/^[\\t\\x0B\\f \\xA0\\r\\n]+/', '', $value); + $sign = $value[0] === '-' ? -1 : 1; + $value = preg_replace('/^[+-]/', '', $value); + if (preg_match('/^(\d+\.\d*|\.\d+|\d+)e([+-]?[0-9]+)/i', $value, $m)) { + return (float) ($sign * $m[1] * pow(10, $m[2])); + } + if (preg_match('/^(\d+\.\d*|\.\d+|\d+)/i', $value, $m)) { + return (float) ($m[0] * $sign); + } + return NAN; }, - 'isNaN' => function($value) { - return is_nan(to_number($value)); - } -); + 'isNaN' => function ($value) { + return is_nan(to_number($value)); + }, +]; -Number::$protoMethods = array( - 'valueOf' => function() { - $self = Func::getContext(); - return $self->value; +Number::$protoMethods = [ + 'valueOf' => function () { + $self = Func::getContext(); + return $self->value; }, - 'toString' => function($radix = null) { - $self = Func::getContext(); - //todo: radix - return to_string($self->value); - } -); + 'toString' => function ($radix = null) { + $self = Func::getContext(); + //todo: radix + return to_string($self->value); + }, +]; Number::$protoObject = new ObjectClass(); Number::$protoObject->setMethods(Number::$protoMethods, true, false, true); diff --git a/php/classes/ObjectClass.php b/php/classes/ObjectClass.php index 9998182..43e2e9f 100644 --- a/php/classes/ObjectClass.php +++ b/php/classes/ObjectClass.php @@ -1,451 +1,535 @@ proto = self::$protoObject; - $args = func_get_args(); - if (count($args) > 0) { - $this->init($args); + function __construct() + { + $this->proto = self::$protoObject; + $args = func_get_args(); + if (count($args) > 0) { + $this->init($args); + } } - } - /** - * Sets properties from an array (arguments) similar to: - * `array('key1', $value1, 'key2', $value2)` - * @param array $arr - */ - function init($arr) { - $len = count($arr); - for ($i = 0; $i < $len; $i += 2) { - $this->set($arr[$i], $arr[$i + 1]); + /** + * Sets properties from an array (arguments) similar to: + * `array('key1', $value1, 'key2', $value2)` + * @param array $arr + */ + function init($arr) + { + $len = count($arr); + for ($i = 0; $i < $len; $i += 2) { + $this->set($arr[$i], $arr[$i + 1]); + } } - } - function get($key) { - $key = (string)$key; - if (method_exists($this, 'get_' . $key)) { - return $this->{'get_' . $key}(); - } - $obj = $this; - while ($obj !== ObjectClass::$null) { - if (array_key_exists($key, $obj->data)) { - return $obj->data[$key]; - } - $obj = $obj->proto; + function get($key) + { + $key = (string) $key; + if (method_exists($this, 'get_' . $key)) { + return $this->{'get_' . $key}(); + } + $obj = $this; + while ($obj !== ObjectClass::$null) { + if (array_key_exists($key, $obj->data)) { + return $obj->data[$key]; + } + $obj = $obj->proto; + } + return null; } - return null; - } - function set($key, $value) { - $key = (string)$key; - if (method_exists($this, 'set_' . $key)) { - return $this->{'set_' . $key}($value); - } - if (!array_key_exists($key, $this->dscr) || $this->dscr[$key]->writable) { - $this->data[$key] = $value; + function set($key, $value) + { + $key = (string) $key; + if (method_exists($this, 'set_' . $key)) { + return $this->{'set_' . $key}($value); + } + if ( + !array_key_exists($key, $this->dscr) || + $this->dscr[$key]->writable + ) { + $this->data[$key] = $value; + } + return $value; } - return $value; - } - function remove($key) { - $key = (string)$key; - if (array_key_exists($key, $this->dscr)) { - if (!$this->dscr[$key]->configurable) { - return false; - } - unset($this->dscr[$key]); + function remove($key) + { + $key = (string) $key; + if (array_key_exists($key, $this->dscr)) { + if (!$this->dscr[$key]->configurable) { + return false; + } + unset($this->dscr[$key]); + } + unset($this->data[$key]); + return true; } - unset($this->data[$key]); - return true; - } - //determine if the given property exists (don't walk proto) - function hasOwnProperty($key) { - $key = (string)$key; - if (method_exists($this, 'get_' . $key)) { - return true; + //determine if the given property exists (don't walk proto) + function hasOwnProperty($key) + { + $key = (string) $key; + if (method_exists($this, 'get_' . $key)) { + return true; + } + return array_key_exists($key, $this->data); } - return array_key_exists($key, $this->data); - } - //determine if the given property exists (walk proto) - function hasProperty($key) { - $key = (string)$key; - if ($this->hasOwnProperty($key)) { - return true; - } - $proto = $this->proto; - if ($proto instanceof ObjectClass) { - return $proto->hasProperty($key); + //determine if the given property exists (walk proto) + function hasProperty($key) + { + $key = (string) $key; + if ($this->hasOwnProperty($key)) { + return true; + } + $proto = $this->proto; + if ($proto instanceof ObjectClass) { + return $proto->hasProperty($key); + } + return false; } - return false; - } - //produce the list of keys (optionally get only enumerable keys) - function getOwnKeys($onlyEnumerable) { - $arr = array(); - foreach ($this->data as $key => $value) { - $key = (string)$key; - if ($onlyEnumerable) { - $dscr = isset($this->dscr[$key]) ? $this->dscr[$key] : null; - if (!$dscr || $dscr->enumerable) { - $arr[] = $key; - } - } else { - $arr[] = $key; - } + //produce the list of keys (optionally get only enumerable keys) + function getOwnKeys($onlyEnumerable) + { + $arr = []; + foreach ($this->data as $key => $value) { + $key = (string) $key; + if ($onlyEnumerable) { + $dscr = isset($this->dscr[$key]) ? $this->dscr[$key] : null; + if (!$dscr || $dscr->enumerable) { + $arr[] = $key; + } + } else { + $arr[] = $key; + } + } + return $arr; } - return $arr; - } - //produce the list of keys that are considered to be enumerable (walk proto) - function getKeys(&$arr = array()) { - foreach ($this->data as $key => $v) { - $key = (string)$key; - $dscr = isset($this->dscr[$key]) ? $this->dscr[$key] : null; - if (!$dscr || $dscr->enumerable) { - $arr[] = $key; - } - } - $proto = $this->proto; - if ($proto instanceof ObjectClass) { - $proto->getKeys($arr); + //produce the list of keys that are considered to be enumerable (walk proto) + function getKeys(&$arr = []) + { + foreach ($this->data as $key => $v) { + $key = (string) $key; + $dscr = isset($this->dscr[$key]) ? $this->dscr[$key] : null; + if (!$dscr || $dscr->enumerable) { + $arr[] = $key; + } + } + $proto = $this->proto; + if ($proto instanceof ObjectClass) { + $proto->getKeys($arr); + } + return $arr; } - return $arr; - } - /** - * @param string $key - * @param mixed $value - * @param bool $writable - * @param bool $enumerable - * @param bool $configurable - * @return mixed - */ - function setProp($key, $value, $writable = null, $enumerable = null, $configurable = null) { - $key = (string)$key; - if (array_key_exists($key, $this->dscr)) { - $result = $this->dscr[$key]; - unset($this->dscr[$key]); - } else { - $result = new Descriptor(true, true, true); - } - //here we do NOT check if it is configurable (this method is only used internally) - if ($writable !== null) { - $result->writable = !!$writable; - } - if ($enumerable !== null) { - $result->enumerable = !!$enumerable; - } - if ($configurable !== null) { - $result->configurable = !!$configurable; - } - //if all are true don't bother creating a descriptor - if (!$result->writable || !$result->enumerable || !$result->configurable) { - $this->dscr[$key] = $result; + /** + * @param string $key + * @param mixed $value + * @param bool $writable + * @param bool $enumerable + * @param bool $configurable + * @return mixed + */ + function setProp( + $key, + $value, + $writable = null, + $enumerable = null, + $configurable = null + ) { + $key = (string) $key; + if (array_key_exists($key, $this->dscr)) { + $result = $this->dscr[$key]; + unset($this->dscr[$key]); + } else { + $result = new Descriptor(true, true, true); + } + //here we do NOT check if it is configurable (this method is only used internally) + if ($writable !== null) { + $result->writable = !!$writable; + } + if ($enumerable !== null) { + $result->enumerable = !!$enumerable; + } + if ($configurable !== null) { + $result->configurable = !!$configurable; + } + //if all are true don't bother creating a descriptor + if ( + !$result->writable || + !$result->enumerable || + !$result->configurable + ) { + $this->dscr[$key] = $result; + } + //here we do NOT check for a setter (this method is only used internally) + $this->data[$key] = $value; + return $value; } - //here we do NOT check for a setter (this method is only used internally) - $this->data[$key] = $value; - return $value; - } - /** - * @param array $props - * @param bool|null $writable - * @param bool|null $enumerable - * @param bool|null $configurable - */ - function setProps($props, $writable = null, $enumerable = null, $configurable = null) { - foreach ($props as $key => $value) { - $this->setProp($key, $value, $writable, $enumerable, $configurable); + /** + * @param array $props + * @param bool|null $writable + * @param bool|null $enumerable + * @param bool|null $configurable + */ + function setProps( + $props, + $writable = null, + $enumerable = null, + $configurable = null + ) { + foreach ($props as $key => $value) { + $this->setProp($key, $value, $writable, $enumerable, $configurable); + } } - } - /** - * @param array $methods - * @param bool|null $writable - * @param bool|null $enumerable - * @param bool|null $configurable - */ - function setMethods($methods, $writable = null, $enumerable = null, $configurable = null) { - foreach ($methods as $name => $fn) { - $func = new Func((string)$name, $fn); - $func->strict = true; - $this->setProp($name, $func, $writable, $enumerable, $configurable); + /** + * @param array $methods + * @param bool|null $writable + * @param bool|null $enumerable + * @param bool|null $configurable + */ + function setMethods( + $methods, + $writable = null, + $enumerable = null, + $configurable = null + ) { + foreach ($methods as $name => $fn) { + $func = new Func((string) $name, $fn); + $func->strict = true; + $this->setProp($name, $func, $writable, $enumerable, $configurable); + } } - } - /** - * Get a native associative array containing enumerable own properties - * @return array - */ - function toArray() { - $keys = $this->getOwnKeys(true); - $results = array(); - foreach ($keys as $key) { - $results[$key] = $this->get($key); + /** + * Get a native associative array containing enumerable own properties + * @return array + */ + function toArray() + { + $keys = $this->getOwnKeys(true); + $results = []; + foreach ($keys as $key) { + $results[$key] = $this->get($key); + } + return $results; } - return $results; - } - /** - * @param string $name - * @return mixed - */ - function callMethod($name) { - /** @var Func $fn */ - $fn = $this->get($name); - if (!($fn instanceof Func)) { - throw new Ex(Err::create('Invalid method called')); + /** + * @param string $name + * @return mixed + */ + function callMethod($name) + { + /** @var Func $fn */ + $fn = $this->get($name); + if (!($fn instanceof Func)) { + throw new Ex(Err::create('Invalid method called')); + } + $args = array_slice(func_get_args(), 1); + return $fn->apply($this, $args); } - $args = array_slice(func_get_args(), 1); - return $fn->apply($this, $args); - } - /** - * Similar to callMethod, we can call "internal" methods (dynamically-attached - * user functions) which are available in PHP land but not from JS - * - * @param string $name - method name - * @param array $args - arguments with which method was called - * @return mixed - * @throws Ex - */ - function __call($name, $args) { - if (isset($this->{$name})) { - return call_user_func_array($this->{$name}, $args); - } else { - throw new Ex(Err::create('Internal method `' . $name . '` not found on ' . gettype($this))); + /** + * Similar to callMethod, we can call "internal" methods (dynamically-attached + * user functions) which are available in PHP land but not from JS + * + * @param string $name - method name + * @param array $args - arguments with which method was called + * @return mixed + * @throws Ex + */ + function __call($name, $args) + { + if (isset($this->{$name})) { + return call_user_func_array($this->{$name}, $args); + } else { + throw new Ex( + Err::create( + 'Internal method `' . + $name . + '` not found on ' . + gettype($this) + ) + ); + } } - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $Object = new Func(function($value = null) { - if ($value === null || $value === ObjectClass::$null) { - return new ObjectClass(); - } else { - return objectify($value); - } - }); - $Object->set('prototype', ObjectClass::$protoObject); - $Object->setMethods(ObjectClass::$classMethods, true, false, true); - return $Object; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $Object = new Func(function ($value = null) { + if ($value === null || $value === ObjectClass::$null) { + return new ObjectClass(); + } else { + return objectify($value); + } + }); + $Object->set('prototype', ObjectClass::$protoObject); + $Object->setMethods(ObjectClass::$classMethods, true, false, true); + return $Object; + } } -class Descriptor { - public $writable = true; - public $enumerable = true; - public $configurable = true; +class Descriptor +{ + public $writable = true; + public $enumerable = true; + public $configurable = true; - function __construct($writable = null, $enumerable = null, $configurable = null) { - $this->writable = ($writable === null) ? true : !!$writable; - $this->enumerable = ($enumerable === null) ? true : !!$enumerable; - $this->configurable = ($configurable === null) ? true : !!$configurable; - } + function __construct( + $writable = null, + $enumerable = null, + $configurable = null + ) { + $this->writable = $writable === null ? true : !!$writable; + $this->enumerable = $enumerable === null ? true : !!$enumerable; + $this->configurable = $configurable === null ? true : !!$configurable; + } - /** - * @return ObjectClass - */ - function toObject($value = null) { - $result = new ObjectClass(); - $result->set('value', $value); - $result->set('writable', $this->writable); - $result->set('enumerable', $this->enumerable); - $result->set('configurable', $this->configurable); - return $result; - } + /** + * @return ObjectClass + */ + function toObject($value = null) + { + $result = new ObjectClass(); + $result->set('value', $value); + $result->set('writable', $this->writable); + $result->set('enumerable', $this->enumerable); + $result->set('configurable', $this->configurable); + return $result; + } - static function getDefault($value = null) { - return new ObjectClass('value', $value, 'writable', true, 'enumerable', true, 'configurable', true); - } + static function getDefault($value = null) + { + return new ObjectClass( + 'value', + $value, + 'writable', + true, + 'enumerable', + true, + 'configurable', + true + ); + } } -ObjectClass::$classMethods = array( - //todo: getPrototypeOf, seal, freeze, preventExtensions, isSealed, isFrozen, isExtensible - 'create' => function($proto) { - if (!($proto instanceof ObjectClass) && $proto !== ObjectClass::$null) { - throw new Ex(Err::create('Object prototype may only be an Object or null')); - } - $obj = new ObjectClass(); - $obj->proto = $proto; - return $obj; - }, - 'keys' => function($obj) { - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create('Object.keys called on non-object')); - } - return Arr::fromArray($obj->getOwnKeys(true)); +ObjectClass::$classMethods = [ + //todo: getPrototypeOf, seal, freeze, preventExtensions, isSealed, isFrozen, isExtensible + 'create' => function ($proto) { + if (!($proto instanceof ObjectClass) && $proto !== ObjectClass::$null) { + throw new Ex( + Err::create('Object prototype may only be an Object or null') + ); + } + $obj = new ObjectClass(); + $obj->proto = $proto; + return $obj; }, - 'getOwnPropertyNames' => function($obj) { - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create('Object.getOwnPropertyNames called on non-object')); - } - return Arr::fromArray($obj->getOwnKeys(false)); + 'keys' => function ($obj) { + if (!($obj instanceof ObjectClass)) { + throw new Ex(Err::create('Object.keys called on non-object')); + } + return Arr::fromArray($obj->getOwnKeys(true)); }, - 'getOwnPropertyDescriptor' => function($obj, $key) { - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create('Object.getOwnPropertyDescriptor called on non-object')); - } - $key = (string)$key; - if (method_exists($obj, 'get_' . $key)) { - $hasProperty = true; - $value = $obj->{'get_' . $key}(); - } else { - $hasProperty = array_key_exists($key, $obj->data); - $value = $hasProperty ? $obj->data[$key] : null; - } - if (array_key_exists($key, $obj->dscr)) { - return $obj->dscr[$key]->toObject($value); - } else if ($hasProperty) { - return Descriptor::getDefault($value); - } else { - return null; - } + 'getOwnPropertyNames' => function ($obj) { + if (!($obj instanceof ObjectClass)) { + throw new Ex( + Err::create('Object.getOwnPropertyNames called on non-object') + ); + } + return Arr::fromArray($obj->getOwnKeys(false)); }, - 'defineProperty' => function($obj, $key, $desc) { - $key = (string)$key; - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create('Object.defineProperty called on non-object')); - } - $writable = $desc->get('writable'); - $enumerable = $desc->get('enumerable'); - $configurable = $desc->get('configurable'); - $updateValue = false; - if (array_key_exists($key, $obj->data)) { - if (array_key_exists($key, $obj->dscr)) { - $result = $obj->dscr[$key]; + 'getOwnPropertyDescriptor' => function ($obj, $key) { + if (!($obj instanceof ObjectClass)) { + throw new Ex( + Err::create( + 'Object.getOwnPropertyDescriptor called on non-object' + ) + ); + } + $key = (string) $key; + if (method_exists($obj, 'get_' . $key)) { + $hasProperty = true; + $value = $obj->{'get_' . $key}(); } else { - $result = $obj->dscr[$key] = new Descriptor(true, true, true); + $hasProperty = array_key_exists($key, $obj->data); + $value = $hasProperty ? $obj->data[$key] : null; } - if (!$result->configurable) { - throw new Ex(TypeErr::create('Cannot redefine property: ' . $key)); + if (array_key_exists($key, $obj->dscr)) { + return $obj->dscr[$key]->toObject($value); + } elseif ($hasProperty) { + return Descriptor::getDefault($value); + } else { + return null; } - if ($writable !== null) { - $result->writable = !!$writable; + }, + 'defineProperty' => function ($obj, $key, $desc) { + $key = (string) $key; + if (!($obj instanceof ObjectClass)) { + throw new Ex( + Err::create('Object.defineProperty called on non-object') + ); } - if ($enumerable !== null) { - $result->enumerable = !!$enumerable; + $writable = $desc->get('writable'); + $enumerable = $desc->get('enumerable'); + $configurable = $desc->get('configurable'); + $updateValue = false; + if (array_key_exists($key, $obj->data)) { + if (array_key_exists($key, $obj->dscr)) { + $result = $obj->dscr[$key]; + } else { + $result = $obj->dscr[$key] = new Descriptor(true, true, true); + } + if (!$result->configurable) { + throw new Ex( + TypeErr::create('Cannot redefine property: ' . $key) + ); + } + if ($writable !== null) { + $result->writable = !!$writable; + } + if ($enumerable !== null) { + $result->enumerable = !!$enumerable; + } + if ($configurable !== null) { + $result->configurable = !!$configurable; + } + //if all are true don't bother creating a descriptor + if ( + $result->writable && + $result->enumerable && + $result->configurable + ) { + unset($obj->dscr[$key]); + } + if ($desc->hasProperty('value')) { + $value = $desc->get('value'); + $updateValue = true; + } + } else { + $writable = $writable === null ? false : !!$writable; + $enumerable = $enumerable === null ? false : !!$enumerable; + $configurable = $configurable === null ? false : !!$configurable; + //if all are true don't bother creating a descriptor + if (!$writable || !$enumerable || !$configurable) { + $result = new Descriptor($writable, $enumerable, $configurable); + $obj->dscr[$key] = $result; + } + $value = $desc->get('value'); + $updateValue = true; } - if ($configurable !== null) { - $result->configurable = !!$configurable; + if ($updateValue) { + if (method_exists($obj, 'set_' . $key)) { + $obj->{'set_' . $key}($value); + } else { + $obj->data[$key] = $value; + } } - //if all are true don't bother creating a descriptor - if ($result->writable && $result->enumerable && $result->configurable) { - unset($obj->dscr[$key]); + }, + 'defineProperties' => function ($obj, $items) { + if (!($obj instanceof ObjectClass)) { + throw new Ex( + Err::create('Object.defineProperties called on non-object') + ); } - if ($desc->hasProperty('value')) { - $value = $desc->get('value'); - $updateValue = true; + if (!($items instanceof ObjectClass)) { + throw new Ex( + Err::create( + 'Object.defineProperties called with invalid list of properties' + ) + ); } - } else { - $writable = ($writable === null) ? false : !!$writable; - $enumerable = ($enumerable === null) ? false : !!$enumerable; - $configurable = ($configurable === null) ? false : !!$configurable; - //if all are true don't bother creating a descriptor - if (!$writable || !$enumerable || !$configurable) { - $result = new Descriptor($writable, $enumerable, $configurable); - $obj->dscr[$key] = $result; - } - $value = $desc->get('value'); - $updateValue = true; - } - if ($updateValue) { - if (method_exists($obj, 'set_' . $key)) { - $obj->{'set_' . $key}($value); - } else { - $obj->data[$key] = $value; + $defineProperty = ObjectClass::$classMethods['defineProperty']; + foreach ($items->data as $key => $value) { + $dscr = isset($items->dscr[$key]) ? $items->dscr[$key] : null; + if (!$dscr || $dscr->enumerable) { + $defineProperty($obj, $key, $value); + } } - } }, - 'defineProperties' => function($obj, $items) { - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create('Object.defineProperties called on non-object')); - } - if (!($items instanceof ObjectClass)) { - throw new Ex(Err::create('Object.defineProperties called with invalid list of properties')); - } - $defineProperty = ObjectClass::$classMethods['defineProperty']; - foreach ($items->data as $key => $value) { - $dscr = isset($items->dscr[$key]) ? $items->dscr[$key] : null; - if (!$dscr || $dscr->enumerable) { - $defineProperty($obj, $key, $value); - } - } + 'getPrototypeOf' => function () { + throw new Ex(Err::create('Object.getPrototypeOf not implemented')); }, - 'getPrototypeOf' => function() { - throw new Ex(Err::create('Object.getPrototypeOf not implemented')); + 'setPrototypeOf' => function () { + throw new Ex(Err::create('Object.getPrototypeOf not implemented')); }, - 'setPrototypeOf' => function() { - throw new Ex(Err::create('Object.getPrototypeOf not implemented')); + 'preventExtensions' => function () { + //throw new Ex(Err::create('Object.preventExtensions not implemented')); }, - 'preventExtensions' => function() { - //throw new Ex(Err::create('Object.preventExtensions not implemented')); + 'isExtensible' => function () { + //throw new Ex(Err::create('Object.isExtensible not implemented')); + return false; }, - 'isExtensible' => function() { - //throw new Ex(Err::create('Object.isExtensible not implemented')); - return false; + 'seal' => function () { + //throw new Ex(Err::create('Object.seal not implemented')); }, - 'seal' => function() { - //throw new Ex(Err::create('Object.seal not implemented')); + 'isSealed' => function () { + //throw new Ex(Err::create('Object.isSealed not implemented')); + return false; }, - 'isSealed' => function() { - //throw new Ex(Err::create('Object.isSealed not implemented')); - return false; + 'freeze' => function () { + //throw new Ex(Err::create('Object.freeze not implemented')); }, - 'freeze' => function() { - //throw new Ex(Err::create('Object.freeze not implemented')); + 'isFrozen' => function () { + //throw new Ex(Err::create('Object.isFrozen not implemented')); + return false; }, - 'isFrozen' => function() { - //throw new Ex(Err::create('Object.isFrozen not implemented')); - return false; - } -); +]; -ObjectClass::$protoMethods = array( - 'hasOwnProperty' => function($key) { - $self = Func::getContext(); - //this should implicitly ensure $key is a string - return array_key_exists($key, $self->data); +ObjectClass::$protoMethods = [ + 'hasOwnProperty' => function ($key) { + $self = Func::getContext(); + //this should implicitly ensure $key is a string + return array_key_exists($key, $self->data); + }, + 'toString' => function () { + $self = Func::getContext(); + if ($self === null) { + $className = 'Undefined'; + } elseif ($self === ObjectClass::$null) { + $className = 'Null'; + } else { + $obj = objectify($self); + $className = $obj->className; + } + return '[object ' . $className . ']'; }, - 'toString' => function() { - $self = Func::getContext(); - if ($self === null) { - $className = 'Undefined'; - } else if ($self === ObjectClass::$null) { - $className = 'Null'; - } else { - $obj = objectify($self); - $className = $obj->className; - } - return '[object ' . $className . ']'; + 'valueOf' => function () { + return Func::getContext(); }, - 'valueOf' => function() { - return Func::getContext(); - } -); +]; -class NullClass {} +class NullClass +{ +} ObjectClass::$null = new NullClass(); //the methods are not set on Object.prototype until *after* Func class is defined diff --git a/php/classes/RegExp.php b/php/classes/RegExp.php index 068c020..37a7a87 100644 --- a/php/classes/RegExp.php +++ b/php/classes/RegExp.php @@ -1,155 +1,179 @@ proto = self::$protoObject; - $args = func_get_args(); - if (count($args) > 0) { - $this->init($args); +class RegExp extends ObjectClass +{ + public $className = 'RegExp'; + + public $source = ''; + public $ignoreCaseFlag = false; + public $globalFlag = false; + public $multilineFlag = false; + + static $protoObject = null; + static $classMethods = null; + static $protoMethods = null; + + function __construct() + { + parent::__construct(); + $this->proto = self::$protoObject; + $args = func_get_args(); + if (count($args) > 0) { + $this->init($args); + } } - } - - function init($args) { - $this->source = ($args[0] === null) ? '(?:)' : to_string($args[0]); - $flags = array_key_exists('1', $args) ? to_string($args[1]) : ''; - $this->ignoreCaseFlag = (strpos($flags, 'i') !== false); - $this->globalFlag = (strpos($flags, 'g') !== false); - $this->multilineFlag = (strpos($flags, 'm') !== false); - } - - function get_source() { - return $this->source; - } - - function set_source($value) { - return $value; - } - - function get_ignoreCase() { - return $this->ignoreCaseFlag; - } - - function set_ignoreCase($value) { - return $value; - } - - function get_global() { - return $this->globalFlag; - } - - function set_global($value) { - return $value; - } - - function get_multiline() { - return $this->multilineFlag; - } - - function set_multiline($value) { - return $value; - } - - /** - * Note: JS RegExp has different character classes than PCRE. For instance - * in JS `\w` is [ \f\n\r\t\v​\xA0] (and a bunch of unicode spaces) but - * in PCRE it's only [ \f\n\r\t] - * @param bool $pcre - whether to write pcre format. pcre does not allow /g - * flag but does support the non-standard /u flag for utf8 - * @return string - */ - function toString($pcre = true) { - $source = $this->source; - $flags = ''; - if ($this->ignoreCaseFlag) { - $flags .= 'i'; + + function init($args) + { + $this->source = $args[0] === null ? '(?:)' : to_string($args[0]); + $flags = array_key_exists('1', $args) ? to_string($args[1]) : ''; + $this->ignoreCaseFlag = strpos($flags, 'i') !== false; + $this->globalFlag = strpos($flags, 'g') !== false; + $this->multilineFlag = strpos($flags, 'm') !== false; + } + + function get_source() + { + return $this->source; + } + + function set_source($value) + { + return $value; + } + + function get_ignoreCase() + { + return $this->ignoreCaseFlag; + } + + function set_ignoreCase($value) + { + return $value; + } + + function get_global() + { + return $this->globalFlag; } - //pcre doesn't support the global flag - if (!$pcre && $this->globalFlag) { - $flags .= 'g'; + + function set_global($value) + { + return $value; + } + + function get_multiline() + { + return $this->multilineFlag; } - //pcre will interpret the regex and the subject as utf8 with this flag - if ($pcre) { - $flags .= 'u'; + + function set_multiline($value) + { + return $value; } - if ($this->multilineFlag) { - $flags .= 'm'; + + /** + * Note: JS RegExp has different character classes than PCRE. For instance + * in JS `\w` is [ \f\n\r\t\v​\xA0] (and a bunch of unicode spaces) but + * in PCRE it's only [ \f\n\r\t] + * @param bool $pcre - whether to write pcre format. pcre does not allow /g + * flag but does support the non-standard /u flag for utf8 + * @return string + */ + function toString($pcre = true) + { + $source = $this->source; + $flags = ''; + if ($this->ignoreCaseFlag) { + $flags .= 'i'; + } + //pcre doesn't support the global flag + if (!$pcre && $this->globalFlag) { + $flags .= 'g'; + } + //pcre will interpret the regex and the subject as utf8 with this flag + if ($pcre) { + $flags .= 'u'; + } + if ($this->multilineFlag) { + $flags .= 'm'; + } + return '/' . str_replace('/', '\\/', $source) . '/' . $flags; + } + + /** + * Format replacement string for preg_replace + * @param string $str + * @return string + */ + static function toReplacementString($str) + { + $str = str_replace('\\', '\\\\', $str); + $str = str_replace('$&', '$0', $str); + return $str; + } + + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $RegExp = new Func(function () { + $reg = new RegExp(); + $reg->init(func_get_args()); + return $reg; + }); + $RegExp->set('prototype', RegExp::$protoObject); + $RegExp->setMethods(RegExp::$classMethods, true, false, true); + return $RegExp; } - return '/' . str_replace('/', '\\/', $source) . '/' . $flags; - } - - /** - * Format replacement string for preg_replace - * @param string $str - * @return string - */ - static function toReplacementString($str) { - $str = str_replace('\\', '\\\\', $str); - $str = str_replace('$&', '$0', $str); - return $str; - } - - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $RegExp = new Func(function() { - $reg = new RegExp(); - $reg->init(func_get_args()); - return $reg; - }); - $RegExp->set('prototype', RegExp::$protoObject); - $RegExp->setMethods(RegExp::$classMethods, true, false, true); - return $RegExp; - } } -RegExp::$classMethods = array(); - -RegExp::$protoMethods = array( - 'exec' => function($str) { - $self = Func::getContext(); - $str = to_string($str); - //todo $offset - $offset = 0; - $result = preg_match($self->toString(true), $str, $matches, PREG_OFFSET_CAPTURE, $offset); - if ($result === false) { - throw new Ex(Err::create('Error executing Regular Expression: ' . $self->toString())); - } - if ($result === 0) { - return ObjectClass::$null; - } - $index = $matches[0][1]; - $self->set('lastIndex', (float)($index + strlen($matches[0][0]))); - $arr = new Arr(); - foreach ($matches as $match) { - $arr->push($match[0]); - } - $arr->set('index', (float)$index); - $arr->set('input', $str); - return $arr; +RegExp::$classMethods = []; + +RegExp::$protoMethods = [ + 'exec' => function ($str) { + $self = Func::getContext(); + $str = to_string($str); + //todo $offset + $offset = 0; + $result = preg_match( + $self->toString(true), + $str, + $matches, + PREG_OFFSET_CAPTURE, + $offset + ); + if ($result === false) { + throw new Ex( + Err::create( + 'Error executing Regular Expression: ' . $self->toString() + ) + ); + } + if ($result === 0) { + return ObjectClass::$null; + } + $index = $matches[0][1]; + $self->set('lastIndex', (float) ($index + strlen($matches[0][0]))); + $arr = new Arr(); + foreach ($matches as $match) { + $arr->push($match[0]); + } + $arr->set('index', (float) $index); + $arr->set('input', $str); + return $arr; }, - 'test' => function($str) { - $self = Func::getContext(); - $result = preg_match($self->toString(true), to_string($str)); - return ($result !== false); + 'test' => function ($str) { + $self = Func::getContext(); + $result = preg_match($self->toString(true), to_string($str)); + return $result !== false; }, - 'toString' => function() { - $self = Func::getContext(); - return $self->toString(false); - } -); + 'toString' => function () { + $self = Func::getContext(); + return $self->toString(false); + }, +]; RegExp::$protoObject = new ObjectClass(); RegExp::$protoObject->setMethods(RegExp::$protoMethods, true, false, true); diff --git a/php/classes/String.php b/php/classes/String.php index ef83a6f..d217256 100644 --- a/php/classes/String.php +++ b/php/classes/String.php @@ -1,326 +1,367 @@ proto = self::$protoObject; - if (func_num_args() === 1) { - $this->value = $str; - $this->length = mb_strlen($str); + function __construct($str = null) + { + parent::__construct(); + $this->proto = self::$protoObject; + if (func_num_args() === 1) { + $this->value = $str; + $this->length = mb_strlen($str); + } } - } - function get_length() { - return (float)$this->length; - } + function get_length() + { + return (float) $this->length; + } - function set_length($len) { - return $len; - } + function set_length($len) + { + return $len; + } - //allow subscript access to characters: `string[1]` - function get($key) { - if (is_float($key)) { - if ((float)(int)$key === $key && $key >= 0) { - return $this->callMethod('charAt', $key); - } + //allow subscript access to characters: `string[1]` + function get($key) + { + if (is_float($key)) { + if ((float) (int) $key === $key && $key >= 0) { + return $this->callMethod('charAt', $key); + } + } + return parent::get($key); } - return parent::get($key); - } - /** - * Creates the global constructor used in user-land - * @return Func - */ - static function getGlobalConstructor() { - $String = new Func(function($value = '') { - $self = Func::getContext(); - if ($self instanceof Str) { - $self->value = to_string($value); - return $self; - } else { - return to_string($value); - } - }); - $String->instantiate = function() { - return new Str(); - }; - $String->set('prototype', Str::$protoObject); - $String->setMethods(Str::$classMethods, true, false, true); - return $String; - } + /** + * Creates the global constructor used in user-land + * @return Func + */ + static function getGlobalConstructor() + { + $String = new Func(function ($value = '') { + $self = Func::getContext(); + if ($self instanceof Str) { + $self->value = to_string($value); + return $self; + } else { + return to_string($value); + } + }); + $String->instantiate = function () { + return new Str(); + }; + $String->set('prototype', Str::$protoObject); + $String->setMethods(Str::$classMethods, true, false, true); + return $String; + } } -Str::$classMethods = array( - 'fromCharCode' => function($code) { - return chr($code); - } -); +Str::$classMethods = [ + 'fromCharCode' => function ($code) { + return chr($code); + }, +]; -Str::$protoMethods = array( - 'charAt' => function($i) { - $self = Func::getContext(); - $ch = mb_substr($self->value, $i, 1); - return ($ch === false) ? '' : $ch; +Str::$protoMethods = [ + 'charAt' => function ($i) { + $self = Func::getContext(); + $ch = mb_substr($self->value, $i, 1); + return $ch === false ? '' : $ch; }, - 'charCodeAt' => function($i) { - $self = Func::getContext(); - $ch = mb_substr($self->value, $i, 1); - if ($ch === false) return NAN; - $len = strlen($ch); - if ($len === 1) { - $code = ord($ch[0]); - } else { - $ch = mb_convert_encoding($ch, 'UCS-2LE', 'UTF-8'); - $code = ord($ch[1]) * 256 + ord($ch[0]); - } - return (float)$code; + 'charCodeAt' => function ($i) { + $self = Func::getContext(); + $ch = mb_substr($self->value, $i, 1); + if ($ch === false) { + return NAN; + } + $len = strlen($ch); + if ($len === 1) { + $code = ord($ch[0]); + } else { + $ch = mb_convert_encoding($ch, 'UCS-2LE', 'UTF-8'); + $code = ord($ch[1]) * 256 + ord($ch[0]); + } + return (float) $code; }, - 'indexOf' => function($search, $offset = 0) { - $self = Func::getContext(); - $index = mb_strpos($self->value, $search, $offset); - return ($index === false) ? -1.0 : (float)$index; + 'indexOf' => function ($search, $offset = 0) { + $self = Func::getContext(); + $index = mb_strpos($self->value, $search, $offset); + return $index === false ? -1.0 : (float) $index; }, - 'lastIndexOf' => function($search, $offset = null) { - $self = Func::getContext(); - $str = $self->value; - if ($offset !== null) { - $offset = to_number($offset); - if ($offset > 0 && $offset < $self->length) { - $str = mb_substr($str, 0, $offset + 1); + 'lastIndexOf' => function ($search, $offset = null) { + $self = Func::getContext(); + $str = $self->value; + if ($offset !== null) { + $offset = to_number($offset); + if ($offset > 0 && $offset < $self->length) { + $str = mb_substr($str, 0, $offset + 1); + } } - } - $index = mb_strrpos($str, $search); - return ($index === false) ? -1.0 : (float)$index; + $index = mb_strrpos($str, $search); + return $index === false ? -1.0 : (float) $index; }, - 'split' => function($delim) { - $self = Func::getContext(); - $str = $self->value; - if ($delim instanceof RegExp) { - //$arr = mb_split($delim->source, $str); - $arr = preg_split($delim->toString(true), $str); - } else { - $delim = to_string($delim); - if ($delim === '') { - $len = mb_strlen($str); - $arr = array(); - for ($i = 0; $i < $len; $i++) { - $arr[] = mb_substr($str, $i, 1); - } + 'split' => function ($delim) { + $self = Func::getContext(); + $str = $self->value; + if ($delim instanceof RegExp) { + //$arr = mb_split($delim->source, $str); + $arr = preg_split($delim->toString(true), $str); } else { - $arr = explode($delim, $str); + $delim = to_string($delim); + if ($delim === '') { + $len = mb_strlen($str); + $arr = []; + for ($i = 0; $i < $len; $i++) { + $arr[] = mb_substr($str, $i, 1); + } + } else { + $arr = explode($delim, $str); + } } - } - return Arr::fromArray($arr); + return Arr::fromArray($arr); }, - 'substr' => function($start, $num = null) { - $self = Func::getContext(); - $len = $self->length; - if ($len === 0) { - return ''; - } - $start = (int)$start; - if ($start < 0) { - $start = $len + $start; - if ($start < 0) $start = 0; - } - if ($start >= $len) { - return ''; - } - if ($num === null) { - return mb_substr($self->value, $start); - } else { - return mb_substr($self->value, $start, $num); - } + 'substr' => function ($start, $num = null) { + $self = Func::getContext(); + $len = $self->length; + if ($len === 0) { + return ''; + } + $start = (int) $start; + if ($start < 0) { + $start = $len + $start; + if ($start < 0) { + $start = 0; + } + } + if ($start >= $len) { + return ''; + } + if ($num === null) { + return mb_substr($self->value, $start); + } else { + return mb_substr($self->value, $start, $num); + } }, - 'substring' => function($start, $end = null) { - $self = Func::getContext(); - $len = $self->length; - //if second param is absent - if (func_num_args() === 1) { - $end = $len; - } - $start = (int)$start; - $end = (int)$end; - if ($start < 0) $start = 0; - if ($start > $len) $start = $len; - if ($end < 0) $end = 0; - if ($end > $len) $end = $len; - if ($start === $end) { - return ''; - } - if ($end < $start) { - list($start, $end) = array($end, $start); - } - return mb_substr($self->value, $start, $end - $start); + 'substring' => function ($start, $end = null) { + $self = Func::getContext(); + $len = $self->length; + //if second param is absent + if (func_num_args() === 1) { + $end = $len; + } + $start = (int) $start; + $end = (int) $end; + if ($start < 0) { + $start = 0; + } + if ($start > $len) { + $start = $len; + } + if ($end < 0) { + $end = 0; + } + if ($end > $len) { + $end = $len; + } + if ($start === $end) { + return ''; + } + if ($end < $start) { + list($start, $end) = [$end, $start]; + } + return mb_substr($self->value, $start, $end - $start); }, - 'slice' => function($start, $end = null) { - $self = Func::getContext(); - $len = $self->length; - if ($len === 0) { - return ''; - } - $start = (int)$start; - if ($start < 0) { - $start = $len + $start; - if ($start < 0) $start = 0; - } - if ($start >= $len) { - return ''; - } - $end = ($end === null) ? $len : (int)$end; - if ($end < 0) { - $end = $len + $end; - } - if ($end < $start) { - $end = $start; - } - if ($end > $len) { - $end = $len; - } - return mb_substr($self->value, $start, $end - $start); + 'slice' => function ($start, $end = null) { + $self = Func::getContext(); + $len = $self->length; + if ($len === 0) { + return ''; + } + $start = (int) $start; + if ($start < 0) { + $start = $len + $start; + if ($start < 0) { + $start = 0; + } + } + if ($start >= $len) { + return ''; + } + $end = $end === null ? $len : (int) $end; + if ($end < 0) { + $end = $len + $end; + } + if ($end < $start) { + $end = $start; + } + if ($end > $len) { + $end = $len; + } + return mb_substr($self->value, $start, $end - $start); }, - 'trim' => function() { - $self = Func::getContext(); - //todo: unicode [\u1680​\u180e\u2000​\u2001\u2002​\u2003\u2004​\u2005\u2006​\u2007\u2008​\u2009\u200a​\u2028\u2029​​\u202f\u205f​\u3000] - //note: trim doesn't work here because \xA0 is a multibyte character in utf8 - return preg_replace('/^[\s\x0B\xA0]+|[\s\x0B\​xA0]+$/u', '', $self->value); + 'trim' => function () { + $self = Func::getContext(); + //todo: unicode [\u1680​\u180e\u2000​\u2001\u2002​\u2003\u2004​\u2005\u2006​\u2007\u2008​\u2009\u200a​\u2028\u2029​​\u202f\u205f​\u3000] + //note: trim doesn't work here because \xA0 is a multibyte character in utf8 + return preg_replace( + '/^[\s\x0B\xA0]+|[\s\x0B\​xA0]+$/u', + '', + $self->value + ); }, - 'match' => function($regex) use (&$RegExp) { - $self = Func::getContext(); - $str = $self->value; - if (!($regex instanceof RegExp)) { - $regex = $RegExp->construct($regex); - } - if (!$regex->globalFlag) { - return $regex->callMethod('exec', $str); - } - $results = new Arr(); - $index = 0; - $preg = $regex->toString(true); - while (preg_match($preg, $str, $matches, PREG_OFFSET_CAPTURE, $index) === 1) { - $foundAt = $matches[0][1]; - $foundStr = $matches[0][0]; - $index = $foundAt + strlen($foundStr); - $results->push($foundStr); - } - return $results; + 'match' => function ($regex) use (&$RegExp) { + $self = Func::getContext(); + $str = $self->value; + if (!($regex instanceof RegExp)) { + $regex = $RegExp->construct($regex); + } + if (!$regex->globalFlag) { + return $regex->callMethod('exec', $str); + } + $results = new Arr(); + $index = 0; + $preg = $regex->toString(true); + while ( + preg_match($preg, $str, $matches, PREG_OFFSET_CAPTURE, $index) === 1 + ) { + $foundAt = $matches[0][1]; + $foundStr = $matches[0][0]; + $index = $foundAt + strlen($foundStr); + $results->push($foundStr); + } + return $results; }, - 'replace' => function($search, $replace) { - $self = Func::getContext(); - $str = $self->value; - $isRegEx = ($search instanceof RegExp); - $limit = ($isRegEx && $search->globalFlag) ? -1 : 1; - $search = $isRegEx ? $search->toString(true) : to_string($search); - if ($replace instanceof Func) { - if ($isRegEx) { - $count = 0; - $offset = 0; - $result = array(); - $success = null; - while ( - ($limit === -1 || $count < $limit) && - ($success = preg_match($search, $str, $matches, PREG_OFFSET_CAPTURE, $offset)) - ) { - $matchIndex = $matches[0][1]; - $args = array(); - foreach ($matches as $match) { - $args[] = $match[0]; + 'replace' => function ($search, $replace) { + $self = Func::getContext(); + $str = $self->value; + $isRegEx = $search instanceof RegExp; + $limit = $isRegEx && $search->globalFlag ? -1 : 1; + $search = $isRegEx ? $search->toString(true) : to_string($search); + if ($replace instanceof Func) { + if ($isRegEx) { + $count = 0; + $offset = 0; + $result = []; + $success = null; + while ( + ($limit === -1 || $count < $limit) && + ($success = preg_match( + $search, + $str, + $matches, + PREG_OFFSET_CAPTURE, + $offset + )) + ) { + $matchIndex = $matches[0][1]; + $args = []; + foreach ($matches as $match) { + $args[] = $match[0]; + } + $result[] = substr($str, $offset, $matchIndex - $offset); + //calculate multi-byte character index from match index + $mbIndex = mb_strlen(substr($str, 0, $matchIndex)); + array_push($args, $mbIndex); + array_push($args, $str); + $result[] = to_string($replace->apply(null, $args)); + $offset = $matchIndex + strlen($args[0]); + $count += 1; + } + if ($success === false) { + //this can happen in the case of invalid utf8 sequences + throw new Ex( + Err::create('String.prototype.replace() failed') + ); + } + $result[] = substr($str, $offset); + return join('', $result); + } else { + $matchIndex = strpos($str, $search); + if ($matchIndex === false) { + return $str; + } + $before = substr($str, 0, $matchIndex); + $after = substr($str, $matchIndex + strlen($search)); + //mb_strlen used to calculate multi-byte character index + $args = [$search, mb_strlen($before), $str]; + return $before . + to_string($replace->apply(null, $args)) . + $after; } - $result[] = substr($str, $offset, $matchIndex - $offset); - //calculate multi-byte character index from match index - $mbIndex = mb_strlen(substr($str, 0, $matchIndex)); - array_push($args, $mbIndex); - array_push($args, $str); - $result[] = to_string($replace->apply(null, $args)); - $offset = $matchIndex + strlen($args[0]); - $count += 1; - } - if ($success === false) { - //this can happen in the case of invalid utf8 sequences - throw new Ex(Err::create('String.prototype.replace() failed')); - } - $result[] = substr($str, $offset); - return join('', $result); + } + $replace = to_string($replace); + if ($isRegEx) { + $replace = RegExp::toReplacementString($replace); + return preg_replace($search, $replace, $str, $limit); } else { - $matchIndex = strpos($str, $search); - if ($matchIndex === false) { - return $str; - } - $before = substr($str, 0, $matchIndex); - $after = substr($str, $matchIndex + strlen($search)); - //mb_strlen used to calculate multi-byte character index - $args = array($search, mb_strlen($before), $str); - return $before . to_string($replace->apply(null, $args)) . $after; + $parts = explode($search, $str); + $first = array_shift($parts); + return $first . $replace . implode($search, $parts); } - } - $replace = to_string($replace); - if ($isRegEx) { - $replace = RegExp::toReplacementString($replace); - return preg_replace($search, $replace, $str, $limit); - } else { - $parts = explode($search, $str); - $first = array_shift($parts); - return $first . $replace . implode($search, $parts); - } }, - 'concat' => function() { - $self = Func::getContext(); - $result = array($self->value); - foreach (func_get_args() as $arg) { - $result[] = to_string($arg); - } - return implode('', $result); + 'concat' => function () { + $self = Func::getContext(); + $result = [$self->value]; + foreach (func_get_args() as $arg) { + $result[] = to_string($arg); + } + return implode('', $result); }, - 'search' => function($regex) use (&$RegExp) { - $self = Func::getContext(); - if (!($regex instanceof RegExp)) { - $regex = $RegExp->construct($regex); - } - $preg = $regex->toString(true); - $success = preg_match($preg, $self->value, $matches, PREG_OFFSET_CAPTURE); - if (!$success) { - return -1; - } - $start = substr($self->value, 0, $matches[0][1]); - $startLen = mb_strlen($start); - return (float)$startLen; + 'search' => function ($regex) use (&$RegExp) { + $self = Func::getContext(); + if (!($regex instanceof RegExp)) { + $regex = $RegExp->construct($regex); + } + $preg = $regex->toString(true); + $success = preg_match( + $preg, + $self->value, + $matches, + PREG_OFFSET_CAPTURE + ); + if (!$success) { + return -1; + } + $start = substr($self->value, 0, $matches[0][1]); + $startLen = mb_strlen($start); + return (float) $startLen; }, - 'toLowerCase' => function() { - $self = Func::getContext(); - return mb_strtolower($self->value); + 'toLowerCase' => function () { + $self = Func::getContext(); + return mb_strtolower($self->value); }, - 'toLocaleLowerCase' => function() { - $self = Func::getContext(); - return mb_strtolower($self->value); + 'toLocaleLowerCase' => function () { + $self = Func::getContext(); + return mb_strtolower($self->value); }, - 'toUpperCase' => function() { - $self = Func::getContext(); - return mb_strtoupper($self->value); + 'toUpperCase' => function () { + $self = Func::getContext(); + return mb_strtoupper($self->value); }, - 'toLocaleUpperCase' => function() { - $self = Func::getContext(); - return mb_strtoupper($self->value); + 'toLocaleUpperCase' => function () { + $self = Func::getContext(); + return mb_strtoupper($self->value); }, - 'localeCompare' => function($compareTo) { - $self = Func::getContext(); - return (float)strcmp($self->value, to_string($compareTo)); + 'localeCompare' => function ($compareTo) { + $self = Func::getContext(); + return (float) strcmp($self->value, to_string($compareTo)); }, - 'valueOf' => function() { - $self = Func::getContext(); - return $self->value; + 'valueOf' => function () { + $self = Func::getContext(); + return $self->value; }, - 'toString' => function() { - $self = Func::getContext(); - return $self->value; - } -); + 'toString' => function () { + $self = Func::getContext(); + return $self->value; + }, +]; Str::$protoObject = new ObjectClass(); Str::$protoObject->setMethods(Str::$protoMethods, true, false, true); diff --git a/php/globals/JSON.php b/php/globals/JSON.php index 06a119b..cbdc57d 100644 --- a/php/globals/JSON.php +++ b/php/globals/JSON.php @@ -1,151 +1,183 @@ push($decode($item)); - } - } else { - $result = new ObjectClass(); - foreach ($value as $key => $item) { - if ($key === '_empty_') { - $key = ''; +$JSON = call_user_func(function () { + $decode = function ($value) use (&$decode) { + if ($value === null) { + return ObjectClass::$null; + } + $type = gettype($value); + if ($type === 'integer') { + return (float) $value; + } + if ($type === 'string' || $type === 'boolean' || $type === 'double') { + return $value; } - $result->set($key, $decode($item)); - } - } - return $result; - }; + if ($type === 'array') { + $result = new Arr(); + foreach ($value as $item) { + $result->push($decode($item)); + } + } else { + $result = new ObjectClass(); + foreach ($value as $key => $item) { + if ($key === '_empty_') { + $key = ''; + } + $result->set($key, $decode($item)); + } + } + return $result; + }; - $escape = function($str) { - return str_replace("\\/", "/", json_encode($str)); - }; + $escape = function ($str) { + return str_replace('\\/', '/', json_encode($str)); + }; - $encode = function($parent, $key, $value, $opts, $encodeNull = false) use (&$escape, &$encode) { - if ($value instanceof ObjectClass) { - //todo: flatten boxed primitive (use toJSON?) - //class may specify its own toJSON (date/buffer) - if (method_exists($value, 'toJSON')) { - $value = $value->toJSON(); - } else - if (($toJSON = $value->get('toJSON')) instanceof Func) { - $value = $toJSON->call($value); - } else - //todo: why do we need this? - if (($valueOf = $value->get('valueOf')) instanceof Func) { - $value = $valueOf->call($value); - } - } - if ($opts->replacer instanceof Func) { - $value = $opts->replacer->call($parent, $key, $value, $opts->level + 1); - } - if ($value === null) { - return $encodeNull ? 'null' : $value; - } - //todo: handle same as above? - if ($value === ObjectClass::$null || $value === INF || $value === -INF) { - return 'null'; - } - $type = gettype($value); - if ($type === 'boolean') { - return $value ? 'true' : 'false'; - } - if ($type === 'integer' || $type === 'double') { - return ($value !== $value) ? 'null' : $value . ''; - } - if ($type === 'string') { - return $escape($value); - } - $opts->level += 1; - $prevGap = $opts->gap; - if ($opts->gap !== null) { - $opts->gap .= $opts->indent; - } - $result = null; - if ($value instanceof Arr) { - $parts = array(); - $len = $value->length; - for ($i = 0; $i < $len; $i++) { - $parts[] = $encode($value, $i, $value->get($i), $opts, true); - } - if ($opts->gap === null) { - $result = '[' . join(',', $parts) . ']'; - } else { - $result = (count($parts) === 0) ? "[]" : - "[\n" . $opts->gap . join(",\n" . $opts->gap, $parts) . "\n" . $prevGap . "]"; - } - } - if ($result === null) { - $parts = array(); - $sep = ($opts->gap === null) ? ':' : ': '; - foreach ($value->getOwnKeys(true) as $key) { - $item = $value->get($key); - if ($item !== null) { - $parts[] = $escape($key) . $sep . $encode($value, $key, $item, $opts); + $encode = function ($parent, $key, $value, $opts, $encodeNull = false) use ( + &$escape, + &$encode + ) { + if ($value instanceof ObjectClass) { + //todo: flatten boxed primitive (use toJSON?) + //class may specify its own toJSON (date/buffer) + if (method_exists($value, 'toJSON')) { + $value = $value->toJSON(); + } elseif (($toJSON = $value->get('toJSON')) instanceof Func) { + $value = $toJSON->call($value); + } + //todo: why do we need this? + elseif (($valueOf = $value->get('valueOf')) instanceof Func) { + $value = $valueOf->call($value); + } + } + if ($opts->replacer instanceof Func) { + $value = $opts->replacer->call( + $parent, + $key, + $value, + $opts->level + 1 + ); } - } - if ($opts->gap === null) { - $result = '{' . join(',', $parts) . '}'; - } else { - $result = (count($parts) === 0) ? "{}" : - "{\n" . $opts->gap . join(",\n" . $opts->gap, $parts) . "\n" . $prevGap . "}"; - } - } - $opts->level -= 1; - $opts->gap = $prevGap; - return $result; - }; - - $methods = array( - 'parse' => function($string, $reviver = null) use(&$decode) { - $string = '{"_":' . $string . '}'; - $value = json_decode($string); if ($value === null) { - throw new Ex(SyntaxErr::create('Unexpected end of input')); + return $encodeNull ? 'null' : $value; } - return $decode($value->_); - }, - 'stringify' => function($value, $replacer = null, $space = null) use (&$encode) { - $opts = new stdClass(); - $opts->indent = null; - $opts->gap = null; - if (is_int_or_float($space)) { - $space = floor($space); - if ($space > 0) { - $space = str_repeat(' ', $space); - } + //todo: handle same as above? + if ( + $value === ObjectClass::$null || + $value === INF || + $value === -INF + ) { + return 'null'; } - if (is_string($space)) { - $length = strlen($space); - if ($length > 10) $space = substr($space, 0, 10); - if ($length > 0) { - $opts->indent = $space; - $opts->gap = ''; - } + $type = gettype($value); + if ($type === 'boolean') { + return $value ? 'true' : 'false'; } - $opts->replacer = ($replacer instanceof Func) ? $replacer : null; - $opts->level = -1.0; - // dummy object required if we have a replacer function (see json2 implementation) - $obj = ($opts->replacer !== null) ? new ObjectClass('', $value) : null; - return $encode($obj, '', $value, $opts); - } - ); + if ($type === 'integer' || $type === 'double') { + return $value !== $value ? 'null' : $value . ''; + } + if ($type === 'string') { + return $escape($value); + } + $opts->level += 1; + $prevGap = $opts->gap; + if ($opts->gap !== null) { + $opts->gap .= $opts->indent; + } + $result = null; + if ($value instanceof Arr) { + $parts = []; + $len = $value->length; + for ($i = 0; $i < $len; $i++) { + $parts[] = $encode($value, $i, $value->get($i), $opts, true); + } + if ($opts->gap === null) { + $result = '[' . join(',', $parts) . ']'; + } else { + $result = + count($parts) === 0 + ? '[]' + : "[\n" . + $opts->gap . + join(",\n" . $opts->gap, $parts) . + "\n" . + $prevGap . + ']'; + } + } + if ($result === null) { + $parts = []; + $sep = $opts->gap === null ? ':' : ': '; + foreach ($value->getOwnKeys(true) as $key) { + $item = $value->get($key); + if ($item !== null) { + $parts[] = + $escape($key) . + $sep . + $encode($value, $key, $item, $opts); + } + } + if ($opts->gap === null) { + $result = '{' . join(',', $parts) . '}'; + } else { + $result = + count($parts) === 0 + ? '{}' + : "{\n" . + $opts->gap . + join(",\n" . $opts->gap, $parts) . + "\n" . + $prevGap . + '}'; + } + } + $opts->level -= 1; + $opts->gap = $prevGap; + return $result; + }; + + $methods = [ + 'parse' => function ($string, $reviver = null) use (&$decode) { + $string = '{"_":' . $string . '}'; + $value = json_decode($string); + if ($value === null) { + throw new Ex(SyntaxErr::create('Unexpected end of input')); + } + return $decode($value->_); + }, + 'stringify' => function ($value, $replacer = null, $space = null) use ( + &$encode + ) { + $opts = new stdClass(); + $opts->indent = null; + $opts->gap = null; + if (is_int_or_float($space)) { + $space = floor($space); + if ($space > 0) { + $space = str_repeat(' ', $space); + } + } + if (is_string($space)) { + $length = strlen($space); + if ($length > 10) { + $space = substr($space, 0, 10); + } + if ($length > 0) { + $opts->indent = $space; + $opts->gap = ''; + } + } + $opts->replacer = $replacer instanceof Func ? $replacer : null; + $opts->level = -1.0; + // dummy object required if we have a replacer function (see json2 implementation) + $obj = + $opts->replacer !== null ? new ObjectClass('', $value) : null; + return $encode($obj, '', $value, $opts); + }, + ]; - $JSON = new ObjectClass(); - $JSON->setMethods($methods, true, false, true); - // expose for use elsewhere in php-land - $JSON->fromNative = $decode; - return $JSON; + $JSON = new ObjectClass(); + $JSON->setMethods($methods, true, false, true); + // expose for use elsewhere in php-land + $JSON->fromNative = $decode; + return $JSON; }); diff --git a/php/globals/Math.php b/php/globals/Math.php index 8d9ea2f..ecf141e 100644 --- a/php/globals/Math.php +++ b/php/globals/Math.php @@ -1,120 +1,128 @@ function() use (&$randMax) { - return (float)(mt_rand() / ($randMax + 1)); - }, - - 'round' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)round($num); - }, - - 'ceil' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)ceil($num); - }, - - 'floor' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)floor($num); - }, - - 'abs' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)abs($num); - }, - - 'max' => function() { - $max = -INF; - foreach (func_get_args() as $num) { - $num = to_number($num); - if (is_nan($num)) return NAN; - if ($num > $max) $max = $num; - } - return (float)$max; - }, - - 'min' => function() { - $min = INF; - foreach (func_get_args() as $num) { - $num = to_number($num); - if (is_nan($num)) return NAN; - if ($num < $min) $min = $num; - } - return (float)$min; - }, - - 'pow' => function($num, $exp) { - $num = to_number($num); - $exp = to_number($exp); - if (is_nan($num) || is_nan($exp)) { - return NAN; - } - return (float)pow($num, $exp); - }, - - 'log' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)log($num); - }, - - 'exp' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)exp($num); - }, - - 'sqrt' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)sqrt($num); - }, - - 'sin' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)sin($num); - }, - - 'cos' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)cos($num); - }, - - 'tan' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)tan($num); - }, - - 'atan' => function($num) { - $num = to_number($num); - return is_nan($num) ? NAN : (float)atan($num); - }, - - 'atan2' => function($y, $x) { - $y = to_number($y); - $x = to_number($x); - if (is_nan($y) || is_nan($x)) { - return NAN; - } - return (float)atan2($y, $x); - } - ); - - $constants = array( - 'E' => M_E, - 'LN10' => M_LN10, - 'LN2' => M_LN2, - 'LOG10E' => M_LOG10E, - 'LOG2E' => M_LOG2E, - 'PI' => M_PI, - 'SQRT1_2' => M_SQRT1_2, - 'SQRT2' => M_SQRT2 - ); - - $Math = new ObjectClass(); - $Math->setMethods($methods, true, false, true); - $Math->setProps($constants, true, false, true); - - return $Math; +$Math = call_user_func(function () { + $randMax = mt_getrandmax(); + + $methods = [ + 'random' => function () use (&$randMax) { + return (float) (mt_rand() / ($randMax + 1)); + }, + + 'round' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) round($num); + }, + + 'ceil' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) ceil($num); + }, + + 'floor' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) floor($num); + }, + + 'abs' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) abs($num); + }, + + 'max' => function () { + $max = -INF; + foreach (func_get_args() as $num) { + $num = to_number($num); + if (is_nan($num)) { + return NAN; + } + if ($num > $max) { + $max = $num; + } + } + return (float) $max; + }, + + 'min' => function () { + $min = INF; + foreach (func_get_args() as $num) { + $num = to_number($num); + if (is_nan($num)) { + return NAN; + } + if ($num < $min) { + $min = $num; + } + } + return (float) $min; + }, + + 'pow' => function ($num, $exp) { + $num = to_number($num); + $exp = to_number($exp); + if (is_nan($num) || is_nan($exp)) { + return NAN; + } + return (float) pow($num, $exp); + }, + + 'log' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) log($num); + }, + + 'exp' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) exp($num); + }, + + 'sqrt' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) sqrt($num); + }, + + 'sin' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) sin($num); + }, + + 'cos' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) cos($num); + }, + + 'tan' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) tan($num); + }, + + 'atan' => function ($num) { + $num = to_number($num); + return is_nan($num) ? NAN : (float) atan($num); + }, + + 'atan2' => function ($y, $x) { + $y = to_number($y); + $x = to_number($x); + if (is_nan($y) || is_nan($x)) { + return NAN; + } + return (float) atan2($y, $x); + }, + ]; + + $constants = [ + 'E' => M_E, + 'LN10' => M_LN10, + 'LN2' => M_LN2, + 'LOG10E' => M_LOG10E, + 'LOG2E' => M_LOG2E, + 'PI' => M_PI, + 'SQRT1_2' => M_SQRT1_2, + 'SQRT2' => M_SQRT2, + ]; + + $Math = new ObjectClass(); + $Math->setMethods($methods, true, false, true); + $Math->setProps($constants, true, false, true); + + return $Math; }); diff --git a/php/globals/console.php b/php/globals/console.php index fc2ea8b..706318e 100644 --- a/php/globals/console.php +++ b/php/globals/console.php @@ -1,50 +1,55 @@ fn, $value); + } + $toString = $value->get('inspect'); + if (!($toString instanceof Func)) { + $toString = $value->get('toString'); + } + if (!($toString instanceof Func)) { + $toString = ObjectClass::$protoObject->get('toString'); + } + return $toString->call($value); + } + return to_string($value); + }; - $toString = function($value) { - if ($value instanceof ObjectClass) { - if (class_exists('Debug')) { - //should be ok to call the underlying function directly - return call_user_func(Debug::$inspect->fn, $value); - } - $toString = $value->get('inspect'); - if (!($toString instanceof Func)) { - $toString = $value->get('toString'); - } - if (!($toString instanceof Func)) { - $toString = ObjectClass::$protoObject->get('toString'); - } - return $toString->call($value); - } - return to_string($value); - }; + $console = new ObjectClass(); - $console = new ObjectClass(); + $console->set( + 'log', + new Func(function () use (&$stdout, &$toString) { + if ($stdout === null) { + $stdout = fopen('php://stdout', 'w'); + } + $output = []; + foreach (func_get_args() as $value) { + $output[] = $toString($value); + } + write_all($stdout, join(' ', $output) . "\n"); + }) + ); - $console->set('log', new Func(function() use (&$stdout, &$toString) { - if ($stdout === null) { - $stdout = fopen('php://stdout', 'w'); - } - $output = array(); - foreach (func_get_args() as $value) { - $output[] = $toString($value); - } - write_all($stdout, join(' ', $output) . "\n"); - })); + $console->set( + 'error', + new Func(function () use (&$stderr, &$toString) { + if ($stderr === null) { + $stderr = fopen('php://stderr', 'w'); + } + $output = []; + foreach (func_get_args() as $value) { + $output[] = $toString($value); + } + write_all($stderr, join(' ', $output) . "\n"); + }) + ); - $console->set('error', new Func(function() use (&$stderr, &$toString) { - if ($stderr === null) { - $stderr = fopen('php://stderr', 'w'); - } - $output = array(); - foreach (func_get_args() as $value) { - $output[] = $toString($value); - } - write_all($stderr, join(' ', $output) . "\n"); - })); - - return $console; + return $console; }); diff --git a/php/globals/globals.php b/php/globals/globals.php index b384d85..08be7a8 100644 --- a/php/globals/globals.php +++ b/php/globals/globals.php @@ -19,135 +19,200 @@ $RegExp = RegExp::getGlobalConstructor(); $Buffer = Buffer::getGlobalConstructor(); -call_user_func(function() use (&$escape, &$unescape, &$encodeURI, &$decodeURI, &$encodeURIComponent, &$decodeURIComponent) { - - $ord = function($ch) { - $i = ord($ch[0]); - if ($i <= 0x7F) { - return $i; - } else if ($i < 0xC2) { - return $i; //invalid byte sequence - } else if ($i <= 0xDF) { - return ($i & 0x1F) << 6 | (ord($ch[1]) & 0x3F); - } else if ($i <= 0xEF) { - return ($i & 0x0F) << 12 | (ord($ch[1]) & 0x3F) << 6 | (ord($ch[2]) & 0x3F); - } else if ($i <= 0xF4) { - return ($i & 0x0F) << 18 | (ord($ch[1]) & 0x3F) << 12 | (ord($ch[2]) & 0x3F) << 6 | (ord($ch[3]) & 0x3F); - } else { - return $i; //invalid byte sequence - } - }; - - $chr = function($i) { - if ($i <= 0x7F) return chr($i); - if ($i <= 0x7FF) return chr(0xC0 | ($i >> 6)) . chr(0x80 | ($i & 0x3F)); - if ($i <= 0xFFFF) return chr(0xE0 | ($i >> 12)) . chr(0x80 | ($i >> 6) & 0x3F) . chr(0x80 | $i & 0x3F); - return chr(0xF0 | ($i >> 18)) . chr(0x80 | ($i >> 12) & 0x3F) . chr(0x80 | ($i >> 6) & 0x3F) . chr(0x80 | $i & 0x3F); - }; +call_user_func(function () use ( + &$escape, + &$unescape, + &$encodeURI, + &$decodeURI, + &$encodeURIComponent, + &$decodeURIComponent +) { + $ord = function ($ch) { + $i = ord($ch[0]); + if ($i <= 0x7f) { + return $i; + } elseif ($i < 0xc2) { + return $i; //invalid byte sequence + } elseif ($i <= 0xdf) { + return (($i & 0x1f) << 6) | (ord($ch[1]) & 0x3f); + } elseif ($i <= 0xef) { + return (($i & 0x0f) << 12) | + ((ord($ch[1]) & 0x3f) << 6) | + (ord($ch[2]) & 0x3f); + } elseif ($i <= 0xf4) { + return (($i & 0x0f) << 18) | + ((ord($ch[1]) & 0x3f) << 12) | + ((ord($ch[2]) & 0x3f) << 6) | + (ord($ch[3]) & 0x3f); + } else { + return $i; //invalid byte sequence + } + }; - $escape = new Func(function($str) use (&$ord) { - $result = ''; - $length = mb_strlen($str); - for ($i = 0; $i < $length; $i++) { - $ch = mb_substr($str, $i, 1); - $j = $ord($ch); - if ($j <= 41 || $j === 44 || ($j >= 58 && $j <= 63) || ($j >= 91 && $j <= 94) || $j === 96 || ($j >= 123 && $j <= 255)) { - $result .= '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); - } else if ($j > 255) { - $result .= '%u' . strtoupper($j < 4096 ? '0' . dechex($j) : dechex($j)); - } else { - $result .= $ch; - } - } - return $result; - }); + $chr = function ($i) { + if ($i <= 0x7f) { + return chr($i); + } + if ($i <= 0x7ff) { + return chr(0xc0 | ($i >> 6)) . chr(0x80 | ($i & 0x3f)); + } + if ($i <= 0xffff) { + return chr(0xe0 | ($i >> 12)) . + chr(0x80 | (($i >> 6) & 0x3f)) . + chr(0x80 | ($i & 0x3f)); + } + return chr(0xf0 | ($i >> 18)) . + chr(0x80 | (($i >> 12) & 0x3f)) . + chr(0x80 | (($i >> 6) & 0x3f)) . + chr(0x80 | ($i & 0x3f)); + }; - $unescape = new Func(function($str) use (&$chr) { - $result = ''; - $length = strlen($str); - for ($i = 0; $i < $length; $i++) { - $ch = $str[$i]; - if ($ch === '%' && $length > $i + 2) { - if ($str[$i + 1] === 'u') { - if ($length > $i + 4) { - $hex = substr($str, $i + 2, 4); - if (ctype_xdigit($hex)) { - $result .= $chr(hexdec($hex)); - $i += 5; - continue; + $escape = new Func(function ($str) use (&$ord) { + $result = ''; + $length = mb_strlen($str); + for ($i = 0; $i < $length; $i++) { + $ch = mb_substr($str, $i, 1); + $j = $ord($ch); + if ( + $j <= 41 || + $j === 44 || + ($j >= 58 && $j <= 63) || + ($j >= 91 && $j <= 94) || + $j === 96 || + ($j >= 123 && $j <= 255) + ) { + $result .= + '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); + } elseif ($j > 255) { + $result .= + '%u' . + strtoupper($j < 4096 ? '0' . dechex($j) : dechex($j)); + } else { + $result .= $ch; } - } - } else { - $hex = substr($str, $i + 1, 2); - if (ctype_xdigit($hex)) { - $result .= $chr(hexdec($hex)); - $i += 2; - continue; - } } - } - $result .= $ch; - } - return $result; - }); + return $result; + }); - $encodeURI = new Func(function($str) { - $result = ''; - $length = strlen($str); - for ($i = 0; $i < $length; $i++) { - $ch = substr($str, $i, 1); - $j = ord($ch); - if ($j === 33 || $j === 35 || $j === 36 || ($j >= 38 && $j <= 59) || $j === 61 || ($j >= 63 && $j <= 90) || $j === 95 || ($j >= 97 && $j <= 122) || $j === 126) { - $result .= $ch; - } else { - $result .= '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); - } - } - return $result; - }); + $unescape = new Func(function ($str) use (&$chr) { + $result = ''; + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + $ch = $str[$i]; + if ($ch === '%' && $length > $i + 2) { + if ($str[$i + 1] === 'u') { + if ($length > $i + 4) { + $hex = substr($str, $i + 2, 4); + if (ctype_xdigit($hex)) { + $result .= $chr(hexdec($hex)); + $i += 5; + continue; + } + } + } else { + $hex = substr($str, $i + 1, 2); + if (ctype_xdigit($hex)) { + $result .= $chr(hexdec($hex)); + $i += 2; + continue; + } + } + } + $result .= $ch; + } + return $result; + }); - //todo: throw on invalid utf8 sequence - $decodeURI = new Func(function($str) { - $result = ''; - $length = strlen($str); - for ($i = 0; $i < $length; $i++) { - $ch = $str[$i]; - if ($ch === '%' && $length > $i + 2) { - $hex = substr($str, $i + 1, 2); - if (ctype_xdigit($hex)) { - $j = hexdec($hex); - if ($j !== 35 && $j !== 36 && $j !== 38 && $j !== 43 && $j !== 44 && $j !== 47 && $j !== 58 && $j !== 59 && $j !== 61 && $j !== 63 && $j !== 64) { - $result .= chr($j); - $i += 2; - continue; - } + $encodeURI = new Func(function ($str) { + $result = ''; + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + $ch = substr($str, $i, 1); + $j = ord($ch); + if ( + $j === 33 || + $j === 35 || + $j === 36 || + ($j >= 38 && $j <= 59) || + $j === 61 || + ($j >= 63 && $j <= 90) || + $j === 95 || + ($j >= 97 && $j <= 122) || + $j === 126 + ) { + $result .= $ch; + } else { + $result .= + '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); + } } - } - $result .= $ch; - } - return $result; - }); + return $result; + }); - $encodeURIComponent = new Func(function($str) { - $result = ''; - $length = strlen($str); - for ($i = 0; $i < $length; $i++) { - $ch = substr($str, $i, 1); - $j = ord($ch); - if ($j === 33 || ($j >= 39 && $j <= 42) || $j === 45 || $j === 46 || ($j >= 48 && $j <= 57) || ($j >= 65 && $j <= 90) || $j === 95 || ($j >= 97 && $j <= 122) || $j === 126) { - $result .= $ch; - } else { - $result .= '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); - } - } - return $result; - }); + //todo: throw on invalid utf8 sequence + $decodeURI = new Func(function ($str) { + $result = ''; + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + $ch = $str[$i]; + if ($ch === '%' && $length > $i + 2) { + $hex = substr($str, $i + 1, 2); + if (ctype_xdigit($hex)) { + $j = hexdec($hex); + if ( + $j !== 35 && + $j !== 36 && + $j !== 38 && + $j !== 43 && + $j !== 44 && + $j !== 47 && + $j !== 58 && + $j !== 59 && + $j !== 61 && + $j !== 63 && + $j !== 64 + ) { + $result .= chr($j); + $i += 2; + continue; + } + } + } + $result .= $ch; + } + return $result; + }); - //todo: throw on invalid utf8 sequence - $decodeURIComponent = new Func(function($str) { - return rawurldecode($str); - }); + $encodeURIComponent = new Func(function ($str) { + $result = ''; + $length = strlen($str); + for ($i = 0; $i < $length; $i++) { + $ch = substr($str, $i, 1); + $j = ord($ch); + if ( + $j === 33 || + ($j >= 39 && $j <= 42) || + $j === 45 || + $j === 46 || + ($j >= 48 && $j <= 57) || + ($j >= 65 && $j <= 90) || + $j === 95 || + ($j >= 97 && $j <= 122) || + $j === 126 + ) { + $result .= $ch; + } else { + $result .= + '%' . strtoupper($j < 16 ? '0' . dechex($j) : dechex($j)); + } + } + return $result; + }); + //todo: throw on invalid utf8 sequence + $decodeURIComponent = new Func(function ($str) { + return rawurldecode($str); + }); }); $isNaN = $Number->get('isNaN'); diff --git a/php/globals/process.php b/php/globals/process.php index 530b78a..c630459 100644 --- a/php/globals/process.php +++ b/php/globals/process.php @@ -4,21 +4,29 @@ // the type of interface between web server and PHP $process->set('sapi_name', php_sapi_name()); -$process->set('exit', new Func(function($code = 0) { - $code = intval($code); - exit($code); -})); +$process->set( + 'exit', + new Func(function ($code = 0) { + $code = intval($code); + exit($code); + }) +); -$process->set('binding', new Func(function($name) { - $module = Module::get($name); - if ($module === null) { - throw new Ex(Err::create("Binding `$name` not found.")); - } - return $module; -})); +$process->set( + 'binding', + new Func(function ($name) { + $module = Module::get($name); + if ($module === null) { + throw new Ex(Err::create("Binding `$name` not found.")); + } + return $module; + }) +); //command line arguments -$process->argv = isset(GlobalObject::$OLD_GLOBALS['argv']) ? GlobalObject::$OLD_GLOBALS['argv'] : array(); +$process->argv = isset(GlobalObject::$OLD_GLOBALS['argv']) + ? GlobalObject::$OLD_GLOBALS['argv'] + : []; //first argument is path to script $process->argv = array_slice($process->argv, 1); $process->set('argv', Arr::fromArray($process->argv)); diff --git a/php/helpers/Debug.php b/php/helpers/Debug.php index 1392671..581b668 100644 --- a/php/helpers/Debug.php +++ b/php/helpers/Debug.php @@ -1,57 +1,65 @@ $item) { - $keys[] = $key; + static function dump() + { + ob_start(); + call_user_func_array('var_dump', func_get_args()); + $output = ob_get_contents(); + ob_end_clean(); + $output = preg_replace('/\n+$/', '', $output); + echo $output . "\n"; } - return join(", ", $keys); - } - static function stringify($value, $depth = 0) { - if ($value === null) { - return 'null'; - } - $type = gettype($value); - if ($type === 'boolean') { - return $value ? 'true' : 'false'; - } - if ($type === 'string' || $type === 'integer' || $type === 'double') { - return $value . ''; - } - if ($type === 'array') { - if ($depth >= self::$MAX_DEPTH) { - return '[object Array](' . count($value) . ')[...]'; - } - $output = array(); - foreach ($value as $item) { - $output[] = self::stringify($item, $depth + 1); - } - return '[object Array](' . count($value) . ')[' . join(', ', $output) . ']'; + static function keys($value) + { + $keys = []; + foreach ($value as $key => $item) { + $keys[] = $key; + } + return join(', ', $keys); } - return '[object ' . get_class($value) . ']'; - } + static function stringify($value, $depth = 0) + { + if ($value === null) { + return 'null'; + } + $type = gettype($value); + if ($type === 'boolean') { + return $value ? 'true' : 'false'; + } + if ($type === 'string' || $type === 'integer' || $type === 'double') { + return $value . ''; + } + if ($type === 'array') { + if ($depth >= self::$MAX_DEPTH) { + return '[object Array](' . count($value) . ')[...]'; + } + $output = []; + foreach ($value as $item) { + $output[] = self::stringify($item, $depth + 1); + } + return '[object Array](' . + count($value) . + ')[' . + join(', ', $output) . + ']'; + } + return '[object ' . get_class($value) . ']'; + } } /* @@ -325,194 +333,529 @@ function objectToString(o) { */ //this is generated from JS; loosely based on inspect module from Node.js -Debug::$inspect = call_user_func(function() use (&$Date, &$Object, &$RegExp, &$JSON, &$Error, &$String, &$Array) { - $inspect = new Func("inspect", function($obj = null, $depth = null) use (&$stylizeNoColor, &$formatValue) { - $ctx = new ObjectClass("seen", new Arr(), "stylize", $stylizeNoColor); - return call($formatValue, $ctx, $obj, (isset($depth) ? _typeof($depth) : "undefined") === "undefined" ? 2.0 : $depth); - }); - $stylizeNoColor = new Func("stylizeNoColor", function($str = null, $styleType = null) { - return $str; - }); - $formatValue = new Func("formatValue", function($ctx = null, $value = null, $recurseTimes = null) use (&$inspect, &$isDate, &$Date, &$formatPrimitive, &$Object, &$isRegExp, &$RegExp, &$isError, &$formatError, &$isArray, &$formatArray, &$reduceToSingleString, &$formatProperty) { - if (is($value) && _typeof(get($value, "inspect")) === "function" && get($value, "inspect") !== $inspect && not((is($and_ = get($value, "constructor")) ? get(get($value, "constructor"), "prototype") === $value : $and_))) { - return call_method($value, "inspect", $recurseTimes); - } - if (is(call($isDate, $value))) { - return call_method($ctx, "stylize", call_method(get(get($Date, "prototype"), "toUTCString"), "call", $value), "date"); - } - if (is($value) && eq(_typeof(get($value, "valueOf")), "function")) { - $value = call_method($value, "valueOf"); - } - $primitive = call($formatPrimitive, $ctx, $value); - if (is($primitive)) { - return $primitive; - } - if ((isset($value) ? _typeof($value) : "undefined") === "object" && !_instanceof($value, $Object)) { - return call_method($ctx, "stylize", "[object Object]", "special"); - } - $keys = call_method($Object, "keys", $value); - if (get($keys, "length") === 0.0) { - if ((isset($value) ? _typeof($value) : "undefined") === "function") { - $name = is(get($value, "name")) ? _concat(": ", get($value, "name")) : ""; - return call_method($ctx, "stylize", _concat("[Function", $name, "]"), "special"); - } - if (is(call($isRegExp, $value))) { - return call_method($ctx, "stylize", call_method(get(get($RegExp, "prototype"), "toString"), "call", $value), "regexp"); - } - if (is(call($isError, $value))) { - return call($formatError, $value); - } - } - $base = ""; $array = false; $braces = new Arr("{", "}"); - if (is(call($isArray, $value))) { - $array = true; - $braces = new Arr("[", "]"); - } - if ((isset($value) ? _typeof($value) : "undefined") === "function") { - $n = is(get($value, "name")) ? _concat(": ", get($value, "name")) : ""; - $base = _concat(" [Function", $n, "]"); - } - if (is(call($isRegExp, $value))) { - $base = _concat(" ", call_method(get(get($RegExp, "prototype"), "toString"), "call", $value)); - } - if (is(call($isDate, $value))) { - $base = _concat(" ", call_method(get(get($Date, "prototype"), "toUTCString"), "call", $value)); - } - if (is(call($isError, $value))) { - $base = _concat(" ", call($formatError, $value)); - } - if (get($keys, "length") === 0.0 && not($array) || get($value, "length") === 0.0) { - return _plus(get($braces, 0.0), $base, get($braces, 1.0)); - } - if ($recurseTimes < 0.0) { - if (is(call($isRegExp, $value))) { - return call_method($ctx, "stylize", call_method(get(get($RegExp, "prototype"), "toString"), "call", $value), "regexp"); - } else { - return call_method($ctx, "stylize", "[Object]", "special"); - } - - } - call_method(get($ctx, "seen"), "push", $value); - if (is($array)) { - $output = call($formatArray, $ctx, $value, $recurseTimes, $keys, $keys); - } else { - $output = call_method($keys, "map", new Func(function($key = null) use (&$formatProperty, &$ctx, &$value, &$recurseTimes, &$keys, &$array) { - return call($formatProperty, $ctx, $value, $recurseTimes, $keys, $key, $array); - })); - } - - call_method(get($ctx, "seen"), "pop"); - return call($reduceToSingleString, $output, $base, $braces); - }); - $formatPrimitive = new Func("formatPrimitive", function($ctx = null, $value = null) use (&$JSON) { - switch ((isset($value) ? _typeof($value) : "undefined")) { - case "undefined": - return call_method($ctx, "stylize", "undefined", "undefined"); - case "string": - $simple = _concat("'", call_method(call_method(call_method(call_method($JSON, "stringify", $value), "replace", new RegExp("^\"|\"\$", "g"), ""), "replace", new RegExp("'", "g"), "\\'"), "replace", new RegExp("\\\\\"", "g"), "\""), "'"); - return call_method($ctx, "stylize", $simple, "string"); - case "number": - return call_method($ctx, "stylize", _concat("", $value), "number"); - case "boolean": - return call_method($ctx, "stylize", _concat("", $value), "boolean"); - } - if ($value === ObjectClass::$null) { - return call_method($ctx, "stylize", "null", "null"); - } - }); - $formatError = new Func("formatError", function($value = null) use (&$Error) { - return _concat("[", call_method(get(get($Error, "prototype"), "toString"), "call", $value), "]"); - }); - $formatArray = new Func("formatArray", function($ctx = null, $value = null, $recurseTimes = null, $keys = null) use (&$Object, &$String, &$formatProperty) { - $output = new Arr(); - for ($i = 0.0, $l = get($value, "length"); $i < $l; ++$i) { - if (is(call_method(get(get($Object, "prototype"), "hasOwnProperty"), "call", $value, call($String, $i)))) { - call_method($output, "push", call($formatProperty, $ctx, $value, $recurseTimes, $keys, call($String, $i), true)); - } else { - call_method($output, "push", ""); - } - - } - call_method($keys, "forEach", new Func(function($key = null) use (&$output, &$formatProperty, &$ctx, &$value, &$recurseTimes, &$keys) { - if (not(call_method($key, "match", new RegExp("^\\d+\$", "")))) { - call_method($output, "push", call($formatProperty, $ctx, $value, $recurseTimes, $keys, $key, true)); - } - })); - return $output; - }); - $formatProperty = new Func("formatProperty", function($ctx = null, $value = null, $recurseTimes = null, $keys = null, $key = null, $array = null) use (&$formatValue, &$JSON) { - $desc = new ObjectClass("value", get($value, $key)); - if (call_method($keys, "indexOf", $key) < 0.0) { - $name = _concat("[", $key, "]"); - } - if (call_method(get($ctx, "seen"), "indexOf", get($desc, "value")) < 0.0) { - if ($recurseTimes === ObjectClass::$null) { - $str = call($formatValue, $ctx, get($desc, "value"), ObjectClass::$null); - } else { - $str = call($formatValue, $ctx, get($desc, "value"), to_number($recurseTimes) - 1.0); - } - - if (call_method($str, "indexOf", "\n") > -1.0) { +Debug::$inspect = call_user_func(function () use ( + &$Date, + &$Object, + &$RegExp, + &$JSON, + &$Error, + &$String, + &$Array +) { + $inspect = new Func('inspect', function ($obj = null, $depth = null) use ( + &$stylizeNoColor, + &$formatValue + ) { + $ctx = new ObjectClass('seen', new Arr(), 'stylize', $stylizeNoColor); + return call( + $formatValue, + $ctx, + $obj, + (isset($depth) ? _typeof($depth) : 'undefined') === 'undefined' + ? 2.0 + : $depth + ); + }); + $stylizeNoColor = new Func('stylizeNoColor', function ( + $str = null, + $styleType = null + ) { + return $str; + }); + $formatValue = new Func('formatValue', function ( + $ctx = null, + $value = null, + $recurseTimes = null + ) use ( + &$inspect, + &$isDate, + &$Date, + &$formatPrimitive, + &$Object, + &$isRegExp, + &$RegExp, + &$isError, + &$formatError, + &$isArray, + &$formatArray, + &$reduceToSingleString, + &$formatProperty + ) { + if ( + is($value) && + _typeof(get($value, 'inspect')) === 'function' && + get($value, 'inspect') !== $inspect && + not( + is($and_ = get($value, 'constructor')) + ? get(get($value, 'constructor'), 'prototype') === $value + : $and_ + ) + ) { + return call_method($value, 'inspect', $recurseTimes); + } + if (is(call($isDate, $value))) { + return call_method( + $ctx, + 'stylize', + call_method( + get(get($Date, 'prototype'), 'toUTCString'), + 'call', + $value + ), + 'date' + ); + } + if (is($value) && eq(_typeof(get($value, 'valueOf')), 'function')) { + $value = call_method($value, 'valueOf'); + } + $primitive = call($formatPrimitive, $ctx, $value); + if (is($primitive)) { + return $primitive; + } + if ( + (isset($value) ? _typeof($value) : 'undefined') === 'object' && + !_instanceof($value, $Object) + ) { + return call_method($ctx, 'stylize', '[object Object]', 'special'); + } + $keys = call_method($Object, 'keys', $value); + if (get($keys, 'length') === 0.0) { + if ( + (isset($value) ? _typeof($value) : 'undefined') === 'function' + ) { + $name = is(get($value, 'name')) + ? _concat(': ', get($value, 'name')) + : ''; + return call_method( + $ctx, + 'stylize', + _concat('[Function', $name, ']'), + 'special' + ); + } + if (is(call($isRegExp, $value))) { + return call_method( + $ctx, + 'stylize', + call_method( + get(get($RegExp, 'prototype'), 'toString'), + 'call', + $value + ), + 'regexp' + ); + } + if (is(call($isError, $value))) { + return call($formatError, $value); + } + } + $base = ''; + $array = false; + $braces = new Arr('{', '}'); + if (is(call($isArray, $value))) { + $array = true; + $braces = new Arr('[', ']'); + } + if ((isset($value) ? _typeof($value) : 'undefined') === 'function') { + $n = is(get($value, 'name')) + ? _concat(': ', get($value, 'name')) + : ''; + $base = _concat(' [Function', $n, ']'); + } + if (is(call($isRegExp, $value))) { + $base = _concat( + ' ', + call_method( + get(get($RegExp, 'prototype'), 'toString'), + 'call', + $value + ) + ); + } + if (is(call($isDate, $value))) { + $base = _concat( + ' ', + call_method( + get(get($Date, 'prototype'), 'toUTCString'), + 'call', + $value + ) + ); + } + if (is(call($isError, $value))) { + $base = _concat(' ', call($formatError, $value)); + } + if ( + (get($keys, 'length') === 0.0 && not($array)) || + get($value, 'length') === 0.0 + ) { + return _plus(get($braces, 0.0), $base, get($braces, 1.0)); + } + if ($recurseTimes < 0.0) { + if (is(call($isRegExp, $value))) { + return call_method( + $ctx, + 'stylize', + call_method( + get(get($RegExp, 'prototype'), 'toString'), + 'call', + $value + ), + 'regexp' + ); + } else { + return call_method($ctx, 'stylize', '[Object]', 'special'); + } + } + call_method(get($ctx, 'seen'), 'push', $value); if (is($array)) { - $str = call_method(call_method(call_method(call_method($str, "split", "\n"), "map", new Func(function($line = null) { - return _concat(" ", $line); - })), "join", "\n"), "substr", 2.0); + $output = call( + $formatArray, + $ctx, + $value, + $recurseTimes, + $keys, + $keys + ); } else { - $str = _concat("\n", call_method(call_method(call_method($str, "split", "\n"), "map", new Func(function($line = null) { - return _concat(" ", $line); - })), "join", "\n")); + $output = call_method( + $keys, + 'map', + new Func(function ($key = null) use ( + &$formatProperty, + &$ctx, + &$value, + &$recurseTimes, + &$keys, + &$array + ) { + return call( + $formatProperty, + $ctx, + $value, + $recurseTimes, + $keys, + $key, + $array + ); + }) + ); } - } - } else { - $str = call_method($ctx, "stylize", "[Circular]", "special"); - } - - if ((isset($name) ? _typeof($name) : "undefined") === "undefined") { - if (is($array) && is(call_method($key, "match", new RegExp("^\\d+\$", "")))) { - return $str; - } - $name = call_method($JSON, "stringify", _concat("", $key)); - if (is(call_method($name, "match", new RegExp("^\"([a-zA-Z_][a-zA-Z_0-9]*)\"\$", "")))) { - $name = call_method($name, "substr", 1.0, to_number(get($name, "length")) - 2.0); - $name = call_method($ctx, "stylize", $name, "name"); - } else { - $name = call_method(call_method(call_method($name, "replace", new RegExp("'", "g"), "\\'"), "replace", new RegExp("\\\\\"", "g"), "\""), "replace", new RegExp("(^\"|\"\$)", "g"), "'"); - $name = call_method($ctx, "stylize", $name, "string"); - } + call_method(get($ctx, 'seen'), 'pop'); + return call($reduceToSingleString, $output, $base, $braces); + }); + $formatPrimitive = new Func('formatPrimitive', function ( + $ctx = null, + $value = null + ) use (&$JSON) { + switch (isset($value) ? _typeof($value) : 'undefined') { + case 'undefined': + return call_method($ctx, 'stylize', 'undefined', 'undefined'); + case 'string': + $simple = _concat( + "'", + call_method( + call_method( + call_method( + call_method($JSON, 'stringify', $value), + 'replace', + new RegExp("^\"|\"\$", 'g'), + '' + ), + 'replace', + new RegExp("'", 'g'), + "\\'" + ), + 'replace', + new RegExp("\\\\\"", 'g'), + "\"" + ), + "'" + ); + return call_method($ctx, 'stylize', $simple, 'string'); + case 'number': + return call_method( + $ctx, + 'stylize', + _concat('', $value), + 'number' + ); + case 'boolean': + return call_method( + $ctx, + 'stylize', + _concat('', $value), + 'boolean' + ); + } + if ($value === ObjectClass::$null) { + return call_method($ctx, 'stylize', 'null', 'null'); + } + }); + $formatError = new Func('formatError', function ($value = null) use ( + &$Error + ) { + return _concat( + '[', + call_method( + get(get($Error, 'prototype'), 'toString'), + 'call', + $value + ), + ']' + ); + }); + $formatArray = new Func('formatArray', function ( + $ctx = null, + $value = null, + $recurseTimes = null, + $keys = null + ) use (&$Object, &$String, &$formatProperty) { + $output = new Arr(); + for ($i = 0.0, $l = get($value, 'length'); $i < $l; ++$i) { + if ( + is( + call_method( + get(get($Object, 'prototype'), 'hasOwnProperty'), + 'call', + $value, + call($String, $i) + ) + ) + ) { + call_method( + $output, + 'push', + call( + $formatProperty, + $ctx, + $value, + $recurseTimes, + $keys, + call($String, $i), + true + ) + ); + } else { + call_method($output, 'push', ''); + } + } + call_method( + $keys, + 'forEach', + new Func(function ($key = null) use ( + &$output, + &$formatProperty, + &$ctx, + &$value, + &$recurseTimes, + &$keys + ) { + if ( + not(call_method($key, 'match', new RegExp("^\\d+\$", ''))) + ) { + call_method( + $output, + 'push', + call( + $formatProperty, + $ctx, + $value, + $recurseTimes, + $keys, + $key, + true + ) + ); + } + }) + ); + return $output; + }); + $formatProperty = new Func('formatProperty', function ( + $ctx = null, + $value = null, + $recurseTimes = null, + $keys = null, + $key = null, + $array = null + ) use (&$formatValue, &$JSON) { + $desc = new ObjectClass('value', get($value, $key)); + if (call_method($keys, 'indexOf', $key) < 0.0) { + $name = _concat('[', $key, ']'); + } + if ( + call_method(get($ctx, 'seen'), 'indexOf', get($desc, 'value')) < 0.0 + ) { + if ($recurseTimes === ObjectClass::$null) { + $str = call( + $formatValue, + $ctx, + get($desc, 'value'), + ObjectClass::$null + ); + } else { + $str = call( + $formatValue, + $ctx, + get($desc, 'value'), + to_number($recurseTimes) - 1.0 + ); + } + + if (call_method($str, 'indexOf', "\n") > -1.0) { + if (is($array)) { + $str = call_method( + call_method( + call_method( + call_method($str, 'split', "\n"), + 'map', + new Func(function ($line = null) { + return _concat(' ', $line); + }) + ), + 'join', + "\n" + ), + 'substr', + 2.0 + ); + } else { + $str = _concat( + "\n", + call_method( + call_method( + call_method($str, 'split', "\n"), + 'map', + new Func(function ($line = null) { + return _concat(' ', $line); + }) + ), + 'join', + "\n" + ) + ); + } + } + } else { + $str = call_method($ctx, 'stylize', '[Circular]', 'special'); + } - } - return _concat($name, ": ", $str); - }); - $reduceToSingleString = new Func("reduceToSingleString", function($output = null, $base = null, $braces = null) { - $numLinesEst = 0.0; - $length = 0.0; - call_method($output, "forEach", new Func(function($str = null) use (&$numLinesEst, &$length) { - $numLinesEst++; - if (call_method($str, "indexOf", "\n") >= 0.0) { - $numLinesEst++; - } - $length += _plus(get($str, "length"), 1.0); - })); - if ($length > 60.0) { - return _concat(get($braces, 0.0), $base === "" ? "" : (_concat($base, "\n ")), " ", call_method($output, "join", ",\n "), " ", get($braces, 1.0)); - } - return _concat(get($braces, 0.0), $base, " ", call_method($output, "join", ", "), " ", get($braces, 1.0)); - }); - $isArray = new Func("isArray", function($ar = null) use (&$Array, &$objectToString) { - return (is($or_ = call_method($Array, "isArray", $ar)) ? $or_ : (isset($ar) ? _typeof($ar) : "undefined") === "object" && call($objectToString, $ar) === "[object Array]"); - }); - $isRegExp = new Func("isRegExp", function($re = null) use (&$objectToString) { - return (isset($re) ? _typeof($re) : "undefined") === "object" && call($objectToString, $re) === "[object RegExp]"; - }); - $isDate = new Func("isDate", function($d = null) use (&$objectToString) { - return (isset($d) ? _typeof($d) : "undefined") === "object" && call($objectToString, $d) === "[object Date]"; - }); - $isError = new Func("isError", function($e = null) use (&$objectToString) { - return (isset($e) ? _typeof($e) : "undefined") === "object" && call($objectToString, $e) === "[object Error]"; - }); - $objectToString = new Func("objectToString", function($o = null) use (&$Object) { - return call_method(get(get($Object, "prototype"), "toString"), "call", $o); - }); - return $inspect; + if ((isset($name) ? _typeof($name) : 'undefined') === 'undefined') { + if ( + is($array) && + is(call_method($key, 'match', new RegExp("^\\d+\$", ''))) + ) { + return $str; + } + $name = call_method($JSON, 'stringify', _concat('', $key)); + if ( + is( + call_method( + $name, + 'match', + new RegExp("^\"([a-zA-Z_][a-zA-Z_0-9]*)\"\$", '') + ) + ) + ) { + $name = call_method( + $name, + 'substr', + 1.0, + to_number(get($name, 'length')) - 2.0 + ); + $name = call_method($ctx, 'stylize', $name, 'name'); + } else { + $name = call_method( + call_method( + call_method( + $name, + 'replace', + new RegExp("'", 'g'), + "\\'" + ), + 'replace', + new RegExp("\\\\\"", 'g'), + "\"" + ), + 'replace', + new RegExp("(^\"|\"\$)", 'g'), + "'" + ); + $name = call_method($ctx, 'stylize', $name, 'string'); + } + } + return _concat($name, ': ', $str); + }); + $reduceToSingleString = new Func('reduceToSingleString', function ( + $output = null, + $base = null, + $braces = null + ) { + $numLinesEst = 0.0; + $length = 0.0; + call_method( + $output, + 'forEach', + new Func(function ($str = null) use (&$numLinesEst, &$length) { + $numLinesEst++; + if (call_method($str, 'indexOf', "\n") >= 0.0) { + $numLinesEst++; + } + $length += _plus(get($str, 'length'), 1.0); + }) + ); + if ($length > 60.0) { + return _concat( + get($braces, 0.0), + $base === '' ? '' : _concat($base, "\n "), + ' ', + call_method($output, 'join', ",\n "), + ' ', + get($braces, 1.0) + ); + } + return _concat( + get($braces, 0.0), + $base, + ' ', + call_method($output, 'join', ', '), + ' ', + get($braces, 1.0) + ); + }); + $isArray = new Func('isArray', function ($ar = null) use ( + &$Array, + &$objectToString + ) { + return is($or_ = call_method($Array, 'isArray', $ar)) + ? $or_ + : (isset($ar) ? _typeof($ar) : 'undefined') === 'object' && + call($objectToString, $ar) === '[object Array]'; + }); + $isRegExp = new Func('isRegExp', function ($re = null) use ( + &$objectToString + ) { + return (isset($re) ? _typeof($re) : 'undefined') === 'object' && + call($objectToString, $re) === '[object RegExp]'; + }); + $isDate = new Func('isDate', function ($d = null) use (&$objectToString) { + return (isset($d) ? _typeof($d) : 'undefined') === 'object' && + call($objectToString, $d) === '[object Date]'; + }); + $isError = new Func('isError', function ($e = null) use (&$objectToString) { + return (isset($e) ? _typeof($e) : 'undefined') === 'object' && + call($objectToString, $e) === '[object Error]'; + }); + $objectToString = new Func('objectToString', function ($o = null) use ( + &$Object + ) { + return call_method( + get(get($Object, 'prototype'), 'toString'), + 'call', + $o + ); + }); + return $inspect; }); diff --git a/php/helpers/Module.php b/php/helpers/Module.php index 8283277..5266790 100644 --- a/php/helpers/Module.php +++ b/php/helpers/Module.php @@ -1,78 +1,81 @@ set('on', self::$on); - $obj->set('emit', self::$emit); - } + /** + * @param string $name + * @return mixed + */ + static function get($name) + { + $modules = self::$modules; + if (array_key_exists($name, $modules)) { + return $modules[$name]; + } + $definitions = self::$definitions; + if (array_key_exists($name, $definitions)) { + $definition = $definitions[$name]; + $module = $definition(); + $modules[$name] = $module; + return $module; + } + return null; + } + /** + * @param ObjectClass $obj + */ + static function eventify($obj) + { + $obj->set('on', self::$on); + $obj->set('emit', self::$emit); + } } -Module::$on = new Func(function($name, $listener) { - $self = Func::getContext(); - $events = $self->get('_events'); - if ($events === null) { - $events = new ObjectClass(); - $self->set('_events', $events); - } - $listeners = $events->get($name); - if ($listeners === null) { - $listeners = new Arr(); - $events->set($name, $listeners); - } - $listeners->push($listener); +Module::$on = new Func(function ($name, $listener) { + $self = Func::getContext(); + $events = $self->get('_events'); + if ($events === null) { + $events = new ObjectClass(); + $self->set('_events', $events); + } + $listeners = $events->get($name); + if ($listeners === null) { + $listeners = new Arr(); + $events->set($name, $listeners); + } + $listeners->push($listener); }); -Module::$emit = new Func(function($name) { - $self = Func::getContext(); - $events = $self->get('_events'); - if ($events === null) { - return; - } - $listeners = $events->get($name); - if ($listeners === null) { - return; - } - $args = array_slice(func_get_args(), 1); - for ($i = 0; $i < $listeners->length; $i++) { - $listeners->get($i)->apply($self, $args); - } +Module::$emit = new Func(function ($name) { + $self = Func::getContext(); + $events = $self->get('_events'); + if ($events === null) { + return; + } + $listeners = $events->get($name); + if ($listeners === null) { + return; + } + $args = array_slice(func_get_args(), 1); + for ($i = 0; $i < $listeners->length; $i++) { + $listeners->get($i)->apply($self, $args); + } }); diff --git a/php/helpers/Test.php b/php/helpers/Test.php index cbb3e6b..5fefb6f 100644 --- a/php/helpers/Test.php +++ b/php/helpers/Test.php @@ -1,24 +1,26 @@ =0) return (float)($a>>$b); - if($b==0) return (float)((($a>>1)&0x7fffffff)*2+(($a>>$b)&1)); - return (float)(((~$a)>>$b)^(0x7fffffff>>($b-1))); +function _bitwise_zfrs($a, $b) +{ + if ($a >= 0) { + return (float) ($a >> $b); + } + if ($b == 0) { + return (float) ((($a >> 1) & 0x7fffffff) * 2 + (($a >> $b) & 1)); + } + return (float) ((~$a >> $b) ^ (0x7fffffff >> $b - 1)); } /** @@ -119,92 +157,111 @@ function _bitwise_zfrs($a, $b) { * @param array $arr * @return array */ -function keys($obj, &$arr = array()) { - if (!($obj instanceof ObjectClass)) { - return $arr; - } - return $obj->getKeys($arr); +function keys($obj, &$arr = []) +{ + if (!($obj instanceof ObjectClass)) { + return $arr; + } + return $obj->getKeys($arr); } -function is_primitive($value) { - return ($value === null || $value === ObjectClass::$null || is_scalar($value)); +function is_primitive($value) +{ + return $value === null || + $value === ObjectClass::$null || + is_scalar($value); } -function is_int_or_float($value) { - return (is_int($value) || is_float($value)); +function is_int_or_float($value) +{ + return is_int($value) || is_float($value); } -function to_string($value) { - if ($value === null) { - return 'undefined'; - } - if ($value === ObjectClass::$null) { - return 'null'; - } - $type = gettype($value); - if ($type === 'string') { - return $value; - } - if ($type === 'boolean') { - return $value ? 'true' : 'false'; - } - if ($type === 'integer' || $type === 'double') { - if ($value !== $value) return 'NaN'; - if ($value === INF) return 'Infinity'; - if ($value === -INF) return '-Infinity'; - return $value . ''; - } - if ($value instanceof ObjectClass) { - $fn = $value->get('toString'); - if ($fn instanceof Func) { - return $fn->call($value); - } else { - throw new Ex(Err::create('Cannot convert object to primitive value')); - } - } - throw new Ex(Err::create('Cannot cast PHP value to string: ' . _stringify($value))); +function to_string($value) +{ + if ($value === null) { + return 'undefined'; + } + if ($value === ObjectClass::$null) { + return 'null'; + } + $type = gettype($value); + if ($type === 'string') { + return $value; + } + if ($type === 'boolean') { + return $value ? 'true' : 'false'; + } + if ($type === 'integer' || $type === 'double') { + if ($value !== $value) { + return 'NaN'; + } + if ($value === INF) { + return 'Infinity'; + } + if ($value === -INF) { + return '-Infinity'; + } + return $value . ''; + } + if ($value instanceof ObjectClass) { + $fn = $value->get('toString'); + if ($fn instanceof Func) { + return $fn->call($value); + } else { + throw new Ex( + Err::create('Cannot convert object to primitive value') + ); + } + } + throw new Ex( + Err::create('Cannot cast PHP value to string: ' . _stringify($value)) + ); } -function to_number($value) { - if ($value === null) { +function to_number($value) +{ + if ($value === null) { + return NAN; + } + if ($value === ObjectClass::$null) { + return 0.0; + } + if (is_float($value)) { + return $value; + } + if (is_numeric($value)) { + return (float) $value; + } + if (is_bool($value)) { + return $value ? 1.0 : 0.0; + } + if ($value instanceof ObjectClass) { + return to_number(to_primitive($value)); + } + //trim whitespace + $value = preg_replace('/^[\s\x0B\xA0]+|[\s\x0B\xA0]+$/u', '', $value); + if ($value === '') { + return 0.0; + } + if ($value === 'Infinity' || $value === '+Infinity') { + return INF; + } + if ($value === '-Infinity') { + return -INF; + } + if (preg_match('/^([+-]?)(\d+\.\d*|\.\d+|\d+)$/i', $value)) { + return (float) $value; + } + if ( + preg_match('/^([+-]?)(\d+\.\d*|\.\d+|\d+)e([+-]?[0-9]+)$/i', $value, $m) + ) { + return pow($m[1] . $m[2], $m[3]); + } + if (preg_match('/^0x[a-z0-9]+$/i', $value)) { + return (float) hexdec(substr($value, 2)); + } return NAN; - } - if ($value === ObjectClass::$null) { - return 0.0; - } - if (is_float($value)) { - return $value; - } - if (is_numeric($value)) { - return (float)$value; - } - if (is_bool($value)) { - return ($value ? 1.0 : 0.0); - } - if ($value instanceof ObjectClass) { - return to_number(to_primitive($value)); - } - //trim whitespace - $value = preg_replace('/^[\s\x0B\xA0]+|[\s\x0B\xA0]+$/u', '', $value); - if ($value === '') { - return 0.0; - } - if ($value === 'Infinity' || $value === '+Infinity') { - return INF; - } - if ($value === '-Infinity') { - return -INF; - } - if (preg_match('/^([+-]?)(\d+\.\d*|\.\d+|\d+)$/i', $value)) { - return (float)$value; - } - if (preg_match('/^([+-]?)(\d+\.\d*|\.\d+|\d+)e([+-]?[0-9]+)$/i', $value, $m)) { - return pow($m[1] . $m[2], $m[3]); - } - if (preg_match('/^0x[a-z0-9]+$/i', $value)) { - return (float)hexdec(substr($value, 2)); - } - return NAN; } /** @@ -212,12 +269,13 @@ function to_number($value) { * @param ObjectClass $obj * @return mixed */ -function to_primitive($obj) { - $value = $obj->callMethod('valueOf'); - if ($value instanceof ObjectClass) { - $value = to_string($value); - } - return $value; +function to_primitive($obj) +{ + $value = $obj->callMethod('valueOf'); + if ($value instanceof ObjectClass) { + $value = to_string($value); + } + return $value; } /** @@ -225,19 +283,19 @@ function to_primitive($obj) { * @param $value * @return ObjectClass */ -function objectify($value) { - $type = gettype($value); - if ($type === 'string') { - return new Str($value); - } elseif ($type === 'integer' || $type === 'double') { - return new Number($value); - } elseif ($type === 'boolean') { - return new Bln($value); - } - return $value; +function objectify($value) +{ + $type = gettype($value); + if ($type === 'string') { + return new Str($value); + } elseif ($type === 'integer' || $type === 'double') { + return new Number($value); + } elseif ($type === 'boolean') { + return new Bln($value); + } + return $value; } - //getters, setters and function callers /** @@ -247,12 +305,17 @@ function objectify($value) { * @return mixed * @throws Exception */ -function get($obj, $name) { - if ($obj === null || $obj === ObjectClass::$null) { - throw new Ex(Err::create("Cannot read property '" . $name . "' of " . to_string($obj))); - } - $obj = objectify($obj); - return $obj->get($name); +function get($obj, $name) +{ + if ($obj === null || $obj === ObjectClass::$null) { + throw new Ex( + Err::create( + "Cannot read property '" . $name . "' of " . to_string($obj) + ) + ); + } + $obj = objectify($obj); + return $obj->get($name); } /** @@ -266,35 +329,40 @@ function get($obj, $name) { * @return float|int|null|string * @throws Exception */ -function set($obj, $name, $value, $op = '=', $returnOld = false) { - if ($obj === null || $obj === ObjectClass::$null) { - throw new Ex(Err::create("Cannot set property '" . $name . "' of " . to_string($obj))); - } - $obj = objectify($obj); - if ($op === '=') { - return $obj->set($name, $value); - } - $oldValue = $obj->get($name); - //todo: bitwise operators: << >> >>> & ^ | - switch ($op) { - case '+=': - $newValue = _plus($oldValue, $value); - break; - case '-=': - $newValue = $oldValue - $value; - break; - case '*=': - $newValue = $oldValue * $value; - break; - case '/=': - $newValue = $oldValue / $value; - break; - case '%=': - $newValue = $oldValue % $value; - break; - } - $obj->set($name, $newValue); - return $returnOld ? $oldValue : $newValue; +function set($obj, $name, $value, $op = '=', $returnOld = false) +{ + if ($obj === null || $obj === ObjectClass::$null) { + throw new Ex( + Err::create( + "Cannot set property '" . $name . "' of " . to_string($obj) + ) + ); + } + $obj = objectify($obj); + if ($op === '=') { + return $obj->set($name, $value); + } + $oldValue = $obj->get($name); + //todo: bitwise operators: << >> >>> & ^ | + switch ($op) { + case '+=': + $newValue = _plus($oldValue, $value); + break; + case '-=': + $newValue = $oldValue - $value; + break; + case '*=': + $newValue = $oldValue * $value; + break; + case '/=': + $newValue = $oldValue / $value; + break; + case '%=': + $newValue = $oldValue % $value; + break; + } + $obj->set($name, $newValue); + return $returnOld ? $oldValue : $newValue; } /** @@ -303,12 +371,13 @@ function set($obj, $name, $value, $op = '=', $returnOld = false) { * @return mixed * @throws Exception */ -function call($fn) { - if (!($fn instanceof Func)) { - throw new Ex(Err::create(_typeof($fn) . " is not a function")); - } - $args = array_slice(func_get_args(), 1); - return $fn->apply(ObjectClass::$global, $args); +function call($fn) +{ + if (!($fn instanceof Func)) { + throw new Ex(Err::create(_typeof($fn) . ' is not a function')); + } + $args = array_slice(func_get_args(), 1); + return $fn->apply(ObjectClass::$global, $args); } /** @@ -317,17 +386,22 @@ function call($fn) { * @return mixed * @throws Exception */ -function call_method($obj, $name) { - if ($obj === null || $obj === ObjectClass::$null) { - throw new Ex(Err::create("Cannot read property '" . $name . "' of " . to_string($obj))); - } - $obj = objectify($obj); - $fn = $obj->get($name); - if (!($fn instanceof Func)) { - throw new Ex(Err::create(_typeof($fn) . " is not a function")); - } - $args = array_slice(func_get_args(), 2); - return $fn->apply($obj, $args); +function call_method($obj, $name) +{ + if ($obj === null || $obj === ObjectClass::$null) { + throw new Ex( + Err::create( + "Cannot read property '" . $name . "' of " . to_string($obj) + ) + ); + } + $obj = objectify($obj); + $fn = $obj->get($name); + if (!($fn instanceof Func)) { + throw new Ex(Err::create(_typeof($fn) . ' is not a function')); + } + $args = array_slice(func_get_args(), 2); + return $fn->apply($obj, $args); } /** @@ -335,13 +409,14 @@ function call_method($obj, $name) { * @param string $data * @param int|float|null $bytesTotal */ -function write_all($stream, $data, $bytesTotal = null) { - if ($bytesTotal === null) { - $bytesTotal = strlen($data); - } - $bytesWritten = fwrite($stream, $data); - //some platforms require multiple calls to fwrite - while ($bytesWritten < $bytesTotal) { - $bytesWritten += fwrite($stream, substr($data, $bytesWritten)); - } +function write_all($stream, $data, $bytesTotal = null) +{ + if ($bytesTotal === null) { + $bytesTotal = strlen($data); + } + $bytesWritten = fwrite($stream, $data); + //some platforms require multiple calls to fwrite + while ($bytesWritten < $bytesTotal) { + $bytesWritten += fwrite($stream, substr($data, $bytesWritten)); + } } diff --git a/php/helpers/operators.php b/php/helpers/operators.php index 22ca2db..119f233 100644 --- a/php/helpers/operators.php +++ b/php/helpers/operators.php @@ -1,7 +1,8 @@ proto; + $prototype = get($fn, 'prototype'); + while ($proto !== ObjectClass::$null) { + if ($proto === $prototype) { + return true; + } + $proto = $proto->proto; + } return false; - } - if (!($fn instanceof Func)) { - throw new Ex(Err::create('Expecting a function in instanceof check')); - } - $proto = $obj->proto; - $prototype = get($fn, 'prototype'); - while ($proto !== ObjectClass::$null) { - if ($proto === $prototype) { - return true; - } - $proto = $proto->proto; - } - return false; } -function _divide($a, $b) { - $a = to_number($a); - $b = to_number($b); - if ($b === 0.0) { - if ($a === 0.0) return NAN; - return ($a < 0.0) ? -INF : INF; - } - return $a / $b; +function _divide($a, $b) +{ + $a = to_number($a); + $b = to_number($b); + if ($b === 0.0) { + if ($a === 0.0) { + return NAN; + } + return $a < 0.0 ? -INF : INF; + } + return $a / $b; } -function _plus() { - $total = 0; - $strings = array(); - $isString = false; - foreach (func_get_args() as $arg) { - if (is_string($arg)) { - $isString = true; - } - $strings[] = to_string($arg); - if (!$isString) { - $total += to_number($arg); - } - } - return $isString ? join('', $strings) : $total; +function _plus() +{ + $total = 0; + $strings = []; + $isString = false; + foreach (func_get_args() as $arg) { + if (is_string($arg)) { + $isString = true; + } + $strings[] = to_string($arg); + if (!$isString) { + $total += to_number($arg); + } + } + return $isString ? join('', $strings) : $total; } -function _concat() { - $strings = array(); - foreach (func_get_args() as $arg) { - $strings[] = to_string($arg); - } - return join('', $strings); +function _concat() +{ + $strings = []; + foreach (func_get_args() as $arg) { + $strings[] = to_string($arg); + } + return join('', $strings); } -function _negate($val) { - return (float)(0 - $val); +function _negate($val) +{ + return (float) (0 - $val); } -function _and($a, $b) { - return $a ? $b : $a; +function _and($a, $b) +{ + return $a ? $b : $a; } -function _or($a, $b) { - return $a ? $a : $b; +function _or($a, $b) +{ + return $a ? $a : $b; } /** @@ -92,17 +103,18 @@ function _or($a, $b) { * @return bool * @throws Exception */ -function _delete($obj, $key = null) { - //don't allow deleting of variables, only properties - if (func_num_args() === 1) { - return false; - } - if ($obj === null || $obj === ObjectClass::$null) { - throw new Ex(Err::create("Cannot convert undefined or null to object")); - } - $obj = objectify($obj); - $obj->remove($key); - return true; +function _delete($obj, $key = null) +{ + //don't allow deleting of variables, only properties + if (func_num_args() === 1) { + return false; + } + if ($obj === null || $obj === ObjectClass::$null) { + throw new Ex(Err::create('Cannot convert undefined or null to object')); + } + $obj = objectify($obj); + $obj->remove($key); + return true; } /** @@ -111,38 +123,48 @@ function _delete($obj, $key = null) { * @return bool * @throws Exception */ -function _in($key, $obj) { - if (!($obj instanceof ObjectClass)) { - throw new Ex(Err::create("Cannot use 'in' operator to search for '" . $key . "' in " . to_string($obj))); - } - return $obj->hasProperty($key); +function _in($key, $obj) +{ + if (!($obj instanceof ObjectClass)) { + throw new Ex( + Err::create( + "Cannot use 'in' operator to search for '" . + $key . + "' in " . + to_string($obj) + ) + ); + } + return $obj->hasProperty($key); } -function _typeof($value) { - if ($value === null) { - return 'undefined'; - } - //js weirdness - if ($value === ObjectClass::$null) { - return 'object'; - } - $type = gettype($value); - if ($type === 'integer' || $type === 'double') { - return 'number'; - } - if ($type === 'string' || $type === 'boolean') { - return $type; - } - if ($value instanceof Func) { - return 'function'; - } - if ($value instanceof ObjectClass) { - return 'object'; - } - return 'unknown'; +function _typeof($value) +{ + if ($value === null) { + return 'undefined'; + } + //js weirdness + if ($value === ObjectClass::$null) { + return 'object'; + } + $type = gettype($value); + if ($type === 'integer' || $type === 'double') { + return 'number'; + } + if ($type === 'string' || $type === 'boolean') { + return $type; + } + if ($value instanceof Func) { + return 'function'; + } + if ($value instanceof ObjectClass) { + return 'object'; + } + return 'unknown'; } -function _seq() { - $args = func_get_args(); - return array_pop($args); +function _seq() +{ + $args = func_get_args(); + return array_pop($args); } diff --git a/php/modules/fs.php b/php/modules/fs.php index fedc32b..690e6a1 100644 --- a/php/modules/fs.php +++ b/php/modules/fs.php @@ -1,496 +1,542 @@ set('path', $fullPath); - $opts = ($opts instanceof ObjectClass) ? $opts : new ObjectClass(); - $self->set('opts', $opts); - if (!$opts->hasOwnProperty('chunkSize')) { - $opts->set('chunkSize', $CHUNK_SIZE); - } - try { - $stream = fopen($fullPath, 'rb'); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($stream === false) { - $helpers['throwError']('EIO', $fullPath); - } - $self->stream = $stream; - }); - - /* @var ObjectClass $prototype */ - $prototype = $ReadStream->get('prototype'); - Module::eventify($prototype); - $prototype->setMethods(array( - 'readBytes' => function($bytes) { - $self = Func::getContext(); - $stream = $self->stream; - if (feof($stream)) { - return null; - } - $data = fread($stream, $bytes); - $buffer = new Buffer($data); - $bytesRead = $self->get('bytesRead'); - if ($bytesRead === null) $bytesRead = 0.0; - $bytesRead += $buffer->length; - $self->set('bytesRead', $bytesRead); - return $buffer; - }, - 'readAll' => function() { - $self = Func::getContext(); - $chunkSize = $self->get('opts')->get('chunkSize'); - $stream = $self->stream; - $data = array(); - while (!feof($stream)) { - $data[] = fread($stream, $chunkSize); - } - fclose($stream); - return new Buffer(join('', $data)); - }, - 'size' => function() { - $self = Func::getContext(); - $size = $self->get('bytesTotal'); - if ($size === null) { - $stat = fstat($self->stream); - $size = (float)$stat['size']; - $self->set('bytesTotal', $size); - } - return $size; - }, - 'read' => function() { - $self = Func::getContext(); - $chunkSize = $self->get('opts')->get('chunkSize'); - $stream = $self->stream; - while (!feof($stream)) { - $data = fread($stream, $chunkSize); - $self->callMethod('emit', 'data', new Buffer($data)); - } - fclose($stream); - $self->callMethod('emit', 'end'); - } - ), true, false, true); - - - $WriteStream = new Func('WriteStream', function($path, $opts = null) use (&$helpers) { - $self = Func::getContext(); - $fullPath = $helpers['mapPath']($path); - $self->set('path', $fullPath); - $opts = ($opts instanceof ObjectClass) ? $opts : new ObjectClass(); - $self->set('opts', $opts); - //default is to append - $append = $opts->get('append') !== false; - //overwrite option will override append - if ($opts->get('overwrite') === true) { - $append = false; - } - $opts->set('append', $append); - $mode = $append ? 'ab' : 'wb'; - try { - $stream = fopen($fullPath, $mode); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($stream === false) { - $helpers['throwError']('EIO', $fullPath); - } - $self->stream = $stream; - $self->finished = false; - }); - - $prototype = $WriteStream->get('prototype'); - $prototype->setMethods(array( - 'setEncoding' => function($enc) { - $self = Func::getContext(); - $self->opts->set('encoding', $enc); - }, - 'write' => function($data, $enc = null) use (&$helpers) { + $ReadStream = new Func('ReadStream', function ($path, $opts = null) use ( + &$helpers, + &$CHUNK_SIZE + ) { $self = Func::getContext(); - if ($self->finished) return; - $data = ($data instanceof Buffer) ? $data->raw : $data; - write_all($self->stream, $data); - }, - 'end' => function() { - $self = Func::getContext(); - if ($self->finished) return; - $self->finished = true; - fclose($self->stream); - } - ), true, false, true); - - - $methods = array( - 'isFile' => function($path) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - return $helpers['isFile']($fullPath); - }, - 'isDir' => function($path) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - return $helpers['isDir']($fullPath); - }, - 'copyFile' => function($src, $dst) use (&$helpers) { - $src = $helpers['mapPath']($src); - if (!is_file($src)) { - $helpers['throwError']('ENOENT', $src); - } - $dst = $helpers['mapPath']($dst); - if (is_dir($dst)) { - $dstDir = $dst; - $dstName = basename($src); - } else { - $dstDir = dirname($dst); - $dstName = basename($dst); - if (!is_dir($dstDir)) { - $helpers['throwError']('ENOTDIR', $dstDir); - } - } - $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; - try { - $result = copy($src, $dst); - } catch(Exception $e) { - $helpers['handleException']($e, $src, $dst); - } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', array($src, $dst)); - } - }, - 'moveFile' => function($src, $dst) use (&$helpers) { - $src = $helpers['mapPath']($src); - if (!is_file($src)) { - $helpers['throwError']('ENOENT', $src); - } - $dst = $helpers['mapPath']($dst); - if (is_dir($dst)) { - $dstDir = $dst; - $dstName = basename($src); - } else { - $dstDir = dirname($dst); - $dstName = basename($dst); - if (!is_dir($dstDir)) { - $helpers['throwError']('ENOENT', $dstDir); - } - } - $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; - try { - $result = rename($src, $dst); - } catch(Exception $e) { - $helpers['handleException']($e, $src, $dst); - } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', array($src, $dst)); - } - }, - 'deleteFile' => function($path) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - $helpers['deleteFile']($fullPath); - }, - 'deleteFileIfExists' => function($path) use (&$helpers, &$fs) { - $fullPath = $helpers['mapPath']($path); - if (!is_file($fullPath)) { - return; - } - $helpers['deleteFile']($fullPath); - }, - 'createDir' => function($path, $opts = null) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - $opts = ($opts instanceof ObjectClass) ? $opts : new ObjectClass(); - $mode = $helpers['normalizeMode']($opts->get('mode'), 0777); - $deep = ($opts->get('deep') === true); - try { - $result = mkdir($fullPath, $mode, $deep); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', $fullPath); - } - }, - 'removeDir' => function($path, $deep = false) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - $helpers['removeDir']($fullPath, $deep); - }, - 'removeDirIfExists' => function($path, $deep = false) use (&$helpers, &$fs) { $fullPath = $helpers['mapPath']($path); - if (!is_dir($fullPath)) { - return; - } - $helpers['removeDir']($fullPath, $deep); - }, - 'moveDir' => function($src, $dst) use (&$helpers) { - $src = $helpers['mapPath']($src); - if (!is_dir($src)) { - $helpers['throwError']('ENOENT', $src); - } - $dst = $helpers['mapPath']($dst); - $dstDir = dirname($dst); - $dstName = basename($dst); - if (!is_dir($dstDir)) { - $helpers['throwError']('ENOENT', $dstDir); - } - $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; - try { - $result = rename($src, $dst); - } catch(Exception $e) { - $helpers['handleException']($e, $src, $dst); + $self->set('path', $fullPath); + $opts = $opts instanceof ObjectClass ? $opts : new ObjectClass(); + $self->set('opts', $opts); + if (!$opts->hasOwnProperty('chunkSize')) { + $opts->set('chunkSize', $CHUNK_SIZE); } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', array($src, $dst)); - } - }, - 'getDirContents' => function($path) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); try { - $list = scandir($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($list === false) { - $helpers['throwError']('EIO', $fullPath); - } - $arr = array(); - foreach ($list as $item) { - if ($item === '.' || $item === '..') continue; - $arr[] = $item; - } - return Arr::fromArray($arr); - }, - 'getInfo' => function($path, $deep = false) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - return $helpers['getInfo']($fullPath, $deep); - }, - 'readFile' => function($path, $enc = null) use (&$helpers) { - $fullPath = $helpers['mapPath']($path); - //file_get_contents returns an empty string for a directory - if (is_dir($fullPath)) { - $helpers['throwError']('EISDIR', $fullPath); - } - try { - $data = file_get_contents($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); + $stream = fopen($fullPath, 'rb'); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); } //fallback for if set_error_handler didn't do it's thing - if ($data === false) { - $helpers['throwError']('EIO', $fullPath); - } - if ($enc === null) { - return new Buffer($data, 'binary'); - } else { - return $data; + if ($stream === false) { + $helpers['throwError']('EIO', $fullPath); } - }, - 'writeFile' => function($path, $data, $opts = null) use (&$helpers) { + $self->stream = $stream; + }); + + /* @var ObjectClass $prototype */ + $prototype = $ReadStream->get('prototype'); + Module::eventify($prototype); + $prototype->setMethods( + [ + 'readBytes' => function ($bytes) { + $self = Func::getContext(); + $stream = $self->stream; + if (feof($stream)) { + return null; + } + $data = fread($stream, $bytes); + $buffer = new Buffer($data); + $bytesRead = $self->get('bytesRead'); + if ($bytesRead === null) { + $bytesRead = 0.0; + } + $bytesRead += $buffer->length; + $self->set('bytesRead', $bytesRead); + return $buffer; + }, + 'readAll' => function () { + $self = Func::getContext(); + $chunkSize = $self->get('opts')->get('chunkSize'); + $stream = $self->stream; + $data = []; + while (!feof($stream)) { + $data[] = fread($stream, $chunkSize); + } + fclose($stream); + return new Buffer(join('', $data)); + }, + 'size' => function () { + $self = Func::getContext(); + $size = $self->get('bytesTotal'); + if ($size === null) { + $stat = fstat($self->stream); + $size = (float) $stat['size']; + $self->set('bytesTotal', $size); + } + return $size; + }, + 'read' => function () { + $self = Func::getContext(); + $chunkSize = $self->get('opts')->get('chunkSize'); + $stream = $self->stream; + while (!feof($stream)) { + $data = fread($stream, $chunkSize); + $self->callMethod('emit', 'data', new Buffer($data)); + } + fclose($stream); + $self->callMethod('emit', 'end'); + }, + ], + true, + false, + true + ); + + $WriteStream = new Func('WriteStream', function ($path, $opts = null) use ( + &$helpers + ) { + $self = Func::getContext(); $fullPath = $helpers['mapPath']($path); - $opts = ($opts instanceof ObjectClass) ? $opts : new ObjectClass(); + $self->set('path', $fullPath); + $opts = $opts instanceof ObjectClass ? $opts : new ObjectClass(); + $self->set('opts', $opts); //default is to append $append = $opts->get('append') !== false; //overwrite option will override append if ($opts->get('overwrite') === true) { - $append = false; + $append = false; } - $flags = $append ? FILE_APPEND : 0; - $data = ($data instanceof Buffer) ? $data->raw : $data; + $opts->set('append', $append); + $mode = $append ? 'ab' : 'wb'; try { - $result = file_put_contents($fullPath, $data, $flags); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); + $stream = fopen($fullPath, $mode); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); } //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', $fullPath); + if ($stream === false) { + $helpers['throwError']('EIO', $fullPath); } - }, - 'createReadStream' => function($path, $opts = null) use (&$helpers, &$ReadStream) { - return $ReadStream->construct($path, $opts); - }, - 'createWriteStream' => function($path, $opts = null) use (&$helpers, &$WriteStream) { - return $WriteStream->construct($path, $opts); - } - ); + $self->stream = $stream; + $self->finished = false; + }); - $helpers = array( - 'basePath' => getcwd(), + $prototype = $WriteStream->get('prototype'); + $prototype->setMethods( + [ + 'setEncoding' => function ($enc) { + $self = Func::getContext(); + $self->opts->set('encoding', $enc); + }, + 'write' => function ($data, $enc = null) use (&$helpers) { + $self = Func::getContext(); + if ($self->finished) { + return; + } + $data = $data instanceof Buffer ? $data->raw : $data; + write_all($self->stream, $data); + }, + 'end' => function () { + $self = Func::getContext(); + if ($self->finished) { + return; + } + $self->finished = true; + fclose($self->stream); + }, + ], + true, + false, + true + ); - 'ERR_MAP' => array( - 'EACCES' => "EACCES, permission denied", - 'EBADF' => "EBADF, bad file descriptor", //todo - 'EEXIST' => "EEXIST, file already exists", //todo (mkdir existing) - 'EIO' => "EIO, input/output error", //unknown or other - 'ENOENT' => "ENOENT, no such file or directory", - 'ENOTDIR' => "ENOTDIR, not a directory", //todo (rmdir file) - 'ENOTEMPTY' => "ENOTEMPTY, directory not empty", - 'EPERM' => "EPERM, operation not permitted", //todo (unlink dir) - 'EISDIR' => "EISDIR, is a directory", - ), - 'throwError' => function($code, $paths = array(), $framesToPop = 0) use (&$helpers) { - $message = $helpers['ERR_MAP'][$code]; - $paths = is_array($paths) ? $paths : array($paths); - foreach ($paths as $path) { - $message .= " '" . $helpers['reverseMapPath']($path) . "'"; - } - $err = Err::create($message, $framesToPop + 1); - $err->set('code', $code); - throw new Ex($err); - }, - 'handleException' => function($ex, $paths = array()) use (&$helpers) { - $message = $ex->getMessage(); - $paths = is_array($paths) ? $paths : array($paths); - //get the error message with the path(s) removed. this prevents words - // in the path from effecting our parsing below. - foreach ($paths as $path) { - $message = str_replace($path, '', $message); - } - $slice = array_slice(explode(':', $message), -1); - $message = trim($slice[0]); - if (strpos($message, 'No such file or directory') !== false) { - $helpers['throwError']('ENOENT', $paths, 1); - } else if (strpos($message, 'Permission denied') !== false) { - $helpers['throwError']('EACCES', $paths, 1); - } else if (strpos($message, 'stat failed for') !== false) { - $helpers['throwError']('ENOENT', $paths, 1); - } else if (strpos($message, 'Directory not empty') !== false) { - $helpers['throwError']('ENOTEMPTY', $paths, 1); - } else if (strpos($message, 'Is a directory') !== false) { - $helpers['throwError']('EISDIR', $paths, 1); - } else { - throw $ex; - } - }, - 'mapPath' => function($path) use (&$helpers) { - $path = str_replace('\\', '/', $path); - $parts = explode('/', $path); - $normalized = array(); - foreach ($parts as $part) { - if ($part === '' || $part === '.' || $part === '..') continue; - $normalized[] = $part; - } - return $helpers['basePath'] . DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $normalized); - }, - 'reverseMapPath' => function($path) use (&$helpers) { - $basePath = $helpers['basePath']; - if ($path === $basePath) { - return './'; - } - if (strpos($path, $basePath . DIRECTORY_SEPARATOR) === 0) { - $path = './' . substr($path, strlen($basePath) + 1); - } - return str_replace('\\', '/', $path); - }, - 'isFile' => function($fullPath) use (&$helpers) { - try { - $result = is_file($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - return $result; - }, - 'isDir' => function($fullPath) use (&$helpers) { - try { - $result = is_dir($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - return $result; - }, - 'getInfo' => function($fullPath, $deep) use (&$helpers) { - try { - $stat = stat($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($stat === false) { - $helpers['throwError']('EIO', $fullPath); - } - $isDir = is_dir($fullPath); - $result = new ObjectClass(); - $result->set('name', basename($fullPath)); - $result->set('dateCreated', new Date($stat['ctime'] * 1000)); - $result->set('dateLastAccessed', new Date($stat['atime'] * 1000)); - $result->set('dateLastModified', new Date($stat['mtime'] * 1000)); - $result->set('type', $isDir ? 'directory' : 'file'); - if (!$isDir) { - $result->set('size', (float)$stat['size']); - } else if ($deep) { - $size = 0.0; - $children = new Arr(); - foreach (scandir($fullPath) as $item) { - if ($item === '.' || $item === '..') continue; - $child = $helpers['getInfo']($fullPath . DIRECTORY_SEPARATOR . $item, $deep); - $size += $child->get('size'); - $children->push($child); - } - $result->set('children', $children); - $result->set('size', $size); - } else { - $result->set('size', 0.0); - } - return $result; - }, - 'deleteFile' => function($fullPath) use (&$helpers) { - try { - $result = unlink($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', $fullPath); - } - }, - 'removeDir' => function($fullPath, $deep = false) use (&$helpers) { - if ($deep === true) { - try { - $list = scandir($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($list === false) { - $helpers['throwError']('EIO', $fullPath); - } - foreach ($list as $name) { - if ($name === '.' || $name === '..') { - continue; - } - $itemPath = $fullPath . DIRECTORY_SEPARATOR . $name; - if (is_dir($itemPath)) { - $helpers['removeDir']($itemPath, true); + $methods = [ + 'isFile' => function ($path) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + return $helpers['isFile']($fullPath); + }, + 'isDir' => function ($path) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + return $helpers['isDir']($fullPath); + }, + 'copyFile' => function ($src, $dst) use (&$helpers) { + $src = $helpers['mapPath']($src); + if (!is_file($src)) { + $helpers['throwError']('ENOENT', $src); + } + $dst = $helpers['mapPath']($dst); + if (is_dir($dst)) { + $dstDir = $dst; + $dstName = basename($src); } else { - $helpers['deleteFile']($itemPath); + $dstDir = dirname($dst); + $dstName = basename($dst); + if (!is_dir($dstDir)) { + $helpers['throwError']('ENOTDIR', $dstDir); + } } - } - } - try { - $result = rmdir($fullPath); - } catch(Exception $e) { - $helpers['handleException']($e, $fullPath); - } - //fallback for if set_error_handler didn't do it's thing - if ($result === false) { - $helpers['throwError']('EIO', $fullPath); - } - }, - 'normalizeMode' => function($mode, $default) { - if (is_float($mode)) $mode = (int)$mode; - if (is_string($mode)) $mode = octdec($mode); - if (!is_int($mode)) $mode = $default; - return $mode; - } - ); + $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; + try { + $result = copy($src, $dst); + } catch (Exception $e) { + $helpers['handleException']($e, $src, $dst); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', [$src, $dst]); + } + }, + 'moveFile' => function ($src, $dst) use (&$helpers) { + $src = $helpers['mapPath']($src); + if (!is_file($src)) { + $helpers['throwError']('ENOENT', $src); + } + $dst = $helpers['mapPath']($dst); + if (is_dir($dst)) { + $dstDir = $dst; + $dstName = basename($src); + } else { + $dstDir = dirname($dst); + $dstName = basename($dst); + if (!is_dir($dstDir)) { + $helpers['throwError']('ENOENT', $dstDir); + } + } + $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; + try { + $result = rename($src, $dst); + } catch (Exception $e) { + $helpers['handleException']($e, $src, $dst); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', [$src, $dst]); + } + }, + 'deleteFile' => function ($path) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + $helpers['deleteFile']($fullPath); + }, + 'deleteFileIfExists' => function ($path) use (&$helpers, &$fs) { + $fullPath = $helpers['mapPath']($path); + if (!is_file($fullPath)) { + return; + } + $helpers['deleteFile']($fullPath); + }, + 'createDir' => function ($path, $opts = null) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + $opts = $opts instanceof ObjectClass ? $opts : new ObjectClass(); + $mode = $helpers['normalizeMode']($opts->get('mode'), 0777); + $deep = $opts->get('deep') === true; + try { + $result = mkdir($fullPath, $mode, $deep); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', $fullPath); + } + }, + 'removeDir' => function ($path, $deep = false) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + $helpers['removeDir']($fullPath, $deep); + }, + 'removeDirIfExists' => function ($path, $deep = false) use ( + &$helpers, + &$fs + ) { + $fullPath = $helpers['mapPath']($path); + if (!is_dir($fullPath)) { + return; + } + $helpers['removeDir']($fullPath, $deep); + }, + 'moveDir' => function ($src, $dst) use (&$helpers) { + $src = $helpers['mapPath']($src); + if (!is_dir($src)) { + $helpers['throwError']('ENOENT', $src); + } + $dst = $helpers['mapPath']($dst); + $dstDir = dirname($dst); + $dstName = basename($dst); + if (!is_dir($dstDir)) { + $helpers['throwError']('ENOENT', $dstDir); + } + $dst = $dstDir . DIRECTORY_SEPARATOR . $dstName; + try { + $result = rename($src, $dst); + } catch (Exception $e) { + $helpers['handleException']($e, $src, $dst); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', [$src, $dst]); + } + }, + 'getDirContents' => function ($path) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + try { + $list = scandir($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($list === false) { + $helpers['throwError']('EIO', $fullPath); + } + $arr = []; + foreach ($list as $item) { + if ($item === '.' || $item === '..') { + continue; + } + $arr[] = $item; + } + return Arr::fromArray($arr); + }, + 'getInfo' => function ($path, $deep = false) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + return $helpers['getInfo']($fullPath, $deep); + }, + 'readFile' => function ($path, $enc = null) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + //file_get_contents returns an empty string for a directory + if (is_dir($fullPath)) { + $helpers['throwError']('EISDIR', $fullPath); + } + try { + $data = file_get_contents($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($data === false) { + $helpers['throwError']('EIO', $fullPath); + } + if ($enc === null) { + return new Buffer($data, 'binary'); + } else { + return $data; + } + }, + 'writeFile' => function ($path, $data, $opts = null) use (&$helpers) { + $fullPath = $helpers['mapPath']($path); + $opts = $opts instanceof ObjectClass ? $opts : new ObjectClass(); + //default is to append + $append = $opts->get('append') !== false; + //overwrite option will override append + if ($opts->get('overwrite') === true) { + $append = false; + } + $flags = $append ? FILE_APPEND : 0; + $data = $data instanceof Buffer ? $data->raw : $data; + try { + $result = file_put_contents($fullPath, $data, $flags); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', $fullPath); + } + }, + 'createReadStream' => function ($path, $opts = null) use ( + &$helpers, + &$ReadStream + ) { + return $ReadStream->construct($path, $opts); + }, + 'createWriteStream' => function ($path, $opts = null) use ( + &$helpers, + &$WriteStream + ) { + return $WriteStream->construct($path, $opts); + }, + ]; + + $helpers = [ + 'basePath' => getcwd(), + + 'ERR_MAP' => [ + 'EACCES' => 'EACCES, permission denied', + 'EBADF' => 'EBADF, bad file descriptor', //todo + 'EEXIST' => 'EEXIST, file already exists', //todo (mkdir existing) + 'EIO' => 'EIO, input/output error', //unknown or other + 'ENOENT' => 'ENOENT, no such file or directory', + 'ENOTDIR' => 'ENOTDIR, not a directory', //todo (rmdir file) + 'ENOTEMPTY' => 'ENOTEMPTY, directory not empty', + 'EPERM' => 'EPERM, operation not permitted', //todo (unlink dir) + 'EISDIR' => 'EISDIR, is a directory', + ], + 'throwError' => function ($code, $paths = [], $framesToPop = 0) use ( + &$helpers + ) { + $message = $helpers['ERR_MAP'][$code]; + $paths = is_array($paths) ? $paths : [$paths]; + foreach ($paths as $path) { + $message .= " '" . $helpers['reverseMapPath']($path) . "'"; + } + $err = Err::create($message, $framesToPop + 1); + $err->set('code', $code); + throw new Ex($err); + }, + 'handleException' => function ($ex, $paths = []) use (&$helpers) { + $message = $ex->getMessage(); + $paths = is_array($paths) ? $paths : [$paths]; + //get the error message with the path(s) removed. this prevents words + // in the path from effecting our parsing below. + foreach ($paths as $path) { + $message = str_replace($path, '', $message); + } + $slice = array_slice(explode(':', $message), -1); + $message = trim($slice[0]); + if (strpos($message, 'No such file or directory') !== false) { + $helpers['throwError']('ENOENT', $paths, 1); + } elseif (strpos($message, 'Permission denied') !== false) { + $helpers['throwError']('EACCES', $paths, 1); + } elseif (strpos($message, 'stat failed for') !== false) { + $helpers['throwError']('ENOENT', $paths, 1); + } elseif (strpos($message, 'Directory not empty') !== false) { + $helpers['throwError']('ENOTEMPTY', $paths, 1); + } elseif (strpos($message, 'Is a directory') !== false) { + $helpers['throwError']('EISDIR', $paths, 1); + } else { + throw $ex; + } + }, + 'mapPath' => function ($path) use (&$helpers) { + $path = str_replace('\\', '/', $path); + $parts = explode('/', $path); + $normalized = []; + foreach ($parts as $part) { + if ($part === '' || $part === '.' || $part === '..') { + continue; + } + $normalized[] = $part; + } + return $helpers['basePath'] . + DIRECTORY_SEPARATOR . + implode(DIRECTORY_SEPARATOR, $normalized); + }, + 'reverseMapPath' => function ($path) use (&$helpers) { + $basePath = $helpers['basePath']; + if ($path === $basePath) { + return './'; + } + if (strpos($path, $basePath . DIRECTORY_SEPARATOR) === 0) { + $path = './' . substr($path, strlen($basePath) + 1); + } + return str_replace('\\', '/', $path); + }, + 'isFile' => function ($fullPath) use (&$helpers) { + try { + $result = is_file($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + return $result; + }, + 'isDir' => function ($fullPath) use (&$helpers) { + try { + $result = is_dir($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + return $result; + }, + 'getInfo' => function ($fullPath, $deep) use (&$helpers) { + try { + $stat = stat($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($stat === false) { + $helpers['throwError']('EIO', $fullPath); + } + $isDir = is_dir($fullPath); + $result = new ObjectClass(); + $result->set('name', basename($fullPath)); + $result->set('dateCreated', new Date($stat['ctime'] * 1000)); + $result->set('dateLastAccessed', new Date($stat['atime'] * 1000)); + $result->set('dateLastModified', new Date($stat['mtime'] * 1000)); + $result->set('type', $isDir ? 'directory' : 'file'); + if (!$isDir) { + $result->set('size', (float) $stat['size']); + } elseif ($deep) { + $size = 0.0; + $children = new Arr(); + foreach (scandir($fullPath) as $item) { + if ($item === '.' || $item === '..') { + continue; + } + $child = $helpers['getInfo']( + $fullPath . DIRECTORY_SEPARATOR . $item, + $deep + ); + $size += $child->get('size'); + $children->push($child); + } + $result->set('children', $children); + $result->set('size', $size); + } else { + $result->set('size', 0.0); + } + return $result; + }, + 'deleteFile' => function ($fullPath) use (&$helpers) { + try { + $result = unlink($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', $fullPath); + } + }, + 'removeDir' => function ($fullPath, $deep = false) use (&$helpers) { + if ($deep === true) { + try { + $list = scandir($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($list === false) { + $helpers['throwError']('EIO', $fullPath); + } + foreach ($list as $name) { + if ($name === '.' || $name === '..') { + continue; + } + $itemPath = $fullPath . DIRECTORY_SEPARATOR . $name; + if (is_dir($itemPath)) { + $helpers['removeDir']($itemPath, true); + } else { + $helpers['deleteFile']($itemPath); + } + } + } + try { + $result = rmdir($fullPath); + } catch (Exception $e) { + $helpers['handleException']($e, $fullPath); + } + //fallback for if set_error_handler didn't do it's thing + if ($result === false) { + $helpers['throwError']('EIO', $fullPath); + } + }, + 'normalizeMode' => function ($mode, $default) { + if (is_float($mode)) { + $mode = (int) $mode; + } + if (is_string($mode)) { + $mode = octdec($mode); + } + if (!is_int($mode)) { + $mode = $default; + } + return $mode; + }, + ]; - $fs = new ObjectClass(); - $fs->setMethods($methods, true, false, true); - return $fs; + $fs = new ObjectClass(); + $fs->setMethods($methods, true, false, true); + return $fs; }); diff --git a/php/modules/http.php b/php/modules/http.php index 9b7316f..e8c07bb 100644 --- a/php/modules/http.php +++ b/php/modules/http.php @@ -1,88 +1,94 @@ function() use (&$SERVER) { - return isset($SERVER['REQUEST_METHOD']) ? $SERVER['REQUEST_METHOD'] : 'GET'; - }, - 'getURL' => function() use (&$SERVER) { - return isset($SERVER['REQUEST_URI']) ? $SERVER['REQUEST_URI'] : '/'; - }, - 'getHeaders' => function() use (&$SERVER, &$headers) { - if ($headers === null) { - $headers = new ObjectClass(); - foreach ($SERVER as $key => $value) { - if (substr($key, 0, 5) === 'HTTP_') { - $key = strtolower(substr($key, 5)); - $key = str_replace('_', '-', $key); - $headers->set($key, $value); +Module::define('request', function () { + $SERVER = isset($_SERVER) ? $_SERVER : []; + $headers = null; + $methods = [ + 'getMethod' => function () use (&$SERVER) { + return isset($SERVER['REQUEST_METHOD']) + ? $SERVER['REQUEST_METHOD'] + : 'GET'; + }, + 'getURL' => function () use (&$SERVER) { + return isset($SERVER['REQUEST_URI']) ? $SERVER['REQUEST_URI'] : '/'; + }, + 'getHeaders' => function () use (&$SERVER, &$headers) { + if ($headers === null) { + $headers = new ObjectClass(); + foreach ($SERVER as $key => $value) { + if (substr($key, 0, 5) === 'HTTP_') { + $key = strtolower(substr($key, 5)); + $key = str_replace('_', '-', $key); + $headers->set($key, $value); + } + } } - } - } - return $headers; - }, - 'getRemoteAddress' => function() use (&$SERVER) { - return isset($SERVER['REMOTE_ADDR']) ? $SERVER['REMOTE_ADDR'] : '127.0.0.1'; - }, - 'read' => function($bytes) { - $self = Func::getContext(); - if (!property_exists($self, 'stream')) { - $self->stream = fopen('php://input', 'r'); - } - if (feof($self->stream)) { - fclose($self->stream); - return ObjectClass::$null; - } - return new Buffer(fread($self->stream, $bytes)); - }, - 'readAll' => function() { - $data = file_get_contents('php://input'); - return new Buffer($data); - } - ); - $request = new ObjectClass(); - $request->setMethods($methods, true, false, true); - return $request; + return $headers; + }, + 'getRemoteAddress' => function () use (&$SERVER) { + return isset($SERVER['REMOTE_ADDR']) + ? $SERVER['REMOTE_ADDR'] + : '127.0.0.1'; + }, + 'read' => function ($bytes) { + $self = Func::getContext(); + if (!property_exists($self, 'stream')) { + $self->stream = fopen('php://input', 'r'); + } + if (feof($self->stream)) { + fclose($self->stream); + return ObjectClass::$null; + } + return new Buffer(fread($self->stream, $bytes)); + }, + 'readAll' => function () { + $data = file_get_contents('php://input'); + return new Buffer($data); + }, + ]; + $request = new ObjectClass(); + $request->setMethods($methods, true, false, true); + return $request; }); -Module::define('response', function() { - $methods = array( - 'writeHead' => function($statusCode, $statusReason, $headers) { - //send the response code - $sapiName = substr(php_sapi_name(), 0, 3); - if ($sapiName === 'cgi' || $sapiName === 'fpm') { - header('Status: ' . $statusCode . ' ' . $statusReason); - } else { - $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0'; - header($protocol . ' ' . $statusCode . ' ' . $statusReason); - } - //send the response headers - $keys = $headers->getOwnKeys(true); - foreach ($keys as $key) { - $value = $headers->get($key); - header($key . ": " . $value); - } - }, - 'write' => function($data) { - $data = ($data instanceof Buffer) ? $data->raw : to_string($data); - echo $data; - }, - 'end' => function() { - exit(); - } - ); - $response = new ObjectClass(); - $response->setMethods($methods, true, false, true); - return $response; +Module::define('response', function () { + $methods = [ + 'writeHead' => function ($statusCode, $statusReason, $headers) { + //send the response code + $sapiName = substr(php_sapi_name(), 0, 3); + if ($sapiName === 'cgi' || $sapiName === 'fpm') { + header('Status: ' . $statusCode . ' ' . $statusReason); + } else { + $protocol = isset($_SERVER['SERVER_PROTOCOL']) + ? $_SERVER['SERVER_PROTOCOL'] + : 'HTTP/1.0'; + header($protocol . ' ' . $statusCode . ' ' . $statusReason); + } + //send the response headers + $keys = $headers->getOwnKeys(true); + foreach ($keys as $key) { + $value = $headers->get($key); + header($key . ': ' . $value); + } + }, + 'write' => function ($data) { + $data = $data instanceof Buffer ? $data->raw : to_string($data); + echo $data; + }, + 'end' => function () { + exit(); + }, + ]; + $response = new ObjectClass(); + $response->setMethods($methods, true, false, true); + return $response; }); -array_push(Ex::$errorOutputHandlers, function($stackTrace) { - //don't double echo when in cli (test suite and such) - if (php_sapi_name() === 'cli') { - return; - } - http_response_code(500); - header("Content-Type: text/plain"); - echo $stackTrace; +array_push(Ex::$errorOutputHandlers, function ($stackTrace) { + //don't double echo when in cli (test suite and such) + if (php_sapi_name() === 'cli') { + return; + } + http_response_code(500); + header('Content-Type: text/plain'); + echo $stackTrace; }); diff --git a/runtime.php b/runtime.php index fef5873..e5bb6f2 100644 --- a/runtime.php +++ b/runtime.php @@ -3,31 +3,31 @@ mb_internal_encoding('UTF-8'); error_reporting(E_ALL); -require_once('php/helpers/operators.php'); -require_once('php/helpers/helpers.php'); +require_once 'php/helpers/operators.php'; +require_once 'php/helpers/helpers.php'; -require_once('php/classes/ObjectClass.php'); -require_once('php/classes/Function.php'); -require_once('php/classes/Global.php'); -require_once('php/classes/Array.php'); -require_once('php/classes/Date.php'); -require_once('php/classes/RegExp.php'); -require_once('php/classes/String.php'); -require_once('php/classes/Number.php'); -require_once('php/classes/Boolean.php'); -require_once('php/classes/Error.php'); -require_once('php/classes/Exception.php'); -require_once('php/classes/Buffer.php'); +require_once 'php/classes/ObjectClass.php'; +require_once 'php/classes/Function.php'; +require_once 'php/classes/Global.php'; +require_once 'php/classes/Array.php'; +require_once 'php/classes/Date.php'; +require_once 'php/classes/RegExp.php'; +require_once 'php/classes/String.php'; +require_once 'php/classes/Number.php'; +require_once 'php/classes/Boolean.php'; +require_once 'php/classes/Error.php'; +require_once 'php/classes/Exception.php'; +require_once 'php/classes/Buffer.php'; -require_once('php/globals/globals.php'); -require_once('php/globals/Math.php'); -require_once('php/globals/JSON.php'); -require_once('php/globals/console.php'); -require_once('php/globals/process.php'); +require_once 'php/globals/globals.php'; +require_once 'php/globals/Math.php'; +require_once 'php/globals/JSON.php'; +require_once 'php/globals/console.php'; +require_once 'php/globals/process.php'; -require_once('php/helpers/Module.php'); -require_once('php/modules/http.php'); -require_once('php/modules/fs.php'); +require_once 'php/helpers/Module.php'; +require_once 'php/modules/http.php'; +require_once 'php/modules/fs.php'; -require_once('php/helpers/Test.php'); -require_once('php/helpers/Debug.php'); +require_once 'php/helpers/Test.php'; +require_once 'php/helpers/Debug.php'; diff --git a/tests.php b/tests.php index 189a58e..4b742f4 100644 --- a/tests.php +++ b/tests.php @@ -1,12 +1,12 @@ setMethods(array( - //used to test dates - 'sleep' => function($ms) { - usleep($ms * 1000); - } -)); +$process->setMethods([ + //used to test dates + 'sleep' => function ($ms) { + usleep($ms * 1000); + }, +]); //set some implicit globals $foo = 'test'; @@ -15,77 +15,58 @@ call_method($console, 'log', 'Begin', 'Tests:'); -Test::suite( - 'null', - function() { - Test::assert( - 'is falsy', - is(ObjectClass::$null) ? false : true - ); - Test::assert( - 'check typeof', - _typeof(ObjectClass::$null) === 'object' - ); - Test::assert( - 'check to_string', - to_string(ObjectClass::$null) === 'null' - ); - } -); +Test::suite('null', function () { + Test::assert('is falsy', is(ObjectClass::$null) ? false : true); + Test::assert('check typeof', _typeof(ObjectClass::$null) === 'object'); + Test::assert('check to_string', to_string(ObjectClass::$null) === 'null'); +}); -Test::suite( - 'global', - function() { +Test::suite('global', function () { Test::assert( - 'props reflect global vars', - ObjectClass::$global->get('foo') === 'test' + 'props reflect global vars', + ObjectClass::$global->get('foo') === 'test' ); ObjectClass::$global->set('foo', 'test2'); Test::assert( - 'can set global', - ObjectClass::$global->get('foo') === 'test2' + 'can set global', + ObjectClass::$global->get('foo') === 'test2' ); ObjectClass::$global->set('foo2', 'bar'); Test::assert( - 'can set global which was previously not set', - ObjectClass::$global->get('foo2') === 'bar' + 'can set global which was previously not set', + ObjectClass::$global->get('foo2') === 'bar' ); Test::assert( - 'has circular ref', - ObjectClass::$global->get('global') === ObjectClass::$global + 'has circular ref', + ObjectClass::$global->get('global') === ObjectClass::$global ); Test::assert( - 'can access escaped vars', - ObjectClass::$global->get('$') === $GLOBALS['«24»'] + 'can access escaped vars', + ObjectClass::$global->get('$') === $GLOBALS['«24»'] ); Test::assert( - 'can access escaped vars unicode', - ObjectClass::$global->get('ür') === 'ür' + 'can access escaped vars unicode', + ObjectClass::$global->get('ür') === 'ür' ); - } -); +}); -Test::suite( - 'Object.prototype', - function() use ($Object) { +Test::suite('Object.prototype', function () use ($Object) { Test::assert( - 'has method valueOf', - ObjectClass::$protoObject->get('valueOf') instanceof Func + 'has method valueOf', + ObjectClass::$protoObject->get('valueOf') instanceof Func ); Test::assert( - '__proto__ is null', - ObjectClass::$protoObject->proto === ObjectClass::$null + '__proto__ is null', + ObjectClass::$protoObject->proto === ObjectClass::$null ); Test::assert( - 'check toString', - $Object->get('prototype')->get('toString') === ObjectClass::$protoObject->get('toString') + 'check toString', + $Object->get('prototype')->get('toString') === + ObjectClass::$protoObject->get('toString') ); - } -); +}); -Test::suite( - 'keys()', - function() use ($Object) { +Test::suite('keys()', function () use ($Object) { $obj = $Object->construct(); $obj->set('a', true); $obj->set('b', false); @@ -97,69 +78,54 @@ function() use ($Object) { $keys = keys($child); sort($keys, SORT_STRING); Test::assert('inherited keys', join(',', $keys) === 'a,b,c'); - } -); +}); -Test::suite( - 'Object', - function() use ($Object) { - $o = new ObjectClass("a", 1.0, "b", 2.0); - Test::assert( - 'data present', - join(',', array_keys($o->data)) === 'a,b' - ); +Test::suite('Object', function () use ($Object) { + $o = new ObjectClass('a', 1.0, 'b', 2.0); + Test::assert('data present', join(',', array_keys($o->data)) === 'a,b'); Test::assert( - 'descriptors not present', - join(',', array_keys($o->dscr)) === '' + 'descriptors not present', + join(',', array_keys($o->dscr)) === '' ); $o->setProp('c', 3.0, true, false, true); + Test::assert('three keys', join(',', array_keys($o->data)) === 'a,b,c'); Test::assert( - 'three keys', - join(',', array_keys($o->data)) === 'a,b,c' - ); - Test::assert( - 'descriptors present', - join(',', array_keys($o->dscr)) === 'c' + 'descriptors present', + join(',', array_keys($o->dscr)) === 'c' ); _delete($o, 'c'); Test::assert( - 'descriptors now empty', - join(',', array_keys($o->dscr)) === '' - ); - Test::assert( - 'keys now two', - join(',', array_keys($o->data)) === 'a,b' + 'descriptors now empty', + join(',', array_keys($o->dscr)) === '' ); - $o = new ObjectClass("a", 1.0, "b", 2.0); - Test::assert( - 'for..in helper', - join(',', keys($o)) === 'a,b' - ); - } -); + Test::assert('keys now two', join(',', array_keys($o->data)) === 'a,b'); + $o = new ObjectClass('a', 1.0, 'b', 2.0); + Test::assert('for..in helper', join(',', keys($o)) === 'a,b'); +}); -Test::suite( - 'Array.prototype', - function() use ($Object, $Array) { +Test::suite('Array.prototype', function () use ($Object, $Array) { Test::assert( - 'should be distinct from Object.prototype', - $Array->get('prototype') !== $Object->get('prototype') + 'should be distinct from Object.prototype', + $Array->get('prototype') !== $Object->get('prototype') ); Test::assert( - 'should be that of Arr', - $Array->get('prototype') === Arr::$protoObject + 'should be that of Arr', + $Array->get('prototype') === Arr::$protoObject ); - } -); +}); -Test::suite( - 'Array: init, push and join', - function() use ($Array) { +Test::suite('Array: init, push and join', function () use ($Array) { $arr = $Array->construct(); $arr->set(0.0, 'a'); Test::assert('proto exists', $arr->proto instanceof ObjectClass); - Test::assert('proto is set correctly', $arr->proto === $Array->get('prototype')); - Test::assert('proto chain', $arr->proto->proto === ObjectClass::$protoObject); + Test::assert( + 'proto is set correctly', + $arr->proto === $Array->get('prototype') + ); + Test::assert( + 'proto chain', + $arr->proto->proto === ObjectClass::$protoObject + ); Test::assert('is_int length', is_int($arr->length)); $arr->remove('0'); Test::assert('length', $arr->get('length') === 1.0); @@ -171,12 +137,9 @@ function() use ($Array) { $arr->set(2.0, 'x'); Test::assert('length 3', $arr->get('length') === 3.0); Test::assert('join default', call_method($arr, 'join') === ',9,x'); - } -); +}); -Test::suite( - 'Array: set length', - function() use ($Array) { +Test::suite('Array: set length', function () use ($Array) { $arr = $Array->construct(1.0, 2.0); Test::assert('length', $arr->get('length') === 2.0); $arr->set('length', 3.0); @@ -185,81 +148,87 @@ function() use ($Array) { Test::assert('explicit element', $arr->get(2) === 3.0); $arr->set('length', 2); Test::assert('implicit element removal', $arr->get(2) === null); - } -); +}); -Test::suite( - 'Array: sort', - function() use ($Array) { +Test::suite('Array: sort', function () use ($Array) { $arr = $Array->construct('s', 'i', false, 'm'); $arr->callMethod('sort'); Test::assert('length', $arr->get('length') === 4.0); Test::assert('sorted', $arr->callMethod('join', ',') === 'false,i,m,s'); Test::assert('types', $arr->get(0) === false); - } -); +}); -Test::suite( - 'Function', - function() use ($Array) { - $fn = new Func('foo', function() { - return 'bar'; +Test::suite('Function', function () use ($Array) { + $fn = new Func('foo', function () { + return 'bar'; }); - Test::assert('Function should take optional name', $fn->get('name') === 'foo'); + Test::assert( + 'Function should take optional name', + $fn->get('name') === 'foo' + ); $fn->set('name', 'x'); Test::assert('Function name is immutable', $fn->get('name') === 'foo'); Test::assert('Function should run', $fn->call() === 'bar'); - $fn = new Func('foo', function() use (&$fn) { - $self = Func::getContext(); - $arguments = Func::getArguments(); - Test::assert('arguments', $arguments instanceof Args); - Test::assert('arguments.callee', $arguments->get('callee') === $fn); - Test::assert('arguments is object', $arguments instanceof ObjectClass); - Test::assert('arguments is not array', !($arguments instanceof Arr)); - Test::assert('arguments has length', $arguments->get('length') === 0.0); - Test::assert('this is global', $self === ObjectClass::$global); + $fn = new Func('foo', function () use (&$fn) { + $self = Func::getContext(); + $arguments = Func::getArguments(); + Test::assert('arguments', $arguments instanceof Args); + Test::assert('arguments.callee', $arguments->get('callee') === $fn); + Test::assert('arguments is object', $arguments instanceof ObjectClass); + Test::assert('arguments is not array', !($arguments instanceof Arr)); + Test::assert('arguments has length', $arguments->get('length') === 0.0); + Test::assert('this is global', $self === ObjectClass::$global); }); $fn->call(); - $fn = new Func('foo', function() use ($fn, $Array) { - $self = Func::getContext(); - $arguments = Func::getArguments(); - Test::assert('arguments length', $arguments->get('length') === 1.0); - Test::assert('arguments -> args', join(',', $arguments->args) === 'foo'); - Test::assert('this is global', $self === $Array); + $fn = new Func('foo', function () use ($fn, $Array) { + $self = Func::getContext(); + $arguments = Func::getArguments(); + Test::assert('arguments length', $arguments->get('length') === 1.0); + Test::assert( + 'arguments -> args', + join(',', $arguments->args) === 'foo' + ); + Test::assert('this is global', $self === $Array); }); $fn->call($Array, 'foo'); - } -); +}); -Test::suite( - 'Object.create', - function() use ($Object) { - $Animal = new Func(function() {}); - $Animal->get('prototype')->set('speak', new Func(function() { - return 'hi'; - })); - $Dog = new Func(function() {}); - $Dog->set('prototype', $Object->callMethod('create', $Animal->get('prototype'))); +Test::suite('Object.create', function () use ($Object) { + $Animal = new Func(function () {}); + $Animal->get('prototype')->set( + 'speak', + new Func(function () { + return 'hi'; + }) + ); + $Dog = new Func(function () {}); + $Dog->set( + 'prototype', + $Object->callMethod('create', $Animal->get('prototype')) + ); $dog = $Dog->construct(); Test::assert('has method', $dog->get('speak') instanceof Func); Test::assert('method call', $dog->callMethod('speak') === 'hi'); Test::assert('proto inherit', _instanceof($dog, $Dog)); Test::assert('proto inherit parent', _instanceof($dog, $Animal)); Test::assert('proto inherit top', _instanceof($dog, $Object)); - $Thing = new Func(function() {}); + $Thing = new Func(function () {}); Test::assert('proto not instance foreign', !_instanceof($dog, $Thing)); - $Dog->get('prototype')->set('speak', new Func(function() { - return 'woof'; - })); + $Dog->get('prototype')->set( + 'speak', + new Func(function () { + return 'woof'; + }) + ); Test::assert('method override', $dog->callMethod('speak') === 'woof'); $animal = $Animal->construct(); - Test::assert('method still on parent', $animal->callMethod('speak') === 'hi'); - } -); + Test::assert( + 'method still on parent', + $animal->callMethod('speak') === 'hi' + ); +}); -Test::suite( - 'Object.keys', - function() use ($Object, $Array, $JSON) { +Test::suite('Object.keys', function () use ($Object, $Array, $JSON) { $obj = $Object->construct(); $obj->set('a', 1.0); $obj->set('b', 2.0); @@ -267,48 +236,67 @@ function() use ($Object, $Array, $JSON) { Test::assert('basic keys', $keys->callMethod('toString') === 'a,b'); $arr = new Arr(1.0, 2.0); $keys = $Object->callMethod('keys', $arr); - Test::assert('only enumerable keys', $keys->callMethod('toString') === '0,1'); - } -); + Test::assert( + 'only enumerable keys', + $keys->callMethod('toString') === '0,1' + ); +}); -Test::suite( - 'Object.defineProperty', - function() use ($Object) { +Test::suite('Object.defineProperty', function () use ($Object) { $obj = $Object->construct(); $obj->set('a', 1.0); $descriptor = new ObjectClass('enumerable', false); $Object->callMethod('defineProperty', $obj, 'foo', $descriptor); $keys = $Object->callMethod('keys', $obj); - Test::assert('key is not enumerable', $keys->callMethod('toString') === 'a'); + Test::assert( + 'key is not enumerable', + $keys->callMethod('toString') === 'a' + ); $props = new ObjectClass('bar', $descriptor, 'baz', $descriptor); $Object->callMethod('defineProperties', $obj, $props); $keys = $Object->callMethod('keys', $obj); - Test::assert('defineProperties worked', $keys->callMethod('toString') === 'a'); + Test::assert( + 'defineProperties worked', + $keys->callMethod('toString') === 'a' + ); $keys = $Object->callMethod('getOwnPropertyNames', $obj); - Test::assert('keys are present', $keys->callMethod('toString') === 'a,foo,bar,baz'); - } -); + Test::assert( + 'keys are present', + $keys->callMethod('toString') === 'a,foo,bar,baz' + ); +}); //todo: move this to js-land -Test::suite( - 'Date', - function() use ($Date, $Object, $Array) { +Test::suite('Date', function () use ($Date, $Object, $Array) { $date = new Date(2013, 7, 5, 18, 11, 8, 411); //this is based on timezone 'America/Phoenix' which we hard-code for tests Test::assert('date value', $date->value === 1375751468411); - Test::assert('date valueOf', (float)($date->value) === $date->callMethod('valueOf')); - Test::assert('date local string', $date->callMethod('toString') === 'Mon Aug 05 2013 18:11:08 GMT-0700 (MST)'); - Test::assert('date json string', $date->callMethod('toJSON') === '2013-08-06T01:11:08.411Z'); + Test::assert( + 'date valueOf', + (float) $date->value === $date->callMethod('valueOf') + ); + Test::assert( + 'date local string', + $date->callMethod('toString') === + 'Mon Aug 05 2013 18:11:08 GMT-0700 (MST)' + ); + Test::assert( + 'date json string', + $date->callMethod('toJSON') === '2013-08-06T01:11:08.411Z' + ); $date = new Date(1375751468412.0); - Test::assert('init from value', $date->callMethod('toJSON') === '2013-08-06T01:11:08.412Z'); + Test::assert( + 'init from value', + $date->callMethod('toJSON') === '2013-08-06T01:11:08.412Z' + ); $date = new Date('2013-08-06T01:11:08.412Z'); - Test::assert('init from string', $date->callMethod('toJSON') === '2013-08-06T01:11:08.412Z'); - } -); + Test::assert( + 'init from string', + $date->callMethod('toJSON') === '2013-08-06T01:11:08.412Z' + ); +}); -Test::suite( - 'RegExp', - function() use ($RegExp, $Object, $Array) { +Test::suite('RegExp', function () use ($RegExp, $Object, $Array) { $str = 'xabcdef'; $reg = new RegExp('a(b|c)', 'i'); Test::assert('reg source', $reg->get('source') === 'a(b|c)'); @@ -319,12 +307,9 @@ function() use ($RegExp, $Object, $Array) { Test::assert('match index', $match->get('index') === 1.0); Test::assert('match input', $match->get('input') === $str); Test::assert('match lastIndex', $reg->get('lastIndex') === 3.0); - } -); +}); -Test::suite( - 'String object', - function() use ($String, $Object) { +Test::suite('String object', function () use ($String, $Object) { $str = $String->construct('hi'); Test::assert('instanceof', $str instanceof Str); Test::assert('type is object', _typeof($str) === 'object'); @@ -334,28 +319,40 @@ function() use ($String, $Object) { $str = $String->call(null, 'hi'); Test::assert('is not object', !($str instanceof Str)); Test::assert('primitive', _typeof($str) === 'string'); - Test::assert('can call on primitive', call_method($str, 'charAt', 0) === 'h'); - } -); + Test::assert( + 'can call on primitive', + call_method($str, 'charAt', 0) === 'h' + ); +}); -Test::suite( - 'Number helpers', - function() use ($Number) { +Test::suite('Number helpers', function () use ($Number) { Test::assert('to_number ""', to_number('') === 0.0); Test::assert('to_number "x"', is_nan(to_number('x'))); Test::assert('to_number "+0"', to_number('+0') === 0.0); Test::assert('to_number "+Infinity"', to_number('+Infinity') === INF); - Test::assert('to_number "+1234.5678"', to_number('+1234.5678') === to_number('1234.5678')); - Test::assert('to_number "+1234.5678e90"', to_number("+1234.5678e90") === to_number("1234.5678e90")); - Test::assert('to_number "+1234.5678E90"', to_number("+1234.5678E90") === to_number("1234.5678E90")); - Test::assert('to_number "+1234.5678e-90"', to_number("+1234.5678e-90") === to_number("1234.5678e-90")); - Test::assert('to_number "+1234.5678E-90"', to_number("+1234.5678E-90") === to_number("1234.5678E-90")); - } -); + Test::assert( + 'to_number "+1234.5678"', + to_number('+1234.5678') === to_number('1234.5678') + ); + Test::assert( + 'to_number "+1234.5678e90"', + to_number('+1234.5678e90') === to_number('1234.5678e90') + ); + Test::assert( + 'to_number "+1234.5678E90"', + to_number('+1234.5678E90') === to_number('1234.5678E90') + ); + Test::assert( + 'to_number "+1234.5678e-90"', + to_number('+1234.5678e-90') === to_number('1234.5678e-90') + ); + Test::assert( + 'to_number "+1234.5678E-90"', + to_number('+1234.5678E-90') === to_number('1234.5678E-90') + ); +}); -Test::suite( - 'Number object', - function() use ($Number, $Object) { +Test::suite('Number object', function () use ($Number, $Object) { $num = $Number->construct(5.0); Test::assert('instanceof', $num instanceof Number); Test::assert('type is object', _typeof($num) === 'object'); @@ -364,7 +361,10 @@ function() use ($Number, $Object) { $num = $Number->call(null, '5'); Test::assert('is not object', !($num instanceof Str)); Test::assert('primitive', _typeof($num) === 'number'); - Test::assert('can call on primitive', call_method($num, 'toString') === '5'); + Test::assert( + 'can call on primitive', + call_method($num, 'toString') === '5' + ); $num = $Number->call(null, ''); Test::assert('empty coerced', $num === 0.0); $num = $Number->callMethod('parseInt', '5.x'); @@ -381,12 +381,9 @@ function() use ($Number, $Object) { Test::assert('parseFloat 2', $num === 5.0); $num = $Number->callMethod('parseFloat', 'x'); Test::assert('parseFloat 3', is_nan($num)); - } -); +}); -Test::suite( - 'JSON', - function() use ($JSON, $Object, $Array) { +Test::suite('JSON', function () use ($JSON, $Object, $Array) { $str = '{"a": [1], "b": false, "c": []}'; $obj = $JSON->callMethod('parse', $str); Test::assert('parsed empty array', $obj->get('c')->get('length') === 0.0); @@ -394,19 +391,18 @@ function() use ($JSON, $Object, $Array) { Test::assert('parsed boolean', $obj->get('b') === false); $str2 = $JSON->callMethod('stringify', $obj); Test::assert('stringify', $str2 === '{"a":[1],"b":false,"c":[]}'); - } -); +}); -require_once('test/compiled/helpers.php'); -require_once('test/compiled/core.php'); -require_once('test/compiled/number.php'); -require_once('test/compiled/boolean.php'); -require_once('test/compiled/string.php'); -require_once('test/compiled/date.php'); -require_once('test/compiled/regex.php'); -require_once('test/compiled/array.php'); -require_once('test/compiled/buffer.php'); -require_once('test/compiled/json.php'); -require_once('test/compiled/module-fs.php'); +require_once 'test/compiled/helpers.php'; +require_once 'test/compiled/core.php'; +require_once 'test/compiled/number.php'; +require_once 'test/compiled/boolean.php'; +require_once 'test/compiled/string.php'; +require_once 'test/compiled/date.php'; +require_once 'test/compiled/regex.php'; +require_once 'test/compiled/array.php'; +require_once 'test/compiled/buffer.php'; +require_once 'test/compiled/json.php'; +require_once 'test/compiled/module-fs.php'; $console->callMethod('log', 'Success'); diff --git a/yarn.lock b/yarn.lock index a362967..efda32e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,15 @@ # yarn lockfile v1 +"@prettier/plugin-php@^0.19.0": + version "0.19.0" + resolved "https://registry.yarnpkg.com/@prettier/plugin-php/-/plugin-php-0.19.0.tgz#b70bbfac172d6e76acf8a2376c8c8cdde70f0b75" + integrity sha512-WWFd0KDcF1EHsLpxU7DvgHjxdyZSal3ykDaCL9DzJSyIBo9qfk11spJR2svrEc/38CZioYy/OtbdyoQ5YEGASA== + dependencies: + linguist-languages "^7.21.0" + mem "^8.0.0" + php-parser "3.1.0" + escope@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/escope/-/escope-1.0.3.tgz#759dce8496c4248fec2d0caaf4108bcf3f1a7f5d" @@ -19,10 +28,45 @@ estraverse@^2.0.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-2.0.0.tgz#5ae46963243600206674ccb24a09e16674fcdca1" integrity sha1-WuRpYyQ2ACBmdMyySgnhZnT83KE= -prettier@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" - integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== +linguist-languages@^7.21.0: + version "7.21.0" + resolved "https://registry.yarnpkg.com/linguist-languages/-/linguist-languages-7.21.0.tgz#da0184f622367cb092f1f8ba435937a85534f675" + integrity sha512-KrWJJbFOvlDhjlt5OhUipVlXg+plUfRurICAyij1ZVxQcqPt/zeReb9KiUVdGUwwhS/2KS9h3TbyfYLA5MDlxQ== + +map-age-cleaner@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" + integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== + dependencies: + p-defer "^1.0.0" + +mem@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" + integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== + dependencies: + map-age-cleaner "^0.1.3" + mimic-fn "^3.1.0" + +mimic-fn@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" + integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== + +p-defer@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" + integrity sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw== + +php-parser@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/php-parser/-/php-parser-3.1.0.tgz#5ca8e0bac9846e85233a26e3162fdfc4fb9a0a58" + integrity sha512-EwtoPSCqggAgTcPbV4YXsvUjYRQCZCxsQzxa7z8RX8rJ8bWAglMMi1tFNxdzwf5uNPFWFjHYhXA3REuWKzgMuw== + +prettier@^2.7.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" + integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== rocambole@^0.7.0: version "0.7.0"