鹦鹉虚拟机/类和对象
我们之前简要讨论了一些类和对象的 PIR 代码,在本章中,我们将更详细地介绍它。正如我们之前提到的,类有 4 个基本组成部分:命名空间、初始化器、构造函数和方法。命名空间很重要,因为它告诉虚拟机在调用对象的方法时在哪里查找它们。如果我有一个名为“Foo”类的对象,并且我在它上面调用“Bar”方法
.local pmc myobject myobject = new "Foo" myobject.'Bar'()
虚拟机将看到 myobject
是一个类型为 Foo 的 PMC 对象,然后将在命名空间“Foo”中查找方法“Bar”。简而言之,命名空间有助于将所有内容保持在一起。
初始化器是在程序开始时调用的函数,用于设置类。PIR 没有用于直接声明有关类的信息的语法,您需要使用一系列操作码和语句来告诉 Parrot 您的类是什么样的。这意味着您需要创建类中的各种数据字段(此处称为“属性”),并设置与其他类的关系。
初始化器函数通常遵循以下格式
.namespace .sub 'onload' :anon :init :load .end
:anon
标志表示不会将函数的名称存储在命名空间中,因此您最终不会遇到各种命名污染。当然,如果函数的名称没有存储,那么很难对该函数进行额外的调用,尽管如果我们只想调用它一次,这并不重要。:init
标志会导致函数在鹦鹉初始化文件时立即运行,而 :load
标志会导致函数在文件加载时立即运行,如果它是作为外部库加载的。简而言之:我们希望此函数尽快运行,并且我们只希望它运行一次。
还要注意,我们希望初始化器在 HLL 命名空间中声明。
我们可以使用关键字 newclass
创建一个新类。要创建一个名为“MyClass”的类,我们将编写一个执行以下操作的初始化器
.sub 'initmyclass' :init :load :anon newclass $P0, 'MyClass' .end
另外,我们可以使用 PIR 语法简化它
.sub 'initmyclass' :init :load :anon $P0 = newclass 'MyClass .end
在初始化器中,寄存器 $P0 包含对类对象的引用。我们想要对类进行的任何更改或添加都需要对这个类引用变量进行。
一旦我们有了类对象,即 newclass
操作码的输出,我们就可以创建或“实例化”该类的对象。我们使用 new 关键字来做到这一点
.local PMC myobject myobject = new $P0
或者,如果我们知道类的名称,我们可以写
.local PMC myobject myobject = new 'MyClass'
我们可以使用 subclass
命令设置子类/超类关系。例如,如果我们想要创建一个类,它是内置 PMC 类型“ResizablePMCArray”的子类,并且如果我们想要将这个子类称为“List”,我们将编写
.sub 'onload' :anon :load :init subclass $P0, "ResizablePMCArray", "List" .end
这将创建一个名为“List”的类,它是“ResizablePMCArray”类的子类。请注意,与上面的 newclass
指令一样,我们将对类的引用存储在 PMC 寄存器 $P0 中。我们将在下面的部分中使用此引用来修改类。
可以使用 add_attribute
关键字以及从 newclass
或 subclass
关键字接收的类引用来向类添加属性。在这里,我们创建一个名为“MyClass”的新类,并向其中添加两个数据字段:“name”和“value”
.sub 'initmyclass' :init :load :anon newclass $P0, 'MyClass' add_attribute $P0, 'name' add_attribute $P0, 'value' .end
我们将在下面讨论访问这些属性。
正如我们之前提到的,方法与子例程有三个主要区别:它们的标记方式、它们的调用方式以及它们具有一个特殊的 self
变量。我们已经知道方法应该使用 :method
标志。:method
表示 Parrot 需要为方法实现另外两个区别(基于点的调用约定和“self”变量)。一些方法也将使用 :vtable
标志,我们将在下面讨论。
我们想要为堆栈类创建一个类。堆栈有“push”和“pop”方法。幸运的是,Parrot 提供了 push
和 pop
指令,它们可以对类似数组的 PMC(如“ResizablePMCArray”PMC 类)进行操作。但是,我们需要将这些 PIR 指令包装到函数或方法中,以便它们可以从我们的高级语言 (HLL) 中使用。以下是我们可以做到这一点的方法
.namespace .sub 'onload' :anon :load :init subclass $P0, "ResizeablePMCArray", "Stack" .end .namespace ["Stack"] .sub 'push' :method .param pmc arg push self, arg .end .sub 'pop' :method pop $P0, self .return($P0) .end
现在,如果我们有 Parrot 上的 Java 语言编译器,我们可以编写类似以下内容
Stack mystack = new Stack(); mystack.push(5); System.out.println(mystack.pop());
上面的示例将在最后打印值“5”。如果我们查看 Perl 5 等语言中的相同示例,我们将拥有
my $stack = Stack::new(); $stack->push(5); print $stack->pop();
这同样会打印数字“5”。
如果我们的类具有属性,我们可以使用 setattribute
和 getattribute
指令分别写入和读取这些属性。如果我们有一个名为“MyClass”的类,它具有数据属性“name”和“value”,我们可以为这些属性编写访问器和设置器方法
.sub 'set_name' :method .param pmc newname $S0 = 'name' setattribute self, $S0, newname .end .sub 'set_data' :method .param pmc newdata $S0 = 'data' setattribute self, $S0, newdata .end .sub 'get_name' :method $S0 = 'name' $P0 = getattribute self, $S0 .return($P0) .end .sub 'get_value' :method $S0 = 'value' $P0 = getattribute self, $S0 .return($P0) .end
构造函数是我们使用 new
关键字时调用的函数。构造函数初始化数据对象属性,并且可能还执行其他一些簿记任务。构造函数必须是一个名为“new”的方法。除了特殊名称外,构造函数与任何其他方法一样,可以根据需要获取或设置 self
变量上的属性。
- http://www.parrotcode.org/docs/pdd/pdd15_objects.html
- http://www.parrotcode.org/docs/pdd/pdd21_namespaces.html