From b82225075fb0c2cb1ad79d26a1c6f9beb575b577 Mon Sep 17 00:00:00 2001 From: Daniel Phillimore Date: Thu, 9 Nov 2017 22:23:42 +0000 Subject: [PATCH 01/11] Add some experimental and incomplete builtins required by Combyna - Most builtins here have integration tests to ensure basic functionality, but unit tests still need to be written for a large number. - Docblocks are missing from some functions - Any unsupported behaviour needs to throw a meaningful error message --- src/builtin/builtins.js | 10 ++ src/builtin/functions/array.js | 159 ++++++++++++++++++ src/builtin/functions/class.js | 77 ++++++++- src/builtin/functions/filesystem.js | 86 +++++++++- .../functions/optionsAndInfo/config.js | 28 +++ .../functions/optionsAndInfo/environment.js | 20 +++ .../functions/optionsAndInfo/extension.js | 20 +++ src/builtin/functions/optionsAndInfo/php.js | 91 ++++++++++ src/builtin/functions/pcre/basicSupport.js | 63 ++++++- src/builtin/functions/string.js | 50 ++++++ src/builtin/functions/string/html.js | 34 ++++ src/builtin/functions/variableHandling.js | 22 +++ .../builtin/functions/array/array_keysTest.js | 47 ++++++ .../builtin/functions/array/array_mapTest.js | 45 +++++ .../functions/array/array_shiftTest.js | 58 +++++++ .../functions/array/array_uniqueTest.js | 46 +++++ .../functions/array/array_valuesTest.js | 44 +++++ .../builtin/functions/array/endTest.js | 44 +++++ .../builtin/functions/array/in_arrayTest.js | 43 +++++ .../builtin/functions/class/get_classTest.js | 54 ++++++ .../builtin/functions/class/is_aTest.js | 83 +++++++++ .../extension/get_loaded_extensionsTest.js | 45 +++++ .../optionsAndInfo/php/php_unameTest.js | 51 ++++++ .../optionsAndInfo/php/zend_versionTest.js | 37 ++++ .../pcre/basicSupport/preg_replaceTest.js | 64 +++++++ .../builtin/functions/string/explodeTest.js | 42 +++++ .../functions/string/html/htmlentitiesTest.js | 36 ++++ .../builtin/functions/string/strrchrTest.js | 40 +++++ .../functions/string/strtolowerTest.js | 36 ++++ .../functions/string/strtoupperTest.js | 36 ++++ .../functions/variableHandling/gettypeTest.js | 64 +++++++ .../extension/get_loaded_extensionsTest.js | 56 ++++++ .../optionsAndInfo/php/php_unameTest.js | 154 +++++++++++++++++ .../optionsAndInfo/php/zend_versionTest.js | 77 +++++++++ 34 files changed, 1858 insertions(+), 4 deletions(-) create mode 100644 src/builtin/functions/optionsAndInfo/config.js create mode 100644 src/builtin/functions/optionsAndInfo/environment.js create mode 100644 src/builtin/functions/optionsAndInfo/extension.js create mode 100644 src/builtin/functions/optionsAndInfo/php.js create mode 100644 src/builtin/functions/string/html.js create mode 100644 test/integration/builtin/functions/array/array_keysTest.js create mode 100644 test/integration/builtin/functions/array/array_mapTest.js create mode 100644 test/integration/builtin/functions/array/array_shiftTest.js create mode 100644 test/integration/builtin/functions/array/array_uniqueTest.js create mode 100644 test/integration/builtin/functions/array/array_valuesTest.js create mode 100644 test/integration/builtin/functions/array/endTest.js create mode 100644 test/integration/builtin/functions/array/in_arrayTest.js create mode 100644 test/integration/builtin/functions/class/get_classTest.js create mode 100644 test/integration/builtin/functions/class/is_aTest.js create mode 100644 test/integration/builtin/functions/optionsAndInfo/extension/get_loaded_extensionsTest.js create mode 100644 test/integration/builtin/functions/optionsAndInfo/php/php_unameTest.js create mode 100644 test/integration/builtin/functions/optionsAndInfo/php/zend_versionTest.js create mode 100644 test/integration/builtin/functions/pcre/basicSupport/preg_replaceTest.js create mode 100644 test/integration/builtin/functions/string/explodeTest.js create mode 100644 test/integration/builtin/functions/string/html/htmlentitiesTest.js create mode 100644 test/integration/builtin/functions/string/strrchrTest.js create mode 100644 test/integration/builtin/functions/string/strtolowerTest.js create mode 100644 test/integration/builtin/functions/string/strtoupperTest.js create mode 100644 test/integration/builtin/functions/variableHandling/gettypeTest.js create mode 100644 test/unit/builtin/functions/optionsAndInfo/extension/get_loaded_extensionsTest.js create mode 100644 test/unit/builtin/functions/optionsAndInfo/php/php_unameTest.js create mode 100644 test/unit/builtin/functions/optionsAndInfo/php/zend_versionTest.js diff --git a/src/builtin/builtins.js b/src/builtin/builtins.js index 3a09384..85a61ab 100644 --- a/src/builtin/builtins.js +++ b/src/builtin/builtins.js @@ -13,14 +13,19 @@ var arrayConstants = require('./constants/array'), arrayFunctions = require('./functions/array'), baseConversionMathFunctions = require('./functions/math/baseConversion'), classFunctions = require('./functions/class'), + configOptionsAndInfoFunctions = require('./functions/optionsAndInfo/config'), constantFunctions = require('./functions/misc/constant'), Countable = require('./interfaces/Countable'), + environmentFunctions = require('./functions/optionsAndInfo/environment'), + extensionOptionsAndInfoFunctions = require('./functions/optionsAndInfo/extension'), filesystemConstants = require('./constants/filesystem'), filesystemFunctions = require('./functions/filesystem'), functionHandlingFunctions = require('./functions/functionHandling'), + htmlStringFunctions = require('./functions/string/html'), InvalidArgumentException = require('./classes/InvalidArgumentException'), pcreConstants = require('./constants/pcre'), phpConstants = require('./constants/php'), + phpOptionsAndInfoFunctions = require('./functions/optionsAndInfo/php'), stringFunctions = require('./functions/string'), timeDateAndTimeFunctions = require('./functions/dateAndTime/time'), timeFunctions = require('./functions/time'), @@ -41,9 +46,14 @@ module.exports = { arrayFunctions, baseConversionMathFunctions, classFunctions, + configOptionsAndInfoFunctions, constantFunctions, + environmentFunctions, + extensionOptionsAndInfoFunctions, filesystemFunctions, functionHandlingFunctions, + htmlStringFunctions, + phpOptionsAndInfoFunctions, stringFunctions, timeDateAndTimeFunctions, timeFunctions, diff --git a/src/builtin/functions/array.js b/src/builtin/functions/array.js index fcf4e6c..5270f8b 100644 --- a/src/builtin/functions/array.js +++ b/src/builtin/functions/array.js @@ -42,6 +42,51 @@ module.exports = function (internals) { return valueFactory.createBoolean(arrayValue.getElementByKey(keyValue).isDefined()); }, + /** + * Fetch all keys (or a subset of the keys) in an array + * + * @see {@link https://secure.php.net/manual/en/function.array-keys.php} + * + * @param {Variable|ArrayValue} arrayReference + * @param {Variable|Value} searchValueReference + * @param {Variable|BooleanValue} strictMatchReference + * @returns {ArrayValue} + */ + 'array_keys': function (arrayReference, searchValueReference, strictMatchReference) { + var arrayValue; + + if (searchValueReference || strictMatchReference) { + throw new Error('array_keys() :: Search functionality is not yet supported'); + } + + arrayValue = arrayReference.getValue(); + + return valueFactory.createArray(arrayValue.getKeys()); + }, + + /** + * Maps one or more arrays to a new array + * + * @see {@link https://secure.php.net/manual/en/function.array-map.php} + * + * @param {Variable|Value} callbackReference + * @param {Variable|ArrayValue} firstArrayReference + * @returns {ArrayValue} + */ + 'array_map': function (callbackReference, firstArrayReference) { + var callbackValue = callbackReference.getValue(), + firstArrayValue = firstArrayReference.getValue(), + result = []; + + _.each(firstArrayValue.getValueReferences(), function (elementValue) { + var mappedElementValue = callbackValue.call([elementValue]); + + result.push(mappedElementValue); + }); + + return valueFactory.createArray(result); + }, + /** * Merges one or more arrays together, returning a new array with the result * @@ -121,6 +166,71 @@ module.exports = function (internals) { return valueFactory.createInteger(arrayValue.getLength()); }, + + /** + * Shifts an element off the beginning of an array + * + * @see {@link https://secure.php.net/manual/en/function.array-shift.php} + * + * @param {Variable|ArrayValue} arrayReference + * @returns {ArrayValue} + */ + 'array_shift': function (arrayReference) { + var arrayValue = arrayReference.getValue(); + + return arrayValue.shift(); + }, + + 'array_unique': function (arrayReference, sortFlagsReference) { + var arrayValue, + resultPairs = [], + usedValues = {}; + + // TODO: Handle missing `arrayReference` arg etc. + + if (sortFlagsReference) { + throw new Error('array_unique() :: Sort flags are not yet supported'); + } + + arrayValue = arrayReference.getValue(); + + // Work on a clone, so we don't mutate the original array + arrayValue = arrayValue.clone(); + + // First sort the elements alphabetically by value (default/SORT_STRING behaviour) + arrayValue.sort(function (elementA, elementB) { + var nativeValueA = elementA.getValue().coerceToString().getNative(), + nativeValueB = elementB.getValue().coerceToString().getNative(); + + return String(nativeValueB).localeCompare(nativeValueA); + }); + + _.each(arrayValue.getKeys(), function (keyValue) { + var elementPair = arrayValue.getElementPairByKey(keyValue), + nativeValue = elementPair.getValue().coerceToString().getNative(); + + if (hasOwn.call(usedValues, nativeValue)) { + return; + } + + usedValues[nativeValue] = true; + + resultPairs.push(elementPair); + }); + + return valueFactory.createArray(resultPairs); + }, + + 'array_values': function (arrayReference) { + var arrayValue; + + // TODO: Handle missing `arrayReference` arg etc. + + arrayValue = arrayReference.getValue(); + + return valueFactory.createArray(arrayValue.getValues()); + }, + /** * Counts the specified array or object. May be hooked * by implementing interface Countable @@ -157,6 +267,31 @@ module.exports = function (internals) { return arrayValue.getCurrentElement().getValue(); }, + + /** + * Set the internal pointer of an array to its last element, + * returning the value of that last element. + * False will be returned for an empty array + * + * @see {@link https://secure.php.net/manual/en/function.end.php} + * + * @param {Variable|Value} arrayReference + * @returns {Value} + */ + 'end': function (arrayReference) { + var arrayValue = arrayReference.getValue(), + keys = arrayValue.getKeys(); + + if (keys.length === 0) { + return valueFactory.createBoolean(false); + } + + // Advance the array's internal pointer to the last element + arrayValue.setPointer(keys.length - 1); + + return arrayValue.getElementByKey(keys[keys.length - 1]).getValue(); + }, + 'implode': function (glueReference, piecesReference) { var glueValue = glueReference.getValue(), piecesValue = piecesReference.getValue(), @@ -178,6 +313,30 @@ module.exports = function (internals) { return valueFactory.createString(values.join(glueValue.getNative())); }, + + 'in_array': function (needleReference, haystackReference, strictMatchReference) { + var contains = false, + haystackValue, + needleValue, + strictMatch; + + haystackValue = haystackReference.getValue(); + needleValue = needleReference.getValue(); + strictMatch = strictMatchReference ? strictMatchReference.getNative() : false; + + _.each(haystackValue.getValues(), function (elementValue) { + if ( + (strictMatch && elementValue.isIdenticalTo(needleValue).getNative()) || + (!strictMatch && elementValue.isEqualTo(needleValue).getNative()) + ) { + contains = true; + return false; + } + }); + + return valueFactory.createBoolean(contains); + }, + 'join': function (glueReference, piecesReference) { return methods[IMPLODE](glueReference, piecesReference); }, diff --git a/src/builtin/functions/class.js b/src/builtin/functions/class.js index 019a4bc..dcb71cd 100644 --- a/src/builtin/functions/class.js +++ b/src/builtin/functions/class.js @@ -9,8 +9,12 @@ 'use strict'; +var phpCommon = require('phpcommon'), + PHPError = phpCommon.PHPError; + module.exports = function (internals) { - var classAutoloader = internals.classAutoloader, + var callStack = internals.callStack, + classAutoloader = internals.classAutoloader, globalNamespace = internals.globalNamespace, valueFactory = internals.valueFactory; @@ -22,7 +26,7 @@ module.exports = function (internals) { * * @param {Variable|Value} classNameReference The name of the class to check for * @param {Variable|Value} callAutoloaderReference True to invoke the autoloader, false otherwise - * @returns {*} + * @returns {BooleanValue} */ 'class_exists': function (classNameReference, callAutoloaderReference) { var className = classNameReference.getNative(), @@ -34,6 +38,75 @@ module.exports = function (internals) { } return valueFactory.createBoolean(globalNamespace.hasClass(className)); + }, + + /** + * Fetches the name of either the current class or the class of a specified object + * + * @see {@link https://secure.php.net/manual/en/function.get-class.php} + * + * @param {Variable|Value} objectReference + * @returns {StringValue|BooleanValue} + */ + 'get_class': function (objectReference) { + var currentClass; + + if (!objectReference) { + currentClass = callStack.getCallerScope().getCurrentClass(); + + if (!currentClass) { + callStack.raiseError( + PHPError.E_WARNING, + 'get_class() called without object from outside a class' + ); + + return valueFactory.createBoolean(false); + } + + return valueFactory.createString(currentClass.getName()); + } + + return valueFactory.createString(objectReference.getValue().getClassName()); + }, + + /** + * Checks if the object is of this class or has this class as one of its parents + * + * @see {@link https://secure.php.net/manual/en/function.is-a.php} + * + * @param {Variable|Value} objectReference + * @param {Variable|Value} classNameReference + * @param {Variable|Value} allowStringReference + * @returns {BooleanValue} + */ + 'is_a': function (objectReference, classNameReference, allowStringReference) { + var allowString, + className, + classNameValue, + objectValue; + + objectValue = objectReference.getValue(); + classNameValue = classNameReference.getValue(); + + className = classNameValue.getNative(); + allowString = allowStringReference ? allowStringReference.getNative() : false; + + if (objectValue.getType() === 'object') { + return valueFactory.createBoolean(objectValue.classIs(className)); + } + + if (objectValue.getType() === 'string') { + if (!allowString) { + // First arg is not allowed to be a string - just return false (no warning/notice) + return valueFactory.createBoolean(false); + } + + return valueFactory.createBoolean( + globalNamespace.getClass(objectValue.getNative()).is(className) + ); + } + + throw new Error('FIXME: Wrong object arg type given - do what? ...'); } }; }; diff --git a/src/builtin/functions/filesystem.js b/src/builtin/functions/filesystem.js index 3bc332e..7e08300 100644 --- a/src/builtin/functions/filesystem.js +++ b/src/builtin/functions/filesystem.js @@ -9,7 +9,8 @@ 'use strict'; -var INCLUDE_PATH_INI = 'include_path', +var _ = require('microdash'), + INCLUDE_PATH_INI = 'include_path', PHPError = require('phpcommon').PHPError; module.exports = function (internals) { @@ -67,6 +68,70 @@ module.exports = function (internals) { return valueFactory.createBoolean(fileSystem.isFile(path) || fileSystem.isDirectory(path)); }, + 'file_put_contents': function (pathReference, dataReference, flagsReference, contextReference) { + var contextValue, + data, + dataValue, + fileSystem, + flagsValue, + pathValue, + stream, + values; + + pathValue = pathReference.getValue(); + dataValue = dataReference.getValue(); + flagsValue = flagsReference ? ( + flagsReference.getValue() + ) : null; + contextValue = contextReference ? ( + contextReference.getValue() + ) : null; + + // Handle `path` arg + if (pathValue.getType() !== 'string') { + callStack.raiseError( + PHPError.E_WARNING, + 'file_exists() fixme' + ); + return valueFactory.createBoolean(false); + } + + // Handle `data` arg + if (dataValue.getType() === 'string') { + data = dataValue.getNative(); + } else if (dataValue.getType() === 'array') { + values = dataValue.getValues(); + + _.each(values, function (value, key) { + values[key] = value.coerceToString().getNative(); + }); + + data = valueFactory.createString(values.join('')); + } else if (dataValue.getType() === 'resource') { + throw new Error('file_put_contents() :: Resource as data is not yet implemented'); + } else { + throw new Error('fixme'); + } + + // Handle `flags` arg + if (flagsValue && (flagsValue.getType() !== 'integer' || flagsValue.getNative() !== 0)) { + throw new Error('file_put_contents() :: Only flags=0 is supported'); + } + + if (contextValue) { + throw new Error('file_put_contents() :: Custom contexts are not yet implemented'); + } + + fileSystem = getFileSystem(); + + // Open the file, write its new contents to it and close + stream = fileSystem.openSync(pathValue.getNative()); + stream.writeSync(data); + stream.closeSync(); + + // Return the number of bytes written to the file on success + return valueFactory.createInteger(data.length); + }, 'get_include_path': function () { return valueFactory.createString(iniState.get(INCLUDE_PATH_INI)); }, @@ -94,12 +159,31 @@ module.exports = function (internals) { return valueFactory.createBoolean(fileSystem.isFile(path)); }, + 'realpath': function (pathReference) { + // FIXME: Stub + return pathReference; + }, 'set_include_path': function (newIncludePathReference) { var oldIncludePath = iniState.get(INCLUDE_PATH_INI); iniState.set(INCLUDE_PATH_INI, newIncludePathReference.getValue().getNative()); return valueFactory.createString(oldIncludePath); + }, + 'unlink': function (pathReference, contextReference) { + var fileSystem, + path; + + if (contextReference) { + throw new Error('unlink() :: Context arg not yet supported'); + } + + fileSystem = getFileSystem(); + path = pathReference.getValue().getNative(); + + fileSystem.unlinkSync(path); + + return valueFactory.createBoolean(true); } }; }; diff --git a/src/builtin/functions/optionsAndInfo/config.js b/src/builtin/functions/optionsAndInfo/config.js new file mode 100644 index 0000000..eae908f --- /dev/null +++ b/src/builtin/functions/optionsAndInfo/config.js @@ -0,0 +1,28 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +module.exports = function (internals) { + var valueFactory = internals.valueFactory; + + return { + 'get_cfg_var': function (optionNameReference) { + var optionName = optionNameReference ? + optionNameReference.getValue().getNative() : + null; + + if (optionName === 'cfg_file_path') { + return valueFactory.createString('/pseudo/uniter/php.ini'); + } + + throw new Error('Only cfg_file_path config option is currently supported'); + } + }; +}; diff --git a/src/builtin/functions/optionsAndInfo/environment.js b/src/builtin/functions/optionsAndInfo/environment.js new file mode 100644 index 0000000..74c52d6 --- /dev/null +++ b/src/builtin/functions/optionsAndInfo/environment.js @@ -0,0 +1,20 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +module.exports = function (internals) { + var valueFactory = internals.valueFactory; + + return { + 'getenv': function () { + return valueFactory.createBoolean(false); + } + }; +}; diff --git a/src/builtin/functions/optionsAndInfo/extension.js b/src/builtin/functions/optionsAndInfo/extension.js new file mode 100644 index 0000000..7e4e249 --- /dev/null +++ b/src/builtin/functions/optionsAndInfo/extension.js @@ -0,0 +1,20 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +module.exports = function (internals) { + var valueFactory = internals.valueFactory; + + return { + 'get_loaded_extensions': function (/* onlyZendExtensions */) { + return valueFactory.createArray([]); + } + }; +}; diff --git a/src/builtin/functions/optionsAndInfo/php.js b/src/builtin/functions/optionsAndInfo/php.js new file mode 100644 index 0000000..1aa6190 --- /dev/null +++ b/src/builtin/functions/optionsAndInfo/php.js @@ -0,0 +1,91 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var systemConstants = require('../../../../constants'); + +module.exports = function (internals) { + var valueFactory = internals.valueFactory; + + return { + /** + * Returns information about the operating system PHP is running on + * + * @see {@link https://secure.php.net/manual/en/function.php-uname.php} + * + * @param {Variable|Value} modeReference + * @returns {StringValue} + */ + 'php_uname': function (modeReference) { + var mode = modeReference ? modeReference.getValue().getNative() : 'a', + string; + + switch (mode) { + case 's': // Operating system name. eg. FreeBSD + string = systemConstants.operatingSystem.name; + break; + case 'n': // Host name. eg. localhost.example.com + string = systemConstants.operatingSystem.hostName; + break; + case 'r': // Release name. eg. 5.1.2-RELEASE + string = systemConstants.operatingSystem.releaseName; + break; + case 'v': // Version information. Varies a lot between operating systems + string = systemConstants.operatingSystem.versionInfo; + break; + case 'm': // Machine type. eg. i386 + string = systemConstants.operatingSystem.machineType; + break; + default: + case 'a': // All modes together (default) + string = systemConstants.operatingSystem.name + ' ' + + systemConstants.operatingSystem.hostName + ' ' + + systemConstants.operatingSystem.releaseName + ' ' + + systemConstants.operatingSystem.versionInfo + ' ' + + systemConstants.operatingSystem.machineType; + } + + return valueFactory.createString(string); + }, + + 'phpversion': function (extensionName) { + if (extensionName) { + // No extensions are supported (yet) + return valueFactory.createBoolean(false); + } + + // Default behaviour is just to return the PHP version string + return valueFactory.createString( + systemConstants.phpVersion.major + '.' + + systemConstants.phpVersion.minor + '.' + + systemConstants.phpVersion.release + ); + }, + + /** + * Returns the version of the current Zend engine (different to the PHP version) + * + * Uniter is not related to Zend's engine at all, so this function makes little sense here. + * It is required by some scripts though (eg. Zend's test runner `run-tests.php` script), + * so it is added for completeness. HHVM also defines this function for compatibility. + * + * @see {@link https://secure.php.net/manual/en/function.zend-version.php} + * + * @returns {StringValue} + */ + 'zend_version': function () { + return valueFactory.createString( + systemConstants.zendVersion.major + '.' + + systemConstants.zendVersion.minor + '.' + + systemConstants.zendVersion.release + ); + } + }; +}; diff --git a/src/builtin/functions/pcre/basicSupport.js b/src/builtin/functions/pcre/basicSupport.js index e9c5b97..0ff03d3 100644 --- a/src/builtin/functions/pcre/basicSupport.js +++ b/src/builtin/functions/pcre/basicSupport.js @@ -9,7 +9,8 @@ 'use strict'; -var phpCommon = require('phpcommon'), +var _ = require('microdash'), + phpCommon = require('phpcommon'), PHPError = phpCommon.PHPError; /** @@ -137,6 +138,66 @@ module.exports = function (internals) { } return valueFactory.createInteger(matched ? 1 : 0); + }, + + 'preg_replace': function (patternReference, replacementReference, subjectReference /*, limitReference, countReference*/) { + var patterns = [], + patternMatch, + patternValue, + replacements = [], + replacementValue, + singleReplacement, + subject, + subjectValue; + + // TODO: Handle required args etc. + + patternValue = patternReference.getValue(); + replacementValue = replacementReference.getValue(); + + if (patternValue.getType() === 'array') { + _.each(patternValue.getValues(), function (patternValue) { + patterns.push(patternValue.getNative()); + }); + } else if (patternValue.getType() === 'string') { + patterns.push(patternValue.getNative()); + } else { + throw new Error('Invalid type'); + } + + if (replacementValue.getType() === 'array') { + _.each(replacementValue.getValues(), function (replacementValue) { + replacements.push(replacementValue.getNative()); + }); + singleReplacement = false; + } else if (replacementValue.getType() === 'string') { + replacements.push(replacementValue.getNative()); + singleReplacement = true; + } else { + throw new Error('Invalid type'); + } + + subjectValue = subjectReference.getValue(); + subject = subjectValue.getNative(); + + _.each(patterns, function (pattern, index) { + var regex, + replacement = singleReplacement ? + replacements[0] : + (index < replacements.length ? replacements[index] : ''); + + patternMatch = pattern.match(/^([\s\S])([\s\S]*)\1([gi]*)$/); + + if (!patternMatch) { + throw new Error('Invalid regex "' + pattern + '"'); + } + + regex = new RegExp(patternMatch[2], patternMatch[3]); + + subject = subject.replace(regex, replacement); + }); + + return valueFactory.createString(subject); } }; }; diff --git a/src/builtin/functions/string.js b/src/builtin/functions/string.js index 309c7da..afb0ae0 100644 --- a/src/builtin/functions/string.js +++ b/src/builtin/functions/string.js @@ -18,6 +18,31 @@ module.exports = function (internals) { valueFactory = internals.valueFactory; return { + 'explode': function (delimiterReference, stringReference, limitReference) { + var delimiter, + elements, + limit, + string; + + delimiter = delimiterReference.getValue().getNative(); + limit = limitReference ? limitReference.getValue().getNative() : null; + string = stringReference.getValue().getNative(); + + elements = string.split(delimiter); + + if (limit === 0) { + limit = 1; + } + + if (limit < 0) { + elements = elements.slice(0, elements.length + limit); + } else if (limit !== null) { + elements = elements.slice(0, limit - 1).concat(elements.slice(limit - 1).join(delimiter)); + } + + return valueFactory.createArray(elements); + }, + 'strlen': function (stringReference) { var stringValue = stringReference.getValue(); @@ -134,6 +159,19 @@ module.exports = function (internals) { return valueFactory.createInteger(offset + position); }, + 'strrchr': function (haystackReference, needleReference) { + var haystack = haystackReference.getNative(), + needle = needleReference.getNative().charAt(0), + position = haystack.lastIndexOf(needle); + + if (position === -1) { + // Return FALSE if needle is not found in haystack + return valueFactory.createBoolean(false); + } + + return valueFactory.createString(haystack.substr(position)); + }, + 'strrpos': function (haystackReference, needleReference, offsetReference) { var haystack = haystackReference.getValue().getNative(), needle = needleReference.getValue().getNative(), @@ -154,6 +192,18 @@ module.exports = function (internals) { return valueFactory.createInteger(offset + position); }, + 'strtolower': function (stringReference) { + var string = stringReference.getNative(); + + return valueFactory.createString(string.toLowerCase()); + }, + + 'strtoupper': function (stringReference) { + var string = stringReference.getNative(); + + return valueFactory.createString(string.toUpperCase()); + }, + 'strtr': function (stringReference) { var from, to, diff --git a/src/builtin/functions/string/html.js b/src/builtin/functions/string/html.js new file mode 100644 index 0000000..368a623 --- /dev/null +++ b/src/builtin/functions/string/html.js @@ -0,0 +1,34 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +module.exports = function (internals) { + var valueFactory = internals.valueFactory; + + return { + /** + * Converts all applicable characters to HTML entities + * + * @see {@link https://secure.php.net/manual/en/function.htmlentities.php} + * + * @param {Variable|ArrayValue} stringReference + * @returns {StringValue} + */ + 'htmlentities': function (stringReference) { + var string; + + string = stringReference.getNative(); + + string = string.replace(//g, '>'); + + return valueFactory.createString(string); + } + }; +}; diff --git a/src/builtin/functions/variableHandling.js b/src/builtin/functions/variableHandling.js index fa874b7..997bac5 100644 --- a/src/builtin/functions/variableHandling.js +++ b/src/builtin/functions/variableHandling.js @@ -38,6 +38,28 @@ module.exports = function (internals) { } return { + /** + * Fetches the type of a variable or value + * + * @see {@link https://secure.php.net/manual/en/function.gettype.php} + * + * @param {Variable|Value} valueReference The variable or value to fetch the type of + * @returns {StringValue} + */ + 'gettype': function (valueReference) { + var value = valueReference.getValue(), + type = value.getType(); + + if (type === 'float') { + // For historical reasons "double" is returned rather than "float" + type = 'double'; + } else if (type === 'null') { + type = 'NULL'; + } + + return valueFactory.createString(type); + }, + 'is_array': createTypeChecker('is_array', 'array'), 'is_bool': createTypeChecker('is_bool', 'boolean'), diff --git a/test/integration/builtin/functions/array/array_keysTest.js b/test/integration/builtin/functions/array/array_keysTest.js new file mode 100644 index 0000000..67f433d --- /dev/null +++ b/test/integration/builtin/functions/array/array_keysTest.js @@ -0,0 +1,47 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../sync'); + +describe('PHP "array_keys" builtin function integration', function () { + it('should be able to fetch all keys defined by an array', function () { + var php = nowdoc(function () {/*<< 'my value', + 'my_null_element' => null +]; + +return array_keys($myArray); +EOS +*/;}), //jshint ignore:line + js = phpToJS.transpile(phpToAST.create().parse(php)), + module = new Function( + 'require', + 'return ' + js + )(function () { + return syncPHPRuntime; + }), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + 0, + 'my_element', + 'my_null_element' + ]); + }); +}); diff --git a/test/integration/builtin/functions/array/array_mapTest.js b/test/integration/builtin/functions/array/array_mapTest.js new file mode 100644 index 0000000..ed3306e --- /dev/null +++ b/test/integration/builtin/functions/array/array_mapTest.js @@ -0,0 +1,45 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../sync'); + +describe('PHP "array_map" builtin function integration', function () { + it('should be able to map one array to another', function () { + var php = nowdoc(function () {/*<< 'first', 10 => 'second', 2 => 'third', 'fourth']; +// Literal keys will be left untouched +$mySecondArray = ['mine' => 'one', 'yours' => 'two', 'theirs' => 'three']; + +$result[] = array_shift($myFirstArray); +$result[] = $myFirstArray; +$result[] = array_shift($mySecondArray); +$result[] = $mySecondArray; + +// Internal pointer should be reset to the start of the array +$result[] = current($myFirstArray); + +return $result; + +EOS +*/;}), //jshint ignore:line + js = phpToJS.transpile(phpToAST.create().parse(php)), + module = new Function( + 'require', + 'return ' + js + )(function () { + return syncPHPRuntime; + }), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + 'first', // Shifted element from first array + ['second', 'third', 'fourth'], // First (numerically indexed) array after shift + 'one', // Shifted element from second array + {yours: 'two', theirs: 'three'}, // Second (associative) array after shift + 'second' // Value of new first element of first array after shift + ]); + }); +}); diff --git a/test/integration/builtin/functions/array/array_uniqueTest.js b/test/integration/builtin/functions/array/array_uniqueTest.js new file mode 100644 index 0000000..7d6ec62 --- /dev/null +++ b/test/integration/builtin/functions/array/array_uniqueTest.js @@ -0,0 +1,46 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../sync'); + +describe('PHP "array_unique" builtin function integration', function () { + it('should be able to fetch only the unique values of an array', function () { + var php = nowdoc(function () {/*<< 'green', 'red', 'b' => 'green', 'blue', 'red']); + +return $result; +EOS +*/;}), //jshint ignore:line + js = phpToJS.transpile(phpToAST.create().parse(php)), + module = new Function( + 'require', + 'return ' + js + )(function () { + return syncPHPRuntime; + }), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + { + 'a': 'green', + '0': 'red', + '1': 'blue' + } + ]); + }); +}); diff --git a/test/integration/builtin/functions/array/array_valuesTest.js b/test/integration/builtin/functions/array/array_valuesTest.js new file mode 100644 index 0000000..1c1edb8 --- /dev/null +++ b/test/integration/builtin/functions/array/array_valuesTest.js @@ -0,0 +1,44 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../sync'); + +describe('PHP "array_values" builtin function integration', function () { + it('should fetch only the values of an array, without sorting', function () { + var php = nowdoc(function () {/*<< 'two', '0' => 'zero', '1' => 'one']); +$result[] = array_values(['first' => 'value 1', 'second' => 'value 2']); + +return $result; +EOS +*/;}), //jshint ignore:line + js = phpToJS.transpile(phpToAST.create().parse(php)), + module = new Function( + 'require', + 'return ' + js + )(function () { + return syncPHPRuntime; + }), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + ['two', 'zero', 'one'], + ['value 1', 'value 2'] + ]); + }); +}); diff --git a/test/integration/builtin/functions/array/endTest.js b/test/integration/builtin/functions/array/endTest.js new file mode 100644 index 0000000..3dc37e4 --- /dev/null +++ b/test/integration/builtin/functions/array/endTest.js @@ -0,0 +1,44 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../../../tools'); + +describe('PHP "end" builtin function integration', function () { + it('should support both empty and populated arrays', function () { + var php = nowdoc(function () {/*<< 'one', 0 => 'zero']; // Order should be as defined, not numeric + +$result[] = end($myFirstArray); +$result[] = end($mySecondArray); +$result[] = end([]); +$result[] = current($myFirstArray); + +return $result; +EOS +*/;}), //jshint ignore:line + syncRuntime = tools.createSyncRuntime(), + module = tools.transpile(syncRuntime, null, php), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + 21, + 'zero', // Order should be as defined, not numeric + false, // False should be returned for an empty array + 21 // Internal pointer of $myFirstArray should have been moved to the end + ]); + }); +}); diff --git a/test/integration/builtin/functions/array/in_arrayTest.js b/test/integration/builtin/functions/array/in_arrayTest.js new file mode 100644 index 0000000..3766959 --- /dev/null +++ b/test/integration/builtin/functions/array/in_arrayTest.js @@ -0,0 +1,43 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../../../tools'); + +describe('PHP "in_array" builtin function integration', function () { + it('should be able to determine whether an array contains a value', function () { + var php = nowdoc(function () {/*<<getItsClass(); + +return $result; +EOS +*/;}), //jshint ignore:line + syncRuntime = tools.createSyncRuntime(), + module = tools.transpile(syncRuntime, null, php), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + 'My\\Space\\FirstClass', + 'My\\Space\\SecondClass' + ]); + }); +}); diff --git a/test/integration/builtin/functions/class/is_aTest.js b/test/integration/builtin/functions/class/is_aTest.js new file mode 100644 index 0000000..e6a6aa6 --- /dev/null +++ b/test/integration/builtin/functions/class/is_aTest.js @@ -0,0 +1,83 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../../../tools'); + +describe('PHP "is_a" builtin function integration', function () { + it('should be able to determine whether an object or class is the same as or inherits from another', function () { + var php = nowdoc(function () {/*<< is_a + $result[] = is_a($myObject, 'My\Space\MyClass'); + $result[] = is_a($yourObject, 'Your\Space\YourClass'); + $result[] = is_a($yourDerivedObject, 'Your\Space\YourDerivedClass'); + $result[] = is_a($yourDerivedObject, 'My\Space\MyBaseClass'); + $result[] = is_a($yourObject, 'My\Space\MyClass'); + + // is_a when string is allowed + $result[] = is_a('My\Space\MyClass', 'My\Space\MyClass', true); + $result[] = is_a('Your\Space\YourDerivedClass', 'My\Space\MyBaseClass', true); + $result[] = is_a('Your\Space\YourClass', 'My\Space\MyClass', true); + + // is_a when string is not allowed + $result[] = is_a('My\Space\MyClass', 'My\Space\MyClass'); + + return $result; +} +EOS +*/;}), //jshint ignore:line + syncRuntime = tools.createSyncRuntime(), + module = tools.transpile(syncRuntime, null, php), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + // is_a + true, + true, + true, + true, + false, + + // is_a + true, // Unlike is_subclass_of(...), passing the same class name in should return true + true, + false, + + // is_a when string is not allowed + false + ]); + }); +}); diff --git a/test/integration/builtin/functions/optionsAndInfo/extension/get_loaded_extensionsTest.js b/test/integration/builtin/functions/optionsAndInfo/extension/get_loaded_extensionsTest.js new file mode 100644 index 0000000..f3be77f --- /dev/null +++ b/test/integration/builtin/functions/optionsAndInfo/extension/get_loaded_extensionsTest.js @@ -0,0 +1,45 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../../sync'); + +describe('PHP "get_loaded_extensions" builtin function integration', function () { + it('should just return an empty array for now', function () { + var php = nowdoc(function () {/*<< is_array($extensions), + 'count' => count($extensions) +]; +EOS +*/;}), //jshint ignore:line + js = phpToJS.transpile(phpToAST.create().parse(php)), + module = new Function( + 'require', + 'return ' + js + )(function () { + return syncPHPRuntime; + }), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal({ + 'is_array': true, + 'count': 0 + }); + }); +}); diff --git a/test/integration/builtin/functions/optionsAndInfo/php/php_unameTest.js b/test/integration/builtin/functions/optionsAndInfo/php/php_unameTest.js new file mode 100644 index 0000000..406cf27 --- /dev/null +++ b/test/integration/builtin/functions/optionsAndInfo/php/php_unameTest.js @@ -0,0 +1,51 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + phpToAST = require('phptoast'), + phpToJS = require('phptojs'), + syncPHPRuntime = require('../../../../../../sync'); + +describe('PHP "php_uname" builtin function integration', function () { + it('should return the correct string for each mode', function () { + var php = nowdoc(function () {/*<<HTML string'); + +return $result; +EOS +*/;}), //jshint ignore:line + syncRuntime = tools.createSyncRuntime(), + module = tools.transpile(syncRuntime, null, php), + engine = module(); + + expect(engine.execute().getNative()).to.deep.equal([ + 'My <strong>HTML</strong> string' + ]); + }); +}); diff --git a/test/integration/builtin/functions/string/strrchrTest.js b/test/integration/builtin/functions/string/strrchrTest.js new file mode 100644 index 0000000..a27cf72 --- /dev/null +++ b/test/integration/builtin/functions/string/strrchrTest.js @@ -0,0 +1,40 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + nowdoc = require('nowdoc'), + tools = require('../../../tools'); + +describe('PHP "strrchr" builtin function integration', function () { + it('should be able to extract the portion of haystack after the last occurrence of needle', function () { + var php = nowdoc(function () {/*<< Date: Thu, 9 Nov 2017 22:28:13 +0000 Subject: [PATCH 02/11] Tag experimental release with some incomplete features for evaluation --- package-lock.json | 16 ++++++++-------- package.json | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86928a1..bcf4849 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "phpruntime", - "version": "5.9.1", + "version": "5.9.1-experimental.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -89,7 +89,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "date-now": { @@ -183,9 +183,9 @@ "dev": true }, "es5-ext": { - "version": "0.10.37", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.37.tgz", - "integrity": "sha1-DudB0Ui4AGm6J9AgOTdWryV978M=", + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", "requires": { "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" @@ -197,7 +197,7 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-symbol": "3.1.1" } }, @@ -207,7 +207,7 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.37" + "es5-ext": "0.10.38" } }, "es6-weak-map": { @@ -216,7 +216,7 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.37", + "es5-ext": "0.10.38", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } diff --git a/package.json b/package.json index dd82d01..2728a69 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.9.1", + "version": "5.9.1-experimental.1", "name": "phpruntime", "description": "Runtime library for PHP environments", "keywords": [ @@ -25,7 +25,7 @@ "dependencies": { "microdash": "~1", "phpcommon": "~1", - "phpcore": "~5" + "phpcore": "^5.2.1" }, "devDependencies": { "chai": "^2.3.0", @@ -33,8 +33,8 @@ "jshint": "~2.8", "mocha": "^2.3.3", "nowdoc": "^1.0.0", - "phptoast": "~5", - "phptojs": "~5", + "phptoast": "^5.2.1", + "phptojs": "^5.2.0", "sinon": "~1.15", "sinon-chai": "^2.7.0" }, From 1497aac8c2eddedd7a9f58124122898cd565bef3 Mon Sep 17 00:00:00 2001 From: Daniel Phillimore Date: Thu, 18 Jan 2018 22:47:38 +0000 Subject: [PATCH 03/11] Bump version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index bcf4849..707f52f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "phpruntime", - "version": "5.9.1-experimental.1", + "version": "5.9.2-experimental.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 2728a69..abedffe 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.9.1-experimental.1", + "version": "5.9.2-experimental.1", "name": "phpruntime", "description": "Runtime library for PHP environments", "keywords": [ From b8fe5c3e838b72b19f35dcf7c81c70f61c892df1 Mon Sep 17 00:00:00 2001 From: Daniel Phillimore Date: Mon, 12 Mar 2018 21:55:11 +0000 Subject: [PATCH 04/11] Update Uniter dependencies to pull in fix for JS classes calling superconstructors --- package-lock.json | 65 +++++++++++++++++++++++++++-------------------- package.json | 4 +-- 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/package-lock.json b/package-lock.json index 707f52f..db431f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -89,7 +89,7 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "requires": { - "es5-ext": "0.10.38" + "es5-ext": "0.10.40" } }, "date-now": { @@ -183,9 +183,9 @@ "dev": true }, "es5-ext": { - "version": "0.10.38", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", - "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", + "version": "0.10.40", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.40.tgz", + "integrity": "sha512-S9Fh3oya5OOvYSNGvPZJ+vyrs6VYpe1IXPowVe3N1OhaiwVaGlwfn3Zf5P5klYcWOA0toIwYQW8XEv/QqhdHvQ==", "requires": { "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" @@ -197,7 +197,7 @@ "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.38", + "es5-ext": "0.10.40", "es6-symbol": "3.1.1" } }, @@ -207,7 +207,7 @@ "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.38" + "es5-ext": "0.10.40" } }, "es6-weak-map": { @@ -216,7 +216,7 @@ "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "requires": { "d": "1.0.0", - "es5-ext": "0.10.38", + "es5-ext": "0.10.40", "es6-iterator": "2.0.3", "es6-symbol": "3.1.1" } @@ -228,15 +228,23 @@ "dev": true }, "escodegen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", - "integrity": "sha512-v0MYvNQ32bzwoG2OSFzWAkuahDQHK92JBN0pTAALJ4RIxEZe766QJPDR8Hqy7XNUy5K3fnVL76OqYAdc4TZEIw==", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", + "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", "requires": { "esprima": "3.1.3", "estraverse": "4.2.0", "esutils": "2.0.2", "optionator": "0.8.2", - "source-map": "0.5.7" + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true + } } }, "esprima": { @@ -382,9 +390,9 @@ } }, "lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha1-mkNrLMd0bKWd56QfpGmz77dr2H4=", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "requires": { "immediate": "3.0.6" } @@ -484,9 +492,9 @@ } }, "parsing": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/parsing/-/parsing-1.5.0.tgz", - "integrity": "sha1-fxcv2Ls3gIBIPbiMYGX+TudP94g=", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/parsing/-/parsing-1.6.0.tgz", + "integrity": "sha512-9s5nWzzffsIuQgVvE6f5hdHKfMyPaokpvvTUOaeJwom+/wKPmpQRbIM+JIGZH7Fgu50qUifVA40M9ckbiPIOyw==", "dev": true, "requires": { "microdash": "1.3.0" @@ -498,9 +506,9 @@ "integrity": "sha1-NwFzibb0ei5gnwdeAqVugF/b5n8=", "requires": { "acorn": "2.7.0", - "escodegen": "1.9.0", + "escodegen": "1.9.1", "estraverse": "4.2.0", - "lie": "3.1.1", + "lie": "3.3.0", "microdash": "1.3.0" } }, @@ -522,12 +530,12 @@ } }, "phpcore": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/phpcore/-/phpcore-5.2.1.tgz", - "integrity": "sha512-L2WLqsvxFnijsqH/HS0ufVAQomaRLK5FLPzbpPitH0hHiY7WV0EVdo+boEZ+IRZ036D3MLkp1uAqV1YqP+Y0EQ==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/phpcore/-/phpcore-5.2.2.tgz", + "integrity": "sha512-b+Ikt3gVW7AjNikkK88dy1wI7/Er4owpEv6090nUtK5EXWaMyXmpmSzvI/RLMn27kbFWIvZe7AMKZ6H191ffhA==", "requires": { "es6-weak-map": "2.0.2", - "lie": "3.1.1", + "lie": "3.3.0", "microdash": "1.3.0", "pausable": "4.2.0", "pauser": "1.1.1", @@ -535,13 +543,13 @@ } }, "phptoast": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/phptoast/-/phptoast-5.2.1.tgz", - "integrity": "sha512-JcW6OJL2WeXau/TJW/1CKCmtNw8zrR5NapsCs0PBSYclVx2z4d2NijabUM/q4JpP2Y0bvulDNRD+qKyliCKlQA==", + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/phptoast/-/phptoast-5.2.3.tgz", + "integrity": "sha512-ErdimAJwCSw0SddQZTMqDWyzBdHYNbL1IJaVc4FrZFEXZ5UrTR6vzYJ9Na1MatT2w27SjaW8GaG9fswVXFNlcQ==", "dev": true, "requires": { "microdash": "1.3.0", - "parsing": "1.5.0", + "parsing": "1.6.0", "phpcommon": "1.10.0" } }, @@ -614,7 +622,8 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true }, "source-map-to-comment": { "version": "1.1.0", diff --git a/package.json b/package.json index abedffe..6aef37c 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "dependencies": { "microdash": "~1", "phpcommon": "~1", - "phpcore": "^5.2.1" + "phpcore": "^5.2.2" }, "devDependencies": { "chai": "^2.3.0", @@ -33,7 +33,7 @@ "jshint": "~2.8", "mocha": "^2.3.3", "nowdoc": "^1.0.0", - "phptoast": "^5.2.1", + "phptoast": "^5.2.3", "phptojs": "^5.2.0", "sinon": "~1.15", "sinon-chai": "^2.7.0" From 9d58dc69ee2daf7740689aeb9a1a811729254024 Mon Sep 17 00:00:00 2001 From: Daniel Phillimore Date: Mon, 12 Mar 2018 21:56:51 +0000 Subject: [PATCH 05/11] Fix issue with InvalidArgumentException instances' messages getting lost --- .../classes/InvalidArgumentException.js | 16 +++++++++--- .../classes/InvalidArgumentExceptionTest.js | 26 ++++++++++++------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/builtin/classes/InvalidArgumentException.js b/src/builtin/classes/InvalidArgumentException.js index d8bfc6e..ade30b1 100644 --- a/src/builtin/classes/InvalidArgumentException.js +++ b/src/builtin/classes/InvalidArgumentException.js @@ -10,13 +10,23 @@ 'use strict'; module.exports = function (internals) { - var globalNamespace = internals.globalNamespace; + var globalNamespace = internals.globalNamespace, + Exception = globalNamespace.getClass('Exception'); + /** + * Exception thrown if an argument is not of the expected type + * + * @see {@link https://secure.php.net/manual/en/class.invalidargumentexception.php} + * @constructor + */ function InvalidArgumentException() { - + Exception.construct(this, arguments); } - InvalidArgumentException.superClass = globalNamespace.getClass('Exception'); + // Extend the base PHP Exception class + InvalidArgumentException.superClass = Exception; + + internals.disableAutoCoercion(); return InvalidArgumentException; }; diff --git a/test/integration/builtin/classes/InvalidArgumentExceptionTest.js b/test/integration/builtin/classes/InvalidArgumentExceptionTest.js index 972a91f..a281821 100644 --- a/test/integration/builtin/classes/InvalidArgumentExceptionTest.js +++ b/test/integration/builtin/classes/InvalidArgumentExceptionTest.js @@ -11,9 +11,7 @@ var expect = require('chai').expect, nowdoc = require('nowdoc'), - phpToAST = require('phptoast'), - phpToJS = require('phptojs'), - syncPHPRuntime = require('../../../../sync'); + tools = require('../../tools'); describe('PHP "InvalidArgumentException" builtin class integration', function () { it('should extend the Exception base class', function () { @@ -24,15 +22,23 @@ $exception = new InvalidArgumentException('Oops'); return $exception instanceof Exception; EOS */;}), //jshint ignore:line - js = phpToJS.transpile(phpToAST.create().parse(php)), - module = new Function( - 'require', - 'return ' + js - )(function () { - return syncPHPRuntime; - }), + module = tools.syncTranspile(null, php), engine = module(); expect(engine.execute().getNative()).to.be.true; }); + + it('should set the message on the Exception', function () { + var php = nowdoc(function () {/*<<getMessage(); +EOS +*/;}), //jshint ignore:line + module = tools.syncTranspile(null, php), + engine = module(); + + expect(engine.execute().getNative()).to.equal('Oops'); + }); }); From 05102ebb0d384abd5bb4f68d0899466333a9bc42 Mon Sep 17 00:00:00 2001 From: Daniel Phillimore Date: Mon, 12 Mar 2018 21:59:10 +0000 Subject: [PATCH 06/11] Bump version --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index db431f4..f3071ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "phpruntime", - "version": "5.9.2-experimental.1", + "version": "5.9.2-experimental.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 6aef37c..11940e6 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "5.9.2-experimental.1", + "version": "5.9.2-experimental.2", "name": "phpruntime", "description": "Runtime library for PHP environments", "keywords": [ From 4aca6eaaa2fa4a0613cd3762d529d3dea0cec4a8 Mon Sep 17 00:00:00 2001 From: Mike Smart Date: Tue, 20 Mar 2018 21:17:09 +0000 Subject: [PATCH 07/11] add docblock, warnings & tests for array_values --- src/builtin/functions/array.js | 16 +++- .../functions/array/array_valuesTest.js | 91 +++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 test/unit/builtin/functions/array/array_valuesTest.js diff --git a/src/builtin/functions/array.js b/src/builtin/functions/array.js index 5270f8b..e15dbee 100644 --- a/src/builtin/functions/array.js +++ b/src/builtin/functions/array.js @@ -221,10 +221,22 @@ module.exports = function (internals) { return valueFactory.createArray(resultPairs); }, + /** + * Returns all the values from the array and indexes the array numerically. + * + * @see {@link http://php.net/manual/en/function.array-values.php} + * + * @param {Variable|Value} arrayReference + * @returns {ArrayValue} + */ 'array_values': function (arrayReference) { - var arrayValue; + var arrayValue, + indexedArrayValue; - // TODO: Handle missing `arrayReference` arg etc. + if (!arrayReference) { + callStack.raiseError(PHPError.E_WARNING, 'array_values() expects exactly 1 parameter, 0 given'); + return valueFactory.createNull(); + } arrayValue = arrayReference.getValue(); diff --git a/test/unit/builtin/functions/array/array_valuesTest.js b/test/unit/builtin/functions/array/array_valuesTest.js new file mode 100644 index 0000000..7b21846 --- /dev/null +++ b/test/unit/builtin/functions/array/array_valuesTest.js @@ -0,0 +1,91 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + arrayExtension = require('../../../../../src/builtin/functions/array'), + sinon = require('sinon'), + CallStack = require('phpcore/src/CallStack'), + NullValue = require('phpcore/src/Value/Null').sync(), + PHPError = require('phpcommon').PHPError, + ValueFactory = require('phpcore/src/ValueFactory').sync(), + ArrayValue = require('phpcore/src/Value/Array').sync(), + IntegerValue = require('phpcore/src/Value/Integer').sync(), + KeyValuePair = require('phpcore/src/KeyValuePair'); + +describe('PHP "array_values" basic-level builtin function', function () { + beforeEach(function () { + this.callStack = sinon.createStubInstance(CallStack); + this.valueFactory = new ValueFactory(); + + this.createKeyValuePair = function (key, value) { + var keyValuePair = sinon.createStubInstance(KeyValuePair); + keyValuePair.getKey.returns(key); + keyValuePair.getValue.returns(value); + return keyValuePair; + }; + + this.array_values = arrayExtension({ + callStack: this.callStack, + valueFactory: this.valueFactory + }).array_values; + + this.args = []; + this.callArrayValues = function () { + return this.array_values.apply(null, this.args); + }.bind(this); + }); + + it('should return all the values from the array numerically indexed', function () { + var array1Key1 = this.valueFactory.createString('first'), + array1Element1 = this.valueFactory.createInteger(1), + array2Key2 = this.valueFactory.createString('second'), + array2Element2 = this.valueFactory.createInteger(4), + result; + + this.args[0] = this.valueFactory.createArray([ + this.createKeyValuePair(array1Key1, array1Element1), + this.createKeyValuePair(array2Key2, array2Element2) + ]); + + result = this.callArrayValues(); + + expect(result).to.be.an.instanceOf(ArrayValue); + expect(result.getKeys()).to.have.length.of(2); + expect(result.getKeys()[0]).to.be.an.instanceOf(IntegerValue); + expect(result.getKeys()[0].getNative()).to.equal(0); + expect(result.getValues()).to.have.length.of(2); + expect(result.getValues()[0]).to.be.an.instanceOf(IntegerValue); + expect(result.getValues()[0].getNative()).to.equal(1); + expect(result.getKeys()[1]).to.be.an.instanceOf(IntegerValue); + expect(result.getKeys()[1].getNative()).to.equal(1); + expect(result.getValues()[1]).to.be.an.instanceOf(IntegerValue); + expect(result.getValues()[1].getNative()).to.equal(4); + expect(result.getNative()).to.deep.equal([1, 4]); + }); + + describe('when no arguments are provided', function () { + it('should raise a warning', function () { + this.callArrayValues(); + + expect(this.callStack.raiseError).to.have.been.calledOnce; + expect(this.callStack.raiseError).to.have.been.calledWith( + PHPError.E_WARNING, + 'array_values() expects exactly 1 parameter, 0 given' + ); + }); + + it('should return NULL', function () { + var result = this.callArrayValues(); + + expect(result).to.be.an.instanceOf(NullValue); + }); + }); +}); From b4d1bad1bfd505ab54017ed3ae3c0184536d25f7 Mon Sep 17 00:00:00 2001 From: Mike Smart Date: Tue, 20 Mar 2018 21:25:12 +0000 Subject: [PATCH 08/11] remove unused variable --- src/builtin/functions/array.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/builtin/functions/array.js b/src/builtin/functions/array.js index e15dbee..cdc8044 100644 --- a/src/builtin/functions/array.js +++ b/src/builtin/functions/array.js @@ -230,8 +230,7 @@ module.exports = function (internals) { * @returns {ArrayValue} */ 'array_values': function (arrayReference) { - var arrayValue, - indexedArrayValue; + var arrayValue; if (!arrayReference) { callStack.raiseError(PHPError.E_WARNING, 'array_values() expects exactly 1 parameter, 0 given'); From efa0bafb05be7ef8408312ffa6a00368727ae61c Mon Sep 17 00:00:00 2001 From: Mike Smart Date: Tue, 20 Mar 2018 21:32:24 +0000 Subject: [PATCH 09/11] remove basic-level reference --- test/unit/builtin/functions/array/array_valuesTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/builtin/functions/array/array_valuesTest.js b/test/unit/builtin/functions/array/array_valuesTest.js index 7b21846..63eca19 100644 --- a/test/unit/builtin/functions/array/array_valuesTest.js +++ b/test/unit/builtin/functions/array/array_valuesTest.js @@ -20,7 +20,7 @@ var expect = require('chai').expect, IntegerValue = require('phpcore/src/Value/Integer').sync(), KeyValuePair = require('phpcore/src/KeyValuePair'); -describe('PHP "array_values" basic-level builtin function', function () { +describe('PHP "array_values" builtin function', function () { beforeEach(function () { this.callStack = sinon.createStubInstance(CallStack); this.valueFactory = new ValueFactory(); From f7fbd6d262daf12d0ce501a8ba567835f847da70 Mon Sep 17 00:00:00 2001 From: Mike Smart Date: Wed, 21 Mar 2018 19:28:41 +0000 Subject: [PATCH 10/11] add docblock and no argument warning --- src/builtin/functions/array.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/builtin/functions/array.js b/src/builtin/functions/array.js index cdc8044..6ec9424 100644 --- a/src/builtin/functions/array.js +++ b/src/builtin/functions/array.js @@ -269,9 +269,23 @@ module.exports = function (internals) { type === 'array' || type === 'object' ? array.getLength() : 1 ); }, + + /** + * Return the current element in an array. + * + * @see {@link http://php.net/manual/en/function.current.php} + * + * @param {Variable|Value} arrayReference + * @returns {Value} + */ 'current': function (arrayReference) { var arrayValue = arrayReference.getValue(); + if (!arrayReference) { + callStack.raiseError(PHPError.E_WARNING, 'current() expects exactly 1 parameter, 0 given'); + return valueFactory.createNull(); + } + if (arrayValue.getPointer() >= arrayValue.getLength()) { return valueFactory.createBoolean(false); } From 5c91c1b147a22419658732194f51d92925c99073 Mon Sep 17 00:00:00 2001 From: Mike Smart Date: Wed, 21 Mar 2018 19:51:10 +0000 Subject: [PATCH 11/11] add tests for current() --- src/builtin/functions/array.js | 4 +- .../builtin/functions/array/currentTest.js | 92 +++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 test/unit/builtin/functions/array/currentTest.js diff --git a/src/builtin/functions/array.js b/src/builtin/functions/array.js index 6ec9424..c82dd29 100644 --- a/src/builtin/functions/array.js +++ b/src/builtin/functions/array.js @@ -279,13 +279,15 @@ module.exports = function (internals) { * @returns {Value} */ 'current': function (arrayReference) { - var arrayValue = arrayReference.getValue(); + var arrayValue; if (!arrayReference) { callStack.raiseError(PHPError.E_WARNING, 'current() expects exactly 1 parameter, 0 given'); return valueFactory.createNull(); } + arrayValue = arrayReference.getValue(); + if (arrayValue.getPointer() >= arrayValue.getLength()) { return valueFactory.createBoolean(false); } diff --git a/test/unit/builtin/functions/array/currentTest.js b/test/unit/builtin/functions/array/currentTest.js new file mode 100644 index 0000000..a9a3854 --- /dev/null +++ b/test/unit/builtin/functions/array/currentTest.js @@ -0,0 +1,92 @@ +/* + * PHPRuntime - PHP environment runtime components + * Copyright (c) Dan Phillimore (asmblah) + * https://github.com/uniter/phpruntime/ + * + * Released under the MIT license + * https://github.com/uniter/phpruntime/raw/master/MIT-LICENSE.txt + */ + +'use strict'; + +var expect = require('chai').expect, + arrayExtension = require('../../../../../src/builtin/functions/array'), + sinon = require('sinon'), + CallStack = require('phpcore/src/CallStack'), + NullValue = require('phpcore/src/Value/Null').sync(), + PHPError = require('phpcommon').PHPError, + ValueFactory = require('phpcore/src/ValueFactory').sync(), + BooleanValue = require('phpcore/src/Value/Boolean').sync(), + IntegerValue = require('phpcore/src/Value/Integer').sync(); + +describe('PHP "current" builtin function', function () { + beforeEach(function () { + this.callStack = sinon.createStubInstance(CallStack); + this.valueFactory = new ValueFactory(); + + this.current = arrayExtension({ + callStack: this.callStack, + valueFactory: this.valueFactory + }).current; + + this.args = []; + this.callCurrent = function () { + return this.current.apply(null, this.args); + }.bind(this); + + this.array = this.valueFactory.createArray([ + this.valueFactory.createInteger(1), + this.valueFactory.createInteger(4) + ]); + }); + + it('Should return the current element in the array', function () { + var result; + + this.args[0] = this.array; + result = this.callCurrent(); + + expect(result).to.be.an.instanceOf(IntegerValue); + expect(result.getNative()).to.equal(1); + }); + + it('should return the current element in the array when the pointer has changed', function () { + var result; + + this.array.setPointer(1); + this.args[0] = this.array; + result = this.callCurrent(); + + expect(result).to.be.an.instanceOf(IntegerValue); + expect(result.getNative()).to.equal(4); + }); + + it('should return false if the pointer exceeds the length of the array', function () { + var result; + + this.array.setPointer(2); + this.args[0] = this.array; + result = this.callCurrent(); + + expect(result).to.be.an.instanceOf(BooleanValue); + expect(result.getNative()).to.equal(false); + }); + + describe('when no arguments are provided', function () { + it('should raise a warning', function () { + this.callCurrent(); + + expect(this.callStack.raiseError).to.have.been.calledOnce; + expect(this.callStack.raiseError).to.have.been.calledWith( + PHPError.E_WARNING, + 'current() expects exactly 1 parameter, 0 given' + ); + }); + + it('should return NULL', function () { + var result = this.callCurrent(); + + expect(result).to.be.an.instanceOf(NullValue); + }); + }); +});