8
8
use PhpParser\Node\Name;
9
9
use PhpParser\Node\Stmt;
10
10
use PhpParser\Node\Stmt\Class_;
11
+ use PhpParser\Node\Stmt\Enum_;
11
12
use PhpParser\Node\Stmt\Interface_;
13
+ use PhpParser\Node\Stmt\Trait_;
12
14
use PhpParser\PrettyPrinter\Standard;
13
15
use PhpParser\PrettyPrinterAbstract;
14
16
@@ -1324,6 +1326,51 @@ public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDoc
1324
1326
}
1325
1327
}
1326
1328
1329
+ function initializeZval(string $zvalName, $value): string
1330
+ {
1331
+ $code = "\tzval $zvalName;\n";
1332
+
1333
+ switch (gettype($value)) {
1334
+ case "NULL":
1335
+ $code .= "\tZVAL_NULL(&$zvalName);\n";
1336
+ break;
1337
+
1338
+ case "boolean":
1339
+ $code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
1340
+ break;
1341
+
1342
+ case "integer":
1343
+ $code .= "\tZVAL_LONG(&$zvalName, $value);\n";
1344
+ break;
1345
+
1346
+ case "double":
1347
+ $code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
1348
+ break;
1349
+
1350
+ case "string":
1351
+ if ($value === "") {
1352
+ $code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
1353
+ } else {
1354
+ $code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$value\", sizeof(\"$value\") - 1, 1);\n";
1355
+ $code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
1356
+ }
1357
+ break;
1358
+
1359
+ case "array":
1360
+ if (empty($value)) {
1361
+ $code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
1362
+ } else {
1363
+ throw new Exception("Unimplemented default value");
1364
+ }
1365
+ break;
1366
+
1367
+ default:
1368
+ throw new Exception("Invalid default value");
1369
+ }
1370
+
1371
+ return $code;
1372
+ }
1373
+
1327
1374
class PropertyInfo
1328
1375
{
1329
1376
/** @var PropertyName */
@@ -1361,10 +1408,8 @@ public function getDeclaration(): string {
1361
1408
$defaultValueConstant = false;
1362
1409
if ($this->defaultValue === null) {
1363
1410
$defaultValue = null;
1364
- $defaultValueType = "undefined";
1365
1411
} else {
1366
1412
$defaultValue = $this->evaluateDefaultValue($defaultValueConstant);
1367
- $defaultValueType = gettype($defaultValue);
1368
1413
}
1369
1414
1370
1415
if ($defaultValueConstant) {
@@ -1411,80 +1456,26 @@ public function getDeclaration(): string {
1411
1456
}
1412
1457
}
1413
1458
1414
- $code .= $this->initializeValue($defaultValueType, $defaultValue, $this->type !== null);
1459
+ $zvalName = "property_{$this->name->property}_default_value";
1460
+ if ($this->defaultValue === null && $this->type !== null) {
1461
+ $code .= "\tzval $zvalName;\n\tZVAL_UNDEF(&$zvalName);\n";
1462
+ } else {
1463
+ $code .= initializeZval($zvalName, $defaultValue);
1464
+ }
1415
1465
1416
1466
$code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n";
1417
1467
$nameCode = "property_{$propertyName}_name";
1418
1468
1419
1469
if ($this->type !== null) {
1420
- $code .= "\tzend_declare_typed_property(class_entry, $nameCode, &property_{$propertyName}_default_value , " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
1470
+ $code .= "\tzend_declare_typed_property(class_entry, $nameCode, &$zvalName , " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
1421
1471
} else {
1422
- $code .= "\tzend_declare_property_ex(class_entry, $nameCode, &property_{$propertyName}_default_value , " . $this->getFlagsAsString() . ", NULL);\n";
1472
+ $code .= "\tzend_declare_property_ex(class_entry, $nameCode, &$zvalName , " . $this->getFlagsAsString() . ", NULL);\n";
1423
1473
}
1424
1474
$code .= "\tzend_string_release(property_{$propertyName}_name);\n";
1425
1475
1426
1476
return $code;
1427
1477
}
1428
1478
1429
- /**
1430
- * @param mixed $value
1431
- */
1432
- private function initializeValue(string $type, $value, bool $isTyped): string
1433
- {
1434
- $name = $this->name->property;
1435
- $zvalName = "property_{$name}_default_value";
1436
-
1437
- $code = "\tzval $zvalName;\n";
1438
-
1439
- switch ($type) {
1440
- case "undefined":
1441
- if ($isTyped) {
1442
- $code .= "\tZVAL_UNDEF(&$zvalName);\n";
1443
- } else {
1444
- $code .= "\tZVAL_NULL(&$zvalName);\n";
1445
- }
1446
- break;
1447
-
1448
- case "NULL":
1449
- $code .= "\tZVAL_NULL(&$zvalName);\n";
1450
- break;
1451
-
1452
- case "boolean":
1453
- $code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
1454
- break;
1455
-
1456
- case "integer":
1457
- $code .= "\tZVAL_LONG(&$zvalName, $value);\n";
1458
- break;
1459
-
1460
- case "double":
1461
- $code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
1462
- break;
1463
-
1464
- case "string":
1465
- if ($value === "") {
1466
- $code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
1467
- } else {
1468
- $code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$value\", sizeof(\"$value\") - 1, 1);\n";
1469
- $code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
1470
- }
1471
- break;
1472
-
1473
- case "array":
1474
- if (empty($value)) {
1475
- $code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
1476
- } else {
1477
- throw new Exception("Unimplemented property default value");
1478
- }
1479
- break;
1480
-
1481
- default:
1482
- throw new Exception("Invalid property default value");
1483
- }
1484
-
1485
- return $code;
1486
- }
1487
-
1488
1479
private function getFlagsAsString(): string
1489
1480
{
1490
1481
$flags = "ZEND_ACC_PUBLIC";
@@ -1568,6 +1559,33 @@ function (Expr $expr) use (&$defaultValueConstant) {
1568
1559
}
1569
1560
}
1570
1561
1562
+ class EnumCaseInfo {
1563
+ /** @var string */
1564
+ public $name;
1565
+ /** @var Expr|null */
1566
+ public $value;
1567
+
1568
+ public function __construct(string $name, ?Expr $value) {
1569
+ $this->name = $name;
1570
+ $this->value = $value;
1571
+ }
1572
+
1573
+ public function getDeclaration(): string {
1574
+ $escapedName = addslashes($this->name);
1575
+ if ($this->value === null) {
1576
+ $code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n";
1577
+ } else {
1578
+ $evaluator = new ConstExprEvaluator(function (Expr $expr) {
1579
+ throw new Exception("Enum case $this->name has an unsupported value");
1580
+ });
1581
+ $zvalName = "enum_case_{$escapedName}_value";
1582
+ $code = "\n" . initializeZval($zvalName, $evaluator->evaluateDirectly($this->value));
1583
+ $code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n";
1584
+ }
1585
+ return $code;
1586
+ }
1587
+ }
1588
+
1571
1589
class ClassInfo {
1572
1590
/** @var Name */
1573
1591
public $name;
@@ -1577,6 +1595,8 @@ class ClassInfo {
1577
1595
public $type;
1578
1596
/** @var string|null */
1579
1597
public $alias;
1598
+ /** @var SimpleType|null */
1599
+ public $enumBackingType;
1580
1600
/** @var bool */
1581
1601
public $isDeprecated;
1582
1602
/** @var bool */
@@ -1591,37 +1611,44 @@ class ClassInfo {
1591
1611
public $propertyInfos;
1592
1612
/** @var FuncInfo[] */
1593
1613
public $funcInfos;
1614
+ /** @var EnumCaseInfo[] */
1615
+ public $enumCaseInfos;
1594
1616
1595
1617
/**
1596
1618
* @param Name[] $extends
1597
1619
* @param Name[] $implements
1598
1620
* @param PropertyInfo[] $propertyInfos
1599
1621
* @param FuncInfo[] $funcInfos
1622
+ * @param EnumCaseInfo[] $enumCaseInfos
1600
1623
*/
1601
1624
public function __construct(
1602
1625
Name $name,
1603
1626
int $flags,
1604
1627
string $type,
1605
1628
?string $alias,
1629
+ ?SimpleType $enumBackingType,
1606
1630
bool $isDeprecated,
1607
1631
bool $isStrictProperties,
1608
1632
bool $isNotSerializable,
1609
1633
array $extends,
1610
1634
array $implements,
1611
1635
array $propertyInfos,
1612
- array $funcInfos
1636
+ array $funcInfos,
1637
+ array $enumCaseInfos
1613
1638
) {
1614
1639
$this->name = $name;
1615
1640
$this->flags = $flags;
1616
1641
$this->type = $type;
1617
1642
$this->alias = $alias;
1643
+ $this->enumBackingType = $enumBackingType;
1618
1644
$this->isDeprecated = $isDeprecated;
1619
1645
$this->isStrictProperties = $isStrictProperties;
1620
1646
$this->isNotSerializable = $isNotSerializable;
1621
1647
$this->extends = $extends;
1622
1648
$this->implements = $implements;
1623
1649
$this->propertyInfos = $propertyInfos;
1624
1650
$this->funcInfos = $funcInfos;
1651
+ $this->enumCaseInfos = $enumCaseInfos;
1625
1652
}
1626
1653
1627
1654
public function getRegistration(): string
@@ -1639,21 +1666,29 @@ public function getRegistration(): string
1639
1666
$code = "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n";
1640
1667
1641
1668
$code .= "{\n";
1642
- $code .= "\tzend_class_entry ce, *class_entry;\n\n";
1643
- if (count($this->name->parts) > 1) {
1644
- $className = $this->name->getLast();
1645
- $namespace = addslashes((string) $this->name->slice(0, -1));
1646
-
1647
- $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
1669
+ if ($this->type == "enum") {
1670
+ $name = addslashes((string) $this->name);
1671
+ $backingType = $this->enumBackingType
1672
+ ? $this->enumBackingType->toTypeCode() : "IS_UNDEF";
1673
+ $code .= "\tzend_class_entry *class_entry = zend_register_internal_enum(\"$name\", $backingType, class_{$escapedName}_methods);\n";
1648
1674
} else {
1649
- $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
1650
- }
1675
+ $code .= "\tzend_class_entry ce, *class_entry;\n\n";
1676
+ if (count($this->name->parts) > 1) {
1677
+ $className = $this->name->getLast();
1678
+ $namespace = addslashes((string) $this->name->slice(0, -1));
1651
1679
1652
- if ($this->type === "class" || $this->type === "trait") {
1653
- $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
1654
- } else {
1655
- $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
1680
+ $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
1681
+ } else {
1682
+ $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
1683
+ }
1684
+
1685
+ if ($this->type === "class" || $this->type === "trait") {
1686
+ $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
1687
+ } else {
1688
+ $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
1689
+ }
1656
1690
}
1691
+
1657
1692
if ($this->getFlagsAsString()) {
1658
1693
$code .= "\tclass_entry->ce_flags |= " . $this->getFlagsAsString() . ";\n";
1659
1694
}
@@ -1673,6 +1708,10 @@ function (Name $item) {
1673
1708
$code .= "\tzend_register_class_alias(\"" . str_replace("\\", "_", $this->alias) . "\", class_entry);\n";
1674
1709
}
1675
1710
1711
+ foreach ($this->enumCaseInfos as $enumCase) {
1712
+ $code .= $enumCase->getDeclaration();
1713
+ }
1714
+
1676
1715
foreach ($this->propertyInfos as $property) {
1677
1716
$code .= $property->getDeclaration();
1678
1717
}
@@ -2306,8 +2345,11 @@ function parseProperty(
2306
2345
/**
2307
2346
* @param PropertyInfo[] $properties
2308
2347
* @param FuncInfo[] $methods
2348
+ * @param EnumCaseInfo[] $enumCases
2309
2349
*/
2310
- function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array $methods): ClassInfo {
2350
+ function parseClass(
2351
+ Name $name, Stmt\ClassLike $class, array $properties, array $methods, array $enumCases
2352
+ ): ClassInfo {
2311
2353
$flags = $class instanceof Class_ ? $class->flags : 0;
2312
2354
$comment = $class->getDocComment();
2313
2355
$alias = null;
@@ -2334,26 +2376,38 @@ function parseClass(Name $name, Stmt\ClassLike $class, array $properties, array
2334
2376
$implements = [];
2335
2377
2336
2378
if ($class instanceof Class_) {
2379
+ $classKind = "class";
2337
2380
if ($class->extends) {
2338
2381
$extends[] = $class->extends;
2339
2382
}
2340
2383
$implements = $class->implements;
2341
2384
} elseif ($class instanceof Interface_) {
2385
+ $classKind = "interface";
2342
2386
$extends = $class->extends;
2387
+ } else if ($class instanceof Trait_) {
2388
+ $classKind = "trait";
2389
+ } else if ($class instanceof Enum_) {
2390
+ $classKind = "enum";
2391
+ $implements = $class->implements;
2392
+ } else {
2393
+ throw new Exception("Unknown class kind " . get_class($class));
2343
2394
}
2344
2395
2345
2396
return new ClassInfo(
2346
2397
$name,
2347
2398
$flags,
2348
- $class instanceof Class_ ? "class" : ($class instanceof Interface_ ? "interface" : "trait") ,
2399
+ $classKind ,
2349
2400
$alias,
2401
+ $class instanceof Enum_ && $class->scalarType !== null
2402
+ ? SimpleType::fromNode($class->scalarType) : null,
2350
2403
$isDeprecated,
2351
2404
$isStrictProperties,
2352
2405
$isNotSerializable,
2353
2406
$extends,
2354
2407
$implements,
2355
2408
$properties,
2356
- $methods
2409
+ $methods,
2410
+ $enumCases
2357
2411
);
2358
2412
}
2359
2413
@@ -2431,6 +2485,7 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
2431
2485
$className = $stmt->namespacedName;
2432
2486
$propertyInfos = [];
2433
2487
$methodInfos = [];
2488
+ $enumCaseInfos = [];
2434
2489
foreach ($stmt->stmts as $classStmt) {
2435
2490
$cond = handlePreprocessorConditions($conds, $classStmt);
2436
2491
if ($classStmt instanceof Stmt\Nop) {
@@ -2466,12 +2521,16 @@ function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstrac
2466
2521
$classStmt,
2467
2522
$cond
2468
2523
);
2524
+ } else if ($classStmt instanceof Stmt\EnumCase) {
2525
+ $enumCaseInfos[] = new EnumCaseInfo(
2526
+ $classStmt->name->toString(), $classStmt->expr);
2469
2527
} else {
2470
2528
throw new Exception("Not implemented {$classStmt->getType()}");
2471
2529
}
2472
2530
}
2473
2531
2474
- $fileInfo->classInfos[] = parseClass($className, $stmt, $propertyInfos, $methodInfos);
2532
+ $fileInfo->classInfos[] = parseClass(
2533
+ $className, $stmt, $propertyInfos, $methodInfos, $enumCaseInfos);
2475
2534
continue;
2476
2535
}
2477
2536
0 commit comments