NAME

docs/vtables.pod - Parrot Vtables

Implementing Variable Types with Vtables

This is a guide to creating your own PMC (Polymorphic Container) classes. It tells you what you need to write in order to add new variable types to Parrot.

Overview

The guts of the Parrot interpreter are by design ignorant (or, if you want to be less disparaging, agnostic) of the intricacies of variable type behavior. The standard example is the difference between Perl scalars and Python scalars. In Perl, if you have

    $a = "a9";
    $a++;

you end up with $a being b0. This is because of the magic of the Perl increment operator. In Python, on the other hand, you'd get a runtime error.

Since the behavior is a function of the "type" of the PMC, it's natural to consider the various different types of PMC as classes in an object-oriented system. The Parrot interpreter calls methods on the individual PMC objects to manipulate them. So the example above would translate to something like:

  1. Construct a new PMC in the PerlScalar class.
  2. Call a method setting its string value to "a9".
  3. Call a method to tell it to increment itself.

And if you replace PerlScalar with PythonString, you get different behavior but to the fundamental guts of the interpreter, the instructions are the same. PMCs are an abstract virtual class; the interpreter calls a method, the PMC object does the right thing, and the interpreter shouldn't have to care particularly what that right thing happens to be.

Hence, adding a new data type to Parrot is a question of providing methods which implement that data type's expected behavior. Let's now look at how one is supposed to do this.

Starting out

If you're adding data types to the core of Parrot, you should be creating a file in the src/pmc/ subdirectory; this is where all the built-in PMC classes live. (And a good source of examples to plunder even if you're not writing a core data type.)

You should almost always start by running tools/dev/gen_class.pl to generate a skeleton for the class. Let's generate a number type for the beautifully non-existent Fooby language:

    % perl tools/dev/gen_class.pl FoobyNumber > src/pmc/foobynumber.pmc

This will produce a skeleton PMC file (to be preprocessed into ordinary C code by the tools/build/pmc2c.pl program) with stubs for all the methods you need to fill in. Actually, there are more stubs here then you probably need to fill in. Your PMC isn't going to want to support all these methods, and in many cases you may want to fall back to default behavior instead of implementing a dummy method.> The function init allows you to set up anything you need to set up.

Now you'll have to do something a little different depending on whether you're writing a built-in class or an extension class. If you're writing a non-core PMC, called a "dynpmc", you need to add the argument dynpmc to the line that starts with pmclass. Here's an example:

        pmclass FooByNumber dynpmc {
                ...

This alerts the PMC compiler that the PMC type should not be hard-coded into Parrot, and that the PMC definition needs to be loaded in to Parrot dynamically when the user requires it.

To finish up adding a built-in class:

  1. Add src/pmc/YOURCLASS.pmc to the MANIFEST.
  2. Run make realclean, and then run Configure.pl to add your new PMC to the set of built-in PMCs.

What You Can and Cannot Do

The usual way to continue from the tools/dev/gen_class.pl-generated skeleton is to define a structure that will hook onto the data, if your data type needs to use that, and then also define some user-defined flags.

Flags are accessed by pmc->flags. Most of the bits in the flag word are reserved for use by parrot itself, but a number of them have been assigned for general use by individual classes. These are referred to as Pobj_private0_FLAG .. Pobj_private7_FLAG.

Normally, you will want to alias these generic bit names to something more meaningful within your class:

    enum {
        Foobynumber_is_bignum = Pobj_private0_FLAG,
        Foobynumber_is_bigint = Pobj_private1_FLAG,
        ....
    };

To manipulate the flags, use the macros listed in pobj.h.

PMCs also have the ability to store an arbitrary number of user-defined attribute values using the ATTR keyword.

Multimethods

One slightly (potentially) tricky element of implementing vtables is that several of the vtable functions have variant forms depending on the type of data that they're being called with.

For instance, the set_integer method has multiple forms; the default set_integer means that you are being called with a PMC, and you should probably use the get_integer method of the PMC to find its integer value; set_integer_native means you're being passed an INTVAL. The final form is slightly special; if the interpreter calls set_integer_same, you know that the PMC that you are being passed is of the same type as you. Hence, you can break the class abstraction to save a couple of dereferences - if you want to.

Similar shortcuts exist for strings, (native and same) and floating point numbers.

Implementing VTABLE Interfaces

The master list of VTABLE interfaces can be found in src/vtable.tbl in the root directory of the Parrot source, with documentation in docs/pdds/pdd02_vtables.pod. A few of these are very important, for instance:

type
Return the enumeration value of your class.
name
Return a string containing your class name.
init
Initialization. Parrot makes exactly one call to either init or init_pmc at PMC construction time.
init_pmc
Alternative entry point for initialization that takes a PMC argument. Parrot makes exactly one call to either init or init_pmc at PMC construction time.NOTE: It is strongly suggested that init_pmc(PMCNULL) be equivalent to init().
is_equal
True if the passed-in PMC has the same value as you. For instance, a Perl integer and a Python integer could have the same value, but could not be the same thing as defined by is_same.
clone
Copy your data and state into the passed-in destination PMC.

Others are methods you may or may not need, depending on your type:

morph
Turn yourself into the specified type.
destroy
Do any data shut-down and finalization you need to do. To have this method called, you must set the Pobj_active_destroy_FLAG.
get_integer
Return an integer representation of yourself.
get_number
Return a floating-point representation of yourself.
get_string
Return a string representation of yourself (a STRING* object), this should be a copy of whatever string you are holding, not just a pointer to your own string so that anything that calls this method can happily modify this value without making a mess of your guts.
get_bool
Return a boolean representation of yourself.
get_value
Return your private data as a raw pointer.
is_same
True if the passed-in PMC refers to exactly the same data as you. (Contrast is_equal)
set_integer
Set yourself to the passed-in integer value. This is an integer multimethod.
set_number
Set yourself to the passed-in float value. This is a floating-point multimethod.
set_string
Set yourself to the passed-in string. This is a string multimethod.
add
Fetch the number part of value and add your numeric value to it, storing the result in dest. (Probably by calling its set_integer or set_number method) This is a numeric multimethod.
subtract
Fetch the number part of value and subtract your numeric value from it, storing the result in dest. (Probably by calling its set_integer or set_number method) This is a numeric multimethod.
multiply
divide
modulus
You get the picture.
concatenate
Fetch the string part of value and concatenate it to yourself, storing the result in dest. (Probably by calling its set_string method) This is a string multimethod.
logical_or
logical_and
Perform the given short-circuiting logical operations between your boolean value and the value passed in, storing the result in dest.
logical_not
Set yourself to be a logical negation of the value passed in.
repeat
Repeat your string representation value times and store the result in dest.

If any method doesn't fit into your class, just don't implement it and don't provide an empty function body. The default class, which all classes inherit from will throw an exception if the missing method ever gets called.

If your class is a modification of an existing class, you may wish to use inheritance. At the beginning of your VTABLE specification in src/pmc/YOURCLASS.pmc, add the extends SUPERCLASS phrase. For example:

  pmclass PackedArray extends Array { ...

See the POD documentation in tools/build/pmc2c.pl for a list of useful keywords that you may use in the .pmc file.