PHP
A subset that can write a normal program: <?php ... ?> tags (text outside a tag is emitted verbatim as HTML, and with no opening tag at all the whole file is PHP), $variables, echo / print, the full set of arithmetic / comparison / logical / bitwise operators, if / while / do-while / for / foreach / switch, functions (default arguments, recursion, global) and subscripted arrays (both [] and array() literals).
Arithmetic, control flow and function calls are all there.
How to run it on your phone
vml run examples/php/fib.php
Compiling takes a while (one to two minutes for C, a few seconds for scripting languages). Once the program is running, the gamepad is at the bottom of the screen - the D-pad and the four action buttons are all there. Tap the back arrow in the top-right corner to return to the Shell.
What to know before you write
ui_*is written directly (every other name is emitted as a bareCALL, and the linker pulls in the matching.vmlby prefix)echotakes several arguments at once (printed with no separator); string concatenation uses.- The array literals
[10, 20, 30]andarray(10, 20, 30)are equivalent;$a[] = vappends require/includeare declarations (no-ops) here, so removing them still compiles- Variables (
$name) and keywords are case-insensitive; comments come in three forms://,#,/* */
Examples
| File | What it shows |
|---|---|
fib.php | Fibonacci (recursive plus iterative plus arrays) |
hello.php | The smallest program (echo plus string concatenation plus arithmetic) |
sort.php | Bubble sort (array read/write plus nested loops plus swapping) |
Pitfalls we hit on real devices
- ⚠ Associative arrays (
["k" => 1]) are not supported, and that is a compile-time error (keys are never silently dropped). Objects / classes / interfaces / closures / anonymous functions /try-catch/isset/unset/empty/gotoare not supported either. - ⚠ Array elements carry no type:
echo $arr[i]always goes down the integer channel, so a string element prints as a pointer value (the case24-knownred-elemfmt.phppins this down). - ⚠ Implicit string/number concatenation does not convert:
"x" . 5gives"x", not"x5"- at runtime that cell cannot tell an integer from a string pointer, and forcing a conversion would be worse. - ⚠
switchbreaks out of every case automatically (fall-through is not supported), and a case value must be a compile-time integer constant (a string or range case is a compile-time error). - ⚠ A user function's float parameters / return value are passed as integers (truncated with
D2I) - floats are fully usable only at the top level and between builtins; crossing a user function loses the fraction. - An undefined variable is treated as 0 (no
Undefined variablewarning, deliberately unlike real PHP);break 2;/continue 2;ignore the level (treated as 1).
The rest of this page is taken straight from the VML source (third_party/vml/VMLPrepares/PhpCompiler/): the README covers what this front end supports and how to compile it, and the language reference covers the syntax itself. When upstream changes, regenerating this page brings it up to date.
Language reference
PHP language subset specification (Dolaima)
Applies to: VMLPrepares/PhpCompiler. The single source of truth for the criteria is scripts/vml-php-probe/run.sh (currently 25 pass / 0 fail / 1 known-red). Everything described here as "supported" has a case that can actually go red; most "unsupported" items are pinned by an EXPECT-ERR case.
Version: v1.0 | Date: 2026-10-13 | Revised by: Shenzhen Tanso Intelligent Technology Co., Ltd.
Specification standard
| Field | Value |
|---|---|
| Target standard | PHP 5/7 subset |
| Publication year | 2004 |
| Completeness | Subset (not itemised) |
| Tests | 25 passing / 0 failing / 1 known-red |
Keywords
if elseif else endif while endwhile do for endfor foreach endforeach as switch endswitch case default break continue return function global echo print true false null nil and or xor not array
Overview
A subset of PHP 5/7 is supported, compiled to VML assembly. It accepts both <?php … ?> segments interleaved with HTML (text after ?> is emitted verbatim as HTML; with no opening tag at all the whole file is treated as PHP) and plain PHP files. Values are statically tiered: every value is classified at compile time as Int (32-bit / 4 bytes) or Float (double precision); / is always double precision, and any other arithmetic goes double precision if either side is a float. Builtins are emitted with dedicated code paths, and every other name is emitted as CALL <original name>, with the linker's SharedPrefixMap pulling in the library by prefix (ui_ → vmlui, tty_ → tty, …); a name that is not in the library is a link-time error, never silent. require / include are declarations only and load nothing. One deliberate difference from the other frontends: no preprocessor is run (in PHP # starts a comment).
Supported language features
The features this frontend accepts are organised by the sections below; each one states what is accepted and what it means:
- Files and tags:
<?php … ?>/<?= … ?>/ no opening tag means the whole file is PHP — §1 - Lexical: variables
$name, comments (//,#,/* */), numbers, single- and double-quoted strings with interpolation, identifiers (keywords are case-insensitive) — §2 - Statements:
echo/print, assignment and compound assignment, subscript read/write and[]append,if/elseif/else(both{ }and: … endif;),while/do…while,for,foreach,switch,break/continue,function/return,global,require/include, HTML segments — §3 - Expressions and operators: the precedence table, arithmetic, comparison (including
===), two logical families, bitwise operators,.concatenation, ternary and?:/??, increment/decrement — §4 - Type tiers: how
Int/Floatis decided, and howechodispatches — §5 - Builtin functions:
strlen/count/strtoupper/strtolower/substr/strpos/implode/explode/intval/floatval/abs/max/min/rand/sprintf/printf/array_push/array_pop/intdivand more — §6 - Arrays: the
[count, e0, e1, …]layout and pointer semantics — §7 - Unsupported items: associative arrays, objects and classes, closures,
try/catch,isset/unset/empty,gotoand more (most report loudly) — §8 - Known differences: ten items that run but differ from real PHP — §9
See the sections below for details.
1. Files and tags
| Form | Behaviour |
|---|---|
<?php … ?> | the PHP segment runs; text after ?> is emitted verbatim as HTML, and ?> swallows one following newline |
<?= expr ?> | equivalent to <?php echo expr ?> |
| no opening tag at all | the whole file is PHP (the main usage in this repo's examples) |
Criteria: cases/17-tagmode.php.
"Is there an opening tag" is a textual pre-check (looking for
<?php/<?=). If you write"<?php"inside a string literal it will be taken as a tag — a documented trade-off.
2. Lexical
- Variables:
$name. Reading an unassigned variable yields 0 (a subset behaviour, deliberately different from PHP'sWarning: Undefined variable). - Comments:
// …,# …,/* … */(not nested). - Numbers: decimal,
0xhex, fractions,1e3. All are 32-bit integers; double precision is used only where floating point is required (section 5). - Strings: single quotes recognise only
\\and\'; double quotes recognise\n \t \r \0 \\ \" \$and perform$varinterpolation. - Identifiers: bare names (function names). Keywords are case-insensitive.
3. Statements
echo/print, assignment (including += -= *= /= %= .=), if/elseif/else (both { } and : … endif;), while, do…while, for, foreach (as $v and as $k => $v), switch/case/default, break/continue, function with default parameters, return, global, require/include, and HTML segments (which behave as echo "text";).
4. Operators
Precedence roughly follows PHP. Arithmetic + - * / % «bold» («/» uses the shared ipow, so it is an integer power); comparisons == != === !== < <= > >= <> (===/!== are synonyms for ==/!= — there are no type tags to distinguish); logical && || ! and and or not xor (both short-circuit and normalise to 0/1); bitwise & | ^ ~ << >>; concatenation .; ternary ?: and ??; prefix/postfix ++/--.
5. Value tiers
Every value is statically classified as Int (32-bit general register / 4-byte stack slot) or Float (D0 / 8-byte slot).
- A literal with a fraction or exponent is
Float, otherwiseInt. /is always double precision (PHP semantics:17/25is0.68, not0).- Any other arithmetic: if either side is
Float, the double path is used. - Strings and arrays are pointers (4 bytes).
echodispatch:String→print_str;Float→print_float(afterD2F); anything else →print_int.
Integers stay 32-bit / 4-byte so that they line up with
Lib/'s 4-byte parameter area. Making everything a double breaks that immediately (the Lua front end hit exactly this).
6. Built-in functions
Special-cased (the front end lays out the arguments itself): strlen, count/sizeof, strtoupper, strtolower, substr, strpos, implode, explode, intval, floatval, abs, max, min, rand (0 or 2 args), sprintf, printf, array_push, array_pop, intdiv, print_r/var_dump, and the require/include family (no-ops).
Everything else is emitted as CALL <name> directly (ui_2d_clear(...) → CALL ui_2d_clear); at link time SharedPrefixMap (ui_ → vmlui, str → string, …) pulls the library in. A name that does not exist in any library is reported at link time (undefined function 'x'), never silently ignored.
7. Arrays
Same layout as Lib/shared/array.c: [count, e0, e1, …], 4 bytes per cell. An array value is a pointer, so it can be passed and returned.
8. Unsupported
Associative arrays (key => value — a compile-time error, keys are never silently dropped), {$expr} interpolation, $a[i] interpolation, objects/classes/traits, closures, try/catch/throw, string or range switch cases (compile-time error), floating-point parameters/returns of user-defined functions (truncated to integers), isset/unset/empty/list/die/exit, namespaces/autoloading, goto.
9. Known deviations (runs, but differs from real PHP)
- Array elements carry no type:
echo $arr[i]always uses the integer channel, so a string element prints as its pointer value (cases/24-knownred-elemfmt.php,KNOWN-RED). - No implicit string/number concatenation conversion:
"x" . 5gives"x", not"x5". - Every
switchcase breaks automatically — fallthrough is not supported (an explicitbreak;is harmless). - User functions cross the boundary as integers: float arguments are
D2I-truncated, likewise return values. Floats are fully usable at the top level and between built-ins. break 2;/continue 2;levels are ignored.$a[i]++evaluates the index expression twice (observable if it has side effects).- Undefined variables are 0 (no warning).
echoof an array prints its pointer.implodehandles integer elements only (the library'sjoin_strdoes).printdoes not return 1 (it is handled as a statement).
10. Criteria
scripts/vml-php-probe/run.sh # 25 pass / 0 fail / 1 known-red
This uses the real channel: every case is compiled, assembled, linked (including the Lib/php module set) and run by the single command vmlcli <case.php> --lang php.
Two regression cases that genuinely go red (both measured):
cases/23-ctrlflow-vmb.php— changingCodeGenerator.PlaceLblback toAddLabel(emitting aLABELpseudo-instruction) shifts the.vmbsymbol table and the program prints nothing (expected6 30).cases/03-strings.php— droppinglen(a)from the concatenation buffer size produces a long run ofababab…(overlapping heap blocks, self-replication).
Full records of the platform-level defects (symptom / root cause / measurements / workaround) are in [bugs/vml_php_bugs/README.md](../../../bugs/vml_php_bugs/README.md).
Compiler README
PHP compiler (VMLPrepares/PhpCompiler)
Path: third_party/vml/VMLPrepares/PhpCompiler/ Extension: .php Standard library: Lib/php/ (builtin.vml = library aggregator; vml_ui.php = documentation-level declaration table) Probe: scripts/vml-php-probe/run.sh — measured 25 pass / 0 fail / 1 known-red Language subset and deviations: [PHP_LANGUAGE_SPEC.md](PHP_LANGUAGE_SPEC.md) (EN) Wiring: [INTEGRATION.md](INTEGRATION.md) (the shared files are intentionally left untouched here)
What it is
A PHP subset front end that is enough to write ordinary programs: <?php … ?> tags (text outside the tags is emitted verbatim as HTML), $variables, echo/print, full arithmetic/comparison/logical/bitwise operators, if/elseif/else (including the endif alternate syntax), while / do-while / for / foreach (including as $k => $v), switch, break/continue, functions (default parameters, recursion, global), indexed arrays ([] and array()), single/double quoted strings (double quotes support $var interpolation), // # /* */ comments, and a set of built-in functions (see the spec).
The three essentials — complete operators + control flow + function calls — are all present: the three programs under Examples/php/ run (recursion, arrays passed to and returned from functions, bubble sort).
Running it
Wiring is done (see INTEGRATION.md), so this uses the real channel — the same "compile → assemble → link (including the Lib/php module set) → run" pipeline as the mobile host MauiVml:
dotnet build scripts/vmlcli/vmlcli.csproj -c Release
export VML_HOME=$PWD/third_party/vml
dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll third_party/vml/Examples/php/hello.php --lang php
scripts/vml-php-probe/run.sh
⚠ Criteria must enter through the real channel. An earlier version of the probe compiled with
PhpCompiler.dll --vmband then ran the.vmbwith vmlcli; that bypassed the compiler registry andLib/<lang>/resolution, so defects like "php is not registered in vmlcli", "the php entry is missing fromvmltool.config.xml" or "array.vmlis missing fromLibs=" could not be observed at all. See PHP-BUG-06 in [bugs/vml_php_bugs/](../../../bugs/vml_php_bugs/README.md).
Where library functions come from
Two paths, neither of which reads a declaration file:
- Built-in table (
CodeGenerator.Builtins.cs):strlen,count,substr,strtoupper,strtolower,strpos,implode,explode,intval,floatval,abs,max,min,rand,sprintf,printf,array_push,array_pop,intdiv,print_r/var_dump,requirefamily. These need the front end to lay out arguments itself (a destination buffer for strings, the[len, e0, …]layout for arrays), so they do not go through name resolution. - Everything else: emit
CALL <name>directly (ui_2d_clear,tty_puts, any library export). At link timeCompilerHelper'sSharedPrefixMap(ui_→vmlui,str→string, …) pulls the matching.vmlin — the same rule the Lua / Ruby / Python front ends use.
require 'vml_ui.php'; is a declaration (the front end treats require/include as no-ops): deleting it still compiles and runs correctly (LoadKind = documentation-level). Lib/php/vml_ui.php is therefore a comment table for humans.
One deliberate difference from the other front ends: no preprocessor
CompilerHelper.CompileFileStandard runs the preprocessor whenever the source contains #, but in PHP # starts a comment — the two collide. So PHP deliberately bypasses that template: the preprocessor only runs when the source really contains #include.
Known defects / limitations
See [bugs/vml_php_bugs/](../../../bugs/vml_php_bugs/README.md) (2 platform-level defects, 2 front-end bugs that were fixed, 2 limitations) and the "Unsupported" section of the spec. The most important platform-level defect: in a .vmb, the symbol table is **shifted by one entry per LABEL pseudo-instruction**. This front end avoids it by never emitting pseudo-instructions for labels, guarded by a regression case that really does go red (scripts/vml-php-probe/cases/23-ctrlflow-vmb.php).