Skip to content

Commit 51bc623

Browse files
committed
Generate function entries from stubs
If @generate-function-entries is specified in the stub file, also generate function entries for the extension. Currently limited to free functions only.
1 parent 305b17e commit 51bc623

File tree

4 files changed

+1502
-762
lines changed

4 files changed

+1502
-762
lines changed

build/gen_stub.php

100755100644
Lines changed: 147 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/usr/bin/env php
22
<?php declare(strict_types=1);
33

4+
use PhpParser\Comment\Doc as DocComment;
45
use PhpParser\Node;
56
use PhpParser\Node\Expr;
67
use PhpParser\Node\Stmt;
@@ -46,8 +47,8 @@ function processStubFile(string $stubFile) {
4647
$arginfoFile = str_replace('.stub.php', '', $stubFile) . '_arginfo.h';
4748

4849
try {
49-
$funcInfos = parseStubFile($stubFile);
50-
$arginfoCode = generateArgInfoCode($funcInfos);
50+
$fileInfo = parseStubFile($stubFile);
51+
$arginfoCode = generateArgInfoCode($fileInfo);
5152
file_put_contents($arginfoFile, $arginfoCode);
5253
} catch (Exception $e) {
5354
echo "In $stubFile:\n{$e->getMessage()}\n";
@@ -299,6 +300,10 @@ public function equals(ReturnInfo $other): bool {
299300
class FuncInfo {
300301
/** @var string */
301302
public $name;
303+
/** @var ?string */
304+
public $className;
305+
/** @var ?string */
306+
public $alias;
302307
/** @var ArgInfo[] */
303308
public $args;
304309
/** @var ReturnInfo */
@@ -309,9 +314,12 @@ class FuncInfo {
309314
public $cond;
310315

311316
public function __construct(
312-
string $name, array $args, ReturnInfo $return, int $numRequiredArgs, ?string $cond
317+
string $name, ?string $className, ?string $alias, array $args, ReturnInfo $return,
318+
int $numRequiredArgs, ?string $cond
313319
) {
314320
$this->name = $name;
321+
$this->className = $className;
322+
$this->alias = $alias;
315323
$this->args = $args;
316324
$this->return = $return;
317325
$this->numRequiredArgs = $numRequiredArgs;
@@ -333,11 +341,33 @@ public function equalsApartFromName(FuncInfo $other): bool {
333341
&& $this->numRequiredArgs === $other->numRequiredArgs
334342
&& $this->cond === $other->cond;
335343
}
344+
345+
public function getArgInfoName(): string {
346+
if ($this->className) {
347+
return 'arginfo_class_' . $this->className . '_' . $this->name;
348+
}
349+
return 'arginfo_' . $this->name;
350+
}
351+
}
352+
353+
class FileInfo {
354+
/** @var FuncInfo[] */
355+
public $funcInfos;
356+
/** @var bool */
357+
public $generateFunctionEntries;
358+
359+
public function __construct(array $funcInfos, bool $generateFunctionEntries) {
360+
$this->funcInfos = $funcInfos;
361+
$this->generateFunctionEntries = $generateFunctionEntries;
362+
}
336363
}
337364

338-
function parseFunctionLike(string $name, Node\FunctionLike $func, ?string $cond): FuncInfo {
365+
function parseFunctionLike(
366+
string $name, ?string $className, Node\FunctionLike $func, ?string $cond
367+
): FuncInfo {
339368
$comment = $func->getDocComment();
340369
$paramMeta = [];
370+
$alias = null;
341371

342372
if ($comment) {
343373
$commentText = substr($comment->getText(), 2, -2);
@@ -349,6 +379,8 @@ function parseFunctionLike(string $name, Node\FunctionLike $func, ?string $cond)
349379
$paramMeta[$varName] = [];
350380
}
351381
$paramMeta[$varName]['preferRef'] = true;
382+
} else if (preg_match('/^\*\s*@alias\s+(.+)$/', trim($commentLine), $matches)) {
383+
$alias = $matches[1];
352384
}
353385
}
354386
}
@@ -403,7 +435,7 @@ function parseFunctionLike(string $name, Node\FunctionLike $func, ?string $cond)
403435
$return = new ReturnInfo(
404436
$func->returnsByRef(),
405437
$returnType ? Type::fromNode($returnType) : null);
406-
return new FuncInfo($name, $args, $return, $numRequiredArgs, $cond);
438+
return new FuncInfo($name, $className, $alias, $args, $return, $numRequiredArgs, $cond);
407439
}
408440

409441
function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string {
@@ -434,8 +466,24 @@ function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string {
434466
return empty($conds) ? null : implode(' && ', $conds);
435467
}
436468

437-
/** @return FuncInfo[] */
438-
function parseStubFile(string $fileName) {
469+
function getFileDocComment(array $stmts): ?DocComment {
470+
if (empty($stmts)) {
471+
return null;
472+
}
473+
474+
$comments = $stmts[0]->getComments();
475+
if (empty($comments)) {
476+
return null;
477+
}
478+
479+
if ($comments[0] instanceof DocComment) {
480+
return $comments[0];
481+
}
482+
483+
return null;
484+
}
485+
486+
function parseStubFile(string $fileName): FileInfo {
439487
if (!file_exists($fileName)) {
440488
throw new Exception("File $fileName does not exist");
441489
}
@@ -450,6 +498,14 @@ function parseStubFile(string $fileName) {
450498
$stmts = $parser->parse($code);
451499
$nodeTraverser->traverse($stmts);
452500

501+
$generateFunctionEntries = false;
502+
$fileDocComment = getFileDocComment($stmts);
503+
if ($fileDocComment) {
504+
if (strpos($fileDocComment->getText(), '@generate-function-entries') !== false) {
505+
$generateFunctionEntries = true;
506+
}
507+
}
508+
453509
$funcInfos = [];
454510
$conds = [];
455511
foreach ($stmts as $stmt) {
@@ -459,7 +515,7 @@ function parseStubFile(string $fileName) {
459515
}
460516

461517
if ($stmt instanceof Stmt\Function_) {
462-
$funcInfos[] = parseFunctionLike($stmt->name->toString(), $stmt, $cond);
518+
$funcInfos[] = parseFunctionLike($stmt->name->toString(), null, $stmt, $cond);
463519
continue;
464520
}
465521

@@ -476,15 +532,15 @@ function parseStubFile(string $fileName) {
476532
}
477533

478534
$funcInfos[] = parseFunctionLike(
479-
'class_' . $className . '_' . $classStmt->name->toString(), $classStmt, $cond);
535+
$classStmt->name->toString(), $className, $classStmt, $cond);
480536
}
481537
continue;
482538
}
483539

484540
throw new Exception("Unexpected node {$stmt->getType()}");
485541
}
486542

487-
return $funcInfos;
543+
return new FileInfo($funcInfos, $generateFunctionEntries);
488544
}
489545

490546
function funcInfoToCode(FuncInfo $funcInfo): string {
@@ -494,28 +550,32 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
494550
if (null !== $simpleReturnType = $returnType->tryToSimpleType()) {
495551
if ($simpleReturnType->isBuiltin) {
496552
$code .= sprintf(
497-
"ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
498-
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
553+
"ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(%s, %d, %d, %s, %d)\n",
554+
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
555+
$funcInfo->numRequiredArgs,
499556
$simpleReturnType->toTypeCode(), $returnType->isNullable()
500557
);
501558
} else {
502559
$code .= sprintf(
503-
"ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_%s, %d, %d, %s, %d)\n",
504-
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
560+
"ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(%s, %d, %d, %s, %d)\n",
561+
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
562+
$funcInfo->numRequiredArgs,
505563
$simpleReturnType->toEscapedName(), $returnType->isNullable()
506564
);
507565
}
508566
} else if (null !== $representableType = $returnType->tryToRepresentableType()) {
509567
if ($representableType->classType !== null) {
510568
$code .= sprintf(
511-
"ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_%s, %d, %d, %s, %s)\n",
512-
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
569+
"ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(%s, %d, %d, %s, %s)\n",
570+
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
571+
$funcInfo->numRequiredArgs,
513572
$representableType->classType->toEscapedName(), $representableType->toTypeMask()
514573
);
515574
} else {
516575
$code .= sprintf(
517-
"ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_%s, %d, %d, %s)\n",
518-
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs,
576+
"ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(%s, %d, %d, %s)\n",
577+
$funcInfo->getArgInfoName(), $funcInfo->return->byRef,
578+
$funcInfo->numRequiredArgs,
519579
$representableType->toTypeMask()
520580
);
521581
}
@@ -524,8 +584,8 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
524584
}
525585
} else {
526586
$code .= sprintf(
527-
"ZEND_BEGIN_ARG_INFO_EX(arginfo_%s, 0, %d, %d)\n",
528-
$funcInfo->name, $funcInfo->return->byRef, $funcInfo->numRequiredArgs
587+
"ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n",
588+
$funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs
529589
);
530590
}
531591

@@ -566,7 +626,7 @@ function funcInfoToCode(FuncInfo $funcInfo): string {
566626
}
567627

568628
$code .= "ZEND_END_ARG_INFO()";
569-
return $code;
629+
return $code . "\n";
570630
}
571631

572632
function findEquivalentFuncInfo(array $generatedFuncInfos, $funcInfo): ?FuncInfo {
@@ -578,33 +638,80 @@ function findEquivalentFuncInfo(array $generatedFuncInfos, $funcInfo): ?FuncInfo
578638
return null;
579639
}
580640

581-
/** @param FuncInfo[] $funcInfos */
582-
function generateArginfoCode(array $funcInfos): string {
583-
$code = "/* This is a generated file, edit the .stub.php file instead. */";
584-
$generatedFuncInfos = [];
585-
foreach ($funcInfos as $funcInfo) {
586-
$code .= "\n\n";
587-
if ($funcInfo->cond) {
588-
$code .= "#if {$funcInfo->cond}\n";
641+
function generateCodeWithConditions(
642+
FileInfo $fileInfo, string $separator, Closure $codeGenerator): string {
643+
$code = "";
644+
foreach ($fileInfo->funcInfos as $funcInfo) {
645+
$funcCode = $codeGenerator($funcInfo);
646+
if ($funcCode === null) {
647+
continue;
589648
}
590649

591-
/* If there already is an equivalent arginfo structure, only emit a #define */
592-
if ($generatedFuncInfo = findEquivalentFuncInfo($generatedFuncInfos, $funcInfo)) {
593-
$code .= sprintf(
594-
"#define arginfo_%s arginfo_%s",
595-
$funcInfo->name, $generatedFuncInfo->name
596-
);
650+
$code .= $separator;
651+
if ($funcInfo->cond) {
652+
$code .= "#if {$funcInfo->cond}\n";
653+
$code .= $funcCode;
654+
$code .= "#endif\n";
597655
} else {
598-
$code .= funcInfoToCode($funcInfo);
656+
$code .= $funcCode;
599657
}
658+
}
659+
return $code;
660+
}
600661

601-
if ($funcInfo->cond) {
602-
$code .= "\n#endif";
662+
function generateArgInfoCode(FileInfo $fileInfo): string {
663+
$funcInfos = $fileInfo->funcInfos;
664+
665+
$code = "/* This is a generated file, edit the .stub.php file instead. */\n";
666+
$generatedFuncInfos = [];
667+
$code .= generateCodeWithConditions(
668+
$fileInfo, "\n",
669+
function(FuncInfo $funcInfo) use(&$generatedFuncInfos) {
670+
/* If there already is an equivalent arginfo structure, only emit a #define */
671+
if ($generatedFuncInfo = findEquivalentFuncInfo($generatedFuncInfos, $funcInfo)) {
672+
$code = sprintf(
673+
"#define %s %s\n",
674+
$funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName()
675+
);
676+
} else {
677+
$code = funcInfoToCode($funcInfo);
678+
}
679+
680+
$generatedFuncInfos[] = $funcInfo;
681+
return $code;
603682
}
683+
);
684+
685+
if ($fileInfo->generateFunctionEntries) {
686+
$code .= "\n\n";
687+
$code .= generateCodeWithConditions($fileInfo, "", function(FuncInfo $funcInfo) {
688+
if ($funcInfo->className || $funcInfo->alias) {
689+
return null;
690+
}
691+
692+
return "ZEND_FUNCTION($funcInfo->name);\n";
693+
});
694+
695+
$code .= "\n\nstatic const zend_function_entry ext_functions[] = {\n";
696+
$code .= generateCodeWithConditions($fileInfo, "", function(FuncInfo $funcInfo) {
697+
if ($funcInfo->className) {
698+
return null;
699+
}
604700

605-
$generatedFuncInfos[] = $funcInfo;
701+
if ($funcInfo->alias) {
702+
return sprintf(
703+
"\tZEND_FALIAS(%s, %s, %s)\n",
704+
$funcInfo->name, $funcInfo->alias, $funcInfo->getArgInfoName()
705+
);
706+
} else {
707+
return sprintf("\tZEND_FE(%s, %s)\n", $funcInfo->name, $funcInfo->getArgInfoName());
708+
}
709+
});
710+
$code .= "\tZEND_FE_END\n";
711+
$code .= "};\n";
606712
}
607-
return $code . "\n";
713+
714+
return $code;
608715
}
609716

610717
function initPhpParser() {

0 commit comments

Comments
 (0)