parent
7ccdd3add9
commit
d55bb041f6
13 changed files with 571 additions and 0 deletions
@ -0,0 +1,50 @@ |
||||
--TEST-- |
||||
preprocessor comment condition stack |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function handle_preprocessor_conditions(array &$conds, array $comments): ?string |
||||
{ |
||||
foreach ($comments as $comment) { |
||||
$text = trim($comment); |
||||
if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) { |
||||
$conds[] = $matches[1]; |
||||
} elseif (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) { |
||||
$conds[] = "defined($matches[1])"; |
||||
} elseif (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) { |
||||
$conds[] = "!defined($matches[1])"; |
||||
} elseif (preg_match('/^#\s*else$/', $text)) { |
||||
if (empty($conds)) { |
||||
return 'else-error'; |
||||
} |
||||
$cond = array_pop($conds); |
||||
$conds[] = "!($cond)"; |
||||
} elseif (preg_match('/^#\s*endif$/', $text)) { |
||||
if (empty($conds)) { |
||||
return 'endif-error'; |
||||
} |
||||
array_pop($conds); |
||||
} elseif ($text !== '' && $text[0] === '#') { |
||||
return "unknown:$text"; |
||||
} |
||||
} |
||||
|
||||
return empty($conds) ? null : implode(' && ', $conds); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$conds = []; |
||||
var_dump(handle_preprocessor_conditions($conds, ['#ifdef PHP_WIN32', '#if PHP_VERSION_ID >= 80400'])); |
||||
var_dump(handle_preprocessor_conditions($conds, ['#else'])); |
||||
var_dump(handle_preprocessor_conditions($conds, ['#endif', '#endif'])); |
||||
var_dump(handle_preprocessor_conditions($conds, ['#else'])); |
||||
var_dump(handle_preprocessor_conditions($conds, ['#pragma once'])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(45) "defined(PHP_WIN32) && PHP_VERSION_ID >= 80400" |
||||
string(48) "defined(PHP_WIN32) && !(PHP_VERSION_ID >= 80400)" |
||||
NULL |
||||
string(10) "else-error" |
||||
string(20) "unknown:#pragma once" |
||||
@ -0,0 +1,51 @@ |
||||
--TEST-- |
||||
short option preprocessing splits compact short flag values |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function preprocess_short_options(array $argv): array |
||||
{ |
||||
$processed = [$argv[0]]; |
||||
|
||||
for ($i = 1; $i < count($argv); $i++) { |
||||
$arg = $argv[$i]; |
||||
if (preg_match('/^-([a-zA-Z])(.+)$/', $arg, $matches)) { |
||||
$option = $matches[1]; |
||||
$value = $matches[2]; |
||||
$processed[] = "-{$option}"; |
||||
$processed[] = $value; |
||||
} elseif (preg_match('/^-([a-zA-Z]{2,})$/', $arg, $matches)) { |
||||
$options = str_split($matches[1]); |
||||
foreach ($options as $opt) { |
||||
$processed[] = "-{$opt}"; |
||||
} |
||||
} else { |
||||
$processed[] = $arg; |
||||
} |
||||
} |
||||
|
||||
return $processed; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(preprocess_short_options(['compiler.php', '-Iinclude', '-abc', '--debug', 'project.yml'])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(7) { |
||||
[0]=> |
||||
string(12) "compiler.php" |
||||
[1]=> |
||||
string(2) "-I" |
||||
[2]=> |
||||
string(7) "include" |
||||
[3]=> |
||||
string(2) "-a" |
||||
[4]=> |
||||
string(2) "bc" |
||||
[5]=> |
||||
string(7) "--debug" |
||||
[6]=> |
||||
string(11) "project.yml" |
||||
} |
||||
@ -0,0 +1,51 @@ |
||||
--TEST-- |
||||
target name normalization with path separators and identifier validation |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function normalize_target_name(string $name): array |
||||
{ |
||||
$outputDir = ''; |
||||
if (str_contains($name, '/') || str_contains($name, '\\')) { |
||||
$outputDir = dirname($name); |
||||
$name = basename($name); |
||||
} |
||||
|
||||
$name = str_replace(['-', '*'], '_', $name); |
||||
$valid = preg_match('/^[a-zA-Z0-9_]+$/', $name) === 1; |
||||
|
||||
return [$outputDir, $name, $valid]; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(normalize_target_name('build/my-app')); |
||||
var_dump(normalize_target_name('C:\\tmp\\my*app')); |
||||
var_dump(normalize_target_name('bad.name')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(3) { |
||||
[0]=> |
||||
string(5) "build" |
||||
[1]=> |
||||
string(6) "my_app" |
||||
[2]=> |
||||
bool(true) |
||||
} |
||||
array(3) { |
||||
[0]=> |
||||
string(1) "." |
||||
[1]=> |
||||
string(13) "C:\tmp\my_app" |
||||
[2]=> |
||||
bool(false) |
||||
} |
||||
array(3) { |
||||
[0]=> |
||||
string(0) "" |
||||
[1]=> |
||||
string(8) "bad.name" |
||||
[2]=> |
||||
bool(false) |
||||
} |
||||
@ -0,0 +1,29 @@ |
||||
--TEST-- |
||||
compiler command name normalization from command string |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function normalize_compiler_name(string $compilerName): string |
||||
{ |
||||
$firstToken = strtok(trim($compilerName), ' '); |
||||
if ($firstToken === false || $firstToken === '') { |
||||
return ''; |
||||
} |
||||
|
||||
$name = basename(str_replace('\\', '/', $firstToken)); |
||||
$name = strtolower($name); |
||||
|
||||
return preg_replace('/\.exe$/', '', $name); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(normalize_compiler_name(' C:\\LLVM\\bin\\CLANG++.EXE -O2')); |
||||
var_dump(normalize_compiler_name('/usr/bin/g++ -std=c++20')); |
||||
var_dump(normalize_compiler_name('')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(7) "clang++" |
||||
string(3) "g++" |
||||
string(0) "" |
||||
@ -0,0 +1,102 @@ |
||||
--TEST-- |
||||
C++ signature parameter splitter with nested templates and defaults |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function split_cpp_parameters(string $paramsStr): array |
||||
{ |
||||
$params = []; |
||||
$current = ''; |
||||
$depth = 0; |
||||
$length = strlen($paramsStr); |
||||
|
||||
for ($i = 0; $i < $length; $i++) { |
||||
$char = $paramsStr[$i]; |
||||
|
||||
if ($char === '<' || $char === '(' || $char === '[') { |
||||
$depth++; |
||||
$current .= $char; |
||||
} elseif ($char === '>' || $char === ')' || $char === ']') { |
||||
$depth--; |
||||
$current .= $char; |
||||
} elseif ($char === ',' && $depth === 0) { |
||||
$params[] = $current; |
||||
$current = ''; |
||||
} else { |
||||
$current .= $char; |
||||
} |
||||
} |
||||
|
||||
if (!empty($current)) { |
||||
$params[] = $current; |
||||
} |
||||
|
||||
return $params; |
||||
} |
||||
|
||||
function parse_cpp_parameter(string $param): array |
||||
{ |
||||
$param = trim($param); |
||||
$param = preg_replace('/\s*=\s*.*$/', '', $param); |
||||
|
||||
if (preg_match('/^(.+?)\s+(\w+)\s*$/', $param, $matches)) { |
||||
return [ |
||||
'type' => trim($matches[1]), |
||||
'name' => trim($matches[2]), |
||||
]; |
||||
} |
||||
|
||||
return [ |
||||
'type' => $param, |
||||
'name' => '', |
||||
]; |
||||
} |
||||
|
||||
function parse_cpp_parameters(string $signature, string $funcName): array |
||||
{ |
||||
$pattern = '/' . preg_quote($funcName, '/') . '\s*\((.*?)\)/s'; |
||||
if (!preg_match($pattern, $signature, $matches)) { |
||||
return []; |
||||
} |
||||
|
||||
$paramsStr = trim($matches[1]); |
||||
if (empty($paramsStr) || $paramsStr === 'void') { |
||||
return []; |
||||
} |
||||
|
||||
return array_map('parse_cpp_parameter', split_cpp_parameters($paramsStr)); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$signature = 'static std::vector<std::pair<int, std::string>> build(std::map<int, std::string> values, const char* name = "x", Callback<int,string> cb)'; |
||||
var_dump(parse_cpp_parameters($signature, 'build')); |
||||
var_dump(parse_cpp_parameters('void reset(void)', 'reset')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(3) { |
||||
[0]=> |
||||
array(2) { |
||||
["type"]=> |
||||
string(26) "std::map<int, std::string>" |
||||
["name"]=> |
||||
string(6) "values" |
||||
} |
||||
[1]=> |
||||
array(2) { |
||||
["type"]=> |
||||
string(11) "const char*" |
||||
["name"]=> |
||||
string(4) "name" |
||||
} |
||||
[2]=> |
||||
array(2) { |
||||
["type"]=> |
||||
string(20) "Callback<int,string>" |
||||
["name"]=> |
||||
string(2) "cb" |
||||
} |
||||
} |
||||
array(0) { |
||||
} |
||||
@ -0,0 +1,48 @@ |
||||
--TEST-- |
||||
doc comment tag extraction with preg_match_all set order |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function parse_doc_tags(array $comments): array |
||||
{ |
||||
$tags = []; |
||||
|
||||
foreach ($comments as $comment) { |
||||
if (preg_match_all('/@(\w+)\s+(.*)$/m', $comment, $matches, PREG_SET_ORDER)) { |
||||
foreach ($matches as $match) { |
||||
$tagName = $match[1]; |
||||
$tagValue = trim($match[2]); |
||||
|
||||
if (!isset($tags[$tagName])) { |
||||
$tags[$tagName] = []; |
||||
} |
||||
|
||||
$tags[$tagName][] = $tagValue; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $tags; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$comment = "summary\n@param int \$id\n@param string \$name user name\n@return bool"; |
||||
var_dump(parse_doc_tags([$comment])); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(2) { |
||||
["param"]=> |
||||
array(2) { |
||||
[0]=> |
||||
string(7) "int $id" |
||||
[1]=> |
||||
string(22) "string $name user name" |
||||
} |
||||
["return"]=> |
||||
array(1) { |
||||
[0]=> |
||||
string(4) "bool" |
||||
} |
||||
} |
||||
@ -0,0 +1,42 @@ |
||||
--TEST-- |
||||
docblock param regex with recursive named groups |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function parse_doc_param_name(string $value): array |
||||
{ |
||||
$matches = []; |
||||
preg_match('/^\s*(?<type>[\w\|\\\\]+(?<parens>\((?<inparens>(?:(?&parens)|[^(){}[\]<>]*+))++\)|\{(?&inparens)\}|\[(?&inparens)\]|<(?&inparens)>)*+(?::(?&type))?)\s*(\.\.\.)?\$(?<name>\w+).*$/', $value, $matches); |
||||
|
||||
return [ |
||||
$matches['type'] ?? '', |
||||
$matches['name'] ?? '', |
||||
]; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(parse_doc_param_name('callable(array<string, int>):mixed $handler callback')); |
||||
var_dump(parse_doc_param_name('int ...$items')); |
||||
var_dump(parse_doc_param_name('$missingType')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
array(2) { |
||||
[0]=> |
||||
string(34) "callable(array<string, int>):mixed" |
||||
[1]=> |
||||
string(7) "handler" |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
string(3) "int" |
||||
[1]=> |
||||
string(5) "items" |
||||
} |
||||
array(2) { |
||||
[0]=> |
||||
string(0) "" |
||||
[1]=> |
||||
string(0) "" |
||||
} |
||||
@ -0,0 +1,43 @@ |
||||
--TEST-- |
||||
file extension classifier with pathinfo and in_array |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function file_name_of(string $path): string |
||||
{ |
||||
return pathinfo($path, PATHINFO_FILENAME); |
||||
} |
||||
|
||||
function file_ext_of(string $path): string |
||||
{ |
||||
return pathinfo($path, PATHINFO_EXTENSION); |
||||
} |
||||
|
||||
function classify_source_file(string $file): string |
||||
{ |
||||
$ext = file_ext_of($file); |
||||
if (in_array($ext, ['php', 'inc', 'phpt'])) { |
||||
return 'php:' . file_name_of($file); |
||||
} |
||||
if (in_array($ext, ['cc', 'cpp', 'cxx'])) { |
||||
return 'cpp:' . file_name_of($file); |
||||
} |
||||
if (in_array($ext, ['c', 'h', 'hpp'])) { |
||||
return 'native:' . file_name_of($file); |
||||
} |
||||
return 'other:' . $ext; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(classify_source_file('/tmp/src/main.php')); |
||||
var_dump(classify_source_file('lib/compiler.cxx')); |
||||
var_dump(classify_source_file('include/aot.hpp')); |
||||
var_dump(classify_source_file('README')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(8) "php:main" |
||||
string(12) "cpp:compiler" |
||||
string(10) "native:aot" |
||||
string(6) "other:" |
||||
@ -0,0 +1,36 @@ |
||||
--TEST-- |
||||
resource version normalization with ltrim explode preg_replace and slice |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function normalize_resource_version(string $version): string |
||||
{ |
||||
$version = ltrim($version, 'vV'); |
||||
|
||||
if (str_contains($version, ',')) { |
||||
return $version; |
||||
} |
||||
|
||||
$parts = explode('.', $version); |
||||
foreach ($parts as $i => $part) { |
||||
$parts[$i] = preg_replace('/[^0-9]/', '', $part) ?: '0'; |
||||
} |
||||
|
||||
while (count($parts) < 4) { |
||||
$parts[] = '0'; |
||||
} |
||||
|
||||
return implode(',', array_slice($parts, 0, 4)); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(normalize_resource_version('v1.2.3')); |
||||
var_dump(normalize_resource_version('V10.beta.5-rc1.7.9')); |
||||
var_dump(normalize_resource_version('1,2,3,4')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(7) "1,2,3,0" |
||||
string(9) "10,0,51,7" |
||||
string(7) "1,2,3,4" |
||||
@ -0,0 +1,30 @@ |
||||
--TEST-- |
||||
unix library paths normalized into linker flags |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function make_unix_link_lib_flags(array $libraries, string $ext): string |
||||
{ |
||||
$flags = []; |
||||
foreach ($libraries as $lib) { |
||||
$libName = basename($lib); |
||||
if (str_starts_with($libName, 'lib')) { |
||||
$libName = substr($libName, 3); |
||||
} |
||||
$libName = preg_replace('/\.(a|' . $ext . ')$/', '', $libName); |
||||
|
||||
$flags[] = '-l' . $libName; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(make_unix_link_lib_flags(['/usr/lib/libssl.so', 'libcrypto.a', 'z'], 'so')); |
||||
var_dump(make_unix_link_lib_flags(['libcustom.dylib', '/opt/lib/libplain.txt'], 'dylib')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(18) "-lssl -lcrypto -lz" |
||||
string(20) "-lcustom -lplain.txt" |
||||
@ -0,0 +1,21 @@ |
||||
--TEST-- |
||||
camel case to snake case with regex replacement |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function camel_to_snake(string $name): string |
||||
{ |
||||
return strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $name)); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(camel_to_snake('toStdOrderedMap')); |
||||
var_dump(camel_to_snake('HTMLParserValue')); |
||||
var_dump(camel_to_snake('simple')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(18) "to_std_ordered_map" |
||||
string(16) "htmlparser_value" |
||||
string(6) "simple" |
||||
@ -0,0 +1,26 @@ |
||||
--TEST-- |
||||
negative string offset read |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function format_kind(string $format): string |
||||
{ |
||||
if ($format[0] == '.' and $format[-1] == 'f') { |
||||
return 'float:' . substr($format, 1, strlen($format) - 2); |
||||
} elseif (str_contains($format, '%')) { |
||||
return 'date:' . $format; |
||||
} |
||||
return 'other:' . $format[-1]; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(format_kind('.3f')); |
||||
var_dump(format_kind('%Y-%m-%d')); |
||||
var_dump(format_kind('abc')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(7) "float:3" |
||||
string(13) "date:%Y-%m-%d" |
||||
string(7) "other:c" |
||||
@ -0,0 +1,42 @@ |
||||
--TEST-- |
||||
numeric literal classifier style regex and digit extraction |
||||
--FILE-- |
||||
<?php |
||||
|
||||
function strip_numeric_underscores(string $value): string |
||||
{ |
||||
return str_replace('_', '', $value); |
||||
} |
||||
|
||||
function is_big_int_literal_string(string $rawValue): bool |
||||
{ |
||||
$clean = strip_numeric_underscores($rawValue); |
||||
if (!preg_match('/^\d+$/', $clean)) { |
||||
return false; |
||||
} |
||||
return strlen(ltrim($clean, '0')) >= 19; |
||||
} |
||||
|
||||
function is_decimal_literal_string(string $rawValue): bool |
||||
{ |
||||
$clean = strip_numeric_underscores($rawValue); |
||||
if (!preg_match('/[\.eE]/', $clean)) { |
||||
return false; |
||||
} |
||||
$digits = preg_replace('/[^0-9]/', '', $clean); |
||||
return strlen(ltrim($digits, '0')) >= 16; |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump(is_big_int_literal_string('9_223_372_036_854_775_808')); |
||||
var_dump(is_big_int_literal_string('0x10000000000000000')); |
||||
var_dump(is_decimal_literal_string('123_456_789_012_345.6')); |
||||
var_dump(is_decimal_literal_string('123.45')); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
bool(true) |
||||
bool(false) |
||||
bool(true) |
||||
bool(false) |
||||
Loading…
Reference in new issue