TypePHP 编译器
https://swoole.com/aot/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
143 lines
4.7 KiB
143 lines
4.7 KiB
<?php
|
|
/**
|
|
* This file is part of TypePHP(AOT).
|
|
*
|
|
* @link https://www.swoole.com/aot/
|
|
* @contact service@swoole.com
|
|
*/
|
|
|
|
namespace TypePhp\Build;
|
|
|
|
use Symfony\Component\Yaml\Yaml;
|
|
|
|
final readonly class PhpBuilderConfiguration
|
|
{
|
|
/**
|
|
* @param list<string> $extensions
|
|
*/
|
|
public function __construct(
|
|
public bool $zts,
|
|
public array $extensions,
|
|
) {
|
|
}
|
|
|
|
public static function fromYaml(mixed $value): self
|
|
{
|
|
if (!is_array($value) || ($value !== [] && array_is_list($value))) {
|
|
throw new \InvalidArgumentException('`php-builder` must be a mapping');
|
|
}
|
|
return self::fromArray($value);
|
|
}
|
|
|
|
public static function fromCommandLine(string $value): self
|
|
{
|
|
$value = trim($value);
|
|
if ($value === '') {
|
|
throw new \InvalidArgumentException('Option --php-builder requires a non-empty configuration');
|
|
}
|
|
|
|
try {
|
|
$parsed = Yaml::parse(self::semicolonSeparatedYaml($value));
|
|
} catch (\Throwable $exception) {
|
|
throw new \InvalidArgumentException(
|
|
'Invalid --php-builder configuration: ' . $exception->getMessage(),
|
|
previous: $exception,
|
|
);
|
|
}
|
|
return self::fromYaml($parsed);
|
|
}
|
|
|
|
/** @param array<string, mixed> $config */
|
|
private static function fromArray(array $config): self
|
|
{
|
|
$unknown = array_diff(array_keys($config), ['zts', 'extensions']);
|
|
if ($unknown !== []) {
|
|
throw new \InvalidArgumentException(
|
|
'Unknown `php-builder` option' . (count($unknown) > 1 ? 's' : '')
|
|
. ': ' . implode(', ', array_map(static fn (string $key): string => "`{$key}`", $unknown)),
|
|
);
|
|
}
|
|
$extensions = self::stringList($config['extensions'] ?? [], 'php-builder.extensions', true);
|
|
$extensions = SapiExtensionRequirements::merge($extensions);
|
|
|
|
return new self(
|
|
self::boolean($config['zts'] ?? false),
|
|
$extensions,
|
|
);
|
|
}
|
|
|
|
/** @return list<string> */
|
|
private static function stringList(mixed $value, string $name, bool $allowEmpty = false): array
|
|
{
|
|
$values = is_string($value) ? preg_split('/\s*,\s*/', trim($value)) : $value;
|
|
if (!is_array($values)) {
|
|
throw new \InvalidArgumentException("`{$name}` must be a string or list");
|
|
}
|
|
$result = [];
|
|
foreach ($values as $entry) {
|
|
if (!is_string($entry) || trim($entry) === '') {
|
|
throw new \InvalidArgumentException("Each `{$name}` entry must be a non-empty string");
|
|
}
|
|
$result[] = strtolower(trim($entry));
|
|
}
|
|
if (!$allowEmpty && $result === []) {
|
|
throw new \InvalidArgumentException("`{$name}` must not be empty");
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
private static function boolean(mixed $value): bool
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value;
|
|
}
|
|
if ($value === 1 || $value === 0) {
|
|
return (bool) $value;
|
|
}
|
|
if (is_string($value)) {
|
|
return match (strtolower(trim($value))) {
|
|
'on', 'true', 'yes', '1' => true,
|
|
'off', 'false', 'no', '0' => false,
|
|
default => throw new \InvalidArgumentException('`php-builder.zts` must be on or off'),
|
|
};
|
|
}
|
|
throw new \InvalidArgumentException('`php-builder.zts` must be on or off');
|
|
}
|
|
|
|
private static function semicolonSeparatedYaml(string $value): string
|
|
{
|
|
$result = '';
|
|
$quote = null;
|
|
$depth = 0;
|
|
$escaped = false;
|
|
foreach (str_split($value) as $character) {
|
|
if ($escaped) {
|
|
$result .= $character;
|
|
$escaped = false;
|
|
continue;
|
|
}
|
|
if ($character === '\\' && $quote === '"') {
|
|
$result .= $character;
|
|
$escaped = true;
|
|
continue;
|
|
}
|
|
if (($character === '"' || $character === "'") && ($quote === null || $quote === $character)) {
|
|
$quote = $quote === null ? $character : null;
|
|
$result .= $character;
|
|
continue;
|
|
}
|
|
if ($quote === null) {
|
|
if ($character === '[' || $character === '{') {
|
|
$depth++;
|
|
} elseif ($character === ']' || $character === '}') {
|
|
$depth--;
|
|
} elseif ($character === ';' && $depth === 0) {
|
|
$result .= "\n";
|
|
continue;
|
|
}
|
|
}
|
|
$result .= $character;
|
|
}
|
|
return preg_replace('/\n[ \t]+/', "\n", trim($result)) ?? trim($result);
|
|
}
|
|
}
|
|
|