← 都来码← 文档索引PHP原文English

PHP

能写一般程序的子集:<?php … ?> 标签(标签外按 HTML 原样输出,没有任何开标签时整份文件当 PHP)、$变量、echo / print、完整算术 / 比较 / 逻辑 / 位运算、if / while / do-while / for / foreach / switch、函数(含默认参数、递归、global)、下标数组([] 与 array() 两种字面量)。

「完整运算 + 控制流 + 函数调用」三件都齐。

在手机上怎么跑

vml run examples/php/fib.php

编译要等一会儿(C 那种要一两分钟,脚本类语言几秒)。程序跑起来后屏幕底部就是手柄,方向键 + 四个动作键都在;点右上角返回可以回到命令行。

写法要点

示例

文件演示什么
fib.php斐波那契(递归 + 迭代 + 数组)
hello.php最小程序(echo + 字符串拼接 + 算术)
sort.php冒泡排序(数组读写 + 嵌套循环 + 交换)

实测踩过的坑


下面的内容是从 VML 源码里直接带的(third_party/vml/VMLPrepares/PhpCompiler/): README 讲这个前端支持什么、怎么编;语言规范 讲语法本身。上游一改,这里重新生成就是最新的。

语言规范

PHP 语言子集规格(都来码 / Dolaima)

适用:VMLPrepares/PhpCompiler。判据的唯一出处是 scripts/vml-php-probe/run.sh (当前 通过 25 / 失败 0 / 已知红 1)—— 本文里凡是写"支持"的,那条都有一条能红的用例;写"不支持"的,多数有一条 EXPECT-ERR 用例钉着。


版本:v1.0 | 日期:2026-10-13 | 修订者:深圳市探索智能科技有限公司

规范标准

字段值
目标标准PHP 5/7 子集
发布年份2004
完成度子集(未逐项统计)
测试25 通过 / 0 失败 / 1 已知红

关键字

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

概述

支持 PHP 5/7 子集,编译为 VML 汇编代码。既支持 <?php … ?> 段与 HTML 混排(?> 之后的内容按 HTML 原样输出;没有任何开标签时整份文件当 PHP),也支持纯 PHP 文件;值是静态分档的:每个值在编译期归到 Int(32 位 / 4 字节)或 Float(双精度),/ 一律双精度、其余算术任一侧是浮点就走双精度。内置函数专用发射,其余一切名字一律发 CALL <原名>、由链接期的 SharedPrefixMap 按前缀拉库(ui_ → vmlui、tty_ → tty…),库里没有这个名字是链接期报错、不静默;require / include 只是声明、不加载任何东西。与其它前端有一处有意不同:不跑预处理器(PHP 里 # 是注释符)。

支持的语言特性

本前端接受的语言特性按下方原有章节组织,每节都写明「收什么写法、是什么语义」:

详见下方各节。

一、文件与标签

写法行为
<?php … ?>PHP 段执行;?> 之后的内容按 HTML 原样输出,且 ?> 会吃掉紧跟的一个换行
<?= 表达式 ?>等价于 <?php echo 表达式 ?>
没有任何开标签整份文件当 PHP(本仓例程的主用法)

判据:cases/17-tagmode.php。

「有没有开标签」是文本级预判(找 <?php / <?=)。若你在源代码的字符串字面量里写 "<?php",会被当成开标签 —— 已记档的取舍,别指望它。

二、词法

三、语句

语句备注
echo e1, e2, …; / print e;无分隔符;print 按语句处理(不返回 1)
$x = 值; $x op= 值;op ∈ + - * / % .;复合赋值等价于 $x = $x op 值
$a[i] = 值; $a[] = 值;[] 是追加
if / elseif / else两套写法都支持:{ } 与 : … endif;
while do…while也支持 : … endwhile;
for (init; cond; step)cond 里用 , 分隔会被当成 &&
foreach ($a as $v) / as $k => $v只对下标数组(键是 0-based 整数)
switch / case / defaultcase 值必须是编译期整数常量;每个 case 自动 break(见「已知差异」)
break; continue;break 2; 这种层数被忽略(按 1 处理)
function f($a, $b = 默认值) { … } return 值;递归可用;默认值在调用点就地求值补位
global $a, $b;之后函数体里的 $a/$b 指向顶层(全局)变量
require/include[_once] '文件';声明,无操作(库函数按名字解析,见第六节)
HTML 段等价于一条 echo "原文";

四、表达式与运算

优先级从低到高(同 PHP 大体一致):or → xor → and → 赋值(右结合)→ ?: / ?? → || → && → | → ^ → & → 相等 → 关系 → 移位 → + - . → * / % → 一元 ! - + ~ ++ -- → **(右结合)→ 后缀 [] ++ -- 与调用。

五、类型档位(决定发什么指令)

本前端是静态分档的:每个值在编译期归到 Int(32 位通用寄存器 / 4 字节栈槽)或 Float(D0 / 8 字节栈槽)。

⚠ 与 Lib/ 的调用约定对齐:整数保持 32 位/4 字节 —— 全按双精度会当场对不上 库的 4 字节形参区(Lua 前端当初就踩过这条,这里沿用同一结论)。

六、内置函数

专用发射(前端自己排版实参):

函数底层说明
strlen($s)CALL strlen
count($a) / sizeof($a)读 [base]数组长度([len, e0, …] 布局)
strtoupper($s) / strtolower($s)alloc + str_toupper/str_tolower返回新串
substr($s, $start[, $len])alloc + str_substr$len 省略 = 到末尾
strpos($h, $n)单字符字面量 → str_indexof;否则 strstr − 基址找不到回 -1
implode($sep, $arr)join_str⚠ 只对整数元素(库的实现是把元素当整数转十进制串)
explode($sep, $s)str_split + 本前端数组布局分隔符取 $sep 的首字符
intval / floatval就地转换
abs / max / minCALL abs/max/min整数版;max/min 支持多参(折叠成两两调用)
rand() / rand($min, $max)random / random_range
sprintf($fmt, …)printf2…printf5 / sprintf值个数 0–5
printf($fmt, …)同上 + print_str
array_push($a, $v)就地追加数组字面量已留 64 字节余量
array_pop($a)弹末尾空数组回 0
intdiv($a, $b)DIV
print_r / var_dump按 echo 处理子集简化
require / include 家族无操作但 require 可不带括号收裸表达式

其余一切按名字直接 CALL:ui_2d_clear(...) → CALL ui_2d_clear,链接期由 SharedPrefixMap 的 ui_ → vmlui(tty_ → tty、str → string …)拉库。库里没有这个名字 ⇒ 链接期报 未定义的函数 'x'(引用 N 次),不会静默。

七、数组

布局与 Lib/shared/array.c 同源:[元素个数, e0, e1, …],每格 4 字节。数组名本身是指针(4 字节),所以能当参数传、能 return。

$a = [10, 20, 30];      // 等价于 array(10, 20, 30)
echo $a[1], "\n";       // 20
$a[2] = 3;              // 按下标写
$a[] = 30;              // 追加(长度 +1)
echo count($a);         // 4

八、不支持项(有意不做,多数会响亮报错)

项现状
关联数组 ["k" => 1]编译期报错「关联数组(键 => 值)暂不支持」——不静默丢键
{$expr} 插值、$a[i] 插值不支持;双引号串里只认 $名字
对象 / 类 / 接口 / trait不支持
闭包 / 匿名函数 / use不支持
try / catch / throw不支持
switch 的字符串 case、范围 case编译期报错(只收编译期整数常量)
用户函数的 浮点形参 / 返回值按整数传递(先 D2I)—— 见「已知差异」
isset / unset / empty / list / die / exit不支持
include_path / 命名空间 / 自动加载不支持(库按名字解析)
goto不支持

九、已知差异(能跑,但与真 PHP 不同 —— 一条条列出来)

  1. 数组元素不带类型:echo $arr[i] 一律走整数通道 ⇒ 字符串元素打出来是指针值。用例 cases/24-knownred-elemfmt.php 标 KNOWN-RED 钉着这条。
  2. 字符串与数字的隐式拼接不转换:"x" . 5 得 "x"(不是 "x5")—— 运行期那一格分不出"整数"与"串指针",硬转反而更错。
  3. switch 每个 case 自动 break:不支持落穿(fallthrough)。写了 break; 也无害。
  4. 用户函数跨边界按整数:浮点实参先 D2I(截断),返回值同理。 ⇒ 浮点在顶层与内置函数之间完整可用,跨用户函数会丢小数。
  5. break 2; / continue 2; 的层数被忽略。
  6. $a[i]++ 之类会求值下标两次(有副作用的表达式会有差异)。
  7. 未定义变量按 0(不报 Undefined variable)。
  8. echo 一个数组打的是指针值。
  9. implode 只处理整数元素(库的 join_str 如此)。
  10. print 不返回 1(当语句处理)。

十、判据

scripts/vml-php-probe/run.sh          # 通过 25 / 失败 0 / 已知红 1

走的是真实通道:每个用例都由 vmlcli <case.php> --lang php 一条命令「编译 → 汇编 → 链接(含 Lib/php 模块集)→ 运行」。(早先"自己调 PhpCompiler.dll 出 .vmb、再喂给 vmlcli"的写法**绕开了前端注册表与 Lib/<lang>/ 的解析**,把三类缺陷一次性盖住了 —— 见 bugs/vml_php_bugs/README.md 的 PHP-BUG-06。)

两条能红的回归判据(改坏了立刻红,都实测过):

平台级缺陷的完整记录(现象 / 根因 / 测量 / 规避)见 [bugs/vml_php_bugs/README.md](../../../bugs/vml_php_bugs/README.md)。

编译器 README

PHP 编译器(VMLPrepares/PhpCompiler)

路径:third_party/vml/VMLPrepares/PhpCompiler/ 扩展名:.php 标准库:Lib/php/(builtin.vml = 库聚合器;vml_ui.php = 文档级声明表) 判据:scripts/vml-php-probe/run.sh(真实通道:vmlcli <case> --lang php)—— 实测 通过 25 / 失败 0 / 已知红 1 语言子集与差异:[PHP_LANGUAGE_SPEC.md](PHP_LANGUAGE_SPEC.md)(EN) 接线:[INTEGRATION.md](INTEGRATION.md)(本次按约定不动共享文件)

是什么

一个能写一般程序的 PHP 子集前端:<?php … ?> 标签(标签外按 HTML 原样输出)、 $变量、echo/print、完整算术/比较/逻辑/位运算、if/elseif/else(含 endif 备用语法)、 while / do-while / for / foreach(含 as $k => $v)、switch、break/continue、函数(含默认参数、递归、global)、下标数组([] 与 array() 两种字面量)、单/双引号字符串(双引号支持 $var 插值)、// # /* */ 注释,以及一批内置函数(见 spec)。

「完整运算 + 控制流 + 函数调用」三件都齐 —— Examples/php/ 里的三个例程可跑(含递归、数组传参与返回、冒泡排序)。

怎么跑

接线已完成(见 INTEGRATION.md),所以走的是真实通道 —— 与手机端 MauiVml 等价的「编译 → 汇编 → 链接(含 Lib/php 模块集)→ 运行」:

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

# ② 只看前端产出的汇编(不链接、不跑)
dotnet third_party/vml/VMLPrepares/PhpCompiler/bin/Release/net10.0/PhpCompiler.dll        third_party/vml/Examples/php/hello.php -o /tmp/hello.vml

# ③ 语言特性套件
scripts/vml-php-probe/run.sh

⚠ 判据必须从真实通道进(这一条是踩过坑的):上一版套件是"自己调 PhpCompiler.dll 编成 .vmb、再 vmlcli <vmb>"。那条路绕开了前端注册表与 Lib/<lang>/ 的解析,于是「vmlcli 没注册 php」「vmltool.config.xml 缺 php 行」「Libs= 缺 array.vml」这类缺陷一条都照不出来(自己拼的链接集恰好补齐了缺的模块)。细节见 [bugs/vml_php_bugs/](../../../bugs/vml_php_bugs/README.md) 的 PHP-BUG-06。

库函数怎么来

两条路,都不是靠"读声明文件":

  1. 内置表(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 / require 家族。这些要前端自己排版实参(字符串要 dst 缓冲、数组要按 [len, e0, …] 走),所以不走名字解析。
  2. 按名字直接发 CALL:其余一切(ui_2d_clear / tty_puts / 任何库导出名)。链接期由 CompilerHelper 的 SharedPrefixMap(ui_ → vmlui、str → string …)把对应 .vml 拉进来 —— 与 Lua / Ruby / Python 那几门同一条口径。

require 'vml_ui.php'; 是声明(本前端把 require/include 当无操作语句): 删掉它程序照样编得过、跑得对(LoadKind = 文档级,见 docs/UI库导入约定.md)。 Lib/php/vml_ui.php 因此是给人看的注释表。

与其它前端的一处有意不同:不跑预处理器

CompilerHelper.CompileFileStandard 在源码出现 # 时起预处理器,而 PHP 里 # 是注释符 —— 两者在天上打架。⇒ PHP 有意绕开那个模板:只有源码里真出现 #include 时才起预处理器。

目录

PhpCompiler/
├── TokenType.cs / Token.cs        词法单元(Token 实现 ITokenPosition,位置由基类一处取)
├── Lexer.cs                       继承 LexerBase;`<?php ?>` 段切分、双引号串插值与 `\$` 占位
├── ASTNode.cs                     节点(自带行/列)
├── Parser.cs                      继承 ParserBase;优先级爬升 + 备用语法(`endif`/`endwhile`…)
├── CodeGenerator.cs               类型档位表 + 符号表/作用域 + 帧槽(PlaceLbl 是本前端的硬约束)
├── CodeGenerator.Expressions.cs   表达式(含字符串拼接、下标读写、短路逻辑、三元)
├── CodeGenerator.Statements.cs    语句(含 foreach/switch/函数定义/默认参数补位)
├── CodeGenerator.Builtins.cs      内置函数专用发射
├── PhpCompiler.cs / PhpPlugin.cs  静态入口 / 插件(继承 CompilerPluginExBase)
├── Program.cs                     开发期 CLI(`--vmb` / `-o` / `--info` / `--dump`)
├── PhpCompiler.csproj
├── README.md / README.en.md
└── PHP_LANGUAGE_SPEC.md / PHP_LANGUAGE_SPEC.en.md

已知缺陷 / 限制

见 [bugs/vml_php_bugs/](../../../bugs/vml_php_bugs/README.md)(平台级 2 条 + 前端自己的错 2 条 + 限制 2 条),以及 spec 的「不支持项」。最重要的一条平台级缺陷:.vmb 的符号表会被 LABEL 伪指令 整体偏移一格 —— 本前端靠「标签只登记、不发伪指令」规避,且有一条能红的回归判据(scripts/vml-php-probe/cases/23-ctrlflow-vmb.php)。