flaviovs / phpast
抽象语法树解释器
Requires (Dev)
- phpunit/phpunit: ^5.7
This package is not auto-updated.
Last update: 2024-09-25 01:21:32 UTC
README
PHPAST 包提供了一种 抽象语法树 (AST) 结构,以及一个运行由这些结构表示的程序的解释器。PHPAST 不仅提供了编程语言中常见的所有运算符,如加法、减法、逻辑运算符("and"、"or"、"not"、"xor")等,还提供了流程控制结构("if")、循环("while"、"for")。您可以使用变量、定义函数——无论是静态的,还是通过使用 AST 调用来实现的,这允许创建动态例程——,并调用预定义的 PHP 内置函数或回调函数。这使得 PHPAST 非常灵活和强大,并允许您构建自己的编程语言,并几乎可以做任何全功能语言能做的事情。
注意:此包仅包含 AST 结构和解释器,用作编程语言的中间表示形式。要正确实现语言,您需要一个解析器将词法结构转换为 AST。
重要:PHPAST 应被视为仍在进行中的工作。API 尚未冻结,基本组件可能随时会有所不同。如果您需要讨论有关此包的任何您希望/喜欢/不喜欢的内容,请创建一个新问题。
安装
安装和运行 PHPAST 最简单的方法是通过 Composer
$ composer require flaviovs/phpast
不使用 Composer 运行
如果您不使用 Composer 运行 PHP,则必须加载核心库文件,并实现 PSR-4 风格的自动加载器,将 PHPAST
命名空间指向 src/
目录
require 'path/to/phpast/src/core/except.php'; require 'path/to/phpast/src/core/types.php'; $my_autoloader->addPSR4('PHPAST', 'path/to/phpast/src');
基本用法
PHPAST(以及 AST 的一般概念)的核心概念是 评估节点,它由在符号表中评估后产生另一个节点的对象组成。AST 组织成树(当然!),因此节点可以有子节点,这些子节点也会根据父节点要执行的操作进行评估。最终,根节点(您的主程序)返回表示树完整评估的节点——这就是您“程序”的输出。
让我们看看一个基本的例子
// First, create a symbol table for our "program". $table = new PHPAST\FlatSymbolTable(); // Our "program" will contain only the integer value 1. It is very dumb, so // once it is run (evaluated), it should just return the same integer value. $program = new PHPAST\Integer(1); // Now let's run our program $output = $program->evaluate($table);
在这个阶段,$output
包含了评估我们程序的成果,存储在 $program
变量中。由于我们的“程序”仅由表示文字数字 1 的节点组成,因此返回的是同一个节点。换句话说,在上面的代码运行后,$output
与 $program
相同。
请注意,为了评估一个节点,您必须提供一个符号表对象。符号表类似于 PHP 数组,并由 PHPAST 用于存储程序变量。我们将在后面介绍符号表,所以现在只需接受它们是必需的,并且它们的工作方式类似于 PHP 数组。在上面的例子中,我们使用了 $table
来存储我们的符号表。
现在让我们看一个更复杂的例子
$table = new PHPAST\FlatSymbolTable(); // Lets' define some values $number1 = new PHPAST\Integer(2); $number2 = new PHPAST\Integer(4); // Our program now will multiply the two integers. $program = new PHPAST\MulOp($number1, $number2); // Now let's run our program $output = $program->evaluate($table);
哇哦!事情变得有点复杂了。上面的程序由一个 MulOp
节点组成,它接收两个 Integer
节点——代表 2 和 4。 MulOp
是与 mul 乘法操作对应的节点操作(因此有 Op)。它的作用是乘以它的两个节点操作数,所以当你运行上面的程序时,$output
将等同于一个 PHPAST\Integer(8)
对象(8 是 2 × 4 的结果)。
现在是一个更复杂的程序
$number1 = new PHPAST\Integer(2); $number2 = new PHPAST\Integer(4); // Our program now will multiply the two integers. $program = new PHPAST\MulOp( new PHPAST\AddOp($number1, $number2), new PHPAST\SubOp($number1, $number2) ); // Now let's run our program $output = $program->evaluate($table);
我说过 PHPAST 程序是层次结构吗?在上面的例子中,传递给 MulOp
的节点是 AddOp
和 SubOp
类型的节点。正如你可能已经猜到的,这两个代表算术 加法 和 减法。在这个例子中,$output
将等同于一个 PHPAST\Integer(-12)
对象((2 + 4) * (2 - 4))。
这就是 AST 的美妙之处:通过组合小型、自包含的操作节点,你可以创建非常复杂的程序。
符号表
如上所述,需要一个符号表来评估一个节点。符号表就像 PHP 数组,你可以用它来为索引元素分配一个值。不同之处在于,在符号表中,你将节点分配给一个符号(这只是一个花哨的名字,代表“索引元素”)。
实际上,符号表实现了标准的 PHP 数组访问语法
$table = new PHPAST\FlatSymbolTable(); $table['foo'] = new PHPAST\String('bar'); $table['launch'] = PHPAST\Boolean::getFalse(); if ($table['launch'] === PHPAST\Boolean::getTrue()) { print "Launch\n"; }
上面的语法只是为了说明符号表预期的行为。 通常你不会编写代码来操作符号表 —— 它们将由 PHPAST 处理,并由你的 PHPAST 程序管理的符号/值。
FlatSymbolTable
是最简单的符号表,它的工作方式与 PHP 数组完全相同。PHPAST 还提供了 ChainedScopeSymbolTable
类,它可以用来实现局部/全局变量。你可以自由选择最适合你语言的符号表,甚至可以定义你自己的。
变量
变量是符号表中的条目,通过标识符索引,最终用作符号表上节点的索引。PHPAST 提供了几个节点来处理基本变量操作。让我们看看一些例子
use PHPAST\FlatSymbolTable; use PHPAST\Integer; use PHPAST\Float; use PHPAST\Ref; use PHPAST\Identifier; use PHPAST\AssignOp; use PHPAST\PowerOp; use PHPAST\Outln; // Create and initialize a symbol table. $table = new FlatSymbolTable(['foo' => new Float(0)]); // Create a 'foo' identifier. Identifiers work pretty much like as an ordinary // string value. All variable reference must be done using identifiers, so we // here we create one. $foo_id = new Identifier('foo'); // Now create a reference to the variable. $foo_ref = new Ref($foo_id); // This program will assign Float(3.14159) to 'foo'. This would be the // equivalent of the following construct in PHP: // // $foo = 3.14159; $program = new AssignOp($foo_ref, new Float(3.14159)); // Now let's run our program. $program->evaluate($table); // This should print the 'foo' entry in the symbol table with the value 3.14159. print_r($table['foo']); // $bar = $foo ** 2 $bar_ref = new Ref(new Identifier('bar')); $program = new AssignOp($bar_ref, new PowerOp($foo_ref, new Integer(2))); $program->evaluate($table); // Let's use the Outln operation to print the newly created 'bar' variable. // This should print "3.14159 to the power of two is 9.8695877281". $program = new Outln([ $foo_ref, " to the power of two is ", $bar_ref, ]); $program->evaluate($table);
字符串表示
PHPAST 节点具有对节点操作友好的(嗯...程序员友好的)字符串表示形式。这有助于理解和/或调试 PHPAST 程序。要打印字符串表示形式,只需在字符串上下文中使用节点对象。例如
$table = new FlatSymbolTable(['foo' => new Float(0)]); $foo_ref = new Ref(new Identifier('foo')); $program = new Outln([ new PowerOp(new Float(3.14159), new Integer(2)), ]); print "$program\n";
这应该打印
Outln (3.14159 ** 2)
注意:PHPAST 输出的字符串表示形式并不作为——也从未打算作为一个功能齐全的编程语言。它只是节点将执行的内操作的一种天真表示。
复杂操作
PHPAST 还提供了执行复杂操作的节点,例如程序流程控制和函数。以下是这些结构的一些描述。请参阅 src/
目录中的类,以获取 PHPAST 运算符的完整列表。
Prog
这个原始节点用于按顺序评估多个子节点。返回最后节点的评估结果。节点实现了 ArrayAccess,因此 Prog
可以使用普通的 PHP 数组语法进行操作。 Prog
是评估函数体、控制结构和程序中的节点序列的构建块。
示例
$foo_ref = new Ref(new Identifier('foo')); $bar_ref = new Ref(new Identifier('bar')); $prog = new Prog(); // $foo = 0; // $bar = 1; $prog[] = new AssignOp($foo_ref, new Integer(0)); $prog[] = new AssignOp($bar_ref, new Integer(1)); // $bar += 10 $prog[] = new IncOp($foo_ref, 10); // for(;$foo > $bar; $foo -= 2) { print "foo = $foo\n"; } $prog[] = new ForOp(new Nop(), new GtOp($foo_ref, $bar_ref), new DecOp($foo_ref, 2), new Outln([ "foo = ", $foo_ref ])); print "$prog\n";
这应该打印
foo := 0
bar := 1
foo += 10
For (Nop; (foo > bar); foo -= 2)
Outln "foo = ", foo
Func & ReturnOp
Func
节点代表函数。每个函数都有一个 ArgList
对象,它定义了函数期望的参数。
Func
是 Prog
的子类,因此它的“数组”元素定义了构成函数体的节点。
与Prog
类似,Func
返回最后一个节点的评估结果。您可以使用ReturnOp
来结束函数评估并返回一个特定的节点。
定义 & 调用操作
使用Def
可以定义一个函数,这基本上意味着将函数体分配给符号表中的符号。要“调用”函数,请使用CallOp
与指向定义函数的符号的Ref
以及定义函数参数的SymbolTable
对象。
示例
examples/
目录包含一些如何使用PHPAST的示例。