Updated LICENSE

Fixed namespace usages for polyfill packages
Added dependency Symfony\polyfill-uuid
Updated .gitignore
Updated autoload.php
Corrected .gitignore
Added dependency nikic\PhpParser
Removed .idea leftovers
Added classes & objects for Package Structure 1.0
Updated autoload.php for tests
This commit is contained in:
Netkas 2022-09-26 17:45:04 -04:00
parent 9bab67f734
commit 36d89bae8a
322 changed files with 119031 additions and 103 deletions

7
.gitignore vendored
View file

@ -6,8 +6,10 @@ build
# Autoload files
src/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php
src/ncc/ThirdParty/nikic/PhpParser/autoload_spl.php
src/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php
src/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php
src/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php
src/ncc/ThirdParty/Symfony/Process/autoload_spl.php
src/ncc/ThirdParty/Symfony/Uid/autoload_spl.php
src/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php
@ -15,4 +17,7 @@ src/ncc/ThirdParty/Symfony/Yaml/autoload_spl.php
src/ncc/ThirdParty/theseer/Autoload/autoload_spl.php
src/ncc/ThirdParty/theseer/DirectoryScanner/autoload_spl.php
src/ncc/autoload_spl.php
src/ncc/autoload.php
src/ncc/autoload.php
# Test files
tests/example_project/src/project.json

59
LICENSE
View file

@ -126,6 +126,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
------------------------
Symfony - polyfill-uuid
Copyright (c) 2018-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
------------------------
dealnews.com, Inc. - Inline ProgressBar CLI
@ -271,3 +296,37 @@ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
------------------------
nikic - PhpParser
BSD 3-Clause License
Copyright (c) 2011, Nikita Popov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -6,8 +6,10 @@ SRC_PATH=src
autoload:
# Generates/creates all the autoloader files
make $(SRC_PATH)/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/nikic/php-parser/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/Process/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/Uid/autoload_spl.php
make $(SRC_PATH)/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php
@ -21,6 +23,10 @@ $(SRC_PATH)/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/defuse/php-encryption
$(SRC_PATH)/ncc/ThirdParty/nikic/php-parser/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/nikic/PhpParser/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/nikic/PhpParser
$(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-ctype
@ -29,6 +35,10 @@ $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-mbstring
$(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-uuid
$(SRC_PATH)/ncc/ThirdParty/Symfony/Process/autoload_spl.php:
$(PHPCC) $(PHPAB) --output $(SRC_PATH)/ncc/ThirdParty/Symfony/Process/autoload_spl.php \
$(SRC_PATH)/ncc/ThirdParty/Symfony/Process
@ -87,8 +97,10 @@ clean:
rm -rf $(BUILD_PATH)
rm -f $(SRC_PATH)/ncc/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/defuse/php-encryption/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/nikic/PhpParser/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-ctype/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-mbstring/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/polyfill-uuid/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/Process/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/Uid/autoload_spl.php
rm -f $(SRC_PATH)/ncc/ThirdParty/Symfony/Filesystem/autoload_spl.php

View file

@ -31,6 +31,7 @@ a PPM extension may be built in the future to allow for backwards compatibility.
- Copyright (c) 2004-2022, Fabien Potencier
- Copyright (c) 2010, dealnews.com, Inc. All rights reserved.
- Copyright (c) 2013 Austin Hyde
- Copyright (c) 2011, Nikita Popov
# Licenses

View file

@ -14,10 +14,13 @@
$target_files = [
__DIR__ . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'defuse' . DIRECTORY_SEPARATOR . 'php-encryption' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'nikic' . DIRECTORY_SEPARATOR . 'PhpParser' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-ctype' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-ctype' . DIRECTORY_SEPARATOR . 'bootstrap.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-mbstring' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-mbstring' . DIRECTORY_SEPARATOR . 'bootstrap.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-uuid' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'polyfill-uuid' . DIRECTORY_SEPARATOR . 'bootstrap.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Process' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Uid' . DIRECTORY_SEPARATOR . 'autoload_spl.php',
$third_party_path . 'Symfony' . DIRECTORY_SEPARATOR . 'Filesystem' . DIRECTORY_SEPARATOR . 'autoload_spl.php',

View file

@ -1,3 +1,8 @@
# NCC Default configuration file, upon installation the installer will generate a new configuration file
# for your system or update the existing configuration file and only overwriting values that are no
# longer applicable to the current version of NCC, incorrect configuration values can cause
# unexpected behavior or bugs.
ncc:
# The default data directory that is used by NCC to store packages,
# cache, configuration files and other generated files. This includes
@ -21,20 +26,12 @@ php:
# issues/backdoors, use this feature for containerized environments
enable_environment_configurations: false
# Enables/Disables the injection of NCC's include path
# during the initialization phase allowing you to import
# packages using the `import()` function and other ncc
# API Functions without needing to require NCC's autoloader
#
# This feature is highly recommended to be enabled
enable_include_path: true
git:
executable_path: "/usr/bin/git"
composer:
# When enabled, NCC will use it's builtin version of composer
# to execute composer tasks, if disabled it will fallback to
# to execute composer tasks, if disabled it will fall back to
# the `executable_path` option and attempt to use that specified
# location of composer
enable_internal_composer: true

View file

@ -100,6 +100,8 @@
}
$NCC_AUTO_MODE = ($NCC_ARGS !== null && isset($NCC_ARGS['auto']));
$NCC_BYPASS_CLI_CHECK = ($NCC_ARGS !== null && isset($NCC_ARGS['bypass-cli-check']));
$NCC_BYPASS_CHECKSUM = ($NCC_ARGS !== null && isset($NCC_ARGS['bypass-checksum']));
if(isset($NCC_ARGS['help']))
{
@ -108,6 +110,8 @@
new CliHelpSection(['--auto'], 'Automates the installation process'),
new CliHelpSection(['--install-composer'], 'Require composer to be installed alongside NCC'),
new CliHelpSection(['--install-dir'], 'Specifies the installation directory for NCC'),
new CliHelpSection(['--bypass-cli-check'], 'Bypasses the check for a CLI environment'),
new CliHelpSection(['--bypass-checksum'], 'Bypasses the checksum for the installation files'),
];
$options_padding = Functions::detectParametersPadding($options) + 4;
@ -128,27 +132,30 @@
}
// Detect the server API
if(defined('PHP_SAPI'))
if(!$NCC_BYPASS_CLI_CHECK)
{
if(strtolower(PHP_SAPI) !== 'cli')
if(defined('PHP_SAPI'))
{
if(strtolower(PHP_SAPI) !== 'cli')
{
print('This installation script is meant to be running in your terminal' . PHP_EOL);
}
}
elseif(function_exists('php_sapi_name') && strtolower(php_sapi_name()) !== 'cli')
{
print('This installation script is meant to be running in your terminal' . PHP_EOL);
}
}
elseif(function_exists('php_sapi_name') && strtolower(php_sapi_name()) !== 'cli')
{
print('This installation script is meant to be running in your terminal' . PHP_EOL);
}
else
{
Console::outWarning(
'The installer cannot determine the Server API (SAPI), the installer will continue but it is ' .
'recommended to be running this installer in a terminal'
);
else
{
Console::outWarning(
'The installer cannot determine the Server API (SAPI), the installer will continue but it is ' .
'recommended to be running this installer in a terminal'
);
}
}
// Check if running in a TTY
if(stream_isatty(STDERR))
if(stream_isatty(STDERR) && !$NCC_BYPASS_CLI_CHECK)
{
Console::outWarning('Your terminal may have some issues rendering the output of this installer');
}
@ -193,39 +200,42 @@
}
// Preform the checksum validation
if(!file_exists($NCC_CHECKSUM))
if(!$NCC_BYPASS_CHECKSUM)
{
Console::outWarning('The file \'checksum.bin\' was not found, the contents of the program cannot be verified to be safe');
}
else
{
Console::out('Running checksum');
$checksum = ZiProto::decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'checksum.bin'));
$checksum_failed = false;
foreach($checksum as $path => $hash)
if(!file_exists($NCC_CHECKSUM))
{
if(!file_exists(__DIR__ . DIRECTORY_SEPARATOR . $path))
{
Console::outError('Cannot check file, \'' . $path . '\' not found.');
$checksum_failed = true;
}
elseif(hash_file('sha256', __DIR__ . DIRECTORY_SEPARATOR . $path) !== $hash)
{
Console::outWarning('The file \'' . $path . '\' does not match the original checksum');
$checksum_failed = true;
}
}
if($checksum_failed)
{
Console::outError('Checksum failed, the contents of the program cannot be verified to be safe');
exit(1);
Console::outWarning('The file \'checksum.bin\' was not found, the contents of the program cannot be verified to be safe');
}
else
{
Console::out('Checksum passed');
Console::out('Running checksum');
$checksum = ZiProto::decode(file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'checksum.bin'));
$checksum_failed = false;
foreach($checksum as $path => $hash)
{
if(!file_exists(__DIR__ . DIRECTORY_SEPARATOR . $path))
{
Console::outError('Cannot check file, \'' . $path . '\' not found.');
$checksum_failed = true;
}
elseif(hash_file('sha256', __DIR__ . DIRECTORY_SEPARATOR . $path) !== $hash)
{
Console::outWarning('The file \'' . $path . '\' does not match the original checksum');
$checksum_failed = true;
}
}
if($checksum_failed)
{
Console::outError('Checksum failed, the contents of the program cannot be verified to be safe');
exit(1);
}
else
{
Console::out('Checksum passed');
}
}
}
@ -514,7 +524,7 @@
// Verify install
if(!$NCC_FILESYSTEM->exists([$NCC_INSTALL_PATH . DIRECTORY_SEPARATOR . 'composer.phar']))
{
Console::outError("The installation exited without any issues but composer doesn't seem to be installed correctly");
Console::outError("Installation failed, the installation exited without any issues but composer doesn't seem to be installed correctly");
exit(1);
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts;
abstract class CompilerOptions
{
}

View file

@ -0,0 +1,13 @@
<?php
namespace ncc\Abstracts;
abstract class ComponentFileExtensions
{
/**
* The file extensions that the PHP compiler extension will accept as components.
*
* @var array
*/
const Php = ['*.php', '*.php3', '*.php4', '*.php5', '*.phtml'];
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts;
abstract class EncoderType
{
const ZiProto = '3';
}

View file

@ -3,20 +3,26 @@
namespace ncc\Abstracts;
use ncc\Exceptions\AccessDeniedException;
use ncc\Exceptions\AutoloadGeneratorException;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\ComponentVersionNotFoundException;
use ncc\Exceptions\ConstantReadonlyException;
use ncc\Exceptions\DirectoryNotFoundException;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\InvalidConstantNameException;
use ncc\Exceptions\InvalidCredentialsEntryException;
use ncc\Exceptions\InvalidPackageException;
use ncc\Exceptions\InvalidPackageNameException;
use ncc\Exceptions\InvalidProjectBuildConfiguration;
use ncc\Exceptions\InvalidProjectConfigurationException;
use ncc\Exceptions\InvalidProjectNameException;
use ncc\Exceptions\InvalidScopeException;
use ncc\Exceptions\InvalidVersionNumberException;
use ncc\Exceptions\MalformedJsonException;
use ncc\Exceptions\MethodNotAvailableException;
use ncc\Exceptions\NoUnitsFoundException;
use ncc\Exceptions\ProjectAlreadyExistsException;
use ncc\Exceptions\RuntimeException;
use ncc\Exceptions\UnsupportedPackageException;
/**
* @author Zi Xing Narrakas
@ -94,6 +100,51 @@
*/
const ProjectAlreadyExistsException = -1713;
/**
* @see AutoloadGeneratorException
*/
const AutoloadGeneratorException = -1714;
/**
* @see NoUnitsFoundException
*/
const NoUnitsFoundException = -1715;
/**
* @see UnsupportedPackageException
*/
const UnsupportedPackageException = -1716;
/**
* @see NotImplementedException
*/
const NotImplementedException = -1717;
/**
* @see InvalidPackageException
*/
const InvalidPackageException = -1718;
/**
* @see InvalidConstantNameException
*/
const InvalidConstantNameException = -1719;
/**
* @see PackagePreparationFailedException
*/
const PackagePreparationFailedException = -1720;
/**
* @see BuildConfigurationNotFoundException
*/
const BuildConfigurationNotFoundException = -1721;
/**
* @see InvalidProjectBuildConfiguration
*/
const InvalidProjectBuildConfiguration = -1722;
/**
* All the exception codes from NCC
*/
@ -112,5 +163,14 @@
self::InvalidVersionNumberException,
self::InvalidProjectNameException,
self::ProjectAlreadyExistsException,
self::AutoloadGeneratorException,
self::NoUnitsFoundException,
self::UnsupportedPackageException,
self::NotImplementedException,
self::InvalidPackageException,
self::InvalidConstantNameException,
self::PackagePreparationFailedException,
self::BuildConfigurationNotFoundException,
self::InvalidProjectBuildConfiguration
];
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts\Options;
abstract class BuildConfigurationValues
{
const DefaultConfiguration = 'default';
}

View file

@ -0,0 +1,8 @@
<?php
namespace ncc\Abstracts;
abstract class PackageStandardVersions
{
const VERSION_1 = '1.0';
}

View file

@ -21,4 +21,6 @@
const UnixPath = '/^(((?:\.\/|\.\.\/|\/)?(?:\.?\w+\/)*)(\.?\w+\.?\w+))$/m';
const WindowsPath = '/^(([%][^\/:*?<>""|]*[%])|([a-zA-Z][:])|(\\\\))((\\\\{1})|((\\\\{1})[^\\\\]([^\/:*?<>""|]*))+)$/m';
const ConstantName = '/^([^\x00-\x7F]|[\w_\ \.\+\-]){2,16}$/';
}

View file

@ -9,4 +9,8 @@
*/
const CredentialsStoreVersion = '1.0.0';
/**
* The current version of the package structure file format
*/
const PackageStructureVersion = '1.0.0';
}

View file

@ -3,6 +3,9 @@
namespace ncc\CLI;
use Exception;
use ncc\Abstracts\NccBuildFlags;
use ncc\Exceptions\FileNotFoundException;
use ncc\Exceptions\RuntimeException;
use ncc\ncc;
use ncc\Utilities\Console;
use ncc\Utilities\Resolver;
@ -22,7 +25,26 @@
if(isset($args['ncc-cli']))
{
// Initialize NCC
ncc::initialize();
try
{
ncc::initialize();
}
catch (FileNotFoundException $e)
{
Console::outException('Cannot initialize NCC, one or more files were not found.', $e, 1);
}
catch (RuntimeException $e)
{
Console::outException('Cannot initialize NCC due to a runtime error.', $e, 1);
}
// Define CLI stuff
define('NCC_CLI_MODE', 1);
if(in_array(NccBuildFlags::Unstable, NCC_VERSION_FLAGS))
{
Console::outWarning('This is an unstable build of NCC, expect some features to not work as expected');
}
try
{

View file

@ -1,26 +0,0 @@
<?php
namespace ncc\Classes;
use ncc\Objects\ProjectConfiguration;
class AutoloaderGenerator
{
/**
* @var ProjectConfiguration
*/
private ProjectConfiguration $project;
/**
* @param ProjectConfiguration $project
*/
public function __construct(ProjectConfiguration $project)
{
$this->project = $project;
}
public function generateAutoload(string $src, string $output, bool $static=false)
{
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace ncc\Classes\Compilers;
use ncc\Classes\PhpExtension\AutoloaderGenerator;
use ncc\Interfaces\CompilerInterface;
use ncc\Objects\ProjectConfiguration;
class Php implements CompilerInterface
{
/**
* @var ProjectConfiguration
*/
private ProjectConfiguration $project;
/**
* @var AutoloaderGenerator
*/
private AutoloaderGenerator $autoloader;
/**
* @param ProjectConfiguration $project
*/
public function __construct(ProjectConfiguration $project)
{
$this->project = $project;
$this->autoloader = new AutoloaderGenerator($project);
}
public function prepare(array $options)
{
// TODO: Implement prepare() method.
}
public function build(array $options)
{
// TODO: Implement build() method.
}
}

View file

@ -0,0 +1,43 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Classes;
use ncc\Exceptions\FileNotFoundException;
class PackageParser
{
/**
* @var string
*/
private $PackagePath;
/**
* Package Parser public constructor.
*
* @param string $path
*/
public function __construct(string $path)
{
$this->PackagePath = $path;
$this->parseFile();
}
private function parseFile()
{
if(file_exists($this->PackagePath) == false)
{
throw new FileNotFoundException('The given package path \'' . $this->PackagePath . '\' does not exist');
}
if(is_file($this->PackagePath) == false)
{
throw new FileNotFoundException('The given package path \'' . $this->PackagePath . '\' is not a file');
}
$file_handler = fopen($this->PackagePath, 'rb');
$header = fread($file_handler, 14);
var_dump($header);
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace ncc\Classes\PhpExtension;
use ArrayIterator;
use ncc\Abstracts\ComponentFileExtensions;
use ncc\Exceptions\AutoloadGeneratorException;
use ncc\Exceptions\NoUnitsFoundException;
use ncc\Objects\ProjectConfiguration;
use ncc\ThirdParty\theseer\Autoload\CollectorException;
use ncc\ThirdParty\theseer\Autoload\CollectorResult;
use ncc\ThirdParty\theseer\Autoload\Config;
use ncc\ThirdParty\theseer\Autoload\Factory;
use ncc\ThirdParty\theseer\DirectoryScanner\Exception;
use ncc\Utilities\Console;
use SplFileInfo;
class AutoloaderGenerator
{
/**
* @var ProjectConfiguration
*/
private ProjectConfiguration $project;
/**
* @param ProjectConfiguration $project
*/
public function __construct(ProjectConfiguration $project)
{
$this->project = $project;
}
/**
* Processes the project and generates the autoloader source code.
*
* @param string $src
* @param string $output
* @param bool $static
* @return string
* @throws AutoloadGeneratorException
* @throws CollectorException
* @throws Exception
* @throws NoUnitsFoundException
*/
public function generateAutoload(string $src, string $output, bool $static=false): string
{
// Construct configuration
$configuration = new Config([$src]);
$configuration->setFollowSymlinks(false); // Don't follow symlinks, it won't work on some systems.
$configuration->setOutputFile($output);
$configuration->setTrusting(false); // Paranoid
// Official PHP file extensions that are missing from the default configuration (whatever)
$configuration->setInclude(ComponentFileExtensions::Php);
// Construct factory
$factory = new Factory();
$factory->setConfig($configuration);
// Create Collector
$result = self::runCollector($factory, $configuration);
// Exception raises when there are no files in the project that can be processed by the autoloader
if(!$result->hasUnits())
{
throw new NoUnitsFoundException('No units were found in the project');
}
if(!$result->hasDuplicates())
{
foreach($result->getDuplicates() as $unit => $files)
{
Console::outWarning((count($files) -1). ' duplicate unit(s) detected in the project: ' . $unit);
}
}
$template = @file_get_contents($configuration->getTemplate());
if ($template === false)
{
throw new AutoloadGeneratorException("Failed to read the template file '" . $configuration->getTemplate() . "'");
}
$builder = $factory->getRenderer($result);
return $builder->render($template);
}
/**
* Iterates through the target directories through the collector and returns the collector results.
*
* @param Factory $factory
* @param Config $config
* @return CollectorResult
* @throws CollectorException
* @throws Exception
*/
private static function runCollector(Factory $factory, Config $config): CollectorResult
{
$collector = $factory->getCollector();
foreach($config->getDirectories() as $directory)
{
if(is_dir($directory))
{
$scanner = $factory->getScanner()->getIterator($directory);
$collector->processDirectory($scanner);
unset($scanner);
}
else
{
$file = new SplFileInfo($directory);
$filter = $factory->getFilter(new ArrayIterator(array($file)));
foreach($filter as $file)
{
$collector->processFile($file);
}
}
}
return $collector->getResult();
}
}

View file

@ -0,0 +1,169 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Classes\PhpExtension;
use FilesystemIterator;
use ncc\Abstracts\CompilerOptions;
use ncc\Abstracts\ComponentFileExtensions;
use ncc\Abstracts\Options\BuildConfigurationValues;
use ncc\Abstracts\Versions;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\PackagePreparationFailedException;
use ncc\Interfaces\CompilerInterface;
use ncc\ncc;
use ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration;
use ncc\ThirdParty\theseer\DirectoryScanner\DirectoryScanner;
use ncc\ThirdParty\theseer\DirectoryScanner\Exception;
use ncc\Utilities\Console;
use SplFileInfo;
class Compiler implements CompilerInterface
{
/**
* @var ProjectConfiguration
*/
private $project;
/**
* @var Package|null
*/
private $package;
/**
* @param ProjectConfiguration $project
*/
public function __construct(ProjectConfiguration $project)
{
$this->project = $project;
}
/**
* Prepares the PHP package by generating the Autoloader and detecting all components & resources
* This function must be called before calling the build function, otherwise the operation will fail
*
* @param array $options
* @param string $src
* @param string $build_configuration
* @return void
* @throws PackagePreparationFailedException
*/
public function prepare(array $options, string $src, string $build_configuration=BuildConfigurationValues::DefaultConfiguration)
{
// Auto-select the default build configuration
if($build_configuration == BuildConfigurationValues::DefaultConfiguration)
{
$build_configuration = $this->project->Build->DefaultConfiguration;
}
// Select the build configuration
try
{
$selected_build_configuration = $this->project->Build->getBuildConfiguration($build_configuration);
}
catch (BuildConfigurationNotFoundException $e)
{
throw new PackagePreparationFailedException($e->getMessage(), $e);
}
// Create the package object
$this->package = new Package();
$this->package->Assembly = $this->project->Assembly;
$this->package->Dependencies = $this->project->Build->Dependencies;
$this->package->Header->RuntimeConstants = $selected_build_configuration->DefineConstants;
$this->package->Header->CompilerExtension = $this->project->Project->Compiler;
$this->package->Header->CompilerVersion = NCC_VERSION_NUMBER;
if(ncc::cliMode())
{
Console::out('Building autoloader');
Console::out('theseer\DirectoryScanner - Copyright (c) 2009-2014 Arne Blankerts <arne@blankerts.de> All rights reserved.');
Console::out('theseer\Autoload - Copyright (c) 2010-2016 Arne Blankerts <arne@blankerts.de> and Contributors All rights reserved.');
}
// First scan the project files and create a file struct.
$DirectoryScanner = new DirectoryScanner();
try
{
$DirectoryScanner->unsetFlag(FilesystemIterator::
FOLLOW_SYMLINKS);
}
catch (Exception $e)
{
throw new PackagePreparationFailedException('Cannot unset flag \'FOLLOW_SYMLINKS\' in DirectoryScanner, ' . $e->getMessage(), $e);
}
// Include file components that can be compiled
$DirectoryScanner->setIncludes(ComponentFileExtensions::Php);
$DirectoryScanner->setExcludes($selected_build_configuration->ExcludeFiles);
// Scan for components first.
Console::out('Scanning for components...', false);
/** @var SplFileInfo $item */
foreach($DirectoryScanner($src, true) as $item)
{
// Ignore directories, they're not important. :-)
if(is_dir($item->getPath()))
continue;
$Component = new Package\Component();
$Component->Name = $item->getPath();
$this->package->Components[] = $Component;
var_dump($item->getPath());
var_dump($item);
}
if(count($this->package->Components) > 0)
{
Console::out(count($this->package->Components) . ' component(s) found');
}
else
{
Console::out('No components found');
}
// Now scan for resources
Console::out('Scanning for resources...', false);
$DirectoryScanner->setExcludes(array_merge(
$selected_build_configuration->ExcludeFiles, ComponentFileExtensions::Php
));
// Scan for components first.
/** @var SplFileInfo $item */
foreach($DirectoryScanner($src, true) as $item)
{
// Ignore directories, they're not important. :-)
if(is_dir($item->getPath()))
continue;
$Resource = new Package\Resource();
$Resource->Name = $item->getPath();
$this->package->Resources[] = $Resource;
var_dump($item->getPath());
var_dump($item);
}
if(count($this->package->Resources) > 0)
{
Console::out(count($this->package->Resources) . ' resources(s) found');
}
else
{
Console::out('No resources found');
}
var_dump($this->package);
}
public function build(array $options, string $src)
{
// TODO: Implement build() method.
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class AutoloadGeneratorException extends Exception
{
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::AutoloadGeneratorException, $previous);
}
}

View file

@ -0,0 +1,25 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class BuildConfigurationNotFoundException extends Exception
{
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::BuildConfigurationNotFoundException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class InvalidConstantNameException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::InvalidConstantNameException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,25 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class InvalidPackageException extends Exception
{
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::InvalidPackageException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,25 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class InvalidProjectBuildConfiguration extends Exception
{
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::InvalidProjectBuildConfiguration, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class NoUnitsFoundException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::NoUnitsFoundException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class NotImplementedException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::NotImplementedException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,28 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class PackagePreparationFailedException extends Exception
{
/**
* @var Throwable|null
*/
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::PackagePreparationFailedException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,25 @@
<?php
/** @noinspection PhpPropertyOnlyWrittenInspection */
namespace ncc\Exceptions;
use Exception;
use ncc\Abstracts\ExceptionCodes;
use Throwable;
class UnsupportedPackageException extends Exception
{
private ?Throwable $previous;
/**
* @param string $message
* @param Throwable|null $previous
*/
public function __construct(string $message = "", ?Throwable $previous = null)
{
parent::__construct($message, ExceptionCodes::UnsupportedPackageException, $previous);
$this->message = $message;
$this->previous = $previous;
}
}

View file

@ -0,0 +1,10 @@
<?php
namespace ncc\Interfaces;
interface CompilerInterface
{
public function prepare(array $options, string $src);
public function build(array $options, string $src);
}

View file

@ -1,5 +1,7 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Abstracts\ConsoleColors;
@ -81,9 +83,11 @@
/**
* Returns a string representation of the object
*
* @param int $param_padding
* @param bool $basic
* @return string
*/
public function toString(int $param_padding=0, bool $basic=false)
public function toString(int $param_padding=0, bool $basic=false): string
{
$out = [];
@ -91,9 +95,10 @@
{
if($param_padding > 0)
{
/** @noinspection PhpRedundantOptionalArgumentInspection */
$result = str_pad(implode(' ', $this->Parameters), $param_padding, ' ', STR_PAD_RIGHT);
if($basic == false)
if(!$basic)
{
$result = Console::formatColor($result, ConsoleColors::Green);
}

View file

@ -3,7 +3,6 @@
namespace ncc\Objects;
use ncc\Exceptions\ConstantReadonlyException;
use ncc\Symfony\Component\Uid\Uuid;
use ncc\Utilities\Resolver;
class Constant
@ -90,6 +89,7 @@
/**
* @param string $value
* @param bool $readonly
* @throws ConstantReadonlyException
*/
public function setValue(string $value, bool $readonly=false): void

View file

@ -1,5 +1,7 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
class NccUpdateInformation

View file

@ -1,5 +1,7 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Objects\NccVersionInformation\Component;

213
src/ncc/Objects/Package.php Normal file
View file

@ -0,0 +1,213 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects;
use ncc\Exceptions\InvalidPackageException;
use ncc\Exceptions\InvalidProjectConfigurationException;
use ncc\Objects\Package\Component;
use ncc\Objects\Package\Header;
use ncc\Objects\Package\Installer;
use ncc\Objects\Package\MagicBytes;
use ncc\Objects\Package\MainExecutionPolicy;
use ncc\Objects\Package\Resource;
use ncc\Objects\ProjectConfiguration\Assembly;
use ncc\Objects\ProjectConfiguration\Dependency;
use ncc\Utilities\Functions;
class Package
{
/**
* The parsed magic bytes of the package into an object representation
*
* @var MagicBytes
*/
public $MagicBytes;
/**
* The true header of the package
*
* @var Header
*/
public $Header;
/**
* The assembly object of the package
*
* @var Assembly
*/
public $Assembly;
/**
* An array of dependencies that the package depends on
*
* @var Dependency[]
*/
public $Dependencies;
/**
* The Main Execution Policy object for the package if the package is an executable package.
*
* @var MainExecutionPolicy|null
*/
public $MainExecutionPolicy;
/**
* The installer object that is used to install the package if the package is install-able
*
* @var Installer|null
*/
public $Installer;
/**
* An array of resources that the package depends on
*
* @var Resource[]
*/
public $Resources;
/**
* An array of components for the package
*
* @var Component[]
*/
public $Components;
/**
* Public Constructor
*/
public function __construct()
{
$this->MagicBytes = new MagicBytes();
$this->Components = [];
$this->Dependencies = [];
$this->Resources = [];
}
/**
* Validates the package object and returns True if the package contains the correct information
*
* Returns false if the package contains incorrect information which can cause
* an error when compiling the package.
*
* @param bool $throw_exception
* @return bool
* @throws InvalidPackageException
* @throws InvalidProjectConfigurationException
*/
public function validate(bool $throw_exception=True): bool
{
// Validate the MagicBytes constructor
if($this->MagicBytes == null)
{
if($throw_exception)
throw new InvalidPackageException('The MagicBytes property is required and cannot be null');
return false;
}
// Validate the assembly object
if($this->Assembly == null)
{
if($throw_exception)
throw new InvalidPackageException('The Assembly property is required and cannot be null');
return false;
}
if(!$this->Assembly->validate($throw_exception))
return false;
// All checks have passed
return true;
}
/**
* Constructs an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
$_components = [];
/** @var Component $component */
foreach($this->Components as $component)
$_components[] = $component->toArray($bytecode);
$_dependencies = [];
/** @var Dependency $dependency */
foreach($this->Dependencies as $dependency)
$_dependencies[] = $dependency->toArray($bytecode);
$_resources = [];
/** @var Resource $resource */
foreach($this->Resources as $resource)
$_resources[] = $resource->toArray($bytecode);
return [
($bytecode ? Functions::cbc('header') : 'header') => $this->Header->toArray($bytecode),
($bytecode ? Functions::cbc('assembly') : 'assembly') => $this->Assembly->toArray($bytecode),
($bytecode ? Functions::cbc('dependencies') : 'dependencies') => $_dependencies,
($bytecode ? Functions::cbc('main_execution_policy') : 'main_execution_policy') => $this->MainExecutionPolicy->toArray($bytecode),
($bytecode ? Functions::cbc('installer') : 'installer') => $this->Installer->toArray($bytecode),
($bytecode ? Functions::cbc('resources') : 'resources') => $_resources,
($bytecode ? Functions::cbc('components') : 'components') => $_components
];
}
/**
* @param array $data
* @return Package
*/
public static function fromArray(array $data): self
{
$object = new self();
$object->Header = Functions::array_bc($data, 'header');
if($object->Header !== null)
$object->Header = Header::fromArray($object->Header);
$object->Assembly = Functions::array_bc($data, 'assembly');
if($object->Assembly !== null)
$object->Assembly = Assembly::fromArray($object->Assembly);
$object->MainExecutionPolicy = Functions::array_bc($data, 'main_execution_policy');
if($object->MainExecutionPolicy !== null)
$object->MainExecutionPolicy = MainExecutionPolicy::fromArray($object->MainExecutionPolicy);
$object->Installer = Functions::array_bc($data, 'installer');
if($object->Installer !== null)
$object->Installer = Installer::fromArray($object->Installer);
$_dependencies = Functions::array_bc($data, 'dependencies');
if($_dependencies !== null)
{
foreach($_dependencies as $dependency)
{
$object->Dependencies[] = Resource::fromArray($dependency);
}
}
$_resources = Functions::array_bc($data, 'resources');
if($_resources !== null)
{
foreach($_resources as $resource)
{
$object->Resources[] = Resource::fromArray($resource);
}
}
$_components = Functions::array_bc($data, 'components');
if($_components !== null)
{
foreach($_components as $component)
{
$object->Components[] = Component::fromArray($component);
}
}
return $object;
}
}

View file

@ -0,0 +1,91 @@
<?php
namespace ncc\Objects\Package;
use ncc\Utilities\Functions;
class Component
{
/**
* The name of the component or the file name of the component
*
* @var string
*/
public $Name;
/**
* Flags associated with the component created by the compiler extension
*
* @var array
*/
public $Flags;
/**
* A sha1 hash checksum of the component, this will be compared against the data to determine
* the integrity of the component to ensure that the component is not corrupted.
*
* @var string
*/
public $Checksum;
/**
* The raw data of the component, this is to be processed by the compiler extension
*
* @var string
*/
public $Data;
/**
* Validates the checksum of the component, returns false if the checksum or data is invalid or if the checksum
* failed.
*
* @return bool
*/
public function validateChecksum(): bool
{
if($this->Checksum === null)
return false;
if($this->Data === null)
return false;
if(hash('sha1', $this->Data) !== $this->Checksum)
return false;
return true;
}
/**
* Returns an array representation of the component.
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('name') : 'name') => $this->Name,
($bytecode ? Functions::cbc('flags') : 'flags') => $this->Flags,
($bytecode ? Functions::cbc('checksum') : 'checksum') => $this->Checksum,
($bytecode ? Functions::cbc('data') : 'data') => $this->Data,
];
}
/**
* Constructs a new object from an array representation
*
* @param array $data
* @return Component
*/
public static function fromArray(array $data): self
{
$Object = new self();
$Object->Name = Functions::array_bc($data, 'name');
$Object->Flags = Functions::array_bc($data, 'flags');
$Object->Checksum = Functions::array_bc($data, 'checksum');
$Object->Data = Functions::array_bc($data, 'data');
return $Object;
}
}

View file

@ -0,0 +1,73 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Objects\ProjectConfiguration\Compiler;
use ncc\Utilities\Functions;
class Header
{
/**
* The compiler extension information that was used to build the package
*
* @var Compiler
*/
public $CompilerExtension;
/**
* An array of constants that are set when the package is imported or executed during runtime.
*
* @var array
*/
public $RuntimeConstants;
/**
* The version of NCC that was used to compile the package, can be used for backwards compatibility
*
* @var string
*/
public $CompilerVersion;
/**
* Public Constructor
*/
public function __construct()
{
$this->CompilerExtension = new Compiler();
$this->RuntimeConstants = [];
}
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('compiler_extension') : 'compiler_extension') => $this->CompilerExtension->toArray($bytecode),
($bytecode ? Functions::cbc('runtime_constants') : 'runtime_constants') => $this->RuntimeConstants,
($bytecode ? Functions::cbc('compiler_version') : 'compiler_version') => $this->CompilerVersion,
];
}
/**
* Constructs the object from an array representation
*
* @param array $data
* @return static
*/
public static function fromArray(array $data): self
{
$object = new self();
$object->CompilerExtension = Functions::array_bc($data, 'compiler_extension');
$object->RuntimeConstants = Functions::array_bc($data, 'runtime_constants');
$object->CompilerVersion = Functions::array_bc($data, 'compiler_version');
return $object;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace ncc\Objects\Package;
class Installer
{
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return Installer
*/
public static function fromArray(array $data): self
{
$object = new self();
return $object;
}
}

View file

@ -0,0 +1,153 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Abstracts\EncoderType;
use ncc\Abstracts\Versions;
class MagicBytes
{
/**
* The version of the package structure standard that is used
*
* @var string
*/
public $PackageStructureVersion;
/**
* The type of encoder that was used to encode the package structure
*
* @var string|EncoderType
*/
public $Encoder;
/**
* Indicates whether the package structure is compressed
*
* @var bool
*/
public $IsCompressed;
/**
* Indicates whether the package structure is encrypted
*
* @var bool
*/
public $IsEncrypted;
/**
* Indicates whether the package is installable
*
* @var bool
*/
public $IsInstallable;
/**
* Indicates whether the package is a executable
*
* @var bool
*/
public $IsExecutable;
/**
* Basic Public Constructor with default values
*/
public function __construct()
{
$this->PackageStructureVersion = Versions::PackageStructureVersion;
$this->Encoder = EncoderType::ZiProto;
$this->IsCompressed = false;
$this->IsEncrypted = false;
$this->IsInstallable = false;
$this->IsExecutable = false;
}
/**
* Returns an array representation of the object
*
* @return array
*/
public function toArray(): array
{
return [
'package_structure_version' => $this->PackageStructureVersion,
'encoder' => $this->Encoder,
'is_compressed' => $this->IsCompressed,
'is_encrypted' => $this->IsEncrypted,
'is_installable' => $this->IsInstallable,
'is_executable' => $this->IsExecutable
];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return MagicBytes
*/
public static function fromArray(array $data): self
{
$Object = new self();
if(isset($data['is_executable']))
$Object->IsExecutable = (bool)$data['is_executable'];
return $Object;
}
/**
* Builds and returns the string representation of the magic bytes
*
* @return string
*/
public function toString(): string
{
// NCC_PACKAGE1.0
$magic_bytes = 'NCC_PACKAGE' . $this->PackageStructureVersion;
// NCC_PACKAGE1.03
$magic_bytes .= $this->Encoder;
if($this->IsEncrypted)
{
// NCC_PACKAGE1.031
$magic_bytes .= '1';
}
else
{
// NCC_PACKAGE1.030
$magic_bytes .= '0';
}
if($this->IsCompressed)
{
// NCC_PACKAGE1.0301
$magic_bytes .= '1';
}
else
{
// NCC_PACKAGE1.0300
$magic_bytes .= '0';
}
if($this->IsExecutable && $this->IsInstallable)
{
// NCC_PACKAGE1.030142
$magic_bytes .= '42';
}
elseif($this->IsExecutable)
{
// NCC_PACKAGE1.030141
$magic_bytes .= '41';
}
elseif($this->IsInstallable)
{
// NCC_PACKAGE1.030140
$magic_bytes .= '40';
}
return $magic_bytes;
}
}

View file

@ -0,0 +1,30 @@
<?php
namespace ncc\Objects\Package;
class MainExecutionPolicy
{
/**
* Returns an array representation of the object
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [];
}
/**
* Constructs object from an array representation
*
* @param array $data
* @return MainExecutionPolicy
*/
public static function fromArray(array $data): self
{
$object = new self();
return $object;
}
}

View file

@ -0,0 +1,84 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Objects\Package;
use ncc\Utilities\Functions;
class Resource
{
/**
* The file/path name of the resource
*
* @var string
*/
public $Name;
/**
* A sha1 hash checksum of the resource, this will be compared against the data to determine
* the integrity of the resource to ensure that the resource is not corrupted.
*
* @var string
*/
public $Checksum;
/**
* The raw data of the resource
*
* @var string
*/
public $Data;
/**
* Validates the checksum of the resource, returns false if the checksum or data is invalid or if the checksum
* failed.
*
* @return bool
*/
public function validateChecksum(): bool
{
if($this->Checksum === null)
return false;
if($this->Data === null)
return false;
if(hash('sha1', $this->Data) !== $this->Checksum)
return false;
return true;
}
/**
* Returns an array representation of the resource.
*
* @param bool $bytecode
* @return array
*/
public function toArray(bool $bytecode=false): array
{
return [
($bytecode ? Functions::cbc('name') : 'name') => $this->Name,
($bytecode ? Functions::cbc('checksum') : 'checksum') => $this->Checksum,
($bytecode ? Functions::cbc('data') : 'data') => $this->Data,
];
}
/**
* Constructs a new object from an array representation
*
* @param array $data
* @return Resource
*/
public static function fromArray(array $data): self
{
$object = new self();
$object->Name = Functions::array_bc($data, 'name');
$object->Checksum = Functions::array_bc($data, 'checksum');
$object->Data = Functions::array_bc($data, 'data');
return $object;
}
}

View file

@ -54,7 +54,7 @@
* @return bool
* @throws InvalidProjectConfigurationException
*/
public function validate(bool $throw_exception=false): bool
public function validate(bool $throw_exception=True): bool
{
if(!$this->Assembly->validate($throw_exception))
return false;

View file

@ -83,9 +83,9 @@
* @return bool
* @throws InvalidProjectConfigurationException
*/
public function validate(bool $throw_exception=false): bool
public function validate(bool $throw_exception=True): bool
{
if(preg_match(RegexPatterns::UUIDv4, $this->UUID) == false)
if(!preg_match(RegexPatterns::UUIDv4, $this->UUID))
{
if($throw_exception)
throw new InvalidProjectConfigurationException('The UUID is not a valid v4 UUID', 'Assembly.UUID');
@ -93,7 +93,7 @@
return false;
}
if(Validate::version($this->Version) == false)
if(!Validate::version($this->Version))
{
if($throw_exception)
throw new InvalidProjectConfigurationException('The version number is invalid', 'Assembly.Version');
@ -101,7 +101,7 @@
return false;
}
if(preg_match(RegexPatterns::PackageNameFormat, $this->Package) == false)
if(!preg_match(RegexPatterns::PackageNameFormat, $this->Package))
{
if($throw_exception)
throw new InvalidProjectConfigurationException('The package name is invalid', 'Assembly.Package');

View file

@ -4,6 +4,8 @@
namespace ncc\Objects\ProjectConfiguration;
use ncc\Exceptions\BuildConfigurationNotFoundException;
use ncc\Exceptions\InvalidProjectBuildConfiguration;
use ncc\Utilities\Functions;
/**
@ -80,6 +82,71 @@
$this->Configurations = [];
}
/**
* Validates the build configuration object
*
* @param bool $throw_exception
* @return bool
* @throws InvalidProjectBuildConfiguration
*/
public function validate(bool $throw_exception=True): bool
{
// TODO: Implement further validation logic
// Check for duplicate configuration names
$build_configurations = [];
foreach($this->Configurations as $configuration)
{
if(in_array($configuration->Name, $build_configurations))
{
if($throw_exception)
throw new InvalidProjectBuildConfiguration('The build configuration \'' . $configuration->Name . '\' is already defined, build configuration names must be unique');
return false;
}
}
return true;
}
/**
* Returns an array of all the build configurations defined in the project configuration
*
* @return array
*/
public function getBuildConfigurations(): array
{
$build_configurations = [];
foreach($this->Configurations as $configuration)
{
$build_configurations[] = $configuration->Name;
}
return $build_configurations;
}
/**
* Returns the build configurations defined in the project configuration, throw an
* exception if there is no such configuration defined in the project configuration
*
* @param string $name
* @return BuildConfiguration
* @throws BuildConfigurationNotFoundException
*/
public function getBuildConfiguration(string $name): BuildConfiguration
{
foreach($this->Configurations as $configuration)
{
if($configuration->Name == $name)
{
return $configuration;
}
}
throw new BuildConfigurationNotFoundException('The build configuration ' . $name . ' does not exist');
}
/**
* Returns an array representation of the object
*

View file

@ -1,10 +1,14 @@
<?php
/** @noinspection PhpMissingFieldTypeInspection */
namespace ncc\Runtime;
use ncc\Exceptions\ConstantReadonlyException;
use ncc\Exceptions\InvalidConstantNameException;
use ncc\Objects\Constant;
use ncc\Utilities\Resolver;
use ncc\Utilities\Validate;
class Constants
{
@ -24,10 +28,13 @@
* @param bool $readonly Indicates if the constant cannot be changed with the registerConstant function once it's registered
* @return void
* @throws ConstantReadonlyException
* @throws InvalidConstantNameException
*/
public static function register(string $scope, string $name, string $value, bool $readonly=false)
public static function register(string $scope, string $name, string $value, bool $readonly=false): void
{
// TODO: Add functionality to convert the constant name to be more memory-friendly with a size limit
if(!Validate::constantName($name))
throw new InvalidConstantNameException('The name specified is not valid for a constant name');
$constant_hash = Resolver::resolveConstantHash($scope, $name);
if(isset(self::$Constants[$constant_hash]))
@ -47,8 +54,11 @@
* @return void
* @throws ConstantReadonlyException
*/
public static function delete(string $scope, string $name)
public static function delete(string $scope, string $name): void
{
if(!Validate::constantName($name))
return;
$constant_hash = Resolver::resolveConstantHash($scope, $name);
if(isset(self::$Constants[$constant_hash]) && self::$Constants[$constant_hash]->isReadonly())

View file

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\polyfill as p;
use ncc\ThirdParty\Symfony\ctype as p;
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';

View file

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\polyfill as p;
use ncc\ThirdParty\Symfony\ctype as p;
if (!function_exists('ctype_alnum')) {
function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }

View file

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\polyfill as p;
use ncc\ThirdParty\Symfony\mbstring as p;
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';

View file

@ -9,7 +9,7 @@
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\polyfill as p;
use ncc\ThirdParty\Symfony\mbstring as p;
if (!function_exists('mb_convert_encoding')) {
function mb_convert_encoding(array|string|null $string, ?string $to_encoding, array|string|null $from_encoding = null): array|string|false { return p\Mbstring::mb_convert_encoding($string ?? '', (string) $to_encoding, $from_encoding); }

View file

@ -0,0 +1,19 @@
Copyright (c) 2018-2019 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,12 @@
Symfony Polyfill / Uuid
========================
This component provides `uuid_*` functions to users who run PHP versions without the uuid extension.
More information can be found in the
[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
License
=======
This library is released under the [MIT license](LICENSE).

View file

@ -0,0 +1,531 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ncc\ThirdParty\Symfony\uuid;
/**
* @internal
*
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
final class Uuid
{
public const UUID_VARIANT_NCS = 0;
public const UUID_VARIANT_DCE = 1;
public const UUID_VARIANT_MICROSOFT = 2;
public const UUID_VARIANT_OTHER = 3;
public const UUID_TYPE_DEFAULT = 0;
public const UUID_TYPE_TIME = 1;
public const UUID_TYPE_MD5 = 3;
public const UUID_TYPE_DCE = 4; // Deprecated alias
public const UUID_TYPE_NAME = 1; // Deprecated alias
public const UUID_TYPE_RANDOM = 4;
public const UUID_TYPE_SHA1 = 5;
public const UUID_TYPE_NULL = -1;
public const UUID_TYPE_INVALID = -42;
// https://tools.ietf.org/html/rfc4122#section-4.1.4
// 0x01b21dd213814000 is the number of 100-ns intervals between the
// UUID epoch 1582-10-15 00:00:00 and the Unix epoch 1970-01-01 00:00:00.
public const TIME_OFFSET_INT = 0x01b21dd213814000;
public const TIME_OFFSET_BIN = "\x01\xb2\x1d\xd2\x13\x81\x40\x00";
public const TIME_OFFSET_COM = "\xfe\x4d\xe2\x2d\xec\x7e\xc0\x00";
public static function uuid_create($uuid_type = \UUID_TYPE_DEFAULT)
{
if (!is_numeric($uuid_type) && null !== $uuid_type) {
trigger_error(sprintf('uuid_create() expects parameter 1 to be int, %s given', \gettype($uuid_type)), \E_USER_WARNING);
return null;
}
switch ((int) $uuid_type) {
case self::UUID_TYPE_NAME:
case self::UUID_TYPE_TIME:
return self::uuid_generate_time();
case self::UUID_TYPE_DCE:
case self::UUID_TYPE_RANDOM:
case self::UUID_TYPE_DEFAULT:
return self::uuid_generate_random();
default:
trigger_error(sprintf("Unknown/invalid UUID type '%d' requested, using default type instead", $uuid_type), \E_USER_WARNING);
return self::uuid_generate_random();
}
}
public static function uuid_generate_md5($uuid_ns, $name)
{
if (!\is_string($uuid_ns = self::toString($uuid_ns))) {
trigger_error(sprintf('uuid_generate_md5() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
return null;
}
if (!\is_string($name = self::toString($name))) {
trigger_error(sprintf('uuid_generate_md5() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
return null;
}
if (!self::isValid($uuid_ns)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_generate_md5(): Argument #1 ($uuid_ns) UUID expected');
}
$hash = md5(hex2bin(str_replace('-', '', $uuid_ns)).$name);
return sprintf('%08s-%04s-3%03s-%04x-%012s',
// 32 bits for "time_low"
substr($hash, 0, 8),
// 16 bits for "time_mid"
substr($hash, 8, 4),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 3
substr($hash, 13, 3),
// 16 bits:
// * 8 bits for "clk_seq_hi_res",
// * 8 bits for "clk_seq_low",
hexdec(substr($hash, 16, 4)) & 0x3fff | 0x8000,
// 48 bits for "node"
substr($hash, 20, 12)
);
}
public static function uuid_generate_sha1($uuid_ns, $name)
{
if (!\is_string($uuid_ns = self::toString($uuid_ns))) {
trigger_error(sprintf('uuid_generate_sha1() expects parameter 1 to be string, %s given', \gettype($uuid_ns)), \E_USER_WARNING);
return null;
}
if (!\is_string($name = self::toString($name))) {
trigger_error(sprintf('uuid_generate_sha1() expects parameter 2 to be string, %s given', \gettype($name)), \E_USER_WARNING);
return null;
}
if (!self::isValid($uuid_ns)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_generate_sha1(): Argument #1 ($uuid_ns) UUID expected');
}
$hash = sha1(hex2bin(str_replace('-', '', $uuid_ns)).$name);
return sprintf('%08s-%04s-5%03s-%04x-%012s',
// 32 bits for "time_low"
substr($hash, 0, 8),
// 16 bits for "time_mid"
substr($hash, 8, 4),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 5
substr($hash, 13, 3),
// 16 bits:
// * 8 bits for "clk_seq_hi_res",
// * 8 bits for "clk_seq_low",
// WARNING: On old libuuid version, there is a bug. 0x0fff is used instead of 0x3fff
// See https://github.com/karelzak/util-linux/commit/d6ddf07d31dfdc894eb8e7e6842aa856342c526e
hexdec(substr($hash, 16, 4)) & 0x3fff | 0x8000,
// 48 bits for "node"
substr($hash, 20, 12)
);
}
public static function uuid_is_valid($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_is_valid() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
return self::isValid($uuid);
}
public static function uuid_compare($uuid1, $uuid2)
{
if (!\is_string($uuid1 = self::toString($uuid1))) {
trigger_error(sprintf('uuid_compare() expects parameter 1 to be string, %s given', \gettype($uuid1)), \E_USER_WARNING);
return null;
}
if (!\is_string($uuid2 = self::toString($uuid2))) {
trigger_error(sprintf('uuid_compare() expects parameter 2 to be string, %s given', \gettype($uuid2)), \E_USER_WARNING);
return null;
}
if (!self::isValid($uuid1)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_compare(): Argument #1 ($uuid1) UUID expected');
}
if (!self::isValid($uuid2)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_compare(): Argument #2 ($uuid2) UUID expected');
}
return strcasecmp($uuid1, $uuid2);
}
public static function uuid_is_null($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_is_null() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
if (80000 <= \PHP_VERSION_ID && !self::isValid($uuid)) {
throw new \ValueError('uuid_is_null(): Argument #1 ($uuid) UUID expected');
}
return '00000000-0000-0000-0000-000000000000' === $uuid;
}
public static function uuid_type($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_type() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
if ('00000000-0000-0000-0000-000000000000' === $uuid) {
return self::UUID_TYPE_NULL;
}
if (null === $parsed = self::parse($uuid)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_type(): Argument #1 ($uuid) UUID expected');
}
return $parsed['version'];
}
public static function uuid_variant($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_variant() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
if ('00000000-0000-0000-0000-000000000000' === $uuid) {
return self::UUID_TYPE_NULL;
}
if (null === $parsed = self::parse($uuid)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_variant(): Argument #1 ($uuid) UUID expected');
}
if (($parsed['clock_seq'] & 0x8000) === 0) {
return self::UUID_VARIANT_NCS;
}
if (($parsed['clock_seq'] & 0x4000) === 0) {
return self::UUID_VARIANT_DCE;
}
if (($parsed['clock_seq'] & 0x2000) === 0) {
return self::UUID_VARIANT_MICROSOFT;
}
return self::UUID_VARIANT_OTHER;
}
public static function uuid_time($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_time() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
$parsed = self::parse($uuid);
if (self::UUID_TYPE_TIME !== ($parsed['version'] ?? null)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_time(): Argument #1 ($uuid) UUID DCE TIME expected');
}
if (\PHP_INT_SIZE >= 8) {
return intdiv(hexdec($parsed['time']) - self::TIME_OFFSET_INT, 10000000);
}
$time = str_pad(hex2bin($parsed['time']), 8, "\0", \STR_PAD_LEFT);
$time = self::binaryAdd($time, self::TIME_OFFSET_COM);
$time[0] = $time[0] & "\x7F";
return (int) substr(self::toDecimal($time), 0, -7);
}
public static function uuid_mac($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_mac() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
$parsed = self::parse($uuid);
if (self::UUID_TYPE_TIME !== ($parsed['version'] ?? null)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_mac(): Argument #1 ($uuid) UUID DCE TIME expected');
}
return strtr($parsed['node'], 'ABCDEF', 'abcdef');
}
public static function uuid_parse($uuid)
{
if (!\is_string($uuid = self::toString($uuid))) {
trigger_error(sprintf('uuid_parse() expects parameter 1 to be string, %s given', \gettype($uuid)), \E_USER_WARNING);
return null;
}
if (!self::isValid($uuid)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_parse(): Argument #1 ($uuid) UUID expected');
}
return hex2bin(str_replace('-', '', $uuid));
}
public static function uuid_unparse($bytes)
{
if (!\is_string($bytes = self::toString($bytes))) {
trigger_error(sprintf('uuid_unparse() expects parameter 1 to be string, %s given', \gettype($bytes)), \E_USER_WARNING);
return null;
}
if (16 !== \strlen($bytes)) {
if (80000 > \PHP_VERSION_ID) {
return false;
}
throw new \ValueError('uuid_unparse(): Argument #1 ($uuid) UUID expected');
}
$uuid = bin2hex($bytes);
$uuid = substr_replace($uuid, '-', 8, 0);
$uuid = substr_replace($uuid, '-', 13, 0);
$uuid = substr_replace($uuid, '-', 18, 0);
return substr_replace($uuid, '-', 23, 0);
}
private static function uuid_generate_random()
{
$uuid = bin2hex(random_bytes(16));
return sprintf('%08s-%04s-4%03s-%04x-%012s',
// 32 bits for "time_low"
substr($uuid, 0, 8),
// 16 bits for "time_mid"
substr($uuid, 8, 4),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 4
substr($uuid, 13, 3),
// 16 bits:
// * 8 bits for "clk_seq_hi_res",
// * 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
hexdec(substr($uuid, 16, 4)) & 0x3fff | 0x8000,
// 48 bits for "node"
substr($uuid, 20, 12)
);
}
/**
* @see http://tools.ietf.org/html/rfc4122#section-4.2.2
*/
private static function uuid_generate_time()
{
$time = microtime(false);
$time = substr($time, 11).substr($time, 2, 7);
if (\PHP_INT_SIZE >= 8) {
$time = str_pad(dechex($time + self::TIME_OFFSET_INT), 16, '0', \STR_PAD_LEFT);
} else {
$time = str_pad(self::toBinary($time), 8, "\0", \STR_PAD_LEFT);
$time = self::binaryAdd($time, self::TIME_OFFSET_BIN);
$time = bin2hex($time);
}
// https://tools.ietf.org/html/rfc4122#section-4.1.5
// We are using a random data for the sake of simplicity: since we are
// not able to get a super precise timeOfDay as a unique sequence
$clockSeq = random_int(0, 0x3fff);
static $node;
if (null === $node) {
if (\function_exists('apcu_fetch')) {
$node = apcu_fetch('__symfony_uuid_node');
if (false === $node) {
$node = sprintf('%06x%06x',
random_int(0, 0xffffff) | 0x010000,
random_int(0, 0xffffff)
);
apcu_store('__symfony_uuid_node', $node);
}
} else {
$node = sprintf('%06x%06x',
random_int(0, 0xffffff) | 0x010000,
random_int(0, 0xffffff)
);
}
}
return sprintf('%08s-%04s-1%03s-%04x-%012s',
// 32 bits for "time_low"
substr($time, -8),
// 16 bits for "time_mid"
substr($time, -12, 4),
// 16 bits for "time_hi_and_version",
// four most significant bits holds version number 1
substr($time, -15, 3),
// 16 bits:
// * 8 bits for "clk_seq_hi_res",
// * 8 bits for "clk_seq_low",
// two most significant bits holds zero and one for variant DCE1.1
$clockSeq | 0x8000,
// 48 bits for "node"
$node
);
}
private static function isValid($uuid)
{
return (bool) preg_match('{^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$}Di', $uuid);
}
private static function parse($uuid)
{
if (!preg_match('{^(?<time_low>[0-9a-f]{8})-(?<time_mid>[0-9a-f]{4})-(?<version>[0-9a-f])(?<time_hi>[0-9a-f]{3})-(?<clock_seq>[0-9a-f]{4})-(?<node>[0-9a-f]{12})$}Di', $uuid, $matches)) {
return null;
}
return [
'time' => '0'.$matches['time_hi'].$matches['time_mid'].$matches['time_low'],
'version' => hexdec($matches['version']),
'clock_seq' => hexdec($matches['clock_seq']),
'node' => $matches['node'],
];
}
private static function toString($v)
{
if (\is_string($v) || null === $v || (\is_object($v) ? method_exists($v, '__toString') : is_scalar($v))) {
return (string) $v;
}
return $v;
}
private static function toBinary($digits)
{
$bytes = '';
$count = \strlen($digits);
while ($count) {
$quotient = [];
$remainder = 0;
for ($i = 0; $i !== $count; ++$i) {
$carry = $digits[$i] + $remainder * 10;
$digit = $carry >> 8;
$remainder = $carry & 0xFF;
if ($digit || $quotient) {
$quotient[] = $digit;
}
}
$bytes = \chr($remainder).$bytes;
$count = \count($digits = $quotient);
}
return $bytes;
}
private static function toDecimal($bytes)
{
$digits = '';
$bytes = array_values(unpack('C*', $bytes));
while ($count = \count($bytes)) {
$quotient = [];
$remainder = 0;
for ($i = 0; $i !== $count; ++$i) {
$carry = $bytes[$i] + ($remainder << 8);
$digit = (int) ($carry / 10);
$remainder = $carry % 10;
if ($digit || $quotient) {
$quotient[] = $digit;
}
}
$digits = $remainder.$digits;
$bytes = $quotient;
}
return $digits;
}
private static function binaryAdd($a, $b)
{
$sum = 0;
for ($i = 7; 0 <= $i; --$i) {
$sum += \ord($a[$i]) + \ord($b[$i]);
$a[$i] = \chr($sum & 0xFF);
$sum >>= 8;
}
return $a;
}
}

View file

@ -0,0 +1 @@
1.26.0

View file

@ -0,0 +1,97 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\uuid as p;
if (extension_loaded('uuid')) {
return;
}
if (\PHP_VERSION_ID >= 80000) {
return require __DIR__.'/bootstrap80.php';
}
if (!defined('UUID_VARIANT_NCS')) {
define('UUID_VARIANT_NCS', 0);
}
if (!defined('UUID_VARIANT_DCE')) {
define('UUID_VARIANT_DCE', 1);
}
if (!defined('UUID_VARIANT_MICROSOFT')) {
define('UUID_VARIANT_MICROSOFT', 2);
}
if (!defined('UUID_VARIANT_OTHER')) {
define('UUID_VARIANT_OTHER', 3);
}
if (!defined('UUID_TYPE_DEFAULT')) {
define('UUID_TYPE_DEFAULT', 0);
}
if (!defined('UUID_TYPE_TIME')) {
define('UUID_TYPE_TIME', 1);
}
if (!defined('UUID_TYPE_MD5')) {
define('UUID_TYPE_MD5', 3);
}
if (!defined('UUID_TYPE_DCE')) {
define('UUID_TYPE_DCE', 4); // Deprecated alias
}
if (!defined('UUID_TYPE_NAME')) {
define('UUID_TYPE_NAME', 1); // Deprecated alias
}
if (!defined('UUID_TYPE_RANDOM')) {
define('UUID_TYPE_RANDOM', 4);
}
if (!defined('UUID_TYPE_SHA1')) {
define('UUID_TYPE_SHA1', 5);
}
if (!defined('UUID_TYPE_NULL')) {
define('UUID_TYPE_NULL', -1);
}
if (!defined('UUID_TYPE_INVALID')) {
define('UUID_TYPE_INVALID', -42);
}
if (!function_exists('uuid_create')) {
function uuid_create($uuid_type = \UUID_TYPE_DEFAULT) { return p\Uuid::uuid_create($uuid_type); }
}
if (!function_exists('uuid_generate_md5')) {
function uuid_generate_md5($uuid_ns, $name) { return p\Uuid::uuid_generate_md5($uuid_ns, $name); }
}
if (!function_exists('uuid_generate_sha1')) {
function uuid_generate_sha1($uuid_ns, $name) { return p\Uuid::uuid_generate_sha1($uuid_ns, $name); }
}
if (!function_exists('uuid_is_valid')) {
function uuid_is_valid($uuid) { return p\Uuid::uuid_is_valid($uuid); }
}
if (!function_exists('uuid_compare')) {
function uuid_compare($uuid1, $uuid2) { return p\Uuid::uuid_compare($uuid1, $uuid2); }
}
if (!function_exists('uuid_is_null')) {
function uuid_is_null($uuid) { return p\Uuid::uuid_is_null($uuid); }
}
if (!function_exists('uuid_type')) {
function uuid_type($uuid) { return p\Uuid::uuid_type($uuid); }
}
if (!function_exists('uuid_variant')) {
function uuid_variant($uuid) { return p\Uuid::uuid_variant($uuid); }
}
if (!function_exists('uuid_time')) {
function uuid_time($uuid) { return p\Uuid::uuid_time($uuid); }
}
if (!function_exists('uuid_mac')) {
function uuid_mac($uuid) { return p\Uuid::uuid_mac($uuid); }
}
if (!function_exists('uuid_parse')) {
function uuid_parse($uuid) { return p\Uuid::uuid_parse($uuid); }
}
if (!function_exists('uuid_unparse')) {
function uuid_unparse($uuid) { return p\Uuid::uuid_unparse($uuid); }
}

View file

@ -0,0 +1,89 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
use ncc\ThirdParty\Symfony\uuid as p;
if (!defined('UUID_VARIANT_NCS')) {
define('UUID_VARIANT_NCS', 0);
}
if (!defined('UUID_VARIANT_DCE')) {
define('UUID_VARIANT_DCE', 1);
}
if (!defined('UUID_VARIANT_MICROSOFT')) {
define('UUID_VARIANT_MICROSOFT', 2);
}
if (!defined('UUID_VARIANT_OTHER')) {
define('UUID_VARIANT_OTHER', 3);
}
if (!defined('UUID_TYPE_DEFAULT')) {
define('UUID_TYPE_DEFAULT', 0);
}
if (!defined('UUID_TYPE_TIME')) {
define('UUID_TYPE_TIME', 1);
}
if (!defined('UUID_TYPE_MD5')) {
define('UUID_TYPE_MD5', 3);
}
if (!defined('UUID_TYPE_DCE')) {
define('UUID_TYPE_DCE', 4); // Deprecated alias
}
if (!defined('UUID_TYPE_NAME')) {
define('UUID_TYPE_NAME', 1); // Deprecated alias
}
if (!defined('UUID_TYPE_RANDOM')) {
define('UUID_TYPE_RANDOM', 4);
}
if (!defined('UUID_TYPE_SHA1')) {
define('UUID_TYPE_SHA1', 5);
}
if (!defined('UUID_TYPE_NULL')) {
define('UUID_TYPE_NULL', -1);
}
if (!defined('UUID_TYPE_INVALID')) {
define('UUID_TYPE_INVALID', -42);
}
if (!function_exists('uuid_create')) {
function uuid_create(?int $uuid_type = \UUID_TYPE_DEFAULT): string { return p\Uuid::uuid_create((int) $uuid_type); }
}
if (!function_exists('uuid_generate_md5')) {
function uuid_generate_md5(?string $uuid_ns, ?string $name): string { return p\Uuid::uuid_generate_md5((string) $uuid_ns, (string) $name); }
}
if (!function_exists('uuid_generate_sha1')) {
function uuid_generate_sha1(?string $uuid_ns, ?string $name): string { return p\Uuid::uuid_generate_sha1((string) $uuid_ns, (string) $name); }
}
if (!function_exists('uuid_is_valid')) {
function uuid_is_valid(?string $uuid): bool { return p\Uuid::uuid_is_valid((string) $uuid); }
}
if (!function_exists('uuid_compare')) {
function uuid_compare(?string $uuid1, ?string $uuid2): int { return p\Uuid::uuid_compare((string) $uuid1, (string) $uuid2); }
}
if (!function_exists('uuid_is_null')) {
function uuid_is_null(?string $uuid): bool { return p\Uuid::uuid_is_null((string) $uuid); }
}
if (!function_exists('uuid_type')) {
function uuid_type(?string $uuid): int { return p\Uuid::uuid_type((string) $uuid); }
}
if (!function_exists('uuid_variant')) {
function uuid_variant(?string $uuid): int { return p\Uuid::uuid_variant((string) $uuid); }
}
if (!function_exists('uuid_time')) {
function uuid_time(?string $uuid): int { return p\Uuid::uuid_time((string) $uuid); }
}
if (!function_exists('uuid_mac')) {
function uuid_mac(?string $uuid): string { return p\Uuid::uuid_mac((string) $uuid); }
}
if (!function_exists('uuid_parse')) {
function uuid_parse(?string $uuid): string { return p\Uuid::uuid_parse((string) $uuid); }
}
if (!function_exists('uuid_unparse')) {
function uuid_unparse(?string $uuid): string { return p\Uuid::uuid_unparse((string) $uuid); }
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
interface Builder
{
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node;
}

View file

@ -0,0 +1,132 @@
<?php
declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Const_;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class ClassConst implements PhpParser\Builder
{
protected $flags = 0;
protected $attributes = [];
protected $constants = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a class constant builder
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*/
public function __construct($name, $value) {
$this->constants = [new Const_($name, BuilderHelpers::normalizeValue($value))];
}
/**
* Add another constant to const group
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*
* @return $this The builder instance (for fluid interface)
*/
public function addConst($name, $value) {
$this->constants[] = new Const_($name, BuilderHelpers::normalizeValue($value));
return $this;
}
/**
* Makes the constant public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the constant protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the constant private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the constant final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\ClassConst The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\ClassConst(
$this->constants,
$this->flags,
$this->attributes,
$this->attributeGroups
);
}
}

View file

@ -0,0 +1,146 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Class_ extends Declaration
{
protected $name;
protected $extends = null;
protected $implements = [];
protected $flags = 0;
protected $uses = [];
protected $constants = [];
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a class builder.
*
* @param string $name Name of the class
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Extends a class.
*
* @param Name|string $class Name of class to extend
*
* @return $this The builder instance (for fluid interface)
*/
public function extend($class) {
$this->extends = BuilderHelpers::normalizeName($class);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Makes the class abstract.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
return $this;
}
/**
* Makes the class final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
public function makeReadonly() {
$this->flags = BuilderHelpers::addClassModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\ClassConst::class => &$this->constants,
Stmt\Property::class => &$this->properties,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Class_ The built class node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Class_($this->name, [
'flags' => $this->flags,
'extends' => $this->extends,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->constants, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View file

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
abstract class Declaration implements PhpParser\Builder
{
protected $attributes = [];
abstract public function addStmt($stmt);
/**
* Adds multiple statements.
*
* @param array $stmts The statements to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmts(array $stmts) {
foreach ($stmts as $stmt) {
$this->addStmt($stmt);
}
return $this;
}
/**
* Sets doc comment for the declaration.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes['comments'] = [
BuilderHelpers::normalizeDocComment($docComment)
];
return $this;
}
}

View file

@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class EnumCase implements PhpParser\Builder
{
protected $name;
protected $value = null;
protected $attributes = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*/
public function __construct($name) {
$this->name = $name;
}
/**
* Sets the value.
*
* @param Node\Expr|string|int $value
*
* @return $this
*/
public function setValue($value) {
$this->value = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the constant.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built enum case node.
*
* @return Stmt\EnumCase The built constant node
*/
public function getNode(): PhpParser\Node {
return new Stmt\EnumCase(
$this->name,
$this->value,
$this->attributes,
$this->attributeGroups
);
}
}

View file

@ -0,0 +1,117 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Enum_ extends Declaration
{
protected $name;
protected $scalarType = null;
protected $implements = [];
protected $uses = [];
protected $enumCases = [];
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets the scalar type.
*
* @param string|Identifier $type
*
* @return $this
*/
public function setScalarType($scalarType) {
$this->scalarType = BuilderHelpers::normalizeType($scalarType);
return $this;
}
/**
* Implements one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to implement
*
* @return $this The builder instance (for fluid interface)
*/
public function implement(...$interfaces) {
foreach ($interfaces as $interface) {
$this->implements[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
$targets = [
Stmt\TraitUse::class => &$this->uses,
Stmt\EnumCase::class => &$this->enumCases,
Stmt\ClassConst::class => &$this->constants,
Stmt\ClassMethod::class => &$this->methods,
];
$class = \get_class($stmt);
if (!isset($targets[$class])) {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
$targets[$class][] = $stmt;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Enum_ The built enum node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Enum_($this->name, [
'scalarType' => $this->scalarType,
'implements' => $this->implements,
'stmts' => array_merge($this->uses, $this->enumCases, $this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View file

@ -0,0 +1,73 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
abstract class FunctionLike extends Declaration
{
protected $returnByRef = false;
protected $params = [];
/** @var string|Node\Name|Node\NullableType|null */
protected $returnType = null;
/**
* Make the function return by reference.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReturnByRef() {
$this->returnByRef = true;
return $this;
}
/**
* Adds a parameter.
*
* @param Node\Param|Param $param The parameter to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addParam($param) {
$param = BuilderHelpers::normalizeNode($param);
if (!$param instanceof Node\Param) {
throw new \LogicException(sprintf('Expected parameter node, got "%s"', $param->getType()));
}
$this->params[] = $param;
return $this;
}
/**
* Adds multiple parameters.
*
* @param array $params The parameters to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addParams(array $params) {
foreach ($params as $param) {
$this->addParam($param);
}
return $this;
}
/**
* Sets the return type for PHP 7.
*
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type
*
* @return $this The builder instance (for fluid interface)
*/
public function setReturnType($type) {
$this->returnType = BuilderHelpers::normalizeType($type);
return $this;
}
}

View file

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Function_ extends FunctionLike
{
protected $name;
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a function builder.
*
* @param string $name Name of the function
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built function node.
*
* @return Stmt\Function_ The built function node
*/
public function getNode() : Node {
return new Stmt\Function_($this->name, [
'byRef' => $this->returnByRef,
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View file

@ -0,0 +1,93 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Interface_ extends Declaration
{
protected $name;
protected $extends = [];
protected $constants = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Extends one or more interfaces.
*
* @param Name|string ...$interfaces Names of interfaces to extend
*
* @return $this The builder instance (for fluid interface)
*/
public function extend(...$interfaces) {
foreach ($interfaces as $interface) {
$this->extends[] = BuilderHelpers::normalizeName($interface);
}
return $this;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
if ($stmt instanceof Stmt\ClassConst) {
$this->constants[] = $stmt;
} elseif ($stmt instanceof Stmt\ClassMethod) {
// we erase all statements in the body of an interface method
$stmt->stmts = null;
$this->methods[] = $stmt;
} else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built interface node.
*
* @return Stmt\Interface_ The built interface node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Interface_($this->name, [
'extends' => $this->extends,
'stmts' => array_merge($this->constants, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View file

@ -0,0 +1,146 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Method extends FunctionLike
{
protected $name;
protected $flags = 0;
/** @var array|null */
protected $stmts = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a method builder.
*
* @param string $name Name of the method
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Makes the method public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the method protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the method private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the method static.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeStatic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
return $this;
}
/**
* Makes the method abstract.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeAbstract() {
if (!empty($this->stmts)) {
throw new \LogicException('Cannot make method with statements abstract');
}
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_ABSTRACT);
$this->stmts = null; // abstract methods don't have statements
return $this;
}
/**
* Makes the method final.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeFinal() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_FINAL);
return $this;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
if (null === $this->stmts) {
throw new \LogicException('Cannot add statements to an abstract method');
}
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built method node.
*
* @return Stmt\ClassMethod The built method node
*/
public function getNode() : Node {
return new Stmt\ClassMethod($this->name, [
'flags' => $this->flags,
'byRef' => $this->returnByRef,
'params' => $this->params,
'returnType' => $this->returnType,
'stmts' => $this->stmts,
'attrGroups' => $this->attributeGroups,
], $this->attributes);
}
}

View file

@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Namespace_ extends Declaration
{
private $name;
private $stmts = [];
/**
* Creates a namespace builder.
*
* @param Node\Name|string|null $name Name of the namespace
*/
public function __construct($name) {
$this->name = null !== $name ? BuilderHelpers::normalizeName($name) : null;
}
/**
* Adds a statement.
*
* @param Node|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$this->stmts[] = BuilderHelpers::normalizeStmt($stmt);
return $this;
}
/**
* Returns the built node.
*
* @return Stmt\Namespace_ The built node
*/
public function getNode() : Node {
return new Stmt\Namespace_($this->name, $this->stmts, $this->attributes);
}
}

View file

@ -0,0 +1,122 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
class Param implements PhpParser\Builder
{
protected $name;
protected $default = null;
/** @var Node\Identifier|Node\Name|Node\NullableType|null */
protected $type = null;
protected $byRef = false;
protected $variadic = false;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a parameter builder.
*
* @param string $name Name of the parameter
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Sets default value for the parameter.
*
* @param mixed $value Default value to use
*
* @return $this The builder instance (for fluid interface)
*/
public function setDefault($value) {
$this->default = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
if ($this->type == 'void') {
throw new \LogicException('Parameter type cannot be void');
}
return $this;
}
/**
* Sets type for the parameter.
*
* @param string|Node\Name|Node\Identifier|Node\ComplexType $type Parameter type
*
* @return $this The builder instance (for fluid interface)
*
* @deprecated Use setType() instead
*/
public function setTypeHint($type) {
return $this->setType($type);
}
/**
* Make the parameter accept the value by reference.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeByRef() {
$this->byRef = true;
return $this;
}
/**
* Make the parameter variadic
*
* @return $this The builder instance (for fluid interface)
*/
public function makeVariadic() {
$this->variadic = true;
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built parameter node.
*
* @return Node\Param The built parameter node
*/
public function getNode() : Node {
return new Node\Param(
new Node\Expr\Variable($this->name),
$this->default, $this->type, $this->byRef, $this->variadic, [], 0, $this->attributeGroups
);
}
}

View file

@ -0,0 +1,161 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
use ncc\ThirdParty\nikic\PhpParser\Node\ComplexType;
class Property implements PhpParser\Builder
{
protected $name;
protected $flags = 0;
protected $default = null;
protected $attributes = [];
/** @var null|Identifier|Name|NullableType */
protected $type;
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates a property builder.
*
* @param string $name Name of the property
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Makes the property public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Makes the property protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Makes the property private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Makes the property static.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeStatic() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_STATIC);
return $this;
}
/**
* Makes the property readonly.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeReadonly() {
$this->flags = BuilderHelpers::addModifier($this->flags, Stmt\Class_::MODIFIER_READONLY);
return $this;
}
/**
* Sets default value for the property.
*
* @param mixed $value Default value to use
*
* @return $this The builder instance (for fluid interface)
*/
public function setDefault($value) {
$this->default = BuilderHelpers::normalizeValue($value);
return $this;
}
/**
* Sets doc comment for the property.
*
* @param PhpParser\Comment\Doc|string $docComment Doc comment to set
*
* @return $this The builder instance (for fluid interface)
*/
public function setDocComment($docComment) {
$this->attributes = [
'comments' => [BuilderHelpers::normalizeDocComment($docComment)]
];
return $this;
}
/**
* Sets the property type for PHP 7.4+.
*
* @param string|Name|Identifier|ComplexType $type
*
* @return $this
*/
public function setType($type) {
$this->type = BuilderHelpers::normalizeType($type);
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built class node.
*
* @return Stmt\Property The built property node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Property(
$this->flags !== 0 ? $this->flags : Stmt\Class_::MODIFIER_PUBLIC,
[
new Stmt\PropertyProperty($this->name, $this->default)
],
$this->attributes,
$this->type,
$this->attributeGroups
);
}
}

View file

@ -0,0 +1,64 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class TraitUse implements Builder
{
protected $traits = [];
protected $adaptations = [];
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Names of used traits
*/
public function __construct(...$traits) {
foreach ($traits as $trait) {
$this->and($trait);
}
}
/**
* Adds used trait.
*
* @param Node\Name|string $trait Trait name
*
* @return $this The builder instance (for fluid interface)
*/
public function and($trait) {
$this->traits[] = BuilderHelpers::normalizeName($trait);
return $this;
}
/**
* Adds trait adaptation.
*
* @param Stmt\TraitUseAdaptation|Builder\TraitUseAdaptation $adaptation Trait adaptation
*
* @return $this The builder instance (for fluid interface)
*/
public function with($adaptation) {
$adaptation = BuilderHelpers::normalizeNode($adaptation);
if (!$adaptation instanceof Stmt\TraitUseAdaptation) {
throw new \LogicException('Adaptation must have type TraitUseAdaptation');
}
$this->adaptations[] = $adaptation;
return $this;
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
return new Stmt\TraitUse($this->traits, $this->adaptations);
}
}

View file

@ -0,0 +1,148 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class TraitUseAdaptation implements Builder
{
const TYPE_UNDEFINED = 0;
const TYPE_ALIAS = 1;
const TYPE_PRECEDENCE = 2;
/** @var int Type of building adaptation */
protected $type;
protected $trait;
protected $method;
protected $modifier = null;
protected $alias = null;
protected $insteadof = [];
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Name of adaptated trait
* @param Node\Identifier|string $method Name of adaptated method
*/
public function __construct($trait, $method) {
$this->type = self::TYPE_UNDEFINED;
$this->trait = is_null($trait)? null: BuilderHelpers::normalizeName($trait);
$this->method = BuilderHelpers::normalizeIdentifier($method);
}
/**
* Sets alias of method.
*
* @param Node\Identifier|string $alias Alias for adaptated method
*
* @return $this The builder instance (for fluid interface)
*/
public function as($alias) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set alias for not alias adaptation buider');
}
$this->alias = $alias;
return $this;
}
/**
* Sets adaptated method public.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePublic() {
$this->setModifier(Stmt\Class_::MODIFIER_PUBLIC);
return $this;
}
/**
* Sets adaptated method protected.
*
* @return $this The builder instance (for fluid interface)
*/
public function makeProtected() {
$this->setModifier(Stmt\Class_::MODIFIER_PROTECTED);
return $this;
}
/**
* Sets adaptated method private.
*
* @return $this The builder instance (for fluid interface)
*/
public function makePrivate() {
$this->setModifier(Stmt\Class_::MODIFIER_PRIVATE);
return $this;
}
/**
* Adds overwritten traits.
*
* @param Node\Name|string ...$traits Traits for overwrite
*
* @return $this The builder instance (for fluid interface)
*/
public function insteadof(...$traits) {
if ($this->type === self::TYPE_UNDEFINED) {
if (is_null($this->trait)) {
throw new \LogicException('Precedence adaptation must have trait');
}
$this->type = self::TYPE_PRECEDENCE;
}
if ($this->type !== self::TYPE_PRECEDENCE) {
throw new \LogicException('Cannot add overwritten traits for not precedence adaptation buider');
}
foreach ($traits as $trait) {
$this->insteadof[] = BuilderHelpers::normalizeName($trait);
}
return $this;
}
protected function setModifier(int $modifier) {
if ($this->type === self::TYPE_UNDEFINED) {
$this->type = self::TYPE_ALIAS;
}
if ($this->type !== self::TYPE_ALIAS) {
throw new \LogicException('Cannot set access modifier for not alias adaptation buider');
}
if (is_null($this->modifier)) {
$this->modifier = $modifier;
} else {
throw new \LogicException('Multiple access type modifiers are not allowed');
}
}
/**
* Returns the built node.
*
* @return Node The built node
*/
public function getNode() : Node {
switch ($this->type) {
case self::TYPE_ALIAS:
return new \ncc\ThirdParty\nikic\PhpParser\Node\Stmt\TraitUseAdaptation\Alias($this->trait, $this->method, $this->modifier, $this->alias);
case self::TYPE_PRECEDENCE:
return new \ncc\ThirdParty\nikic\PhpParser\Node\Stmt\TraitUseAdaptation\Precedence($this->trait, $this->method, $this->insteadof);
default:
throw new \LogicException('Type of adaptation is not defined');
}
}
}

View file

@ -0,0 +1,78 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Trait_ extends Declaration
{
protected $name;
protected $uses = [];
protected $properties = [];
protected $methods = [];
/** @var Node\AttributeGroup[] */
protected $attributeGroups = [];
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*/
public function __construct(string $name) {
$this->name = $name;
}
/**
* Adds a statement.
*
* @param Stmt|PhpParser\Builder $stmt The statement to add
*
* @return $this The builder instance (for fluid interface)
*/
public function addStmt($stmt) {
$stmt = BuilderHelpers::normalizeNode($stmt);
if ($stmt instanceof Stmt\Property) {
$this->properties[] = $stmt;
} elseif ($stmt instanceof Stmt\ClassMethod) {
$this->methods[] = $stmt;
} elseif ($stmt instanceof Stmt\TraitUse) {
$this->uses[] = $stmt;
} else {
throw new \LogicException(sprintf('Unexpected node of type "%s"', $stmt->getType()));
}
return $this;
}
/**
* Adds an attribute group.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return $this The builder instance (for fluid interface)
*/
public function addAttribute($attribute) {
$this->attributeGroups[] = BuilderHelpers::normalizeAttribute($attribute);
return $this;
}
/**
* Returns the built trait node.
*
* @return Stmt\Trait_ The built interface node
*/
public function getNode() : PhpParser\Node {
return new Stmt\Trait_(
$this->name, [
'stmts' => array_merge($this->uses, $this->properties, $this->methods),
'attrGroups' => $this->attributeGroups,
], $this->attributes
);
}
}

View file

@ -0,0 +1,49 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\Builder;
use ncc\ThirdParty\nikic\PhpParser\BuilderHelpers;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
class Use_ implements Builder
{
protected $name;
protected $type;
protected $alias = null;
/**
* Creates a name use (alias) builder.
*
* @param Node\Name|string $name Name of the entity (namespace, class, function, constant) to alias
* @param int $type One of the Stmt\Use_::TYPE_* constants
*/
public function __construct($name, int $type) {
$this->name = BuilderHelpers::normalizeName($name);
$this->type = $type;
}
/**
* Sets alias for used name.
*
* @param string $alias Alias to use (last component of full name by default)
*
* @return $this The builder instance (for fluid interface)
*/
public function as(string $alias) {
$this->alias = $alias;
return $this;
}
/**
* Returns the built node.
*
* @return Stmt\Use_ The built node
*/
public function getNode() : Node {
return new Stmt\Use_([
new Stmt\UseUse($this->name, $this->alias)
], $this->type);
}
}

View file

@ -0,0 +1,399 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\Node\Arg;
use ncc\ThirdParty\nikic\PhpParser\Node\Expr;
use ncc\ThirdParty\nikic\PhpParser\Node\Expr\BinaryOp\Concat;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\Scalar\String_;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt\Use_;
class BuilderFactory
{
/**
* Creates an attribute node.
*
* @param string|Name $name Name of the attribute
* @param array $args Attribute named arguments
*
* @return Node\Attribute
*/
public function attribute($name, array $args = []) : Node\Attribute {
return new Node\Attribute(
BuilderHelpers::normalizeName($name),
$this->args($args)
);
}
/**
* Creates a namespace builder.
*
* @param null|string|Node\Name $name Name of the namespace
*
* @return Builder\Namespace_ The created namespace builder
*/
public function namespace($name) : Builder\Namespace_ {
return new Builder\Namespace_($name);
}
/**
* Creates a class builder.
*
* @param string $name Name of the class
*
* @return Builder\Class_ The created class builder
*/
public function class(string $name) : Builder\Class_ {
return new Builder\Class_($name);
}
/**
* Creates an interface builder.
*
* @param string $name Name of the interface
*
* @return Builder\Interface_ The created interface builder
*/
public function interface(string $name) : Builder\Interface_ {
return new Builder\Interface_($name);
}
/**
* Creates a trait builder.
*
* @param string $name Name of the trait
*
* @return Builder\Trait_ The created trait builder
*/
public function trait(string $name) : Builder\Trait_ {
return new Builder\Trait_($name);
}
/**
* Creates an enum builder.
*
* @param string $name Name of the enum
*
* @return Builder\Enum_ The created enum builder
*/
public function enum(string $name) : Builder\Enum_ {
return new Builder\Enum_($name);
}
/**
* Creates a trait use builder.
*
* @param Node\Name|string ...$traits Trait names
*
* @return Builder\TraitUse The create trait use builder
*/
public function useTrait(...$traits) : Builder\TraitUse {
return new Builder\TraitUse(...$traits);
}
/**
* Creates a trait use adaptation builder.
*
* @param Node\Name|string|null $trait Trait name
* @param Node\Identifier|string $method Method name
*
* @return Builder\TraitUseAdaptation The create trait use adaptation builder
*/
public function traitUseAdaptation($trait, $method = null) : Builder\TraitUseAdaptation {
if ($method === null) {
$method = $trait;
$trait = null;
}
return new Builder\TraitUseAdaptation($trait, $method);
}
/**
* Creates a method builder.
*
* @param string $name Name of the method
*
* @return Builder\Method The created method builder
*/
public function method(string $name) : Builder\Method {
return new Builder\Method($name);
}
/**
* Creates a parameter builder.
*
* @param string $name Name of the parameter
*
* @return Builder\Param The created parameter builder
*/
public function param(string $name) : Builder\Param {
return new Builder\Param($name);
}
/**
* Creates a property builder.
*
* @param string $name Name of the property
*
* @return Builder\Property The created property builder
*/
public function property(string $name) : Builder\Property {
return new Builder\Property($name);
}
/**
* Creates a function builder.
*
* @param string $name Name of the function
*
* @return Builder\Function_ The created function builder
*/
public function function(string $name) : Builder\Function_ {
return new Builder\Function_($name);
}
/**
* Creates a namespace/class use builder.
*
* @param Node\Name|string $name Name of the entity (namespace or class) to alias
*
* @return Builder\Use_ The created use builder
*/
public function use($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_NORMAL);
}
/**
* Creates a function use builder.
*
* @param Node\Name|string $name Name of the function to alias
*
* @return Builder\Use_ The created use function builder
*/
public function useFunction($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_FUNCTION);
}
/**
* Creates a constant use builder.
*
* @param Node\Name|string $name Name of the const to alias
*
* @return Builder\Use_ The created use const builder
*/
public function useConst($name) : Builder\Use_ {
return new Builder\Use_($name, Use_::TYPE_CONSTANT);
}
/**
* Creates a class constant builder.
*
* @param string|Identifier $name Name
* @param Node\Expr|bool|null|int|float|string|array $value Value
*
* @return Builder\ClassConst The created use const builder
*/
public function classConst($name, $value) : Builder\ClassConst {
return new Builder\ClassConst($name, $value);
}
/**
* Creates an enum case builder.
*
* @param string|Identifier $name Name
*
* @return Builder\EnumCase The created use const builder
*/
public function enumCase($name) : Builder\EnumCase {
return new Builder\EnumCase($name);
}
/**
* Creates node a for a literal value.
*
* @param Expr|bool|null|int|float|string|array $value $value
*
* @return Expr
*/
public function val($value) : Expr {
return BuilderHelpers::normalizeValue($value);
}
/**
* Creates variable node.
*
* @param string|Expr $name Name
*
* @return Expr\Variable
*/
public function var($name) : Expr\Variable {
if (!\is_string($name) && !$name instanceof Expr) {
throw new \LogicException('Variable name must be string or Expr');
}
return new Expr\Variable($name);
}
/**
* Normalizes an argument list.
*
* Creates Arg nodes for all arguments and converts literal values to expressions.
*
* @param array $args List of arguments to normalize
*
* @return Arg[]
*/
public function args(array $args) : array {
$normalizedArgs = [];
foreach ($args as $key => $arg) {
if (!($arg instanceof Arg)) {
$arg = new Arg(BuilderHelpers::normalizeValue($arg));
}
if (\is_string($key)) {
$arg->name = BuilderHelpers::normalizeIdentifier($key);
}
$normalizedArgs[] = $arg;
}
return $normalizedArgs;
}
/**
* Creates a function call node.
*
* @param string|Name|Expr $name Function name
* @param array $args Function arguments
*
* @return Expr\FuncCall
*/
public function funcCall($name, array $args = []) : Expr\FuncCall {
return new Expr\FuncCall(
BuilderHelpers::normalizeNameOrExpr($name),
$this->args($args)
);
}
/**
* Creates a method call node.
*
* @param Expr $var Variable the method is called on
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\MethodCall
*/
public function methodCall(Expr $var, $name, array $args = []) : Expr\MethodCall {
return new Expr\MethodCall(
$var,
BuilderHelpers::normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates a static method call node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier|Expr $name Method name
* @param array $args Method arguments
*
* @return Expr\StaticCall
*/
public function staticCall($class, $name, array $args = []) : Expr\StaticCall {
return new Expr\StaticCall(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifierOrExpr($name),
$this->args($args)
);
}
/**
* Creates an object creation node.
*
* @param string|Name|Expr $class Class name
* @param array $args Constructor arguments
*
* @return Expr\New_
*/
public function new($class, array $args = []) : Expr\New_ {
return new Expr\New_(
BuilderHelpers::normalizeNameOrExpr($class),
$this->args($args)
);
}
/**
* Creates a constant fetch node.
*
* @param string|Name $name Constant name
*
* @return Expr\ConstFetch
*/
public function constFetch($name) : Expr\ConstFetch {
return new Expr\ConstFetch(BuilderHelpers::normalizeName($name));
}
/**
* Creates a property fetch node.
*
* @param Expr $var Variable holding object
* @param string|Identifier|Expr $name Property name
*
* @return Expr\PropertyFetch
*/
public function propertyFetch(Expr $var, $name) : Expr\PropertyFetch {
return new Expr\PropertyFetch($var, BuilderHelpers::normalizeIdentifierOrExpr($name));
}
/**
* Creates a class constant fetch node.
*
* @param string|Name|Expr $class Class name
* @param string|Identifier $name Constant name
*
* @return Expr\ClassConstFetch
*/
public function classConstFetch($class, $name): Expr\ClassConstFetch {
return new Expr\ClassConstFetch(
BuilderHelpers::normalizeNameOrExpr($class),
BuilderHelpers::normalizeIdentifier($name)
);
}
/**
* Creates nested Concat nodes from a list of expressions.
*
* @param Expr|string ...$exprs Expressions or literal strings
*
* @return Concat
*/
public function concat(...$exprs) : Concat {
$numExprs = count($exprs);
if ($numExprs < 2) {
throw new \LogicException('Expected at least two expressions');
}
$lastConcat = $this->normalizeStringExpr($exprs[0]);
for ($i = 1; $i < $numExprs; $i++) {
$lastConcat = new Concat($lastConcat, $this->normalizeStringExpr($exprs[$i]));
}
return $lastConcat;
}
/**
* @param string|Expr $expr
* @return Expr
*/
private function normalizeStringExpr($expr) : Expr {
if ($expr instanceof Expr) {
return $expr;
}
if (\is_string($expr)) {
return new String_($expr);
}
throw new \LogicException('Expected string or Expr');
}
}

View file

@ -0,0 +1,335 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\Node\ComplexType;
use ncc\ThirdParty\nikic\PhpParser\Node\Expr;
use ncc\ThirdParty\nikic\PhpParser\Node\Identifier;
use ncc\ThirdParty\nikic\PhpParser\Node\Name;
use ncc\ThirdParty\nikic\PhpParser\Node\NullableType;
use ncc\ThirdParty\nikic\PhpParser\Node\Scalar;
use ncc\ThirdParty\nikic\PhpParser\Node\Stmt;
/**
* This class defines helpers used in the implementation of builders. Don't use it directly.
*
* @internal
*/
final class BuilderHelpers
{
/**
* Normalizes a node: Converts builder objects to nodes.
*
* @param Node|Builder $node The node to normalize
*
* @return Node The normalized node
*/
public static function normalizeNode($node) : Node {
if ($node instanceof Builder) {
return $node->getNode();
}
if ($node instanceof Node) {
return $node;
}
throw new \LogicException('Expected node or builder object');
}
/**
* Normalizes a node to a statement.
*
* Expressions are wrapped in a Stmt\Expression node.
*
* @param Node|Builder $node The node to normalize
*
* @return Stmt The normalized statement node
*/
public static function normalizeStmt($node) : Stmt {
$node = self::normalizeNode($node);
if ($node instanceof Stmt) {
return $node;
}
if ($node instanceof Expr) {
return new Stmt\Expression($node);
}
throw new \LogicException('Expected statement or expression node');
}
/**
* Normalizes strings to Identifier.
*
* @param string|Identifier $name The identifier to normalize
*
* @return Identifier The normalized identifier
*/
public static function normalizeIdentifier($name) : Identifier {
if ($name instanceof Identifier) {
return $name;
}
if (\is_string($name)) {
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier');
}
/**
* Normalizes strings to Identifier, also allowing expressions.
*
* @param string|Identifier|Expr $name The identifier to normalize
*
* @return Identifier|Expr The normalized identifier or expression
*/
public static function normalizeIdentifierOrExpr($name) {
if ($name instanceof Identifier || $name instanceof Expr) {
return $name;
}
if (\is_string($name)) {
return new Identifier($name);
}
throw new \LogicException('Expected string or instance of Node\Identifier or Node\Expr');
}
/**
* Normalizes a name: Converts string names to Name nodes.
*
* @param Name|string $name The name to normalize
*
* @return Name The normalized name
*/
public static function normalizeName($name) : Name {
if ($name instanceof Name) {
return $name;
}
if (is_string($name)) {
if (!$name) {
throw new \LogicException('Name cannot be empty');
}
if ($name[0] === '\\') {
return new Name\FullyQualified(substr($name, 1));
}
if (0 === strpos($name, 'namespace\\')) {
return new Name\Relative(substr($name, strlen('namespace\\')));
}
return new Name($name);
}
throw new \LogicException('Name must be a string or an instance of Node\Name');
}
/**
* Normalizes a name: Converts string names to Name nodes, while also allowing expressions.
*
* @param Expr|Name|string $name The name to normalize
*
* @return Name|Expr The normalized name or expression
*/
public static function normalizeNameOrExpr($name) {
if ($name instanceof Expr) {
return $name;
}
if (!is_string($name) && !($name instanceof Name)) {
throw new \LogicException(
'Name must be a string or an instance of Node\Name or Node\Expr'
);
}
return self::normalizeName($name);
}
/**
* Normalizes a type: Converts plain-text type names into proper AST representation.
*
* In particular, builtin types become Identifiers, custom types become Names and nullables
* are wrapped in NullableType nodes.
*
* @param string|Name|Identifier|ComplexType $type The type to normalize
*
* @return Name|Identifier|ComplexType The normalized type
*/
public static function normalizeType($type) {
if (!is_string($type)) {
if (
!$type instanceof Name && !$type instanceof Identifier &&
!$type instanceof ComplexType
) {
throw new \LogicException(
'Type must be a string, or an instance of Name, Identifier or ComplexType'
);
}
return $type;
}
$nullable = false;
if (strlen($type) > 0 && $type[0] === '?') {
$nullable = true;
$type = substr($type, 1);
}
$builtinTypes = [
'array',
'callable',
'bool',
'int',
'float',
'string',
'iterable',
'void',
'object',
'null',
'false',
'mixed',
'never',
'true',
];
$lowerType = strtolower($type);
if (in_array($lowerType, $builtinTypes)) {
$type = new Identifier($lowerType);
} else {
$type = self::normalizeName($type);
}
$notNullableTypes = [
'void', 'mixed', 'never',
];
if ($nullable && in_array((string) $type, $notNullableTypes)) {
throw new \LogicException(sprintf('%s type cannot be nullable', $type));
}
return $nullable ? new NullableType($type) : $type;
}
/**
* Normalizes a value: Converts nulls, booleans, integers,
* floats, strings and arrays into their respective nodes
*
* @param Node\Expr|bool|null|int|float|string|array $value The value to normalize
*
* @return Expr The normalized value
*/
public static function normalizeValue($value) : Expr {
if ($value instanceof Node\Expr) {
return $value;
}
if (is_null($value)) {
return new Expr\ConstFetch(
new Name('null')
);
}
if (is_bool($value)) {
return new Expr\ConstFetch(
new Name($value ? 'true' : 'false')
);
}
if (is_int($value)) {
return new Scalar\LNumber($value);
}
if (is_float($value)) {
return new Scalar\DNumber($value);
}
if (is_string($value)) {
return new Scalar\String_($value);
}
if (is_array($value)) {
$items = [];
$lastKey = -1;
foreach ($value as $itemKey => $itemValue) {
// for consecutive, numeric keys don't generate keys
if (null !== $lastKey && ++$lastKey === $itemKey) {
$items[] = new Expr\ArrayItem(
self::normalizeValue($itemValue)
);
} else {
$lastKey = null;
$items[] = new Expr\ArrayItem(
self::normalizeValue($itemValue),
self::normalizeValue($itemKey)
);
}
}
return new Expr\Array_($items);
}
throw new \LogicException('Invalid value');
}
/**
* Normalizes a doc comment: Converts plain strings to PhpParser\Comment\Doc.
*
* @param Comment\Doc|string $docComment The doc comment to normalize
*
* @return Comment\Doc The normalized doc comment
*/
public static function normalizeDocComment($docComment) : Comment\Doc {
if ($docComment instanceof Comment\Doc) {
return $docComment;
}
if (is_string($docComment)) {
return new Comment\Doc($docComment);
}
throw new \LogicException('Doc comment must be a string or an instance of PhpParser\Comment\Doc');
}
/**
* Normalizes a attribute: Converts attribute to the Attribute Group if needed.
*
* @param Node\Attribute|Node\AttributeGroup $attribute
*
* @return Node\AttributeGroup The Attribute Group
*/
public static function normalizeAttribute($attribute) : Node\AttributeGroup
{
if ($attribute instanceof Node\AttributeGroup) {
return $attribute;
}
if (!($attribute instanceof Node\Attribute)) {
throw new \LogicException('Attribute must be an instance of PhpParser\Node\Attribute or PhpParser\Node\AttributeGroup');
}
return new Node\AttributeGroup([$attribute]);
}
/**
* Adds a modifier and returns new modifier bitmask.
*
* @param int $modifiers Existing modifiers
* @param int $modifier Modifier to set
*
* @return int New modifiers
*/
public static function addModifier(int $modifiers, int $modifier) : int {
Stmt\Class_::verifyModifier($modifiers, $modifier);
return $modifiers | $modifier;
}
/**
* Adds a modifier and returns new modifier bitmask.
* @return int New modifiers
*/
public static function addClassModifier(int $existingModifiers, int $modifierToSet) : int {
Stmt\Class_::verifyClassModifier($existingModifiers, $modifierToSet);
return $existingModifiers | $modifierToSet;
}
}

View file

@ -0,0 +1,239 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
class Comment implements \JsonSerializable
{
protected $text;
protected $startLine;
protected $startFilePos;
protected $startTokenPos;
protected $endLine;
protected $endFilePos;
protected $endTokenPos;
/**
* Constructs a comment node.
*
* @param string $text Comment text (including comment delimiters like /*)
* @param int $startLine Line number the comment started on
* @param int $startFilePos File offset the comment started on
* @param int $startTokenPos Token offset the comment started on
*/
public function __construct(
string $text,
int $startLine = -1, int $startFilePos = -1, int $startTokenPos = -1,
int $endLine = -1, int $endFilePos = -1, int $endTokenPos = -1
) {
$this->text = $text;
$this->startLine = $startLine;
$this->startFilePos = $startFilePos;
$this->startTokenPos = $startTokenPos;
$this->endLine = $endLine;
$this->endFilePos = $endFilePos;
$this->endTokenPos = $endTokenPos;
}
/**
* Gets the comment text.
*
* @return string The comment text (including comment delimiters like /*)
*/
public function getText() : string {
return $this->text;
}
/**
* Gets the line number the comment started on.
*
* @return int Line number (or -1 if not available)
*/
public function getStartLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @return int File offset (or -1 if not available)
*/
public function getStartFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @return int Token offset (or -1 if not available)
*/
public function getStartTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the line number the comment ends on.
*
* @return int Line number (or -1 if not available)
*/
public function getEndLine() : int {
return $this->endLine;
}
/**
* Gets the file offset the comment ends on.
*
* @return int File offset (or -1 if not available)
*/
public function getEndFilePos() : int {
return $this->endFilePos;
}
/**
* Gets the token offset the comment ends on.
*
* @return int Token offset (or -1 if not available)
*/
public function getEndTokenPos() : int {
return $this->endTokenPos;
}
/**
* Gets the line number the comment started on.
*
* @deprecated Use getStartLine() instead
*
* @return int Line number
*/
public function getLine() : int {
return $this->startLine;
}
/**
* Gets the file offset the comment started on.
*
* @deprecated Use getStartFilePos() instead
*
* @return int File offset
*/
public function getFilePos() : int {
return $this->startFilePos;
}
/**
* Gets the token offset the comment started on.
*
* @deprecated Use getStartTokenPos() instead
*
* @return int Token offset
*/
public function getTokenPos() : int {
return $this->startTokenPos;
}
/**
* Gets the comment text.
*
* @return string The comment text (including comment delimiters like /*)
*/
public function __toString() : string {
return $this->text;
}
/**
* Gets the reformatted comment text.
*
* "Reformatted" here means that we try to clean up the whitespace at the
* starts of the lines. This is necessary because we receive the comments
* without trailing whitespace on the first line, but with trailing whitespace
* on all subsequent lines.
*
* @return mixed|string
*/
public function getReformattedText() {
$text = trim($this->text);
$newlinePos = strpos($text, "\n");
if (false === $newlinePos) {
// Single line comments don't need further processing
return $text;
} elseif (preg_match('((*BSR_ANYCRLF)(*ANYCRLF)^.*(?:\R\s+\*.*)+$)', $text)) {
// Multi line comment of the type
//
// /*
// * Some text.
// * Some more text.
// */
//
// is handled by replacing the whitespace sequences before the * by a single space
return preg_replace('(^\s+\*)m', ' *', $this->text);
} elseif (preg_match('(^/\*\*?\s*[\r\n])', $text) && preg_match('(\n(\s*)\*/$)', $text, $matches)) {
// Multi line comment of the type
//
// /*
// Some text.
// Some more text.
// */
//
// is handled by removing the whitespace sequence on the line before the closing
// */ on all lines. So if the last line is " */", then " " is removed at the
// start of all lines.
return preg_replace('(^' . preg_quote($matches[1]) . ')m', '', $text);
} elseif (preg_match('(^/\*\*?\s*(?!\s))', $text, $matches)) {
// Multi line comment of the type
//
// /* Some text.
// Some more text.
// Indented text.
// Even more text. */
//
// is handled by removing the difference between the shortest whitespace prefix on all
// lines and the length of the "/* " opening sequence.
$prefixLen = $this->getShortestWhitespacePrefixLen(substr($text, $newlinePos + 1));
$removeLen = $prefixLen - strlen($matches[0]);
return preg_replace('(^\s{' . $removeLen . '})m', '', $text);
}
// No idea how to format this comment, so simply return as is
return $text;
}
/**
* Get length of shortest whitespace prefix (at the start of a line).
*
* If there is a line with no prefix whitespace, 0 is a valid return value.
*
* @param string $str String to check
* @return int Length in characters. Tabs count as single characters.
*/
private function getShortestWhitespacePrefixLen(string $str) : int {
$lines = explode("\n", $str);
$shortestPrefixLen = \INF;
foreach ($lines as $line) {
preg_match('(^\s*)', $line, $matches);
$prefixLen = strlen($matches[0]);
if ($prefixLen < $shortestPrefixLen) {
$shortestPrefixLen = $prefixLen;
}
}
return $shortestPrefixLen;
}
/**
* @return array
* @psalm-return array{nodeType:string, text:mixed, line:mixed, filePos:mixed}
*/
public function jsonSerialize() : array {
// Technically not a node, but we make it look like one anyway
$type = $this instanceof Comment\Doc ? 'Comment_Doc' : 'Comment';
return [
'nodeType' => $type,
'text' => $this->text,
// TODO: Rename these to include "start".
'line' => $this->startLine,
'filePos' => $this->startFilePos,
'tokenPos' => $this->startTokenPos,
'endLine' => $this->endLine,
'endFilePos' => $this->endFilePos,
'endTokenPos' => $this->endTokenPos,
];
}
}

View file

@ -0,0 +1,7 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Comment;
class Doc extends \ncc\ThirdParty\nikic\PhpParser\Comment
{
}

View file

@ -0,0 +1,6 @@
<?php
namespace ncc\ThirdParty\nikic\PhpParser;
class ConstExprEvaluationException extends \Exception
{}

View file

@ -0,0 +1,229 @@
<?php
namespace ncc\ThirdParty\nikic\PhpParser;
use function array_merge;
use ncc\ThirdParty\nikic\PhpParser\Node\Expr;
use ncc\ThirdParty\nikic\PhpParser\Node\Scalar;
/**
* Evaluates constant expressions.
*
* This evaluator is able to evaluate all constant expressions (as defined by PHP), which can be
* evaluated without further context. If a subexpression is not of this type, a user-provided
* fallback evaluator is invoked. To support all constant expressions that are also supported by
* PHP (and not already handled by this class), the fallback evaluator must be able to handle the
* following node types:
*
* * All Scalar\MagicConst\* nodes.
* * Expr\ConstFetch nodes. Only null/false/true are already handled by this class.
* * Expr\ClassConstFetch nodes.
*
* The fallback evaluator should throw ConstExprEvaluationException for nodes it cannot evaluate.
*
* The evaluation is dependent on runtime configuration in two respects: Firstly, floating
* point to string conversions are affected by the precision ini setting. Secondly, they are also
* affected by the LC_NUMERIC locale.
*/
class ConstExprEvaluator
{
private $fallbackEvaluator;
/**
* Create a constant expression evaluator.
*
* The provided fallback evaluator is invoked whenever a subexpression cannot be evaluated. See
* class doc comment for more information.
*
* @param callable|null $fallbackEvaluator To call if subexpression cannot be evaluated
*/
public function __construct(callable $fallbackEvaluator = null) {
$this->fallbackEvaluator = $fallbackEvaluator ?? function(Expr $expr) {
throw new ConstExprEvaluationException(
"Expression of type {$expr->getType()} cannot be evaluated"
);
};
}
/**
* Silently evaluates a constant expression into a PHP value.
*
* Thrown Errors, warnings or notices will be converted into a ConstExprEvaluationException.
* The original source of the exception is available through getPrevious().
*
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
* constructor will be invoked. By default, if no fallback is provided, an exception of type
* ConstExprEvaluationException is thrown.
*
* See class doc comment for caveats and limitations.
*
* @param Expr $expr Constant expression to evaluate
* @return mixed Result of evaluation
*
* @throws ConstExprEvaluationException if the expression cannot be evaluated or an error occurred
*/
public function evaluateSilently(Expr $expr) {
set_error_handler(function($num, $str, $file, $line) {
throw new \ErrorException($str, 0, $num, $file, $line);
});
try {
return $this->evaluate($expr);
} catch (\Throwable $e) {
if (!$e instanceof ConstExprEvaluationException) {
$e = new ConstExprEvaluationException(
"An error occurred during constant expression evaluation", 0, $e);
}
throw $e;
} finally {
restore_error_handler();
}
}
/**
* Directly evaluates a constant expression into a PHP value.
*
* May generate Error exceptions, warnings or notices. Use evaluateSilently() to convert these
* into a ConstExprEvaluationException.
*
* If some part of the expression cannot be evaluated, the fallback evaluator passed to the
* constructor will be invoked. By default, if no fallback is provided, an exception of type
* ConstExprEvaluationException is thrown.
*
* See class doc comment for caveats and limitations.
*
* @param Expr $expr Constant expression to evaluate
* @return mixed Result of evaluation
*
* @throws ConstExprEvaluationException if the expression cannot be evaluated
*/
public function evaluateDirectly(Expr $expr) {
return $this->evaluate($expr);
}
private function evaluate(Expr $expr) {
if ($expr instanceof Scalar\LNumber
|| $expr instanceof Scalar\DNumber
|| $expr instanceof Scalar\String_
) {
return $expr->value;
}
if ($expr instanceof Expr\Array_) {
return $this->evaluateArray($expr);
}
// Unary operators
if ($expr instanceof Expr\UnaryPlus) {
return +$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\UnaryMinus) {
return -$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BooleanNot) {
return !$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BitwiseNot) {
return ~$this->evaluate($expr->expr);
}
if ($expr instanceof Expr\BinaryOp) {
return $this->evaluateBinaryOp($expr);
}
if ($expr instanceof Expr\Ternary) {
return $this->evaluateTernary($expr);
}
if ($expr instanceof Expr\ArrayDimFetch && null !== $expr->dim) {
return $this->evaluate($expr->var)[$this->evaluate($expr->dim)];
}
if ($expr instanceof Expr\ConstFetch) {
return $this->evaluateConstFetch($expr);
}
return ($this->fallbackEvaluator)($expr);
}
private function evaluateArray(Expr\Array_ $expr) {
$array = [];
foreach ($expr->items as $item) {
if (null !== $item->key) {
$array[$this->evaluate($item->key)] = $this->evaluate($item->value);
} elseif ($item->unpack) {
$array = array_merge($array, $this->evaluate($item->value));
} else {
$array[] = $this->evaluate($item->value);
}
}
return $array;
}
private function evaluateTernary(Expr\Ternary $expr) {
if (null === $expr->if) {
return $this->evaluate($expr->cond) ?: $this->evaluate($expr->else);
}
return $this->evaluate($expr->cond)
? $this->evaluate($expr->if)
: $this->evaluate($expr->else);
}
private function evaluateBinaryOp(Expr\BinaryOp $expr) {
if ($expr instanceof Expr\BinaryOp\Coalesce
&& $expr->left instanceof Expr\ArrayDimFetch
) {
// This needs to be special cased to respect BP_VAR_IS fetch semantics
return $this->evaluate($expr->left->var)[$this->evaluate($expr->left->dim)]
?? $this->evaluate($expr->right);
}
// The evaluate() calls are repeated in each branch, because some of the operators are
// short-circuiting and evaluating the RHS in advance may be illegal in that case
$l = $expr->left;
$r = $expr->right;
switch ($expr->getOperatorSigil()) {
case '&': return $this->evaluate($l) & $this->evaluate($r);
case '|': return $this->evaluate($l) | $this->evaluate($r);
case '^': return $this->evaluate($l) ^ $this->evaluate($r);
case '&&': return $this->evaluate($l) && $this->evaluate($r);
case '||': return $this->evaluate($l) || $this->evaluate($r);
case '??': return $this->evaluate($l) ?? $this->evaluate($r);
case '.': return $this->evaluate($l) . $this->evaluate($r);
case '/': return $this->evaluate($l) / $this->evaluate($r);
case '==': return $this->evaluate($l) == $this->evaluate($r);
case '>': return $this->evaluate($l) > $this->evaluate($r);
case '>=': return $this->evaluate($l) >= $this->evaluate($r);
case '===': return $this->evaluate($l) === $this->evaluate($r);
case 'and': return $this->evaluate($l) and $this->evaluate($r);
case 'or': return $this->evaluate($l) or $this->evaluate($r);
case 'xor': return $this->evaluate($l) xor $this->evaluate($r);
case '-': return $this->evaluate($l) - $this->evaluate($r);
case '%': return $this->evaluate($l) % $this->evaluate($r);
case '*': return $this->evaluate($l) * $this->evaluate($r);
case '!=': return $this->evaluate($l) != $this->evaluate($r);
case '!==': return $this->evaluate($l) !== $this->evaluate($r);
case '+': return $this->evaluate($l) + $this->evaluate($r);
case '**': return $this->evaluate($l) ** $this->evaluate($r);
case '<<': return $this->evaluate($l) << $this->evaluate($r);
case '>>': return $this->evaluate($l) >> $this->evaluate($r);
case '<': return $this->evaluate($l) < $this->evaluate($r);
case '<=': return $this->evaluate($l) <= $this->evaluate($r);
case '<=>': return $this->evaluate($l) <=> $this->evaluate($r);
}
throw new \Exception('Should not happen');
}
private function evaluateConstFetch(Expr\ConstFetch $expr) {
$name = $expr->name->toLowerString();
switch ($name) {
case 'null': return null;
case 'false': return false;
case 'true': return true;
}
return ($this->fallbackEvaluator)($expr);
}
}

View file

@ -0,0 +1,180 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
class Error extends \RuntimeException
{
protected $rawMessage;
protected $attributes;
/**
* Creates an Exception signifying a parse error.
*
* @param string $message Error message
* @param array|int $attributes Attributes of node/token where error occurred
* (or start line of error -- deprecated)
*/
public function __construct(string $message, $attributes = []) {
$this->rawMessage = $message;
if (is_array($attributes)) {
$this->attributes = $attributes;
} else {
$this->attributes = ['startLine' => $attributes];
}
$this->updateMessage();
}
/**
* Gets the error message
*
* @return string Error message
*/
public function getRawMessage() : string {
return $this->rawMessage;
}
/**
* Gets the line the error starts in.
*
* @return int Error start line
*/
public function getStartLine() : int {
return $this->attributes['startLine'] ?? -1;
}
/**
* Gets the line the error ends in.
*
* @return int Error end line
*/
public function getEndLine() : int {
return $this->attributes['endLine'] ?? -1;
}
/**
* Gets the attributes of the node/token the error occurred at.
*
* @return array
*/
public function getAttributes() : array {
return $this->attributes;
}
/**
* Sets the attributes of the node/token the error occurred at.
*
* @param array $attributes
*/
public function setAttributes(array $attributes) {
$this->attributes = $attributes;
$this->updateMessage();
}
/**
* Sets the line of the PHP file the error occurred in.
*
* @param string $message Error message
*/
public function setRawMessage(string $message) {
$this->rawMessage = $message;
$this->updateMessage();
}
/**
* Sets the line the error starts in.
*
* @param int $line Error start line
*/
public function setStartLine(int $line) {
$this->attributes['startLine'] = $line;
$this->updateMessage();
}
/**
* Returns whether the error has start and end column information.
*
* For column information enable the startFilePos and endFilePos in the lexer options.
*
* @return bool
*/
public function hasColumnInfo() : bool {
return isset($this->attributes['startFilePos'], $this->attributes['endFilePos']);
}
/**
* Gets the start column (1-based) into the line where the error started.
*
* @param string $code Source code of the file
* @return int
*/
public function getStartColumn(string $code) : int {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['startFilePos']);
}
/**
* Gets the end column (1-based) into the line where the error ended.
*
* @param string $code Source code of the file
* @return int
*/
public function getEndColumn(string $code) : int {
if (!$this->hasColumnInfo()) {
throw new \RuntimeException('Error does not have column information');
}
return $this->toColumn($code, $this->attributes['endFilePos']);
}
/**
* Formats message including line and column information.
*
* @param string $code Source code associated with the error, for calculation of the columns
*
* @return string Formatted message
*/
public function getMessageWithColumnInfo(string $code) : string {
return sprintf(
'%s from %d:%d to %d:%d', $this->getRawMessage(),
$this->getStartLine(), $this->getStartColumn($code),
$this->getEndLine(), $this->getEndColumn($code)
);
}
/**
* Converts a file offset into a column.
*
* @param string $code Source code that $pos indexes into
* @param int $pos 0-based position in $code
*
* @return int 1-based column (relative to start of line)
*/
private function toColumn(string $code, int $pos) : int {
if ($pos > strlen($code)) {
throw new \RuntimeException('Invalid position information');
}
$lineStartPos = strrpos($code, "\n", $pos - strlen($code));
if (false === $lineStartPos) {
$lineStartPos = -1;
}
return $pos - $lineStartPos;
}
/**
* Updates the exception message after a change to rawMessage or rawLine.
*/
protected function updateMessage() {
$this->message = $this->rawMessage;
if (-1 === $this->getStartLine()) {
$this->message .= ' on unknown line';
} else {
$this->message .= ' on line ' . $this->getStartLine();
}
}
}

View file

@ -0,0 +1,13 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
interface ErrorHandler
{
/**
* Handle an error generated during lexing, parsing or some other operation.
*
* @param Error $error The error that needs to be handled
*/
public function handleError(Error $error);
}

View file

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\ErrorHandler;
use ncc\ThirdParty\nikic\PhpParser\Error;
use ncc\ThirdParty\nikic\PhpParser\ErrorHandler;
/**
* Error handler that collects all errors into an array.
*
* This allows graceful handling of errors.
*/
class Collecting implements ErrorHandler
{
/** @var Error[] Collected errors */
private $errors = [];
public function handleError(Error $error) {
$this->errors[] = $error;
}
/**
* Get collected errors.
*
* @return Error[]
*/
public function getErrors() : array {
return $this->errors;
}
/**
* Check whether there are any errors.
*
* @return bool
*/
public function hasErrors() : bool {
return !empty($this->errors);
}
/**
* Reset/clear collected errors.
*/
public function clearErrors() {
$this->errors = [];
}
}

View file

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\ErrorHandler;
use ncc\ThirdParty\nikic\PhpParser\Error;
use ncc\ThirdParty\nikic\PhpParser\ErrorHandler;
/**
* Error handler that handles all errors by throwing them.
*
* This is the default strategy used by all components.
*/
class Throwing implements ErrorHandler
{
public function handleError(Error $error) {
throw $error;
}
}

View file

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Internal;
/**
* @internal
*/
class DiffElem
{
const TYPE_KEEP = 0;
const TYPE_REMOVE = 1;
const TYPE_ADD = 2;
const TYPE_REPLACE = 3;
/** @var int One of the TYPE_* constants */
public $type;
/** @var mixed Is null for add operations */
public $old;
/** @var mixed Is null for remove operations */
public $new;
public function __construct(int $type, $old, $new) {
$this->type = $type;
$this->old = $old;
$this->new = $new;
}
}

View file

@ -0,0 +1,164 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Internal;
/**
* Implements the Myers diff algorithm.
*
* Myers, Eugene W. "An O (ND) difference algorithm and its variations."
* Algorithmica 1.1 (1986): 251-266.
*
* @internal
*/
class Differ
{
private $isEqual;
/**
* Create differ over the given equality relation.
*
* @param callable $isEqual Equality relation with signature function($a, $b) : bool
*/
public function __construct(callable $isEqual) {
$this->isEqual = $isEqual;
}
/**
* Calculate diff (edit script) from $old to $new.
*
* @param array $old Original array
* @param array $new New array
*
* @return DiffElem[] Diff (edit script)
*/
public function diff(array $old, array $new) {
list($trace, $x, $y) = $this->calculateTrace($old, $new);
return $this->extractDiff($trace, $x, $y, $old, $new);
}
/**
* Calculate diff, including "replace" operations.
*
* If a sequence of remove operations is followed by the same number of add operations, these
* will be coalesced into replace operations.
*
* @param array $old Original array
* @param array $new New array
*
* @return DiffElem[] Diff (edit script), including replace operations
*/
public function diffWithReplacements(array $old, array $new) {
return $this->coalesceReplacements($this->diff($old, $new));
}
private function calculateTrace(array $a, array $b) {
$n = \count($a);
$m = \count($b);
$max = $n + $m;
$v = [1 => 0];
$trace = [];
for ($d = 0; $d <= $max; $d++) {
$trace[] = $v;
for ($k = -$d; $k <= $d; $k += 2) {
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
$x = $v[$k+1];
} else {
$x = $v[$k-1] + 1;
}
$y = $x - $k;
while ($x < $n && $y < $m && ($this->isEqual)($a[$x], $b[$y])) {
$x++;
$y++;
}
$v[$k] = $x;
if ($x >= $n && $y >= $m) {
return [$trace, $x, $y];
}
}
}
throw new \Exception('Should not happen');
}
private function extractDiff(array $trace, int $x, int $y, array $a, array $b) {
$result = [];
for ($d = \count($trace) - 1; $d >= 0; $d--) {
$v = $trace[$d];
$k = $x - $y;
if ($k === -$d || ($k !== $d && $v[$k-1] < $v[$k+1])) {
$prevK = $k + 1;
} else {
$prevK = $k - 1;
}
$prevX = $v[$prevK];
$prevY = $prevX - $prevK;
while ($x > $prevX && $y > $prevY) {
$result[] = new DiffElem(DiffElem::TYPE_KEEP, $a[$x-1], $b[$y-1]);
$x--;
$y--;
}
if ($d === 0) {
break;
}
while ($x > $prevX) {
$result[] = new DiffElem(DiffElem::TYPE_REMOVE, $a[$x-1], null);
$x--;
}
while ($y > $prevY) {
$result[] = new DiffElem(DiffElem::TYPE_ADD, null, $b[$y-1]);
$y--;
}
}
return array_reverse($result);
}
/**
* Coalesce equal-length sequences of remove+add into a replace operation.
*
* @param DiffElem[] $diff
* @return DiffElem[]
*/
private function coalesceReplacements(array $diff) {
$newDiff = [];
$c = \count($diff);
for ($i = 0; $i < $c; $i++) {
$diffType = $diff[$i]->type;
if ($diffType !== DiffElem::TYPE_REMOVE) {
$newDiff[] = $diff[$i];
continue;
}
$j = $i;
while ($j < $c && $diff[$j]->type === DiffElem::TYPE_REMOVE) {
$j++;
}
$k = $j;
while ($k < $c && $diff[$k]->type === DiffElem::TYPE_ADD) {
$k++;
}
if ($j - $i === $k - $j) {
$len = $j - $i;
for ($n = 0; $n < $len; $n++) {
$newDiff[] = new DiffElem(
DiffElem::TYPE_REPLACE, $diff[$i + $n]->old, $diff[$j + $n]->new
);
}
} else {
for (; $i < $k; $i++) {
$newDiff[] = $diff[$i];
}
}
$i = $k - 1;
}
return $newDiff;
}
}

View file

@ -0,0 +1,61 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Internal;
use ncc\ThirdParty\nikic\PhpParser\Node;
use ncc\ThirdParty\nikic\PhpParser\Node\Expr;
/**
* This node is used internally by the format-preserving pretty printer to print anonymous classes.
*
* The normal anonymous class structure violates assumptions about the order of token offsets.
* Namely, the constructor arguments are part of the Expr\New_ node and follow the class node, even
* though they are actually interleaved with them. This special node type is used temporarily to
* restore a sane token offset order.
*
* @internal
*/
class PrintableNewAnonClassNode extends Expr
{
/** @var Node\AttributeGroup[] PHP attribute groups */
public $attrGroups;
/** @var Node\Arg[] Arguments */
public $args;
/** @var null|Node\Name Name of extended class */
public $extends;
/** @var Node\Name[] Names of implemented interfaces */
public $implements;
/** @var Node\Stmt[] Statements */
public $stmts;
public function __construct(
array $attrGroups, array $args, Node\Name $extends = null, array $implements,
array $stmts, array $attributes
) {
parent::__construct($attributes);
$this->attrGroups = $attrGroups;
$this->args = $args;
$this->extends = $extends;
$this->implements = $implements;
$this->stmts = $stmts;
}
public static function fromNewNode(Expr\New_ $newNode) {
$class = $newNode->class;
assert($class instanceof Node\Stmt\Class_);
// We don't assert that $class->name is null here, to allow consumers to assign unique names
// to anonymous classes for their own purposes. We simplify ignore the name here.
return new self(
$class->attrGroups, $newNode->args, $class->extends, $class->implements,
$class->stmts, $newNode->getAttributes()
);
}
public function getType() : string {
return 'Expr_PrintableNewAnonClass';
}
public function getSubNodeNames() : array {
return ['attrGroups', 'args', 'extends', 'implements', 'stmts'];
}
}

View file

@ -0,0 +1,281 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Internal;
/**
* Provides operations on token streams, for use by pretty printer.
*
* @internal
*/
class TokenStream
{
/** @var array Tokens (in token_get_all format) */
private $tokens;
/** @var int[] Map from position to indentation */
private $indentMap;
/**
* Create token stream instance.
*
* @param array $tokens Tokens in token_get_all() format
*/
public function __construct(array $tokens) {
$this->tokens = $tokens;
$this->indentMap = $this->calcIndentMap();
}
/**
* Whether the given position is immediately surrounded by parenthesis.
*
* @param int $startPos Start position
* @param int $endPos End position
*
* @return bool
*/
public function haveParens(int $startPos, int $endPos) : bool {
return $this->haveTokenImmediatelyBefore($startPos, '(')
&& $this->haveTokenImmediatelyAfter($endPos, ')');
}
/**
* Whether the given position is immediately surrounded by braces.
*
* @param int $startPos Start position
* @param int $endPos End position
*
* @return bool
*/
public function haveBraces(int $startPos, int $endPos) : bool {
return ($this->haveTokenImmediatelyBefore($startPos, '{')
|| $this->haveTokenImmediatelyBefore($startPos, T_CURLY_OPEN))
&& $this->haveTokenImmediatelyAfter($endPos, '}');
}
/**
* Check whether the position is directly preceded by a certain token type.
*
* During this check whitespace and comments are skipped.
*
* @param int $pos Position before which the token should occur
* @param int|string $expectedTokenType Token to check for
*
* @return bool Whether the expected token was found
*/
public function haveTokenImmediatelyBefore(int $pos, $expectedTokenType) : bool {
$tokens = $this->tokens;
$pos--;
for (; $pos >= 0; $pos--) {
$tokenType = $tokens[$pos][0];
if ($tokenType === $expectedTokenType) {
return true;
}
if ($tokenType !== \T_WHITESPACE
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
break;
}
}
return false;
}
/**
* Check whether the position is directly followed by a certain token type.
*
* During this check whitespace and comments are skipped.
*
* @param int $pos Position after which the token should occur
* @param int|string $expectedTokenType Token to check for
*
* @return bool Whether the expected token was found
*/
public function haveTokenImmediatelyAfter(int $pos, $expectedTokenType) : bool {
$tokens = $this->tokens;
$pos++;
for (; $pos < \count($tokens); $pos++) {
$tokenType = $tokens[$pos][0];
if ($tokenType === $expectedTokenType) {
return true;
}
if ($tokenType !== \T_WHITESPACE
&& $tokenType !== \T_COMMENT && $tokenType !== \T_DOC_COMMENT) {
break;
}
}
return false;
}
public function skipLeft(int $pos, $skipTokenType) {
$tokens = $this->tokens;
$pos = $this->skipLeftWhitespace($pos);
if ($skipTokenType === \T_WHITESPACE) {
return $pos;
}
if ($tokens[$pos][0] !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token');
}
$pos--;
return $this->skipLeftWhitespace($pos);
}
public function skipRight(int $pos, $skipTokenType) {
$tokens = $this->tokens;
$pos = $this->skipRightWhitespace($pos);
if ($skipTokenType === \T_WHITESPACE) {
return $pos;
}
if ($tokens[$pos][0] !== $skipTokenType) {
// Shouldn't happen. The skip token MUST be there
throw new \Exception('Encountered unexpected token');
}
$pos++;
return $this->skipRightWhitespace($pos);
}
/**
* Return first non-whitespace token position smaller or equal to passed position.
*
* @param int $pos Token position
* @return int Non-whitespace token position
*/
public function skipLeftWhitespace(int $pos) {
$tokens = $this->tokens;
for (; $pos >= 0; $pos--) {
$type = $tokens[$pos][0];
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break;
}
}
return $pos;
}
/**
* Return first non-whitespace position greater or equal to passed position.
*
* @param int $pos Token position
* @return int Non-whitespace token position
*/
public function skipRightWhitespace(int $pos) {
$tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
if ($type !== \T_WHITESPACE && $type !== \T_COMMENT && $type !== \T_DOC_COMMENT) {
break;
}
}
return $pos;
}
public function findRight(int $pos, $findTokenType) {
$tokens = $this->tokens;
for ($count = \count($tokens); $pos < $count; $pos++) {
$type = $tokens[$pos][0];
if ($type === $findTokenType) {
return $pos;
}
}
return -1;
}
/**
* Whether the given position range contains a certain token type.
*
* @param int $startPos Starting position (inclusive)
* @param int $endPos Ending position (exclusive)
* @param int|string $tokenType Token type to look for
* @return bool Whether the token occurs in the given range
*/
public function haveTokenInRange(int $startPos, int $endPos, $tokenType) {
$tokens = $this->tokens;
for ($pos = $startPos; $pos < $endPos; $pos++) {
if ($tokens[$pos][0] === $tokenType) {
return true;
}
}
return false;
}
public function haveBracesInRange(int $startPos, int $endPos) {
return $this->haveTokenInRange($startPos, $endPos, '{')
|| $this->haveTokenInRange($startPos, $endPos, T_CURLY_OPEN)
|| $this->haveTokenInRange($startPos, $endPos, '}');
}
/**
* Get indentation before token position.
*
* @param int $pos Token position
*
* @return int Indentation depth (in spaces)
*/
public function getIndentationBefore(int $pos) : int {
return $this->indentMap[$pos];
}
/**
* Get the code corresponding to a token offset range, optionally adjusted for indentation.
*
* @param int $from Token start position (inclusive)
* @param int $to Token end position (exclusive)
* @param int $indent By how much the code should be indented (can be negative as well)
*
* @return string Code corresponding to token range, adjusted for indentation
*/
public function getTokenCode(int $from, int $to, int $indent) : string {
$tokens = $this->tokens;
$result = '';
for ($pos = $from; $pos < $to; $pos++) {
$token = $tokens[$pos];
if (\is_array($token)) {
$type = $token[0];
$content = $token[1];
if ($type === \T_CONSTANT_ENCAPSED_STRING || $type === \T_ENCAPSED_AND_WHITESPACE) {
$result .= $content;
} else {
// TODO Handle non-space indentation
if ($indent < 0) {
$result .= str_replace("\n" . str_repeat(" ", -$indent), "\n", $content);
} elseif ($indent > 0) {
$result .= str_replace("\n", "\n" . str_repeat(" ", $indent), $content);
} else {
$result .= $content;
}
}
} else {
$result .= $token;
}
}
return $result;
}
/**
* Precalculate the indentation at every token position.
*
* @return int[] Token position to indentation map
*/
private function calcIndentMap() {
$indentMap = [];
$indent = 0;
foreach ($this->tokens as $token) {
$indentMap[] = $indent;
if ($token[0] === \T_WHITESPACE) {
$content = $token[1];
$newlinePos = \strrpos($content, "\n");
if (false !== $newlinePos) {
$indent = \strlen($content) - $newlinePos - 1;
}
}
}
// Add a sentinel for one past end of the file
$indentMap[] = $indent;
return $indentMap;
}
}

View file

@ -0,0 +1,103 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
class JsonDecoder
{
/** @var \ReflectionClass[] Node type to reflection class map */
private $reflectionClassCache;
public function decode(string $json) {
$value = json_decode($json, true);
if (json_last_error()) {
throw new \RuntimeException('JSON decoding error: ' . json_last_error_msg());
}
return $this->decodeRecursive($value);
}
private function decodeRecursive($value) {
if (\is_array($value)) {
if (isset($value['nodeType'])) {
if ($value['nodeType'] === 'Comment' || $value['nodeType'] === 'Comment_Doc') {
return $this->decodeComment($value);
}
return $this->decodeNode($value);
}
return $this->decodeArray($value);
}
return $value;
}
private function decodeArray(array $array) : array {
$decodedArray = [];
foreach ($array as $key => $value) {
$decodedArray[$key] = $this->decodeRecursive($value);
}
return $decodedArray;
}
private function decodeNode(array $value) : Node {
$nodeType = $value['nodeType'];
if (!\is_string($nodeType)) {
throw new \RuntimeException('Node type must be a string');
}
$reflectionClass = $this->reflectionClassFromNodeType($nodeType);
/** @var Node $node */
$node = $reflectionClass->newInstanceWithoutConstructor();
if (isset($value['attributes'])) {
if (!\is_array($value['attributes'])) {
throw new \RuntimeException('Attributes must be an array');
}
$node->setAttributes($this->decodeArray($value['attributes']));
}
foreach ($value as $name => $subNode) {
if ($name === 'nodeType' || $name === 'attributes') {
continue;
}
$node->$name = $this->decodeRecursive($subNode);
}
return $node;
}
private function decodeComment(array $value) : Comment {
$className = $value['nodeType'] === 'Comment' ? Comment::class : Comment\Doc::class;
if (!isset($value['text'])) {
throw new \RuntimeException('Comment must have text');
}
return new $className(
$value['text'],
$value['line'] ?? -1, $value['filePos'] ?? -1, $value['tokenPos'] ?? -1,
$value['endLine'] ?? -1, $value['endFilePos'] ?? -1, $value['endTokenPos'] ?? -1
);
}
private function reflectionClassFromNodeType(string $nodeType) : \ReflectionClass {
if (!isset($this->reflectionClassCache[$nodeType])) {
$className = $this->classNameFromNodeType($nodeType);
$this->reflectionClassCache[$nodeType] = new \ReflectionClass($className);
}
return $this->reflectionClassCache[$nodeType];
}
private function classNameFromNodeType(string $nodeType) : string {
$className = 'PhpParser\\Node\\' . strtr($nodeType, '_', '\\');
if (class_exists($className)) {
return $className;
}
$className .= '_';
if (class_exists($className)) {
return $className;
}
throw new \RuntimeException("Unknown node type \"$nodeType\"");
}
}

View file

@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2011, Nikita Popov
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -0,0 +1,560 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser;
use ncc\ThirdParty\nikic\PhpParser\Parser\Tokens;
class Lexer
{
protected $code;
protected $tokens;
protected $pos;
protected $line;
protected $filePos;
protected $prevCloseTagHasNewline;
protected $tokenMap;
protected $dropTokens;
protected $identifierTokens;
private $attributeStartLineUsed;
private $attributeEndLineUsed;
private $attributeStartTokenPosUsed;
private $attributeEndTokenPosUsed;
private $attributeStartFilePosUsed;
private $attributeEndFilePosUsed;
private $attributeCommentsUsed;
/**
* Creates a Lexer.
*
* @param array $options Options array. Currently only the 'usedAttributes' option is supported,
* which is an array of attributes to add to the AST nodes. Possible
* attributes are: 'comments', 'startLine', 'endLine', 'startTokenPos',
* 'endTokenPos', 'startFilePos', 'endFilePos'. The option defaults to the
* first three. For more info see getNextToken() docs.
*/
public function __construct(array $options = []) {
// Create Map from internal tokens to PhpParser tokens.
$this->defineCompatibilityTokens();
$this->tokenMap = $this->createTokenMap();
$this->identifierTokens = $this->createIdentifierTokenMap();
// map of tokens to drop while lexing (the map is only used for isset lookup,
// that's why the value is simply set to 1; the value is never actually used.)
$this->dropTokens = array_fill_keys(
[\T_WHITESPACE, \T_OPEN_TAG, \T_COMMENT, \T_DOC_COMMENT, \T_BAD_CHARACTER], 1
);
$defaultAttributes = ['comments', 'startLine', 'endLine'];
$usedAttributes = array_fill_keys($options['usedAttributes'] ?? $defaultAttributes, true);
// Create individual boolean properties to make these checks faster.
$this->attributeStartLineUsed = isset($usedAttributes['startLine']);
$this->attributeEndLineUsed = isset($usedAttributes['endLine']);
$this->attributeStartTokenPosUsed = isset($usedAttributes['startTokenPos']);
$this->attributeEndTokenPosUsed = isset($usedAttributes['endTokenPos']);
$this->attributeStartFilePosUsed = isset($usedAttributes['startFilePos']);
$this->attributeEndFilePosUsed = isset($usedAttributes['endFilePos']);
$this->attributeCommentsUsed = isset($usedAttributes['comments']);
}
/**
* Initializes the lexer for lexing the provided source code.
*
* This function does not throw if lexing errors occur. Instead, errors may be retrieved using
* the getErrors() method.
*
* @param string $code The source code to lex
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
* ErrorHandler\Throwing
*/
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
if (null === $errorHandler) {
$errorHandler = new ErrorHandler\Throwing();
}
$this->code = $code; // keep the code around for __halt_compiler() handling
$this->pos = -1;
$this->line = 1;
$this->filePos = 0;
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
// This ensures proper composability, because having a newline is the "safe" assumption.
$this->prevCloseTagHasNewline = true;
$scream = ini_set('xdebug.scream', '0');
$this->tokens = @token_get_all($code);
$this->postprocessTokens($errorHandler);
if (false !== $scream) {
ini_set('xdebug.scream', $scream);
}
}
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
$tokens = [];
for ($i = $start; $i < $end; $i++) {
$chr = $this->code[$i];
if ($chr === "\0") {
// PHP cuts error message after null byte, so need special case
$errorMsg = 'Unexpected null byte';
} else {
$errorMsg = sprintf(
'Unexpected character "%s" (ASCII %d)', $chr, ord($chr)
);
}
$tokens[] = [\T_BAD_CHARACTER, $chr, $line];
$errorHandler->handleError(new Error($errorMsg, [
'startLine' => $line,
'endLine' => $line,
'startFilePos' => $i,
'endFilePos' => $i,
]));
}
return $tokens;
}
/**
* Check whether comment token is unterminated.
*
* @return bool
*/
private function isUnterminatedComment($token) : bool {
return ($token[0] === \T_COMMENT || $token[0] === \T_DOC_COMMENT)
&& substr($token[1], 0, 2) === '/*'
&& substr($token[1], -2) !== '*/';
}
protected function postprocessTokens(ErrorHandler $errorHandler) {
// PHP's error handling for token_get_all() is rather bad, so if we want detailed
// error information we need to compute it ourselves. Invalid character errors are
// detected by finding "gaps" in the token array. Unterminated comments are detected
// by checking if a trailing comment has a "*/" at the end.
//
// Additionally, we perform a number of canonicalizations here:
// * Use the PHP 8.0 comment format, which does not include trailing whitespace anymore.
// * Use PHP 8.0 T_NAME_* tokens.
// * Use PHP 8.1 T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG and
// T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG tokens used to disambiguate intersection types.
$filePos = 0;
$line = 1;
$numTokens = \count($this->tokens);
for ($i = 0; $i < $numTokens; $i++) {
$token = $this->tokens[$i];
// Since PHP 7.4 invalid characters are represented by a T_BAD_CHARACTER token.
// In this case we only need to emit an error.
if ($token[0] === \T_BAD_CHARACTER) {
$this->handleInvalidCharacterRange($filePos, $filePos + 1, $line, $errorHandler);
}
if ($token[0] === \T_COMMENT && substr($token[1], 0, 2) !== '/*'
&& preg_match('/(\r\n|\n|\r)$/D', $token[1], $matches)) {
$trailingNewline = $matches[0];
$token[1] = substr($token[1], 0, -strlen($trailingNewline));
$this->tokens[$i] = $token;
if (isset($this->tokens[$i + 1]) && $this->tokens[$i + 1][0] === \T_WHITESPACE) {
// Move trailing newline into following T_WHITESPACE token, if it already exists.
$this->tokens[$i + 1][1] = $trailingNewline . $this->tokens[$i + 1][1];
$this->tokens[$i + 1][2]--;
} else {
// Otherwise, we need to create a new T_WHITESPACE token.
array_splice($this->tokens, $i + 1, 0, [
[\T_WHITESPACE, $trailingNewline, $line],
]);
$numTokens++;
}
}
// Emulate PHP 8 T_NAME_* tokens, by combining sequences of T_NS_SEPARATOR and T_STRING
// into a single token.
if (\is_array($token)
&& ($token[0] === \T_NS_SEPARATOR || isset($this->identifierTokens[$token[0]]))) {
$lastWasSeparator = $token[0] === \T_NS_SEPARATOR;
$text = $token[1];
for ($j = $i + 1; isset($this->tokens[$j]); $j++) {
if ($lastWasSeparator) {
if (!isset($this->identifierTokens[$this->tokens[$j][0]])) {
break;
}
$lastWasSeparator = false;
} else {
if ($this->tokens[$j][0] !== \T_NS_SEPARATOR) {
break;
}
$lastWasSeparator = true;
}
$text .= $this->tokens[$j][1];
}
if ($lastWasSeparator) {
// Trailing separator is not part of the name.
$j--;
$text = substr($text, 0, -1);
}
if ($j > $i + 1) {
if ($token[0] === \T_NS_SEPARATOR) {
$type = \T_NAME_FULLY_QUALIFIED;
} else if ($token[0] === \T_NAMESPACE) {
$type = \T_NAME_RELATIVE;
} else {
$type = \T_NAME_QUALIFIED;
}
$token = [$type, $text, $line];
array_splice($this->tokens, $i, $j - $i, [$token]);
$numTokens -= $j - $i - 1;
}
}
if ($token === '&') {
$next = $i + 1;
while (isset($this->tokens[$next]) && $this->tokens[$next][0] === \T_WHITESPACE) {
$next++;
}
$followedByVarOrVarArg = isset($this->tokens[$next]) &&
($this->tokens[$next][0] === \T_VARIABLE || $this->tokens[$next][0] === \T_ELLIPSIS);
$this->tokens[$i] = $token = [
$followedByVarOrVarArg
? \T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG
: \T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG,
'&',
$line,
];
}
$tokenValue = \is_string($token) ? $token : $token[1];
$tokenLen = \strlen($tokenValue);
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
// Something is missing, must be an invalid character
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, $nextFilePos, $line, $errorHandler);
$filePos = (int) $nextFilePos;
array_splice($this->tokens, $i, 0, $badCharTokens);
$numTokens += \count($badCharTokens);
$i += \count($badCharTokens);
}
$filePos += $tokenLen;
$line += substr_count($tokenValue, "\n");
}
if ($filePos !== \strlen($this->code)) {
if (substr($this->code, $filePos, 2) === '/*') {
// Unlike PHP, HHVM will drop unterminated comments entirely
$comment = substr($this->code, $filePos);
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line,
'endLine' => $line + substr_count($comment, "\n"),
'startFilePos' => $filePos,
'endFilePos' => $filePos + \strlen($comment),
]));
// Emulate the PHP behavior
$isDocComment = isset($comment[3]) && $comment[3] === '*';
$this->tokens[] = [$isDocComment ? \T_DOC_COMMENT : \T_COMMENT, $comment, $line];
} else {
// Invalid characters at the end of the input
$badCharTokens = $this->handleInvalidCharacterRange(
$filePos, \strlen($this->code), $line, $errorHandler);
$this->tokens = array_merge($this->tokens, $badCharTokens);
}
return;
}
if (count($this->tokens) > 0) {
// Check for unterminated comment
$lastToken = $this->tokens[count($this->tokens) - 1];
if ($this->isUnterminatedComment($lastToken)) {
$errorHandler->handleError(new Error('Unterminated comment', [
'startLine' => $line - substr_count($lastToken[1], "\n"),
'endLine' => $line,
'startFilePos' => $filePos - \strlen($lastToken[1]),
'endFilePos' => $filePos,
]));
}
}
}
/**
* Fetches the next token.
*
* The available attributes are determined by the 'usedAttributes' option, which can
* be specified in the constructor. The following attributes are supported:
*
* * 'comments' => Array of PhpParser\Comment or PhpParser\Comment\Doc instances,
* representing all comments that occurred between the previous
* non-discarded token and the current one.
* * 'startLine' => Line in which the node starts.
* * 'endLine' => Line in which the node ends.
* * 'startTokenPos' => Offset into the token array of the first token in the node.
* * 'endTokenPos' => Offset into the token array of the last token in the node.
* * 'startFilePos' => Offset into the code string of the first character that is part of the node.
* * 'endFilePos' => Offset into the code string of the last character that is part of the node.
*
* @param mixed $value Variable to store token content in
* @param mixed $startAttributes Variable to store start attributes in
* @param mixed $endAttributes Variable to store end attributes in
*
* @return int Token id
*/
public function getNextToken(&$value = null, &$startAttributes = null, &$endAttributes = null) : int {
$startAttributes = [];
$endAttributes = [];
while (1) {
if (isset($this->tokens[++$this->pos])) {
$token = $this->tokens[$this->pos];
} else {
// EOF token with ID 0
$token = "\0";
}
if ($this->attributeStartLineUsed) {
$startAttributes['startLine'] = $this->line;
}
if ($this->attributeStartTokenPosUsed) {
$startAttributes['startTokenPos'] = $this->pos;
}
if ($this->attributeStartFilePosUsed) {
$startAttributes['startFilePos'] = $this->filePos;
}
if (\is_string($token)) {
$value = $token;
if (isset($token[1])) {
// bug in token_get_all
$this->filePos += 2;
$id = ord('"');
} else {
$this->filePos += 1;
$id = ord($token);
}
} elseif (!isset($this->dropTokens[$token[0]])) {
$value = $token[1];
$id = $this->tokenMap[$token[0]];
if (\T_CLOSE_TAG === $token[0]) {
$this->prevCloseTagHasNewline = false !== strpos($token[1], "\n")
|| false !== strpos($token[1], "\r");
} elseif (\T_INLINE_HTML === $token[0]) {
$startAttributes['hasLeadingNewline'] = $this->prevCloseTagHasNewline;
}
$this->line += substr_count($value, "\n");
$this->filePos += \strlen($value);
} else {
$origLine = $this->line;
$origFilePos = $this->filePos;
$this->line += substr_count($token[1], "\n");
$this->filePos += \strlen($token[1]);
if (\T_COMMENT === $token[0] || \T_DOC_COMMENT === $token[0]) {
if ($this->attributeCommentsUsed) {
$comment = \T_DOC_COMMENT === $token[0]
? new Comment\Doc($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos)
: new Comment($token[1],
$origLine, $origFilePos, $this->pos,
$this->line, $this->filePos - 1, $this->pos);
$startAttributes['comments'][] = $comment;
}
}
continue;
}
if ($this->attributeEndLineUsed) {
$endAttributes['endLine'] = $this->line;
}
if ($this->attributeEndTokenPosUsed) {
$endAttributes['endTokenPos'] = $this->pos;
}
if ($this->attributeEndFilePosUsed) {
$endAttributes['endFilePos'] = $this->filePos - 1;
}
return $id;
}
throw new \RuntimeException('Reached end of lexer loop');
}
/**
* Returns the token array for current code.
*
* The token array is in the same format as provided by the
* token_get_all() function and does not discard tokens (i.e.
* whitespace and comments are included). The token position
* attributes are against this token array.
*
* @return array Array of tokens in token_get_all() format
*/
public function getTokens() : array {
return $this->tokens;
}
/**
* Handles __halt_compiler() by returning the text after it.
*
* @return string Remaining text
*/
public function handleHaltCompiler() : string {
// text after T_HALT_COMPILER, still including ();
$textAfter = substr($this->code, $this->filePos);
// ensure that it is followed by ();
// this simplifies the situation, by not allowing any comments
// in between of the tokens.
if (!preg_match('~^\s*\(\s*\)\s*(?:;|\?>\r?\n?)~', $textAfter, $matches)) {
throw new Error('__HALT_COMPILER must be followed by "();"');
}
// prevent the lexer from returning any further tokens
$this->pos = count($this->tokens);
// return with (); removed
return substr($textAfter, strlen($matches[0]));
}
private function defineCompatibilityTokens() {
static $compatTokensDefined = false;
if ($compatTokensDefined) {
return;
}
$compatTokens = [
// PHP 7.4
'T_BAD_CHARACTER',
'T_FN',
'T_COALESCE_EQUAL',
// PHP 8.0
'T_NAME_QUALIFIED',
'T_NAME_FULLY_QUALIFIED',
'T_NAME_RELATIVE',
'T_MATCH',
'T_NULLSAFE_OBJECT_OPERATOR',
'T_ATTRIBUTE',
// PHP 8.1
'T_ENUM',
'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
'T_READONLY',
];
// PHP-Parser might be used together with another library that also emulates some or all
// of these tokens. Perform a sanity-check that all already defined tokens have been
// assigned a unique ID.
$usedTokenIds = [];
foreach ($compatTokens as $token) {
if (\defined($token)) {
$tokenId = \constant($token);
$clashingToken = $usedTokenIds[$tokenId] ?? null;
if ($clashingToken !== null) {
throw new \Error(sprintf(
'Token %s has same ID as token %s, ' .
'you may be using a library with broken token emulation',
$token, $clashingToken
));
}
$usedTokenIds[$tokenId] = $token;
}
}
// Now define any tokens that have not yet been emulated. Try to assign IDs from -1
// downwards, but skip any IDs that may already be in use.
$newTokenId = -1;
foreach ($compatTokens as $token) {
if (!\defined($token)) {
while (isset($usedTokenIds[$newTokenId])) {
$newTokenId--;
}
\define($token, $newTokenId);
$newTokenId--;
}
}
$compatTokensDefined = true;
}
/**
* Creates the token map.
*
* The token map maps the PHP internal token identifiers
* to the identifiers used by the Parser. Additionally it
* maps T_OPEN_TAG_WITH_ECHO to T_ECHO and T_CLOSE_TAG to ';'.
*
* @return array The token map
*/
protected function createTokenMap() : array {
$tokenMap = [];
// 256 is the minimum possible token number, as everything below
// it is an ASCII value
for ($i = 256; $i < 1000; ++$i) {
if (\T_DOUBLE_COLON === $i) {
// T_DOUBLE_COLON is equivalent to T_PAAMAYIM_NEKUDOTAYIM
$tokenMap[$i] = Tokens::T_PAAMAYIM_NEKUDOTAYIM;
} elseif(\T_OPEN_TAG_WITH_ECHO === $i) {
// T_OPEN_TAG_WITH_ECHO with dropped T_OPEN_TAG results in T_ECHO
$tokenMap[$i] = Tokens::T_ECHO;
} elseif(\T_CLOSE_TAG === $i) {
// T_CLOSE_TAG is equivalent to ';'
$tokenMap[$i] = ord(';');
} elseif ('UNKNOWN' !== $name = token_name($i)) {
if ('T_HASHBANG' === $name) {
// HHVM uses a special token for #! hashbang lines
$tokenMap[$i] = Tokens::T_INLINE_HTML;
} elseif (defined($name = Tokens::class . '::' . $name)) {
// Other tokens can be mapped directly
$tokenMap[$i] = constant($name);
}
}
}
// HHVM uses a special token for numbers that overflow to double
if (defined('T_ONUMBER')) {
$tokenMap[\T_ONUMBER] = Tokens::T_DNUMBER;
}
// HHVM also has a separate token for the __COMPILER_HALT_OFFSET__ constant
if (defined('T_COMPILER_HALT_OFFSET')) {
$tokenMap[\T_COMPILER_HALT_OFFSET] = Tokens::T_STRING;
}
// Assign tokens for which we define compatibility constants, as token_name() does not know them.
$tokenMap[\T_FN] = Tokens::T_FN;
$tokenMap[\T_COALESCE_EQUAL] = Tokens::T_COALESCE_EQUAL;
$tokenMap[\T_NAME_QUALIFIED] = Tokens::T_NAME_QUALIFIED;
$tokenMap[\T_NAME_FULLY_QUALIFIED] = Tokens::T_NAME_FULLY_QUALIFIED;
$tokenMap[\T_NAME_RELATIVE] = Tokens::T_NAME_RELATIVE;
$tokenMap[\T_MATCH] = Tokens::T_MATCH;
$tokenMap[\T_NULLSAFE_OBJECT_OPERATOR] = Tokens::T_NULLSAFE_OBJECT_OPERATOR;
$tokenMap[\T_ATTRIBUTE] = Tokens::T_ATTRIBUTE;
$tokenMap[\T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG] = Tokens::T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG;
$tokenMap[\T_ENUM] = Tokens::T_ENUM;
$tokenMap[\T_READONLY] = Tokens::T_READONLY;
return $tokenMap;
}
private function createIdentifierTokenMap(): array {
// Based on semi_reserved production.
return array_fill_keys([
\T_STRING,
\T_STATIC, \T_ABSTRACT, \T_FINAL, \T_PRIVATE, \T_PROTECTED, \T_PUBLIC, \T_READONLY,
\T_INCLUDE, \T_INCLUDE_ONCE, \T_EVAL, \T_REQUIRE, \T_REQUIRE_ONCE, \T_LOGICAL_OR, \T_LOGICAL_XOR, \T_LOGICAL_AND,
\T_INSTANCEOF, \T_NEW, \T_CLONE, \T_EXIT, \T_IF, \T_ELSEIF, \T_ELSE, \T_ENDIF, \T_ECHO, \T_DO, \T_WHILE,
\T_ENDWHILE, \T_FOR, \T_ENDFOR, \T_FOREACH, \T_ENDFOREACH, \T_DECLARE, \T_ENDDECLARE, \T_AS, \T_TRY, \T_CATCH,
\T_FINALLY, \T_THROW, \T_USE, \T_INSTEADOF, \T_GLOBAL, \T_VAR, \T_UNSET, \T_ISSET, \T_EMPTY, \T_CONTINUE, \T_GOTO,
\T_FUNCTION, \T_CONST, \T_RETURN, \T_PRINT, \T_YIELD, \T_LIST, \T_SWITCH, \T_ENDSWITCH, \T_CASE, \T_DEFAULT,
\T_BREAK, \T_ARRAY, \T_CALLABLE, \T_EXTENDS, \T_IMPLEMENTS, \T_NAMESPACE, \T_TRAIT, \T_INTERFACE, \T_CLASS,
\T_CLASS_C, \T_TRAIT_C, \T_FUNC_C, \T_METHOD_C, \T_LINE, \T_FILE, \T_DIR, \T_NS_C, \T_HALT_COMPILER, \T_FN,
\T_MATCH,
], true);
}
}

View file

@ -0,0 +1,248 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer;
use ncc\ThirdParty\nikic\PhpParser\Error;
use ncc\ThirdParty\nikic\PhpParser\ErrorHandler;
use ncc\ThirdParty\nikic\PhpParser\Lexer;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\AttributeEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\EnumTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\CoaleseEqualTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\ExplicitOctalEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\FlexibleDocStringEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\FnTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\MatchTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\NullsafeTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\NumericLiteralSeparatorEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\ReadonlyTokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\ReverseEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator\TokenEmulator;
class Emulative extends Lexer
{
const PHP_7_3 = '7.3dev';
const PHP_7_4 = '7.4dev';
const PHP_8_0 = '8.0dev';
const PHP_8_1 = '8.1dev';
/** @var mixed[] Patches used to reverse changes introduced in the code */
private $patches = [];
/** @var TokenEmulator[] */
private $emulators = [];
/** @var string */
private $targetPhpVersion;
/**
* @param mixed[] $options Lexer options. In addition to the usual options,
* accepts a 'phpVersion' string that specifies the
* version to emulate. Defaults to newest supported.
*/
public function __construct(array $options = [])
{
$this->targetPhpVersion = $options['phpVersion'] ?? Emulative::PHP_8_1;
unset($options['phpVersion']);
parent::__construct($options);
$emulators = [
new FlexibleDocStringEmulator(),
new FnTokenEmulator(),
new MatchTokenEmulator(),
new CoaleseEqualTokenEmulator(),
new NumericLiteralSeparatorEmulator(),
new NullsafeTokenEmulator(),
new AttributeEmulator(),
new EnumTokenEmulator(),
new ReadonlyTokenEmulator(),
new ExplicitOctalEmulator(),
];
// Collect emulators that are relevant for the PHP version we're running
// and the PHP version we're targeting for emulation.
foreach ($emulators as $emulator) {
$emulatorPhpVersion = $emulator->getPhpVersion();
if ($this->isForwardEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = $emulator;
} else if ($this->isReverseEmulationNeeded($emulatorPhpVersion)) {
$this->emulators[] = new ReverseEmulator($emulator);
}
}
}
public function startLexing(string $code, ErrorHandler $errorHandler = null) {
$emulators = array_filter($this->emulators, function($emulator) use($code) {
return $emulator->isEmulationNeeded($code);
});
if (empty($emulators)) {
// Nothing to emulate, yay
parent::startLexing($code, $errorHandler);
return;
}
$this->patches = [];
foreach ($emulators as $emulator) {
$code = $emulator->preprocessCode($code, $this->patches);
}
$collector = new ErrorHandler\Collecting();
parent::startLexing($code, $collector);
$this->sortPatches();
$this->fixupTokens();
$errors = $collector->getErrors();
if (!empty($errors)) {
$this->fixupErrors($errors);
foreach ($errors as $error) {
$errorHandler->handleError($error);
}
}
foreach ($emulators as $emulator) {
$this->tokens = $emulator->emulate($code, $this->tokens);
}
}
private function isForwardEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '<')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '>=');
}
private function isReverseEmulationNeeded(string $emulatorPhpVersion): bool {
return version_compare(\PHP_VERSION, $emulatorPhpVersion, '>=')
&& version_compare($this->targetPhpVersion, $emulatorPhpVersion, '<');
}
private function sortPatches()
{
// Patches may be contributed by different emulators.
// Make sure they are sorted by increasing patch position.
usort($this->patches, function($p1, $p2) {
return $p1[0] <=> $p2[0];
});
}
private function fixupTokens()
{
if (\count($this->patches) === 0) {
return;
}
// Load first patch
$patchIdx = 0;
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// We use a manual loop over the tokens, because we modify the array on the fly
$pos = 0;
for ($i = 0, $c = \count($this->tokens); $i < $c; $i++) {
$token = $this->tokens[$i];
if (\is_string($token)) {
if ($patchPos === $pos) {
// Only support replacement for string tokens.
assert($patchType === 'replace');
$this->tokens[$i] = $patchText;
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
}
$pos += \strlen($token);
continue;
}
$len = \strlen($token[1]);
$posDelta = 0;
while ($patchPos >= $pos && $patchPos < $pos + $len) {
$patchTextLen = \strlen($patchText);
if ($patchType === 'remove') {
if ($patchPos === $pos && $patchTextLen === $len) {
// Remove token entirely
array_splice($this->tokens, $i, 1, []);
$i--;
$c--;
} else {
// Remove from token string
$this->tokens[$i][1] = substr_replace(
$token[1], '', $patchPos - $pos + $posDelta, $patchTextLen
);
$posDelta -= $patchTextLen;
}
} elseif ($patchType === 'add') {
// Insert into the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, 0
);
$posDelta += $patchTextLen;
} else if ($patchType === 'replace') {
// Replace inside the token string
$this->tokens[$i][1] = substr_replace(
$token[1], $patchText, $patchPos - $pos + $posDelta, $patchTextLen
);
} else {
assert(false);
}
// Fetch the next patch
$patchIdx++;
if ($patchIdx >= \count($this->patches)) {
// No more patches, we're done
return;
}
list($patchPos, $patchType, $patchText) = $this->patches[$patchIdx];
// Multiple patches may apply to the same token. Reload the current one to check
// If the new patch applies
$token = $this->tokens[$i];
}
$pos += $len;
}
// A patch did not apply
assert(false);
}
/**
* Fixup line and position information in errors.
*
* @param Error[] $errors
*/
private function fixupErrors(array $errors) {
foreach ($errors as $error) {
$attrs = $error->getAttributes();
$posDelta = 0;
$lineDelta = 0;
foreach ($this->patches as $patch) {
list($patchPos, $patchType, $patchText) = $patch;
if ($patchPos >= $attrs['startFilePos']) {
// No longer relevant
break;
}
if ($patchType === 'add') {
$posDelta += strlen($patchText);
$lineDelta += substr_count($patchText, "\n");
} else if ($patchType === 'remove') {
$posDelta -= strlen($patchText);
$lineDelta -= substr_count($patchText, "\n");
}
}
$attrs['startFilePos'] += $posDelta;
$attrs['endFilePos'] += $posDelta;
$attrs['startLine'] += $lineDelta;
$attrs['endLine'] += $lineDelta;
$error->setAttributes($attrs);
}
}
}

View file

@ -0,0 +1,56 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class AttributeEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '#[') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way.
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '#' && isset($tokens[$i + 1]) && $tokens[$i + 1] === '[') {
array_splice($tokens, $i, 2, [
[\T_ATTRIBUTE, '#[', $line]
]);
$c--;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// TODO
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
$pos = 0;
while (false !== $pos = strpos($code, '#[', $pos)) {
// Replace #[ with %[
$code[$pos] = '%';
$patches[] = [$pos, 'replace', '#'];
$pos += 2;
}
return $code;
}
}

View file

@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class CoaleseEqualTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '??=') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if (isset($tokens[$i + 1])) {
if ($tokens[$i][0] === T_COALESCE && $tokens[$i + 1] === '=') {
array_splice($tokens, $i, 2, [
[\T_COALESCE_EQUAL, '??=', $line]
]);
$c--;
continue;
}
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ??= was not valid code previously, don't bother.
return $tokens;
}
}

View file

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class EnumTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_1;
}
public function getKeywordString(): string
{
return 'enum';
}
public function getKeywordToken(): int
{
return \T_ENUM;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
return parent::isKeywordContext($tokens, $pos)
&& isset($tokens[$pos + 2])
&& $tokens[$pos + 1][0] === \T_WHITESPACE
&& $tokens[$pos + 2][0] === \T_STRING;
}
}

View file

@ -0,0 +1,44 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
class ExplicitOctalEmulator extends TokenEmulator {
public function getPhpVersion(): string {
return Emulative::PHP_8_1;
}
public function isEmulationNeeded(string $code): bool {
return strpos($code, '0o') !== false || strpos($code, '0O') !== false;
}
public function emulate(string $code, array $tokens): array {
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i][0] == \T_LNUMBER && $tokens[$i][1] === '0' &&
isset($tokens[$i + 1]) && $tokens[$i + 1][0] == \T_STRING &&
preg_match('/[oO][0-7]+(?:_[0-7]+)*/', $tokens[$i + 1][1])
) {
$tokenKind = $this->resolveIntegerOrFloatToken($tokens[$i + 1][1]);
array_splice($tokens, $i, 2, [
[$tokenKind, '0' . $tokens[$i + 1][1], $tokens[$i][2]],
]);
$c--;
}
}
return $tokens;
}
private function resolveIntegerOrFloatToken(string $str): int
{
$str = substr($str, 1);
$str = str_replace('_', '', $str);
$num = octdec($str);
return is_float($num) ? \T_DNUMBER : \T_LNUMBER;
}
public function reverseEmulate(string $code, array $tokens): array {
// Explicit octals were not legal code previously, don't bother.
return $tokens;
}
}

View file

@ -0,0 +1,76 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class FlexibleDocStringEmulator extends TokenEmulator
{
const FLEXIBLE_DOC_STRING_REGEX = <<<'REGEX'
/<<<[ \t]*(['"]?)([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)\1\r?\n
(?:.*\r?\n)*?
(?<indentation>\h*)\2(?![a-zA-Z0-9_\x80-\xff])(?<separator>(?:;?[\r\n])?)/x
REGEX;
public function getPhpVersion(): string
{
return Emulative::PHP_7_3;
}
public function isEmulationNeeded(string $code) : bool
{
return strpos($code, '<<<') !== false;
}
public function emulate(string $code, array $tokens): array
{
// Handled by preprocessing + fixup.
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// Not supported.
return $tokens;
}
public function preprocessCode(string $code, array &$patches): string {
if (!preg_match_all(self::FLEXIBLE_DOC_STRING_REGEX, $code, $matches, PREG_SET_ORDER|PREG_OFFSET_CAPTURE)) {
// No heredoc/nowdoc found
return $code;
}
// Keep track of how much we need to adjust string offsets due to the modifications we
// already made
$posDelta = 0;
foreach ($matches as $match) {
$indentation = $match['indentation'][0];
$indentationStart = $match['indentation'][1];
$separator = $match['separator'][0];
$separatorStart = $match['separator'][1];
if ($indentation === '' && $separator !== '') {
// Ordinary heredoc/nowdoc
continue;
}
if ($indentation !== '') {
// Remove indentation
$indentationLen = strlen($indentation);
$code = substr_replace($code, '', $indentationStart + $posDelta, $indentationLen);
$patches[] = [$indentationStart + $posDelta, 'add', $indentation];
$posDelta -= $indentationLen;
}
if ($separator === '') {
// Insert newline as separator
$code = substr_replace($code, "\n", $separatorStart + $posDelta, 0);
$patches[] = [$separatorStart + $posDelta, 'remove', "\n"];
$posDelta += 1;
}
}
return $code;
}
}

View file

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class FnTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_7_4;
}
public function getKeywordString(): string
{
return 'fn';
}
public function getKeywordToken(): int
{
return \T_FN;
}
}

View file

@ -0,0 +1,62 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
abstract class KeywordEmulator extends TokenEmulator
{
abstract function getKeywordString(): string;
abstract function getKeywordToken(): int;
public function isEmulationNeeded(string $code): bool
{
return strpos(strtolower($code), $this->getKeywordString()) !== false;
}
protected function isKeywordContext(array $tokens, int $pos): bool
{
$previousNonSpaceToken = $this->getPreviousNonSpaceToken($tokens, $pos);
return $previousNonSpaceToken === null || $previousNonSpaceToken[0] !== \T_OBJECT_OPERATOR;
}
public function emulate(string $code, array $tokens): array
{
$keywordString = $this->getKeywordString();
foreach ($tokens as $i => $token) {
if ($token[0] === T_STRING && strtolower($token[1]) === $keywordString
&& $this->isKeywordContext($tokens, $i)) {
$tokens[$i][0] = $this->getKeywordToken();
}
}
return $tokens;
}
/**
* @param mixed[] $tokens
* @return array|string|null
*/
private function getPreviousNonSpaceToken(array $tokens, int $start)
{
for ($i = $start - 1; $i >= 0; --$i) {
if ($tokens[$i][0] === T_WHITESPACE) {
continue;
}
return $tokens[$i];
}
return null;
}
public function reverseEmulate(string $code, array $tokens): array
{
$keywordToken = $this->getKeywordToken();
foreach ($tokens as $i => $token) {
if ($token[0] === $keywordToken) {
$tokens[$i][0] = \T_STRING;
}
}
return $tokens;
}
}

View file

@ -0,0 +1,23 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class MatchTokenEmulator extends KeywordEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function getKeywordString(): string
{
return 'match';
}
public function getKeywordToken(): int
{
return \T_MATCH;
}
}

View file

@ -0,0 +1,67 @@
<?php declare(strict_types=1);
namespace ncc\ThirdParty\nikic\PhpParser\Lexer\TokenEmulator;
use ncc\ThirdParty\nikic\PhpParser\Lexer\Emulative;
final class NullsafeTokenEmulator extends TokenEmulator
{
public function getPhpVersion(): string
{
return Emulative::PHP_8_0;
}
public function isEmulationNeeded(string $code): bool
{
return strpos($code, '?->') !== false;
}
public function emulate(string $code, array $tokens): array
{
// We need to manually iterate and manage a count because we'll change
// the tokens array on the way
$line = 1;
for ($i = 0, $c = count($tokens); $i < $c; ++$i) {
if ($tokens[$i] === '?' && isset($tokens[$i + 1]) && $tokens[$i + 1][0] === \T_OBJECT_OPERATOR) {
array_splice($tokens, $i, 2, [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line]
]);
$c--;
continue;
}
// Handle ?-> inside encapsed string.
if ($tokens[$i][0] === \T_ENCAPSED_AND_WHITESPACE && isset($tokens[$i - 1])
&& $tokens[$i - 1][0] === \T_VARIABLE
&& preg_match('/^\?->([a-zA-Z_\x80-\xff][a-zA-Z0-9_\x80-\xff]*)/', $tokens[$i][1], $matches)
) {
$replacement = [
[\T_NULLSAFE_OBJECT_OPERATOR, '?->', $line],
[\T_STRING, $matches[1], $line],
];
if (\strlen($matches[0]) !== \strlen($tokens[$i][1])) {
$replacement[] = [
\T_ENCAPSED_AND_WHITESPACE,
\substr($tokens[$i][1], \strlen($matches[0])),
$line
];
}
array_splice($tokens, $i, 1, $replacement);
$c += \count($replacement) - 1;
continue;
}
if (\is_array($tokens[$i])) {
$line += substr_count($tokens[$i][1], "\n");
}
}
return $tokens;
}
public function reverseEmulate(string $code, array $tokens): array
{
// ?-> was not valid code previously, don't bother.
return $tokens;
}
}

Some files were not shown because too many files have changed in this diff Show more