鹦鹉虚拟机/编译器构建
现在我们已经了解了NQP和PGE,我们有了开始编写我们最喜欢的语言编译器的工具。让我们回顾一下创建编译器的步骤
- 使用PGE编写语法
- 使用NQP编写动作
- 动作创建PAST树
- PCT将PAST转换为PIR
- 鹦鹉将PIR转换为字节码。
在步骤5之后,如果我们一切都做对了,我们应该有一个可以工作的编译器。当然,在我们到达步骤5之前还有很多工作要做。幸运的是,步骤4和5由构建过程自动完成,因此我们只需要关注1、2和3。在教程部分,我们将提供一些语言构建教程,你可以参考。
PAST节点是你的NQP动作方法必须创建的对象。PCT会在你创建这些节点后自动将它们插入到树中。PAST节点是具有数组和哈希组件的复杂对象。数组存储对子节点引用的列表,而哈希存储有关节点本身的信息。创建PAST树意味着我们创建低层规则的节点,并将它们插入到高层规则的数组中。在创建过程中,我们为每个规则设置必要的选项,以便PCT可以生成相应的代码。
附录中提供了有关各种PAST节点类型的参考。
PAST::Node是其他PAST节点类型派生的基类。其他类型是PAST::Stmts、PAST::Val、PAST::Var、PAST::Op和PAST::Block。
PAST::Op
- PAST::Op节点是需要执行的操作。
PAST::Val
- PAST::VAL节点是常量值,如整数或字符串
PAST::Var
- PAST::Var节点是变量
PAST::Block
- PAST::Block节点用于词法作用域变量和定义子例程。它们将其他节点放在一起。
PAST::Stmts
- PAST::Stmts节点只是其他节点的组,除了基本的组织之外不执行任何任务。
创建好必要的节点后,可以使用关键字make
将它插入到解析树中。
匹配对象是一个包含哈希的特殊对象。匹配对象哈希包含有关匹配规则的信息。哈希中的每个元素都以规则中的一个子规则命名。例如,如果我们有以下规则
rule sentence { <adjective> <noun> <adverb> <verb> }
那么匹配规则将包含条目“形容词”、“名词”、“副词”和“动词”。这些键的哈希值是使用make
命令从子规则返回的值(如果有)。所以,我们将有以下字段
$<adjective> $<noun> $<adverb> $<verb>
然后,我们将使用这些字段的值来为sentence
规则生成和make
一个节点。如果这些字段中的每一个都返回它们自己的PAST节点,我们应该将它们推入
我们将在这里展示一些非常小的示例,以演示基本的编译器构建方法。教程部分提供了更高级的教程。
- 问题
我们想创建一个基本的计算器程序,它可以对整数执行加法和减法。计算器应将结果打印到屏幕上。
- 解决方案
- 我们首先运行
mk_language_shell.pl
来创建一个基本的语言框架。这还会生成一个名为say
的内置函数。我们将使用say
函数将结果打印到屏幕上。我们将使用基本的自顶向下解析器,我们不会使用操作表。
我们为我们的计算器创建一个基本语法
rule TOP { <expression> {*} } rule expression { <term> <operation> <term> {*} } token operation { '+' | '-' } token term { \d+ }
现在我们需要创建两个动作,一个用于TOP,另一个用于表达式。TOP方法应该获取解析表达式的值并将其传递给say
函数。表达式函数应该生成必要的PIR操作,并将term
值插入这些操作中。这是一个基本的动作文件来完成这项工作
method TOP($/) { my $past := PAST::Op.new(:pasttype('inline')); my $expr := $( $<expression> ); $past.inline('say(%0)'); $past.unshift($expr); make $past; } method expression($/) { my $left := $( $<term>[0] ); my $right := $( $<term>[1] ); my $op := $( $<operation> ); my $pirop := "sub_n"; if $op eq "+" { my $pirop := "add_n"; } my $past := PAST::Op.new(:pasttype('pirop'), :pirop($pirop)); $past.unshift($left); $past.unshift($right); make $past; }