Dynamic C-level Objects
PMCs are one of the four fundamental data types in Parrot, and definitely one of the most useful. A PMC can contain a single scalar value (integer, floating point number, string), an array of values, a subroutine, a namespace, or an entire list of other data types as well. PMCs are the basis for all higher order data types in Parrot, and are flexible enough to be used for any purpose that an HLL needs.
All the common PMC types are included in the Parrot repository and built directly into libparrot and the parrot executable. However, the system is not rigid; new PMC types can be defined externally and loaded into Parrot at a later time. In this way, HLLs and libraries and applications can add new data types to Parrot at the C code level, which helps to ensure speed and efficiency. PMCs loaded this way are called Dynamic PMCs or dynpmcs.
PIR Classes
It's worth a quick diversion here to talk about the difference between a pure PIR class object, and a PMC. Even though classes written in PIR can inherit from an existing PMC type, they aren't all their own type of PMC. In fact, classes written in PIR are all of the Object PMC type. In order to add a new fundamental PMC type to Parrot, it needs to be written in C well, a superset of C anyway and it needs to be compiled using the PMC compiler.
Writing PMCs
In the strictest sense, PMCs are written in C and are compiled by your local C compiler into machine code for linking with libparrot or the parrot executable. However, Parrot's build process makes use of a special PMC compiler program that converts PMCs defined in a special C-like script to ordinary C code. The PMC compiler adds all the necessary boiler plate code and installs the PMC type into Parrot for use. The PMC script is like a macro superset although the functionality is a little bit more involved then is available in the normal C preprocessor over the C language. All valid C is valid in PMC scripts, but there are also a few additions that help to make common tasks a little easier.
The PMC script was born of conflicting necessities. The internals of Parrot are all written according to the ISO C89 standard for maximum portability. However, PIR and languages that are built on top of Parrot are typically object-oriented (or have some OO capabilities). PMCs are like classes, they have data and methods, and they can inherit from parent PMCs.
C is low-level and portable, which is desirable. But Parrot needed some support for OO features that C doesn't have, and the C preprocessor can't support directly. To support the necessary features, and to make the task of writing PMCs just a little bit easier, the Parrot developers created a PMC compiler program that takes a special PMC script and converts it into standard ISO C89.
PMC Files
PMC files have a .pmc
file extension.
They're written in a C-like language with a few additions to help with creating PMCs.
PMC files do not natively allow POD documentation,
so all such documentation must be enclosed in /* */
comments.
All PMC files that ship with Parrot include significant file-level and function-level documentation to help explain how the PMCs operate.
pmclass
Definitions
A PMC file can contain a single PMC class definition and any other helper functions or data structure definitions that are needed to support the PMC.
To define a PMC class in the PMC file,
you use the pmclass
statement.
Everything outside the pmclass
definition will be ignored by the PMC compiler and passed through verbatim into the generated .c
file.
Inside the pmclass
definition are going to be all the VTABLE and METHOD declarations for the PMC.
A standard definition can contain a number of parts. Here's a pseudo-grammar for them:
pmclass CLASSNAME [extends PARENT]? [provides INTERFACE] [FLAGS]* { /* Attributes defined here */ /* VTABLE and METHODs defined here. */ }
The extends
keyword is optional, but allows us to specify that this PMC class is a subtype of the given type. If we have an extends
in the definition, we can use the SUPER
keyword throughout the PMC file to refer to the parent type.
The FLAGS
are a series of flags that can be specified to determine how the PMC behaves and how it's constructed internally. The need_ext
flag assigns a special PMC_EXT
data structure to the PMC structure internally. PMC_EXT
is necessary to handle data sharing between threads or interpreters, storing attributes in the PMC, and a few other uses as well. The singleton
flag means that there can only be one instantiated object of this class. The is_ro
and has_ro
flags indicate that the PMC class is read-only or that it contains read-only data, respectively. The is_shared
flag indicates that the PMC is intended to be shared between multiple interpreters, and therefore special synchronization logic should be applied. The abstract
flag indicates that the PMC class cannot be instantiated directly, but can be inherited from by a non-abstract PMC class.
The provides
keyword is used to show that the PMC provides certain standard interfaces. For instance, you can specify provides array
and then Parrot will enable us to write things like $P0[2]
in PIR code to access the PMC using integer indices. provides hash
means that we can use string and PMC keys to access values in the PMC. These provides
each correspond to a series of VTABLE interfaces that the PMC must provide, or must inherit. Without the necessary VTABLE interfaces available, Parrot may try to perform illegal operations and things will go badly. We'll talk about all the available provides
interfaces and the VTABLE interfaces that they must define.
Attributes
PMCs can be given a custom set of data field attributes using the ATTR
keyword. ATTR allows the PMC to be extended to contain custom data structures that are automatically managed by Parrot's memory subsystem. Here's an example:
pmclass Foo { ATTR INTVAL bar; ATTR PMC baz; ... }
The attributes are stored in a custom data structure that can be accessed using a macro with the same name as the PMC, but all upper-case:
Parrot_Foo_Attributes * attrs = PARROT_FOO(SELF); attrs->bar = 7; /* it's an INTVAL */ attrs->baz = pmc_new( ... ) /* it's a PMC */
Notice how the type name of the attributes structure is Parrot_
, followed by the name of the PMC with the same capitalization as is used in the pmclass
definition, followed by _Attributes
. The macro to return this structure is PARROT_
followed by the name of the PMC in all caps.
INTERP
, SUPER
and SELF
The PMC compiler enables us to use a few pre-defined variable names throughout the file to make things easier. The INTERP
keyword always contains a reference to the current interpreter structure. This keyword is included by default in all VTABLE interfaces and all PMC methods. It is not automatically included in any extra helper functions that you define in the PMC file.
Here's an example of a VTABLE interface function:
VTABLE Foo(INVAR PMC, INVAR INTVAL) { ... }
The PMC compiler will convert this to the following C function definition:
void Foo(PARROT_INTERP, PMC *self, PMC *arg_1, INTVAL arg_2) { ... }
The interp
and self
variables are provided in all VTABLE interfaces, even though you don't have to define them explicitly in the PMC file.
If the pmclass
definition uses the extends
keyword, a reference to a member of the parent class is also contained in the SUPER
variable. The SUPER()
function calls the VTABLE interface from the parent class.
VTABLE destroy() { SUPER(); /* Call the parent PMC's VTABLE */ }
The PMC compiler also allows the use of "method syntax" for the SELF
and SUPER
variables:
SUPER.clone() /* Call the clone VTABLE interface on SUPER */ SELF.destroy() /* Call the destroy VTABLE interface on SELF */
Or, you can call the VTABLE interfaces more directly:
VTABLE_clone(INTERP, SUPER) VTABLE_destroy(INTERP, SELF)
PMC Compiler
The PMC compiler is a small program written in Perl 5 that's part of the normal Parrot build process. It converts all .pmc
files to .c
files for final compilation. The long-term goal for Parrot is to not be dependent on Perl 5 for configuration and building, but for now Perl 5 is required when building Parrot.
VTABLE Function Definitions
VTABLE Functions Parameters
VTABLE functions are defined just like ordinary C functions, almost. Here's a normal definition for a VTABLE method:
VTABLE VTABLENAME (PARAMETERS) { /* ordinary C here, almost */ }
You can't just name your VTABLE functions anything you want. There is a predefined list of VTABLE function names, and you must name it exactly the same as the one you are trying to implement. The PARAMETERS list is pretty particular as well: Each VTABLE function type has a specific parameter list that must be implemented exactly or else the compiler will throw a warning.
Methods
VTABLES are standard, but they're rigid. They need to have the exact name that Parrot expects, and they need to have the exact function signature that Parrot expects too. VTABLES are responsible for the low-level basic access operations that all data types need to implement. However, to get more out of your PMCs, we need a more flexible want to interact with them.
Enter methods, which are ways to extend the functionality of your PMC in ways that the PMC needs. Methods allow the developer to add all sorts of arbitrary functionality to a PMC that the VTABLE functions themselves cannot define.