Parrot Virtual Machine/Classes and Objects
Classes and Objects
We briefly discussed some class and object PIR code earlier, and in this chapter we are going to go into more detail about it. As we mentioned before, classes have 4 basic components: A namespace, an initializer, a constructor, and methods. A namespace is important because it tells the virtual machine where to look for the methods of the object when they are called. If I have an object of class "Foo", and I call the "Bar" method on it:
.local pmc myobject myobject = new "Foo" myobject.'Bar'()
The virtual machine will see that
myobject is a PMC object of type Foo, and then will look for the method 'Bar' in the namespace 'Foo'. In short, the namespace helps to keep everything together.
An initializer is a function that is called at the beginning of the program to set up the class. PIR doesn't have a syntax for declaring information about the class directly, you have to use a series of opcodes and statements to tell Parrot what your class looks like. This means that you need to create the various data fields in your class (called "attributes" here), and set up relationships with other classes.
Initializer functions tend to follow this format:
.namespace .sub 'onload' :anon :init :load .end
:anon flag means that the name of the function will not be stored in the namespace, so you don't end up with all sorts of name pollution. Of course, if the name of the function isn't stored, it can be difficult to make additional calls to this function, although that doesn't matter if we only want to call it once. The
:init flag causes the function to run as soon as parrot initializes the file, and the
:load flag causes the function to run as soon as the file is loaded, if it is loaded as an external library. In short: We want this function to run as soon as possible and we only want it to run once.
Notice also that we want the initializer to be declared in the HLL namespace.
Making a New Class
We can make a new class with the keyword
newclass. To create a class called "MyClass", we would write an initializer that does the following:
.sub 'initmyclass' :init :load :anon newclass $P0, 'MyClass' .end
Also, we can simplify this using PIR syntax:
.sub 'initmyclass' :init :load :anon $P0 = newclass 'MyClass .end
In the initialzer, the register $P0 contains a reference to the class object. Any changes or additions that we want to make to the class need to be made to this class reference variable.
Creating new class objects
Once we have a class object, the output of the newclass opcode, we can create or "instantiate" objects of that class. We do this with the new keyword:
.local PMC myobject myobject = new $P0
Or, if we know the name of the class, we can write:
.local PMC myobject myobject = new 'MyClass'
We can set up a subclass/superclass relationship using the
subclass command. For instance, if we want to create a class that is a subclass of the builtin PMC type "ResizablePMCArray", and if we want to call this subclass "List", we would write:
.sub 'onload' :anon :load :init subclass $P0, "ResizablePMCArray", "List" .end
This creates a class called "List" which is a subclass of the "ResizablePMCArray" class. Notice that like the
newclass instruction above, we store a reference to the class in the PMC register $P0. We'll use this reference to modify the class in the sections below.
Attributes can be added to the class by using the
add_attribute keyword with the class reference that we received from the
subclass keywords. Here, we create a new class 'MyClass', and add two data fields to it: 'name' and 'value':
.sub 'initmyclass' :init :load :anon newclass $P0, 'MyClass' add_attribute $P0, 'name' add_attribute $P0, 'value' .end
We'll talk about accessing these attributes below.
Methods, as we mentioned earlier, have three major differences from subroutines: The way they are flagged, the way they are called, and the fact that they have a special
self variable. We know already that methods should use the
:method indicates to Parrot that the other two differences (dot-based calling convention and "self" variable) need to be implemented for the method. Some methods will also use the
:vtable flag as well, and we will discuss that below.
We want to create a class for a stack class. The stack has "push" and "pop" methods. Luckily, Parrot has
pop instructions available that can operate on array-like PMCs (like the "ResizablePMCArray" PMC class). However, we need to wrap these PIR instructions into functions or methods so that they can be used from our high-level language (HLL). Here is how we can do that:
.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
Now, if we had a language compiler for Java on Parrot, we could write something similar to this:
Stack mystack = new Stack(); mystack.push(5); System.out.println(mystack.pop());
The example above would print the value "5" at the end. If we look at the same example in a language like Perl 5, we would have:
my $stack = Stack::new(); $stack->push(5); print $stack->pop();
This, again, would print out the number "5".
If our class has attributes, we can use the
getattribute instructions to write and read those attributes, respectively. If we have a class 'MyClass' with data attributes 'name' and 'value', we can write accessors and setter methods for these:
.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
The constructor is the function that we call when we use the
new keyword. The constructor initializes the data object attributes, and maybe performs some other bookkeeping tasks as well. A constructor must be a method named 'new'. Besides the special name, the constructor is like any other method, and can get or set attributes on the
self variable as needed.