Parrot Virtual Machine/Classes and Objects

Classes and Objects edit

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.

Initializers edit

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

The :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 edit

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 initializer, 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 edit

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'
 

Subclassing edit

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.

Adding Attributes edit

Attributes can be added to the class by using the add_attribute keyword with the class reference that we received from the newclass or 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 edit

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 flag. :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 push and 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".

Accessing Attributes edit

If our class has attributes, we can use the setattribute and 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

Constructors edit

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.


VTables edit

Resources edit


Previous Parrot Virtual Machine Next
Exception Handling The Parrot Debugger