用原生 PHP 实现的一个小型编译器,无第三方依赖,包含词法分析、语法分析、字节码生成、虚拟机执行四个阶段。支持将源码编译为 .bc 字节码文件,之后可脱离编译器单独运行。
源码 (.l)
→ Lexer (词法分析) → Token 流
→ Parser (语法分析) → AST
→ CodeGen (代码生成) → ModuleBC (字节码模块)
→ VM (虚拟机执行)
↓ compile.php
.bc 字节码文件(可单独分发、运行)
PHP 8.3 或 PHP 8.4。
$ composer require lwlwilliam/tiny-compiler:dev-main$ php examples/run.php examples/codes/demo.l输出:
y = 44; z = 9; foo = 10
arr = [1,42,3]
add(3, 4) = 7
fact(10) = 55
twice(8) = 16
GREETING = Hello
y > 20
sum = 10
Hello world number: 1
Hello world number: 2
Hello world number: 4
Hello world number: 6
Hello world number: 8
j == 666
true && 123 = 123
false && 123 = false
true || 456 = true
false || 456 = 456
PI = 3.14
null = null
10 % 3 = 1
flag is false
first element: 1
nested: 10
tabs: between
say "hello"
inside block: 999
将源码编译成二进制 .bc 字节码文件:
$ php examples/compile.php examples/codes/demo.l
Compiled: examples/codes/demo.l -> examples/codes/demo.bc
size: 3,056 bytes
consts: 47
globals: 16
functions: 4
entry ops: 462生成的 .bc 文件是紧凑的二进制格式(TBC\x00 魔数头),无需 Lexer / Parser / CodeGen,仅需 VM 即可运行:
$ php examples/run.php examples/codes/demo.bc
# 输出与直接运行源码完全一致.bc 文件可分发到任何安装了 TinyCompiler 的环境中直接执行。如需人类可读的字节码文件,可使用 --php 导出为 var_export PHP 格式:
$ php examples/compile.php --php examples/codes/demo.l
Compiled: examples/codes/demo.l -> examples/codes/demo.bc.php (PHP (human-readable))
size: 10,088 bytes导出的 .bc.php 文件结构一览:
<?php return array (
'version' => 1,
'consts' =>
array (
0 => NULL,
1 => 1,
2 => 2,
3 => 'fact2',
4 => 'Hello',
5 => 3.14,
// ... 共 47 个常量
),
'globals' =>
array (
'twice' => 0,
'add' => 1,
'fact' => 2,
// ... 共 16 个
),
'functions' =>
array (
'twice' =>
array (
'code' => array ( 1, 0, 4, 0, 4, 0, 10, 1, 1, 1, 51 ),
'nLocals' => 1,
),
// ... 共 4 个函数
),
'entry' =>
array (
1, 0, 2, 1, 1, ...
// 462 条字节码指令
),
);let const func return if else while for break continue true false null include
// 单行注释
/*
多行注释
*/let x = 10;
let y; // 未初始化,默认 null
y = 2 + 2 * (x + 5 + 6);
print("y = ", y, "\n"); // y = 44
const PI = 3.14;
print("PI = ", PI, "\n"); // PI = 3.14
func add(a, b) {
return a + b;
}
print("add(3, 4) = ", add(3, 4), "\n"); // add(3, 4) = 7
func fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
print("fib(10) = ", fib(10), "\n"); // fib(10) = 55
let score = 85;
if (score >= 90) {
print("A\n");
} else if (score >= 80) {
print("B\n");
} else {
print("C\n");
}
let n = 1;
let sum = 0;
while (n <= 100) {
sum = sum + n;
n = n + 1;
}
print("sum 1..100 = ", sum, "\n"); // sum 1..100 = 5050
let total = 0;
for (let i = 0; i < 5; i = i + 1) {
total = total + i;
}
print("total = ", total, "\n"); // total = 10 (0+1+2+3+4)
// for 的三个表达式都可以省略
let j = 0;
for (; j < 3;) {
print("loop\n");
j = j + 1;
}
let i = 0;
while (true) {
i = i + 1;
if (i == 3 || i == 5) {
continue; // 跳过 3 和 5
}
print("i = ", i, "\n");
if (i == 7) {
break; // 到 7 停止
}
}
// 输出: i = 1, i = 2, i = 4, i = 6, i = 7
func max(a, b) {
if (a > b) {
return a;
}
return b;
}
print("max(10, 20) = ", max(10, 20), "\n"); // max(10, 20) = 20
{} 包裹多条语句,可出现在任何允许单条语句的位置:
if (true) {
print("first\n");
print("second\n");
}
include "lib.l";
print("GREETING = ", GREETING, "\n"); // 使用 lib.l 中定义的常量
print("twice(8) = ", twice(8), "\n"); // 使用 lib.l 中定义的函数
let arr = [1, 2, 3];
arr[1] = 42;
print("arr = ", arr, "\n"); // arr = [1,42,3]
print("arr[0] = ", arr[0], "\n"); // arr[0] = 1
print("true && 123 = ", true && 123, "\n"); // true && 123 = 123
print("false && 123 = ", false && 123, "\n"); // false && 123 = false
print("true || 456 = ", true || 456, "\n"); // true || 456 = true
print("false || 456 = ", false || 456, "\n"); // false || 456 = 456
&&/||有短路语义,返回值是最后一个被求值的表达式的值(不是布尔值)。
| 类型 | 示例 | 说明 |
|---|---|---|
| 整数 | 42, 0, -7 |
|
| 浮点数 | 3.14, -0.5 |
|
| 字符串 | "hello", "a\nb" |
支持 \n \r \t \" \' \\ |
| 布尔值 | true, false |
|
| 空值 | null |
|
| 数组 | [1, "hi", true] |
任意类型混排 |
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 最低 | = |
右结合 |
|| |
左结合 | |
&& |
左结合 | |
== != |
左结合 | |
< <= > >= |
左结合 | |
+ - |
左结合 | |
* / % |
左结合 | |
- (取负) ! (逻辑非) |
右结合 | |
| 最高 | () 调用 [] 索引 |
左结合 |
仅提供一个内置输出函数 print,支持可变参数:
print("Hello", " ", "World", "\n"); // Hello World
print(1, 2, 3, "\n"); // 123
print 对不同类型值的输出格式:
| 值 | 输出 |
|---|---|
"abc" |
abc |
42 |
42 |
3.14 |
3.14 |
true |
true |
false |
false |
null |
null |
[1, 2, 3] |
[1,2,3] |