# Perl

「«bold»完整运算 + 控制流 + 函数调用«/»」那一档：数组（`@a`）、哈希（`%h`）、子程序、字符串处理、循环、递归都能写 —— 覆盖到词频统计那种规模。

`print` 可以一次给多个参数（照原样连着打，不加分隔）；双引号串里 `$var` 会插值。

## 在手机上怎么跑

```
vml run examples/perl/app/wordcount.pl
```

编译要等一会儿（C 那种要一两分钟，脚本类语言几秒）。程序跑起来后**屏幕底部就是手柄**，
方向键 + 四个动作键都在；点右上角返回可以回到命令行。

## 写法要点

- `my $x` / `my @a` / `my %h` 声明；«bold»未声明也能用«/»（落到全局，值为 0）
- «bold»换行就是语句分隔符«/»，行尾的 `;` 常常可以省
- `ui_*` 直接调用（前端按裸名字发 `CALL`）；`use vml_ui;` 只是«bold»声明«/»，删掉照样编得过
- 打字符串用 `print` / `say`（`say` 末尾自动补换行）
- `$#a` 是数组末位下标，`scalar(@a)` 是长度

## 示例

| 文件 | 演示什么 |
|---|---|
| `app/wordcount.pl` | 词频统计（`split` + 哈希计数 + `scalar`） |
| `hello.pl` | 最小程序（`print` 多参 + 双引号插值） |
| `test/syntax.pl` | 语言小抄：每种形态各用一遍 |

## 实测踩过的坑

- ⚠ «bold»没有运行期类型标签«/»：类型是«bold»编译期«/»推的 —— 从 `@_` 取出来的形参、未知库函数的返回值都属于«bold»类型未知«/»（算术按整数算、拼串时当指针）。所以 `print` 一个类型未知的形参，若它其实是字符串，打出来是«bold»地址«/»；要稳就显式拼一次串。
- ⚠ 比较有«bold»两套«/»算子（数值 `== != < > <= >=`、字符串 `eq ne lt gt le ge`），«bold»用错不报错«/»，只是按另一套比。
- ⚠ `sort` / `reverse` / `each` / `ucfirst` / `lcfirst` / `rindex` «bold»未实现，且在编译期报错«/»（不静默）；`push` 的实参不对也报错。
- 双引号串里 «bold»`@arr` 不插值«/»（数组插值要按元素 join，元素类型编译期定不了）—— 要用 `join($sep, @arr)`。
- 没有引用 / 解引用 / 匿名数组哈希 / 正则 / 包 / 对象；整数是 «bold»32 位«/»。
- 子程序«bold»只收一个实参«/»（就是 `@_` 那个实参数组的地址），`my ($a, $b) = @_;` 在函数体里«bold»按下标取«/»；作用域是«bold»扁平«/»的（`my` 是函数级栈槽，没有块作用域）。
- 浮点打印走共享运行时，«bold»固定 6 位小数«/»（`3.4` 可能显示成 `3.399999` —— 那是共享 dtoa 的舍入口径，不是前端算错）。

---

下面的内容是**从 VML 源码里直接带的**（`third_party/vml/VMLPrepares/PerlCompiler/`）：
`README` 讲这个前端支持什么、怎么编；`语言规范` 讲语法本身。
上游一改，这里重新生成就是最新的。

## 语言规范

### Perl 子集语言规格 —— PerlCompiler 实现了什么

本文是 «bold»PerlCompiler 前端«/»的实现口径：支持哪些语法/语义、静默坑在哪、哪些«bold»有意不支持«/»
（且«bold»在编译期报错«/»，不假装支持）。对应英文版见 [`PERL_LANGUAGE_SPEC.en.md`](PERL_LANGUAGE_SPEC.en.md)。

> «bold»版本«/»：v1.0 | «bold»日期«/»：2026-10-13 | «bold»修订者«/»：深圳市探索智能科技有限公司

#### 规范标准

| 字段 | 值 |
|:-----|:----|
| «bold»目标标准«/» | Perl 5 子集 |
| «bold»发布年份«/» | 1994 |
| «bold»完成度«/» | 子集（未逐项统计） |
| «bold»测试«/» | 24 通过 / 0 失败 |

#### 关键字

`my` `our` `sub` `if` `elsif` `else` `unless` `while` `until` `for` `foreach`
`do` `last` `next` `redo` `return` `and` `or` `not` `print` `say` `use`
`defined` `undef` `eq` `ne` `lt` `gt` `le` `ge` `cmp` `qw`

#### 概述
支持 Perl 5 子集，编译为 VML 汇编代码。变量带 «bold»sigil«/»（`$` 标量 / `@` 数组 / `%` 哈希，`$x`、`@x`、`%x` 在词法层就是«bold»三个独立的名字«/»），«bold»换行是语句分隔符«/»（与 `;` 等价）；«bold»有意不跑 C 预处理器«/»（Perl 用 `#` 作行注释，一见 `#` 就跑预处理会把每条注释当成指令）。前端«bold»没有运行期类型标签«/»：按编译期能定的信息在整数 / 浮点 / 字符串三条通道之间选择（从 `@_` 取出的形参、未知库函数的返回值属于类型未知，见 §8）；«bold»未实现的内置在编译期报错«/»，没有「悄悄按 0 跑过去」的兜底。

#### 支持的语言特性

本前端接受的语言特性按下方原有章节组织，每节都写明「收什么写法、是什么语义」：

- «bold»词法«/»：注释、数字、单引号串（只认 `\\` 与 `\'`）、双引号串与 `$name` / `${name}` 插值、标识符、sigil、`$#a`、换行作分隔符 —— §1
- «bold»变量与声明«/»：`my` / `our`（本子集里同义）、标量 / 数组 / 哈希各建各的槽、未声明即可用、扁平作用域 —— §2
- «bold»表达式与运算«/»：算术（`/` 是浮点除法、`«bold»` 是整数幂）、`.` 拼接与 `x` 重复、数值与字符串«/»两套**比较、逻辑两套、`++` / `--` —— §3
- «bold»语句与控制流«/»：`if` / `elsif` / `else` / `unless`、`while` / `until`、`for` / `foreach`、后缀修饰、`last` / `next` / `redo` —— §4
- «bold»字符串«/»：多参 `print` / `say`、`length` / `substr` / `index` / `uc` / `lc` / `chomp` / `chop` / `sprintf` / `int` / `abs`、`@arr` 不插值 —— §5
- «bold»数组与哈希«/»：`$a[i]`（负下标从尾部算）、`$#a`、`push` / `pop` / `shift` / `unshift`、`join` / `split`、`%h` / `exists` / `delete` / `keys` / `values` —— §6
- «bold»子程序与 `use`«/»：`sub` 定义与调用（含 `sub f($x, $y)` 形式）、先调用后定义、`use NAME;` 读声明文件 —— §7
- «bold»类型的编译期推断«/»：为什么会「看走眼」（有意接受的取舍） —— §8
- «bold»有意不支持«/»：正则 / 引用与解引用 / 包与对象 / `local`、`state` 的包语义 / 文件句柄 / 64 位整数等，多数在编译期报错 —— §9

详见下方各节。

#### 0. 总则

* «bold»判据«/»：能写"一般程序" —— 运算 / 控制流 / 函数调用 / 数组 / 哈希 / 字符串处理齐全。
* «bold»不静默«/»：本前端«bold»没有«/»"悄悄按 0 跑过去"的内置。未实现的内置（如 `sort`）会
  在编译期报错；`push` 的实参不对也报错。宁可不编，也不给一个错结果。
* «bold»AOT 无反射、无固定地址«/»：发射的只有标准 VML 指令与 `CALL` 到具名标签。

#### 1. 词法

| 形态 | 说明 |
|---|---|
| 注释 | `#` 到行尾 |
| 数字 | 十进制整数、`0x…` 十六进制、小数（如 `1.5`）。«bold»整数是 32 位«/»（超出按低 32 位截断） |
| 单引号串 | 只认 `\\` 与 `\'` 两个转义，其余原样 |
| 双引号串 | 认 `\n \t \r \0 \\ \" \$ \@`；`$name` / `${name}` «bold»插值«/»；`@name` «bold»不插值«/»（见 §5） |
| 标识符 | 字母 / 数字 / `_` / 汉字 |
| sigil | `$` 标量、`@` 数组、`%` 哈希。`%` 后跟标识符才算 sigil，否则是取模算子 |
| `$#a` | 数组 `a` 的末位下标（= 长度 − 1） |
| 换行 | «bold»是语句分隔符«/»（与 `;` 等价）；但上一 token 是算子/逗号/开括号时«bold»抑制«/»，于是跨行写法照常算一条语句 |

关键字：`my our sub if elsif else unless while until for foreach do last next redo return
and or not print say use defined undef eq ne lt gt le ge cmp qw`。

#### 2. 变量与声明

* `my $x` / `my @a` / `my %h`；`our` 同义（本子集里«bold»没有真正的包变量语义«/»，`our` 与 `my` 行为相同）。
* Perl 同名的 `$x` / `@x` / `%x` 是«bold»三个独立的名字«/»（各自的 sigil 决定取什么）—— 前端按
  `(sigil, 名字)` 分别建槽。
* «bold»未声明即可用«/»（Perl 默认无 `use strict`）：未声明的标量落到数据段的全局，值为 0。
* «bold»作用域是扁平的«/»（本子集的一个有意简化）：`my` 分配的是函数级栈槽，没有块作用域；
  两个同名 `my` 复用同一个槽。子程序之间«bold»共享同一张符号表«/»（同名局部量在第二个函数里
  复用第一个的偏移，与 Lua 前端同一取舍）。

#### 3. 表达式与运算

* 算术：`+ - * / % **`。
  * `/` 是«bold»浮点除法«/»（Perl 语义：`7/2` → `3.5`），任一侧是浮点则整条走双精度。
  * `«bold»` 是«/»整数幂**（走共享库 `ipow`）。浮点底数请先自己转。
* 字符串：`.` 拼接（数字会自动转十进制串）、`x` 重复（`"ab" x 3`）、`.=` 追加。
* 比较«bold»两套«/»（这一条最容易踩）：
  * 数值：`== != < > <= >=`
  * 字符串：`eq ne lt gt le ge`（内部走 `strcmp`）
  * 用错不会报错，只会得到"按另一套比"的结果（与 Perl 本身一样）。
  * `cmp` 返回 `-1/0/1`。
* 逻辑：`&& || !` 与 `and or not` «bold»两套都认«/»，都是«bold»短路«/»并返回 «bold»0/1«/»（不是 Perl 的
  "返回最后一个操作数"）。三目 `?:` 支持。
* `++` / `--`：前缀与后缀都支持；«bold»后缀返回旧值«/»。实现是 `+1` 再 `-2` 的等价式（一步到位）。
* `unless` 是 `if(not …)`；`until` 是 `while(not …)`。

#### 4. 语句与控制流

* `if / elsif / else`、`unless`、`while`、`until`、`for / foreach`、C 风格 `for (init; cond; step)`。
* 后缀修饰：`… if 条件;` / `… unless 条件;` / `… while 条件;`。
* `last` / `next`（`redo` 按 `next` 近似）。
* `{ … }` 裸块按顺序展开。`use NAME;` 见 §7。

#### 5. 字符串

* `print` 可多参数，«bold»不加分隔地连着打«/»；`say` 在末尾补换行。无参 `print` 打 `$_` 近似（打 0）。
* `print @a` 打的是«bold»元素个数«/»（元素类型在编译期定不了，见 §8；要逐个打请 `foreach`）。
* 内置：`length`、`substr($s,$pos[,$len])`（支持负下标/负长度）、`index($s,$sub[,$start])`、
  `uc`、`lc`、`chomp`、`chop`、`sprintf`（`%d %i %x %s %c %%` + 宽度/左对齐，精度只跳过）、
  `int`、`abs`。
* «bold»双引号里 `@arr` 不插值«/»（数组插值要按元素 join，元素类型编译期定不了）。要用
  `join($sep, @arr)`。

#### 6. 数组与哈希

* 数组：`@a`、`$a[i]`（负下标从尾部算）、`$#a`、`scalar(@a)`、`length(@a)`；
  `push / pop / shift / unshift`（第一个实参必须是数组变量）；`join(SEP, @a)`；
  `split(PAT, STR)`（PAT 是«bold»字符串«/»，按逐字匹配切，等价于单分隔符情形）。
* 哈希：`%h`、`$h{k}`（`k` 裸词当字符串）、`exists($h{k})`、`delete($h{k})`、
  `keys %h`、`values %h`、`scalar(%h)`。键是«bold»字符串«/»。
* «bold»元素类型«/»：数组元素 / 哈希值默认按«bold»整数«/»处理。一个数组/哈希被赋过字符串字面量列表
  （或来自 `keys` / `split`）时，前端把它记成"字符串数组/哈希"，`join` 与 `foreach` 按
  指针处理。混装（同一数组里既有整数又有字符串）«bold»不支持«/» —— 运行期无语义标签。
* `sort` / `reverse` / `each` «bold»未实现且在编译期报错«/»。

#### 7. 子程序与 `use`

```perl
sub add { my ($x, $y) = @_; return $x + $y; }   # 或 sub add($x, $y) { … }
print add(3, 4);
```
* 调用约定：«bold»只收一个实参«/» —— 实参数组的地址；`@_` 就是它。
* `my ($a, $b) = @_;` 在函数体里按«bold»下标«/»取（0 起）。
* «bold»先调用后定义允许«/»（前端先扫一遍 `sub` 名）。
* 无 `return` 的子程序返回 0。
* `use NAME;` 读 `Lib/perl/NAME.pl` 声明文件，登记其中
  `sub 名`（函数名）与 `our $名 = 数字;`（编译期常量）。声明文件«bold»纯文本扫描«/»、不执行。
  对库函数，`use` 是«bold»文档级«/»的：前端本来就按«bold»裸名字«/»解析（`ui_2d_clear(0)` 直接发
  `CALL ui_2d_clear`），删掉 `use` 照样编得过。

#### 8. 类型的编译期推断（为什么会"看走眼"）

本前端«bold»没有«/»运行期类型标签。前端按«bold»编译期能定的«/»信息在整数 / 浮点 / 字符串之间选通道：

* 字面量、赋过值的变量、`. `/`x` 的结果、返回字符串的用户子程序 → 能定。
* «bold»从 `@_` 取出来的形参«/»、未知库函数的返回值 → «bold»类型未知«/»：算术按整数、拼串时
  «bold»原样当指针«/»。
* 后果（有意接受）：`"v=" . $from_arg` 对；但 `print $from_arg`（单独打一个未知类型的
  形参）会按«bold»整数«/»打 —— 若它其实是字符串，打出来是地址。要稳就显式拼一次串或加
  `use` 声明。

#### 9. 有意不支持（多数在编译期报错）

| 特性 | 状态 |
|---|---|
| `sort` / `reverse` / `each` / `ucfirst` / `lcfirst` / `rindex` | «bold»编译期报错«/»「尚未实现」 |
| 引用 `\`、解引用、匿名数组/哈希 | 不支持 |
| 正则 `=~`、`s///`、`tr///` | 不支持 |
| 包 / `package` / 对象 / `bless` | 不支持 |
| `local` / `state` / `our` 的包语义 | 不支持（`our` 等同 `my`） |
| `substr` 作为左值、`wantarray`、context（标量/列表上下文） | 不支持 |
| 文件句柄 / `open` / `<>` / `printf` 到句柄 | 不支持（要看文件走共享库 / UI 工具） |
| `qw()` | 支持（拆成字符串列表） |
| 64 位整数 | 不支持（32 位） |
| 浮点打印格式 | 走共享运行时的 `print_double`，«bold»固定 6 位小数«/»（`3.4` 可能显示成 `3.399999`，那是共享 dtoa 的舍入口径，不是本前端算错） |

#### 10. 判据

`scripts/vml-perl-probe/run.sh` 跑 `bugs/vml_perl_bugs/t*.pl`，逐条比 stdout。
判据走的是 «bold»`vmlcli <用例> --lang perl` 这条真实通道«/»（注册表 → 汇编 → 链接 → 运行），
«bold»刻意不绕开注册表«/» —— 绕开那条路自带一份链接集，会把"真实通道缺本语言模块集"盖过去。
«bold»实测（接线 + `GenLib -m -l perl` 之后）：通过 24 / 失败 0。«/»

判据可红性«bold»已实测三遍«/»：
* «bold»期望值注入«/»：把 `t02` 的期望 `22 12 85 2` 改成 `…3` ⇒ 该条立刻 ❌。
* «bold»源码注入«/»：把 `GenerateIncDec` 的后缀 `+1` 改成 `+2` ⇒ `t15` 由 `s=10|c=1` 变 `s=6|c=2` ⇒ ❌；还原后复绿。
* «bold»模块集移除«/»：把 `Lib/perl/` 整个挪走 ⇒ `t07`（依赖 `perlrt.vml`）立刻红。

判据可红性«bold»已实测两遍«/»：
* «bold»期望值注入«/»：把 `t02` 的期望 `22 12 85 2` 改成 `…3` ⇒ 该条立刻 ❌。
* «bold»源码注入«/»：把 `GenerateIncDec` 的后缀 `+1` 改成 `+2` ⇒ `t15` 由 `s=10|c=1` 变 `s=6|c=2` ⇒ ❌；
  还原后复绿。

## 编译器 README

### Perl 前端编译器（PerlCompiler）

把 «bold»Perl 的一个子集«/»编译成 VML 汇编。它是 `third_party/vml/VMLPrepares/` 下的第 23 门前端，
与其它前端同构：`Lexer` / `Parser` / `ASTNode` / `CodeGenerator` / `PerlPlugin`，
继承 `CompilerBase`，由 `CompilerPluginExBase` 接进宿主。

> 语言范围（支持什么、不支持什么、为什么）见 [`PERL_LANGUAGE_SPEC.md`](PERL_LANGUAGE_SPEC.md)。
> 接线到宿主 / GenLib / 打包的逐条改动见 [`INTEGRATION.md`](INTEGRATION.md)。

#### 一句话定位

«bold»「完整运算 + 控制流 + 函数调用」«/» 那一档 —— 能写一般的脚本程序（数组 / 哈希 / 子程序 /
字符串处理 / 循环 / 递归），不是"能编过 hello world"的骨架。覆盖到
`Examples/perl/app/wordcount.pl`（词频统计）这种规模。

#### 快速上手

```bash
# ① 编译前端本体（开发期它就是一个 CLI）
dotnet build third_party/vml/VMLPrepares/PerlCompiler -c Release

# ② 编一个 .pl 成**独立可运行**的 VML 汇编（标准库已链进去）
dotnet third_party/vml/VMLPrepares/PerlCompiler/bin/Release/net10.0/PerlCompiler.dll \
       examples/perl/hello.pl -o /tmp/hello.vml

# ③ 跑
dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll /tmp/hello.vml \
       --vml-home third_party/vml --timeout 5
```

接进宿主之后（见 INTEGRATION.md）就能直接 `vml run examples/perl/hello.pl`。

##### 判据套件

```bash
scripts/vml-perl-probe/run.sh              # 全部
scripts/vml-perl-probe/run.sh t07 t10      # 按前缀挑
```

用例在 `bugs/vml_perl_bugs/t*.pl`，走 «bold»`vmlcli <用例> --lang perl` 这条真实通道«/»
（注册表 → 汇编 → 链接 → 运行），实测 «bold»通过 24 / 失败 0«/»。

> ⚠ «bold»跑之前先接线«/»（见 `INTEGRATION.md`）：① 按第 1/5/7 条把 perl 接进 vmlcli 的注册表；
> ② 跑一次 `GenLib -m -l perl` 生成 `Lib/perl/*.vml`。缺任一条套件要么«bold»响亮退出 2«/»
> （注册表里没 perl），要么«bold»让用例去红«/»（缺本语言模块集 → 裸名 `.linked` 解析到别的语言）。
> 套件«bold»刻意不«/»绕开注册表 —— 绕开那条路自带一份链接集，会把"真实通道缺模块"盖过去。

#### 文件

| 文件 | 作用 |
|---|---|
| `TokenType.cs` / `Token.cs` | 词法单元 |
| `Lexer.cs` | 词法器（`#` 注释、三种 sigil、单/双引号串、换行作为语句分隔） |
| `InterpolationParser.cs` | 双引号串的 `$var` 插值与转义 |
| `ASTNode.cs` | 语法树节点 |
| `Parser.cs` | 语法器（含无括号列表算子、后缀 `if/unless`、三目、`++`/`--`） |
| `CodeGenerator.cs` | 代码生成：核心 + 语句（变量槽 / 控制流 / 子程序 / print） |
| `CodeGenerator.Expressions.cs` | 表达式、类型推断、内置函数分派 |
| `PerlCompiler.cs` | 静态入口（读文件 + `use` 声明 + 链接标准库） |
| `PerlPlugin.cs` | 插件适配器（派生 `CompilerPluginExBase`） |
| `Program.cs` | 开发期 CLI |

运行期与库：

| 路径 | 作用 |
|---|---|
| `Lib/perl/perlrt.c` | 语言专属运行时源（动态数组 `@a` / 哈希 `%h` / 字符串辅助，前缀 `pl_`） |
| `Lib/perl/perlrt.vml` | 上面那份编出来的模块（`vmlcli --rebuild-lib` 产出） |
| `Lib/perl/builtin.vml` | 标准库清单（`.linked` 列表，编译器自动读） |
| `Lib/perl/vml_ui.pl` | UI 库的声明文件（常量 + 函数名，给 `use vml_ui;` 用） |

#### 设计要点（与其它前端不同的地方）

* «bold»不走 C 预处理器«/»：Perl 拿 `#` 当行注释，而 `CompileFileStandard` 一见 `#` 就上 C 预处理器。
  所以 `PerlCompiler` 自己读文件、自己设 `CurrentSourceFile`。
* «bold»子程序收一个「实参数组」«/»：`sub` 编译成只收一个实参（`@_` 那个数组的地址），
  `my ($a, $b) = @_;` 在函数体里按下标取。这样天然支持 Perl 的变参，也免去"按形参个数
  一档档读栈槽"的分支。调用点是 `EmitBuildArray(args)` + `CALL func_<名>`。
* «bold»数组 / 哈希的运行时是 C«/»：`Lib/perl/perlrt.c` 编成 `Lib/perl/perlrt.vml`。
  布局与 `Lib/shared/src/array.c` 同族（`[长度, e0, e1, …]`），哈希多开一份容量头。
  增长型操作（push / unshift / set）«bold»返回新指针«/»，前端回写变量。
* «bold»`%` 的歧义«/»：`%h` 是 sigil、`$a % $b` 是取模 —— 词法器按"后面跟不跟标识符"分辨。

#### 已知限制

见 `PERL_LANGUAGE_SPEC.md` 的「不支持 / 有意简化」一节。要点：
`sort` / `reverse` / `each` / `ucfirst` / `lcfirst` / `rindex` «bold»未实现且在编译期报错«/»（不静默）；
`@数组` 在双引号里不插值；没有引用 / 包 / 正则 / 对象；整数是 32 位。

#### 许可 / License

与仓库其余部分一致（VML 已与上游分家，见 `third_party/vml/FORK.md`）。
