Frontends
PHP Frontend
PHP Frontend
The PHP frontend is a BraceFrontendBase subclass delivered as a single
1,376-line header. It implements modern PHP 8.3 features through a
pipeline of nine static source-rewrite transforms that run before the
brace parser sees the source. Each transform is comment-aware and
string-aware.
| File | Lines | Purpose |
|---|---|---|
frontends/php/PHPFrontend.h |
1,376 | Frontend class with all method bodies inline |
frontends/php/PhpStdLib.h |
86 | findStdLibMapping for echo, print, abs, strlen, json_encode, … |
Language registration
// PHPFrontend.h:12-13
std::string languageName() const override { return "php"; }
std::vector<std::string> fileExtensions() const override { return {".php"}; }
Only .php is recognised.
Preprocessing pipeline
PHPFrontend::parse (line 15) applies the rewrites in this order:
| Order | Transform | Lines | Purpose |
|---|---|---|---|
| 1 | normalizePHPSource |
29-201 | Strip <?php, ?>, heredocs, comments |
| 2 | convertMatchExpressions |
432-639 | Lower match to if/else-if chain |
| 3 | stripNamedArguments |
641-759 | f(name: expr) → f(expr) (positional) |
| 4 | transformTryCatchBindings |
766-827 | catch (E $e) → catch (E) (drop binding) |
| 5 | stripPhpFunctionModifiers |
829-870 | Drop final, abstract from function headers |
| 6 | stripInterfaceMethodDeclarations |
872-911 | Interface method bodies become stubs |
| 7 | transformEnumAccess |
913-1126 | case Foo; becomes case Foo_value; |
| 8 | transformArrowFunctions |
1128-1180 | No-op preservation (passthrough) |
| 9 | wrapPrintArguments |
272-355 | Wrap echo x, y arguments in parens |
After all nine transforms, the source is fed to
BraceFrontendBase::parse.
Print patterns
// PHPFrontend.h:1197-1203
return {
{"echo", false, false},
{"print", true, true},
{"print", false, false}
};
echo is the keyword-form pattern (no parens, comma-separated args);
print accepts both forms.
Features (file:line)
Variables: $x = expr
tryParseVarDecl (line 1213) recognises $x = expr with optional
modifiers public, private, protected, static, readonly. The $
prefix is stripped. Modifier metadata is added:
public readonly int $id = 0;
→ VariableDeclNode(name="id", initializer="0", metadata={"php_modifier"="public", "php_readonly"="true"})
Functions: function name($args) { … }
tryParseFunctionDeclHeader (line 1249). Constructors
(function __construct(...)) are renamed to constructor to match the
JavaScript / Python convention.
Methods: public function name($args): ReturnType { … }
Detected via stripPhpFunctionModifiers (line 829-870) which strips
final, abstract, and visibility modifiers from the function header.
Match expressions (PHP 8.0)
convertMatchExpressions (line 432) translates match ($x) { 1 => 'one', default => 'other' } to a sequence of if ($x === 1) { __match_tmp_0 = 'one'; } else if ($x === 'other') { __match_tmp_0 = 'other'; } followed
by substituting the temporary for the original match. Strict equality
(===) matches PHP match semantics — only the first matching arm fires.
Named arguments (PHP 8.0)
stripNamedArguments (line 641) rewrites f(name: expr) to f(expr),
preserving the position. The named form is dropped because the C++
backend has no named-argument support:
array_fill(start_index: 0, num: 10, value: 0);
→ array_fill(0, 10, 0);
Try/catch with bindings (PHP 8.0)
transformTryCatchBindings (line 766) strips the variable binding from
catch (E $e) so it becomes catch (E). The exception variable is
exposed via $_exception (a runtime-resolved variable):
try { ... } catch (RuntimeException $e) { echo $e->getMessage(); }
→ try { ... } catch (RuntimeException) { echo $_exception->getMessage(); }
Enums (PHP 8.1)
transformEnumAccess (line 913) builds a map of case Name; → case Name_value; and rewrites every Name::Foo access to Name_value. This
allows enums to compile without a true enum runtime.
enum Status { case Pending; case Approved; case Rejected; }
$status = Status::Approved;
→
class Status { const Pending_value = 0; const Approved_value = 1; const Rejected_value = 2; }
$status = Status_Approved;
Readonly classes (PHP 8.2)
A readonly class modifier is detected but currently stripped (no
backend enforcement).
Interfaces
stripInterfaceMethodDeclarations (line 872) replaces interface method
bodies with stub ; so the parser doesn't try to lower abstract methods.
Traits
tryParseLanguageSpecificStatement (line 1305) detects trait Name { … }
and emits ClassDeclNode(metadata["php_kind"]="trait").
Abstract / final classes
tryParseLanguageSpecificStatement (line 1330) handles:
abstract class Base { … }
final class Concrete extends Base { … }
abstract sets metadata["abstract"]="true", final sets
metadata["final"]="true".
Foreach
tryParseLanguageSpecificStatement (line 1345) handles:
foreach ($items as $value) { … }
foreach ($items as $key => $value) { … }
Lowers to ForEachNode(target=value, iterable=items) (key is dropped).
Namespaces
namespace App\Models;
Line 1287. Stored in metadata["php_namespace"].
Use statements
use App\Models\User;
use App\Models\User as U;
Line 1296. Both forms become ImportNode(module=…).
Fibers (PHP 8.1)
Fiber::suspend(), Fiber::resume(), $fiber->start(), $fiber->isStarted()
— recognised as ordinary method calls but not specially lowered. See
limitations.md.
Arrow functions: fn($x) => $x + 1
PHP 7.4 arrow functions are passed through (no body). They're parsed as
ordinary expressions by BraceFrontendBase.
Standard library mapping (PhpStdLib.h)
The complete table at frontends/php/PhpStdLib.h:38-75:
| PHP | MPL module.function |
|---|---|
echo x |
Console.Write(x) |
print x |
Console.Write(x) |
var_dump(x) |
Console.WriteLine(x) |
print_r(x) |
Console.WriteLine(x) |
abs(x) |
Math.Abs(x) |
floor(x) |
Math.Floor(x) |
ceil(x) |
Math.Ceil(x) |
round(x, n) |
Math.Round(x, n) |
sqrt(x) |
Math.Sqrt(x) |
pow(x, y) |
Math.Pow(x, y) |
max(...) |
Math.Max(...) |
min(...) |
Math.Min(...) |
log(x) |
Math.Log(x) |
sin(x) |
Math.Sin(x) |
cos(x) |
Math.Cos(x) |
rand() / rand(min, max) |
Math.Random() |
strlen(s) |
String.Length(s) |
strtolower(s) |
String.ToLower(s) |
strtoupper(s) |
String.ToUpper(s) |
trim(s) |
String.Trim(s) |
explode(sep, s) |
String.Split(s, sep) |
str_starts_with(s, prefix) |
String.StartsWith(s, prefix) |
str_ends_with(s, suffix) |
String.EndsWith(s, suffix) |
exit(code) |
Process.Exit(code) |
die(code) |
Process.Exit(code) |
getenv(name) |
Environment.GetVariable(name) |
json_encode(v) |
Json.Stringify(v) |
json_decode(s, assoc) |
Json.Parse(s) |
file_get_contents(path) |
IO.ReadFileText(path) |
file_put_contents(path, data) |
IO.WriteFileText(path, data) |
file_exists(path) |
IO.FileExists(path) |
IR examples
Example 1 — match expression
$name = match ($code) {
200 => 'OK',
404 => 'Not Found',
default => 'Error',
};
After convertMatchExpressions:
{
$__match_tmp_0 = null;
if ($code === 200) {
$__match_tmp_0 = 'OK';
} else if ($code === 404) {
$__match_tmp_0 = 'Not Found';
} else {
$__match_tmp_0 = 'Error';
}
$__match_tmp_0
}
Lowers to:
BlockNode
VariableDeclNode(name="__match_tmp_0", initializer=null)
IfNode(condition=BinaryOpNode("===", code, 200),
thenBranch=AssignmentNode(target=__match_tmp_0, value="OK"))
IfNode(condition=BinaryOpNode("===", code, 404),
thenBranch=AssignmentNode(…))
BlockNode
AssignmentNode(target=__match_tmp_0, value="Error")
Example 2 — enum
enum Color {
case Red;
case Green;
case Blue;
}
$fav = Color::Blue;
After transformEnumAccess:
class Color {
const Red_value = 0;
const Green_value = 1;
const Blue_value = 2;
}
$fav = Color_Blue;
Example 3 — readonly class
readonly class Point {
public function __construct(
public float $x,
public float $y,
) {}
}
Lowered to ClassDeclNode(name="Point", metadata={"php_readonly"="true", "php_promoted_properties"="true,float,x;float,y"}) with the
constructor's promoted properties tracked in metadata.
What is not supported (PHP-specific gaps)
- Real enum runtime: lowered to
classwithconstvalues. - Fibers:
Fiber::suspend()/Fiber::resume()are ordinary method calls — no coroutine semantics. - Generators with
yield from: lowered as ordinary statements. - Attributes (
#[Attr]): single-line forms are skipped viaisIgnoredLine(line 369) — they're type-erased. - First-class callable syntax (
strlen(...)): emitted as a runtime call, not a closure. matchexpression'sUnhandledMatchError: if no arm matches and there's nodefault, the frontend does NOT throw (PHP would throwUnhandledMatchError).readonlyenforcement: modifier is metadata only.- Property hooks (PHP 8.4 RFC): not yet supported.
- Asymmetric visibility (
public private set): not supported. - Type juggling (string ↔ int coercion in
==): the lowered C++ backend uses strict===always.