flaviovs/phpast

抽象语法树解释器

0.0.4 2017-05-31 18:47 UTC

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 节点——代表 24MulOp 是与 mul 乘法操作对应的节点操作(因此有 Op)。它的作用是乘以它的两个节点操作数,所以当你运行上面的程序时,$output 将等同于一个 PHPAST\Integer(8) 对象(82 × 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 的节点是 AddOpSubOp 类型的节点。正如你可能已经猜到的,这两个代表算术 加法减法。在这个例子中,$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 对象,它定义了函数期望的参数。

FuncProg 的子类,因此它的“数组”元素定义了构成函数体的节点。

Prog类似,Func返回最后一个节点的评估结果。您可以使用ReturnOp来结束函数评估并返回一个特定的节点。

定义 & 调用操作

使用Def可以定义一个函数,这基本上意味着将函数体分配给符号表中的符号。要“调用”函数,请使用CallOp与指向定义函数的符号的Ref以及定义函数参数的SymbolTable对象。

示例

examples/目录包含一些如何使用PHPAST的示例。

支持

主页:https://github.com/flaviovs/phpast