Variables
Parrot is a register-based virtual machine. It has four typed register sets -- integers, floating-point numbers, strings, and objects. All variables in PIR are one of these four types. When you work with register variables or named variables, you're actually working directly with register storage locations in the virtual machine.
If you've ever worked with an assembly language before,
you may immediately jump to the conclusion that $I0
is the zeroth integer register in the register set,
but Parrot is a bit smarter than that.
The number of a register variable does not necessarily correspond to the register used internally; Parrot's compiler maps registers as appropriate for speed and memory considerations.
The only guarantee Parrot gives you is that you'll always get the same storage location when you use $I0
in the same subroutine.
Assignment
The most basic operation on a variable is assignment using the =
operator:
$I0 = 42 # set integer variable to the value 42 $N3 = 3.14159 # set number variable to approximation of pi $I1 = $I0 # set $I1 to the value of $I0
The null
opcode sets an integer or number variable to a zero value,
and undefines a string or object.
null $I0 # 0 null $N0 # 0.0 null $S0 # NULL null $P0 # PMCNULL
Working with Numbers
PIR has an extensive set of instructions that work with integers, floating-point numbers, and numeric PMCs. Many of these instructions have a variant that modifies the result in place:
$I0 = $I1 + $I2 $I0 += $I1
The first form of +
stores the sum of the two arguments in the result variable,
$I0
.
The second variant,
+=
,
adds the single argument to $I0
and stores the sum back in $I0
.
The arguments can be Parrot literals,
variables,
or constants.
If the result is an integer type,
like $I0
,
the arguments must also be integers.
A number result,
like $N0
,
usually requires number arguments,
but many numeric instructions also allow the final argument to be an integer.
Instructions with a PMC result may accept an integer,
floating-point,
or PMC final argument:
$P0 = $P1 * $P2 $P0 = $P1 * $I2 $P0 = $P1 * $N2 $P0 *= $P1 $P0 *= $I1 $P0 *= $N1
Unary numeric opcodes
Unary opcodes have a single argument.
They either return a result or modify the argument in place.
Some of the most common unary numeric opcodes are inc
(increment),
dec
(decrement),
abs
(absolute value),
neg
(negate):
$N0 = abs -5.0 # the absolute value of -5.0 is 5.0 $I0 = 120 inc $I1 # 120 incremented by 1 is 121
Binary numeric opcodes
Binary opcodes have two arguments and a result.
Parrot provides addition (+
or add
),
subtraction (-
or sub
),
multiplication (*
or mul
),
division (/
or div
),
modulus (%
or mod
),
and exponent (pow
) opcodes,
as well as gcd
(greatest common divisor) and lcm
(least common multiple).
$I0 = 12 / 5 $I0 = 12 % 5
Floating-point operations
The most common floating-point operations are ln
(natural log),
log2
(log base 2),
log10
(log base 10),
and exp
(ex),
as well as a full set of trigonometric opcodes such as sin
(sine),
cos
(cosine),
tan
(tangent),
sec
(secant),
sinh
(hyperbolic sine),
cosh
(hyperbolic cosine),
tanh
(hyperbolic tangent),
sech
(hyperbolic secant),
asin
(arc sine),
acos
(arc cosine),
atan
(arc tangent),
asec
(arc secant),
exsec
(exsecant),
hav
(haversine),
and vers
(versine).
All angle arguments for the trigonometric opcodes are in radians:
.loadlib 'trans_ops' # ... $N0 = sin $N1 $N0 = exp 2
The majority of the floating-point operations have a single argument and a single result. The arguments can generally be either an integer or number, but many of these opcodes require the result to be a number.
Logical and Bitwise Operations
The logical opcodes evaluate the truth of their arguments.
They are most useful to make decisions for control flow.
Integers and numeric PMCs are false if they're 0 and true otherwise.
Strings are false if they're the empty string or a single character "0",
and true otherwise.
PMCs are true when their get_bool
vtable function returns a nonzero value.
The and
opcode returns the first argument if it's false and the second argument otherwise:
$I0 = and 0, 1 # returns 0 $I0 = and 1, 2 # returns 2
The or
opcode returns the first argument if it's true and the second argument otherwise:
.loadlib 'bit_ops' # ... $I0 = or 1, 0 # returns 1 $I0 = or 0, 2 # returns 2 $P0 = or $P1, $P2
Both and
and or
are short-circuiting ops.
If they can determine what value to return from the first argument,
they'll never evaluate the second.
This is significant only for PMCs,
as they might have side effects on evaluation.
The xor
opcode returns the first argument if it is the only true value,
returns the second argument if it is the only true value,
and returns false if both values are true or both are false:
$I0 = xor 1, 0 # returns 1 $I0 = xor 0, 1 # returns 1 $I0 = xor 1, 1 # returns 0 $I0 = xor 0, 0 # returns 0
The not
opcode returns a true value when the argument is false and a false value if the argument is true:
$I0 = not $I1 $P0 = not $P1
The bitwise opcodes operate on their values a single bit at a time.
band
,
bor
,
and bxor
return a value that is the logical AND,
OR,
or XOR of each bit in the source arguments.
They each take two arguments.
.loadlib 'bit_ops' # ... $I0 = bor $I1, $I2 $P0 = bxor $P1, $I2
band
,
bor
,
and bxor
also have variants that modify the result in place.
.loadlib 'bit_ops' # ... $I0 = band $I1 $P0 = bor $P1
bnot
is the logical NOT of each bit in the source argument.
.loadlib 'bit_ops' # ... $I0 = bnot $I1
The logical and arithmetic shift operations shift their values by a specified number of bits:
.loadlib 'bit_ops' # ... $I0 = shl $I1, $I2 # shift $I1 left by count $I2 $I0 = shr $I1, $I2 # arithmetic shift right $P0 = lsr $P1, $P2 # logical shift right
Working with Strings
Parrot strings are buffers of variable-sized data. The most common use of strings is to store text data. Strings can also hold binary or other non-textual data, though this is rare.In general, a custom PMC is more useful. Parrot strings are flexible and powerful, to handle the complexity of human-readable (and computer-representable) text data. String operations work with string literals, variables, and constants, and with string-like PMCs.
Escape Sequences
Strings in double-quotes allow escape sequences using backslashes. Strings in single-quotes only allow escapes for nested quotes:
$S0 = "This string is \n on two lines" $S0 = 'This is a \n one-line string with a slash in it'
Table 4.1 shows the escape sequences Parrot supports in double-quoted strings.
Heredocs
If you need more flexibility in defining a string, use a heredoc string literal. The <<
operator starts a heredoc. The string terminator immediately follows. All text until the terminator is part of the string. The terminator must appear on its own line, must appear at the beginning of the line, and may not have any trailing whitespace.
$S2 = <<"End_Token" This is a multi-line string literal. Notice that it doesn't use quotation marks. End_Token
Concatenating strings
Use the .
operator to concatenate strings. The following example concatenates the string "cd" onto the string "ab" and stores the result in $S1
.
$S0 = "ab" $S1 = $S0 . "cd" # concatenates $S0 with "cd" say $S1 # prints "abcd"
Concatenation has a .=
variant to modify the result in place. In the next example, the .=
operation appends "xy" onto the string "abcd" in $S1
.
$S1 .= "xy" # appends "xy" to $S1 say $S1 # prints "abcdxy"
Repeating strings
The repeat
opcode repeats a string a specified number of times:
$S0 = "a" $S1 = repeat $S0, 5 say $S1 # prints "aaaaa"
In this example, repeat
generates a new string with "a" repeated five times and stores it in $S1
.
Length of a string
The length
opcode returns the length of a string in characters. This won't be the same as the length in bytes for multibyte encoded strings:
$S0 = "abcd" $I0 = length $S0 # the length is 4 say $I0
length
has no equivalent for PMC strings.
Substrings
The simplest version of the substr
opcode takes three arguments: a source string, an offset position, and a length. It returns a substring of the original string, starting from the offset position (0 is the first character) and spanning the length:
$S0 = substr "abcde", 1, 2 # $S0 is "bc"
This example extracts a two-character string from "abcde" at a one-character offset from the beginning of the string (starting with the second character). It generates a new string, "bc", in the destination register $S0
.
When the offset position is negative, it counts backward from the end of the string. Thus an offset of -1 starts at the last character of the string.
substr
no longer has a four-argument form, as in-place string operations have been removed. There is a replace
operator which will perform the replacement and return a new_string without modifying the old_string. The arguments are new_string, old_string, offset, count and replacement_string. The old_string is copied to the new_string with the replacement_string inserted from offset replacing the content for count characters.
This example replaces the substring "bc" in $S1
with the string "XYZ", and returns "aXYZde" in $S0
, $S1
is not changed:
$S1 = "abcde" $S0 = replace $S1, 1, 2, "XYZ" say $S0 # prints "aXYZde" say $S1 # prints "abcde"
When the offset position in a replace
is one character beyond the original string length, replace
appends the replacement string just like the concatenation operator. If the replacement string is an empty string, the opcode removes the characters from the original string in the new string.
$S1 = "abcde" $S1 = replace $S1, 1, 2, "XYZ" say $S1 # prints "aXYZde"
Converting characters
The chr
opcode takes an integer value and returns the corresponding character in the ASCII character set as a one-character string. The ord
opcode takes a single character string and returns the integer value of the character at the first position in the string. The integer value of the character will differ depending on the current encoding of the string:
$S0 = chr 65 # $S0 is "A" $I0 = ord $S0 # $I0 is 65, if $S0 is ASCII/UTF-8
ord
has a two-argument variant that takes a character offset to select a single character from a multicharacter string. The offset must be within the length of the string:
$I0 = ord "ABC", 2 # $I0 is 67
A negative offset counts backward from the end of the string, so -1 is the last character.
$I0 = ord "ABC", -1 # $I0 is 67
Formatting strings
The sprintf
opcode generates a formatted string from a series of values. It takes two arguments: a string specifying the format, and an array PMC containing the values to be formatted. The format string and the result can be either strings or PMCs:
$S0 = sprintf $S1, $P2 $P0 = sprintf $P1, $P2
The format string is similar to C's sprintf
function with extensions for Parrot data types. Each format field in the string starts with a %
and ends with a character specifying the output format. Table 4.2 lists the available output format characters.
Each format field supports several specifier options: flags, width, precision, and size. Table 4.3 lists the format flags.
The width is a number defining the minimum width of the output from a field. The precision is the maximum width for strings or integers, and the number of decimal places for floating-point fields. If either width or precision is an asterisk (*
), it takes its value from the next argument in the PMC.
The size modifier defines the type of the argument the field takes. Table 4.4 lists the size flags. The values in the aggregate PMC must have a type compatible with the specified size.
$S0 = sprintf "int %#Px num %+2.3Pf\n", $P2 say $S0 # prints "int 0x2a num +10.000"
The format string of this sprintf
example has two format fields. The first, %#Px
, extracts a PMC argument (P
) from the aggregate $P2
and formats it as a hexadecimal integer (x
) with a leading 0x (#
). The second format field, %+2.3Pf
, takes a PMC argument (P
) and formats it as a floating-point number (f
) with a minimum of two whole digits and a maximum of three decimal places (2.3
) and a leading sign (+
).
The test files t/op/string.t and t/op/sprintf.t have many more examples of format strings.
Joining strings
The join
opcode joins the elements of an array PMC into a single string. The first argument separates the individual elements of the PMC in the final string result.
$P0 = new "ResizablePMCArray" push $P0, "hi" push $P0, 0 push $P0, 1 push $P0, 0 push $P0, "parrot" $S0 = join "__", $P0 say $S0 # prints "hi__0__1__0__parrot"
This example builds a Array
in $P0
with the values "hi"
, 0
, 1
, 0
, and "parrot"
. It then joins those values (separated by the string "__"
) into a single string stored in $S0
.
Splitting strings
Splitting a string yields a new array containing the resulting substrings of the original string.
This example splits the string "abc" into individual characters and stores them in an array in $P0
. It then prints out the first and third elements of the array.
$P0 = split "", "abc" $P1 = $P0[0] say $P1 # 'a' $P1 = $P0[2] say $P1 # 'c'
Testing for substrings
The index
opcode searches for a substring within a string. If it finds the substring, it returns the position where the substring was found as a character offset from the beginning of the string. If it fails to find the substring, it returns -1:
$I0 = index "Beeblebrox", "eb" say $I0 # prints 2 $I0 = index "Beeblebrox", "Ford" say $I0 # prints -1
index
also has a three-argument version, where the final argument defines an offset position for starting the search.
$I0 = index "Beeblebrox", "eb", 3 say $I0 # prints 5
This example finds the second "eb" in "Beeblebrox" instead of the first, because the search skips the first three characters in the string.
Bitwise Operations
The numeric bitwise opcodes also have string variants for AND, OR, and XOR: bors
, bands
, and bxors
. These take string or string-like PMC arguments and perform the logical operation on each byte of the strings to produce the result string. Remember that in-place string operations are no longer available.
.loadlib 'bit_ops' # ... $P0 = bors $P1 $P0 = bands $P1 $S0 = bors $S1, $S2 $P0 = bxors $P1, $S2
The bitwise string opcodes produce meaningful results only when used with simple ASCII strings, because Parrot performs bitwise operations per byte.
Copy-On-Write
Strings use copy-on-write (COW) optimizations. A call to $S1 = $S0
doesn't immediately make a copy of $S0
, it only makes both variables point to the same string. Parrot doesn't make a copy of the string until one of two strings is modified.
$S0 = "Ford" $S1 = $S0 $S1 = "Zaphod" say $S0 # prints "Ford" say $S1 # prints "Zaphod"
Modifying one of the two variables causes Parrot to create a new string. This example preserves the existing value in $S0
and assigns the new value to the new string in $S1
. The benefit of copy-on-write is avoiding the cost of copying strings until the copies are necessary.
Encodings and Charsets
Years ago, strings only needed to support the ASCII character set (or charset), a mapping of 128 bit patterns to symbols and English-language characters. This worked as long as everyone using a computer read and wrote English and only used a small handful of punctuation symbols. In other words, it was woefully insufficient. A modern string system must manage charsets in order to make sense out of all the string data in the world. A modern string system must also handle different encodings -- ways to represent various charsets in memory and on disk.
Every string in Parrot has an associated encoding and character set. The default charset is 8-bit ASCII, which is almost universally supported. Double-quoted string constants can have an optional prefix specifying the string's encoding and charset.As you might suspect, single-quoted strings do not support this. Parrot tracks information about encoding and charset internally, and automatically converts strings when necessary to preserve these characteristics. Strings constants may have prefixes of the form encoding:charset:
.
$S0 = utf8:unicode:"Hello UTF-8 Unicode World!" $S1 = utf16:unicode:"Hello UTF-16 Unicode World!" $S2 = ascii:"This is 8-bit ASCII" $S3 = binary:"This is raw, unformatted binary data"
Parrot supports the character sets ascii
, binary
, iso-8859-1
(Latin 1), and unicode
and the encodings fixed_8
, ucs2
, utf8
, and utf16
.
The binary
charset treats the string as a buffer of raw unformatted binary data. It isn't really a string per se, because binary data contains no readable characters. This exists to support libraries which manipulate binary data that doesn't easily fit into any other primitive data type.
When Parrot operates on two strings (as in concatenation or comparison), they must both use the same character set and encoding. Parrot will automatically upgrade one or both of the strings to the next highest compatible format as necessary. ASCII strings will automatically upgrade to UTF-8 strings if needed, and UTF-8 will upgrade to UTF-16. All of these conversions happen inside Parrot, so the programmer doesn't need to worry about the details.
Working with PMCs
Polymorphic Containers (PMCs) are the basis for complex data types and object-oriented behavior in Parrot. In PIR, any variable that isn't a low-level integer, number, or string is a PMC. PMC variables act much like the low-level variables, but you have to instantiate a new PMC object before you use it. The new
opcode creates a new PMC object of the specified type.
$P0 = new 'String' $P0 = "That's a bollard and not a parrot" say $P0
This example creates a String
object, stores it in the PMC register variable $P0
, assigns it the value "That's a bollard and not a parrot", and prints it.
Every PMC has a type that indicates what data it can store and what behavior it supports. The typeof
opcode reports the type of a PMC. When the result is a string variable, typeof
returns the name of the type:
$P0 = new "String" $S0 = typeof $P0 # $S0 is "String" say $S0 # prints "String"
When the result is a PMC variable, typeof
returns the Class
PMC for that object type.
Scalars
In most of the examples shown so far, PMCs duplicate the behavior of integers, numbers, and strings. Parrot provides a set of PMCs for this exact purpose. Integer
, Float
, and String
are thin overlays on Parrot's low-level integers, numbers, and strings.
A previous example showed a string literal assigned to a PMC variable of type String
. Direct assignment of a literal to a PMC works for all the low-level types and their PMC equivalents:
$P0 = new 'Integer' $P0 = 5 $P1 = new 'String' $P1 = "5 birds" $P2 = new 'Float' $P2 = 3.14
You may also assign non-constant low-level integer, number, or string registers directly to a PMC. The PMC handles the conversion from the low-level type to its own internal storage.This conversion of a simpler type to a more complex type is "boxing".
$I0 = 5 $P0 = new 'Integer' $P0 = $I0 $S1 = "5 birds" $P1 = new 'String' $P1 = $S1 $N2 = 3.14 $P2 = new 'Float' $P2 = $N2
The box
opcode is a handy shortcut to create the appropriate PMC object from an integer, number, or string literal or variable.
$P0 = box 3 # $P0 is an "Integer" $P1 = box $S1 # $P1 is a "String" $P2 = box 3.14 # $P2 is a "Float"
In the reverse situation, when assigning a PMC to an integer, number, or string variable, the PMC also has the ability to convert its value to the low-level type.The reverse of "boxing" is "unboxing".
$P0 = box 5 $S0 = $P0 # the string "5" $N0 = $P0 # the number 5.0 $I0 = $P0 # the integer 5 $P1 = box "5 birds" $S1 = $P1 # the string "5 birds" $I1 = $P1 # the integer 5 $N1 = $P1 # the number 5.0 $P2 = box 3.14 $S2 = $P2 # the string "3.14" $I2 = $P2 # the integer 3 $N2 = $P2 # the number 3.14
This example creates Integer
, Float
, and String
PMCs, and shows the effect of assigning each one back to a low-level type.
Converting a string to an integer or number only makes sense when the contents of the string are a number. The String
PMC will attempt to extract a number from the beginning of the string, but otherwise will return a false value.
Aggregates
PMCs can define complex types that hold multiple values, commonly called aggregates. Two basic aggregate types are ordered arrays and associative arrays. The primary difference between these is that ordered arrays use integer keys for indexes and associative arrays use string keys.
Aggregate PMCs support the use of numeric or string keys. PIR also offers a extensive set of operations for manipulating aggregate data types.
Ordered Arrays
Parrot provides several ordered array PMCs, differentiated by whether the array should store booleans, integers, numbers, strings, or other PMCs, and whether the array should maintain a fixed size or dynamically resize for the number of elements it stores.
The core array types are FixedPMCArray
, ResizablePMCArray
, FixedIntegerArray
, ResizableIntegerArray
, FixedFloatArray
, ResizableFloatArray
, FixedStringArray
, ResizableStringArray
, FixedBooleanArray
, and ResizableBooleanArray
. The array types that start with "Fixed" have a fixed size and do not allow elements to be added outside their allocated size. The "Resizable" variants automatically extend themselves as more elements are added.With some additional overhead for checking array bounds and reallocating array memory. The array types that include "String", "Integer", or "Boolean" in the name use alternate packing methods for greater memory efficiency.
Parrot's core ordered array PMCs all have zero-based integer keys. Extracting or inserting an element into the array uses PIR's standard key syntax, with the key in square brackets after the variable name. An lvalue key sets the value for that key. An rvalue key extracts the value for that key in the aggregate to use as the argument value:
$P0 = new "ResizablePMCArray" # create a new array object $P0[0] = 10 # set first element to 10 $P0[1] = $I31 # set second element to $I31 $I0 = $P0[0] # get the first element
Setting the array to an integer value directly (without a key) sets the number of elements of the array. Assigning an array directly to an integer retrieves the number of elements of the array.
$P0 = 2 # set array size $I1 = $P0 # get array size
This is equivalent to using the elements
opcode to retrieve the number of items currently in an array:
elements $I0, $P0 # get element count
Some other useful instructions for working with ordered arrays are push
, pop
, shift
, and unshift
, to add or remove elements. push
and pop
work on the end of the array, the highest numbered index. shift
and unshift
work on the start of the array, adding or removing the zeroth element, and renumbering all the following elements.
push $P0, 'banana' # add to end $S0 = pop $P0 # fetch from end unshift $P0, 74 # add to start $I0 = shift $P0 # fetch from start
Associative Arrays
An associative array is an unordered aggregate that uses string keys to identify elements. You may know them as "hash tables", "hashes", "maps", or "dictionaries". Parrot provides one core associative array PMC, called Hash
. String keys work very much like integer keys. An lvalue key sets the value of an element, and an rvalue key extracts the value of an element. The string in the key must always be in single or double quotes.
new $P1, "Hash" # create a new associative array $P1["key"] = 10 # set key and value $I0 = $P1["key"] # get value for key
Assigning a Hash
PMC (without a key) to an integer result fetches the number of elements in the hash.You may not set a Hash
PMC directly to an integer value.
$I1 = $P1 # number of entries
The exists
opcode tests whether a keyed value exists in an aggregate. It returns 1 if it finds the key in the aggregate and 0 otherwise. It doesn't care if the value itself is true or false, only that an entry exists for that key:
new $P0, "Hash" $P0["key"] = 0 exists $I0, $P0["key"] # does a value exist at "key"? say $I0 # prints 1
The delete
opcode removes an element from an associative array:
delete $P0["key"]
Iterators
An iterator extracts values from an aggregate PMC one at a time. Iterators are most useful in loops which perform an action on every element in an aggregate. The iter
opcode creates a new iterator from an aggregate PMC. It takes one argument, the PMC over which to iterate:
$P1 = iter $P2
The shift
opcode extracts the next value from the iterator.
$P5 = shift $P1
Evaluating the iterator PMC as a boolean returns whether the iterator has reached the end of the aggregate:
if $P1 goto iter_repeat
Parrot provides predefined constants for working with iterators. .ITERATE_FROM_START
and .ITERATE_FROM_END
constants select whether an ordered array iterator starts from the beginning or end of the array. These two constants have no effect on associative array iterators, as their elements are unordered.
Load the iterator constants with the .include
directive to include the file iterator.pasm. To use them, set the iterator PMC to the value of the constant:
.include "iterator.pasm" # ... $P1 = .ITERATE_FROM_START
With all of those separate pieces in one place, this example loads the iterator constants, creates an ordered array of "a", "b", "c", creates an iterator from that array, and then loops over the iterator using a conditional goto
to checks the boolean value of the iterator and another unconditional goto
:
.include "iterator.pasm" $P2 = new "ResizablePMCArray" push $P2, "a" push $P2, "b" push $P2, "c" $P1 = iter $P2 $P1 = .ITERATE_FROM_START iter_loop: unless $P1 goto iter_end $P5 = shift $P1 say $P5 # prints "a", "b", "c" goto iter_loop iter_end:
Associative array iterators work similarly to ordered array iterators. When iterating over associative arrays, the shift
opcode extracts keys instead of values. The key looks up the value in the original hash PMC.
$P2 = new "Hash" $P2["a"] = 10 $P2["b"] = 20 $P2["c"] = 30 $P1 = iter $P2 iter_loop: unless $P1 goto iter_end $S5 = shift $P1 # the key "a", "b", or "c" $I9 = $P2[$S5] # the value 10, 20, or 30 say $I9 goto iter_loop iter_end:
This example creates an associative array $P2
that contains three keys "a", "b", and "c", assigning them the values 10, 20, and 30. It creates an iterator ($P1
) from the associative array using the iter
opcode, and then starts a loop over the iterator. At the start of each loop, the unless
instruction checks whether the iterator has any more elements. If there are no more elements, goto
jumps to the end of the loop, marked by the label iter_end
. If there are more elements, the shift
opcode extracts the next key. Keyed assignment stores the integer value of the element indexed by the key in $I9
. After printing the integer value, goto
jumps back to the start of the loop, marked by iter_loop
.
Multi-level Keys
Aggregates can hold any data type, including other aggregates. Accessing elements deep within nested data structures is a common operation, so PIR provides a way to do it in a single instruction. Complex keys specify a series of nested data structures, with each individual key separated by a semicolon.
$P0 = new "Hash" $P1 = new "ResizablePMCArray" $P1[2] = 42 $P0["answer"] = $P1 $I1 = 2 $I0 = $P0["answer";$I1] say $I0
This example builds up a data structure of an associative array containing an ordered array. The complex key ["answer"; $I1]
retrieves an element of the array within the hash. You can also set a value using a complex key:
$P0["answer";0] = 5
The individual keys are integer or string literals, or variables with integer or string values.
Copying and Cloning
PMC registers don't directly store the data for a PMC, they only store a pointer to the structure that stores the data. As a result, the =
operator doesn't copy the entire PMC, it only copies the pointer to the PMC data. If you later modify the copy of the variable, it will also modify the original.
$P0 = new "String" $P0 = "Ford" $P1 = $P0 $P1 = "Zaphod" say $P0 # prints "Zaphod" say $P1 # prints "Zaphod"
In this example, $P0
and $P1
are both pointers to the same internal data structure. Setting $P1
to the string literal "Zaphod", it overwrites the previous value "Ford". Both $P0
and $P1
refer to the String
PMC "Zaphod".
The clone
opcode makes a deep copy of a PMC, instead of copying the pointer like =
does.
$P0 = new "String" $P0 = "Ford" $P1 = clone $P0 $P0 = "Zaphod" say $P0 # prints "Zaphod" say $P1 # prints "Ford"
This example creates an identical, independent clone of the PMC in $P0
and puts it in $P1
. Later changes to $P0
have no effect on the PMC in $P1
.With low-level strings, the copies created by clone
are copy-on-write exactly the same as the copy created by =
.
To assign the value of one PMC to another PMC that already exists, use the assign
opcode:
$P0 = new "Integer" $P1 = new "Integer" $P0 = 42 assign $P1, $P0 # note: $P1 must exist already inc $P0 say $P0 # prints 43 say $P1 # prints 42
This example creates two Integer
PMCs, $P1
and $P2
, and gives the first one the value 42. It then uses assign
to pass the same integer value on to $P1
. Though $P0
increments, $P1
doesn't change. The result for assign
must have an existing object of the right type in it, because assign
neither creates a new duplicate object (as does clone
) or reuses the source object (as does =
).
Properties
PMCs can have additional values attached to them as "properties" of the PMC. Most properties hold extra metadata about the PMC.
The setprop
opcode sets the value of a named property on a PMC. It takes three arguments: the PMC on which to set a property, the name of the property, and a PMC containing the value of the property.
setprop $P0, "name", $P1
The getprop
opcode returns the value of a property. It takes two arguments: the name of the property and the PMC from which to retrieve the property value.
$P2 = getprop "name", $P0
This example creates a String
object in $P0
and an Integer
object with the value 1 in $P1
. setprop
sets a property named "eric" on the object in $P0
and gives the property the value of $P1
. getprop
retrieves the value of the property "eric" on $P0
and stores it in $P2
.
$P0 = new "String" $P0 = "Half-a-Bee" $P1 = new "Integer" $P1 = 1 setprop $P0, "eric", $P1 # set a property on $P0 $P2 = getprop "eric", $P0 # retrieve a property from $P0 say $P2 # prints 1
Parrot stores PMC properties in an associative array where the name of the property is the key.
delprop
deletes a property from a PMC.
delprop $P1, "constant" # delete property
You can fetch a complete hash of all properties on a PMC with prophash
:
$P0 = prophash $P1 # set $P0 to the property hash of $P1
Fetching the value of a non-existent property returns an Undef
PMC.
Vtable Functions
You may have noticed that a simple operation sometimes has a different effect on different PMCs. Assigning a low-level integer value to a Integer
PMC sets its integer value of the PMC, but assigning that same integer to an ordered array sets the size of the array.
Every PMC defines a standard set of low-level operations called vtable functions. When you perform an assignment like:
$P0 = 5
... Parrot calls the set_integer_native
vtable function on the PMC referred to by register $P0
.
Parrot has a fixed set of vtable functions, so that any PMC can stand in for any other PMC; they're polymorphic.Hence the name "Polymorphic Container". Every PMC defines some behavior for every vtable function. The default behavior is to throw an exception reporting that the PMC doesn't implement that vtable function. The full set of vtable functions for a PMC defines the PMC's basic interface, but PMCs may also define methods to extend their behavior beyond the vtable set.
Namespaces
Parrot performs operations on variables stored in small register sets local to each subroutine. For more complex tasks,...and for most high-level languages that Parrot supports. it's also useful to have variables that live beyond the scope of a single subroutine. These variables may be global to the entire program or restricted to a particular library. Parrot stores long-lived variables in a hierarchy of namespaces.
The opcodes set_global
and get_global
store and fetch a variable in a namespace:
$P0 = new "String" $P0 = "buzz, buzz" set_global "bee", $P0 # ... $P1 = get_global "bee" say $P1 # prints "buzz, buzz"
The first two statements in this example create a String
PMC in $P0
and assign it a value. In the third statement, set_global
stores that PMC as the named global variable bee
. At some later point in the program, get_global
retrieves the global variable by name, and stores it in $P1
to print.
Namespaces can only store PMC variables. Parrot boxes all primitive integer, number, or string values into the corresponding PMCs before storing them in a namespace.
The name of every variable stored in a particular namespace must be unique. You can't have store both an Integer
PMC and an array PMC both named "bee", stored in the same namespace.You may wonder why anyone would want to do this. We wonder the same thing, but Perl 5 does it all the time. The Perl 6 implementation on Parrot includes type sigils in the names of the variables it stores in namespaces so each name is unique, e.g. $bee
, @bee
....
Namespace Hierarchy
A single global namespace would be far too limiting for most languages or applications. The risk of accidental collisions -- where two libraries try to use the same name for some variable -- would be quite high for larger code bases. Parrot maintains a collection of namespaces arranged as a tree, with the parrot
namespace as the root. Every namespace you declare is a child of the parrot
namespace (or a child of a child....).
The set_global
and get_global
opcodes both have alternate forms that take a key name to access a variable in a particular namespace within the tree. This code example stores a variable as bill
in the Duck namespace and retrieves it again:
set_global ["Duck"], "bill", $P0 $P1 = get_global ["Duck"], "bill"
The key name for the namespace can have multiple levels, which correspond to levels in the namespace hierarchy. This example stores a variable as bill
in the Electric namespace under the General namespace in the hierarchy.
set_global ["General";"Electric"], "bill", $P0 $P1 = get_global ["General";"Electric"], "bill"
The set_global
and get_global
opcode operate on the currently selected namespace. The default top-level namespace is the "root" namespace. The .namespace
directive allows you to declare any namespace for subsequent code. If you select the General Electric namespace, then store or retrieve the bill
variable without specifying a namespace, you will work with the General Electric bill, not the Duck bill.
.namespace ["General";"Electric"] #... set_global "bill", $P0 $P1 = get_global "bill"
Passing an empty key to the .namespace
directive resets the selected namespace to the root namespace. The brackets are required even when the key is empty.
.namespace [ ]
When you need to be absolutely sure you're working with the root namespace regardless of what namespace is currently active, use the set_root_global
and get_root_global
opcodes instead of set_global
and get_global
. This example sets and retrieves the variable bill
in the Dollar namespace, which is directly under the root namespace:
set_root_global ["Dollar"], "bill", $P0 $P1 = get_root_global ["Dollar"], "bill"
To prevent further collisions, each high-level language running on Parrot operates within its own virtual namespace root. The default virtual root is parrot
, and the .HLL
directive (for High-Level Language) selects an alternate virtual root for a particular high-level language:
.HLL 'ruby'
The set_hll_global
and get_hll_global
opcodes are like set_root_global
and get_root_global
, except they always operate on the virtual root for the currently selected HLL. This example stores and retrieves a bill
variable in the Euro namespace, under the Dutch HLL namespace root:
.HLL 'Dutch' #... set_hll_global ["Euro"], "bill", $P0 $P1 = get_hll_global ["Euro"], "bill"
NameSpace PMC
Namespaces are just PMCs. They implement the standard vtable functions and a few extra methods. The get_namespace
opcode retrieves the currently selected namespace as a PMC object:
$P0 = get_namespace
The get_root_namespace
opcode retrieves the namespace object for the root namespace. The get_hll_namespace
opcode retrieves the virtual root for the currently selected HLL.
$P0 = get_root_namespace $P0 = get_hll_namespace
Each of these three opcodes can take a key argument to retrieve a namespace under the currenly selected namespace, root namespace, or HLL root namespace:
$P0 = get_namespace ["Duck"] $P0 = get_root_namespace ["General";"Electric"] $P0 = get_hll_namespace ["Euro"]
Once you have a namespace object you can use it to retrieve variables from the namespace instead of using a keyed lookup. This example first looks up the Euro namespace in the currently selected HLL, then retrieves the bill
variable from that namespace:
$P0 = get_hll_namespace ["Euro"] $P1 = get_global $P0, "bill"
Namespaces also provide a set of methods to provide more complex behavior than the standard vtable functions allow. The get_name
method returns the name of the namespace as a ResizableStringArray
:
$P3 = $P0.'get_name'()
The get_parent
method retrieves a namespace object for the parent namespace that contains this one:
$P5 = $P0.'get_parent'()
The get_class
method retrieves any Class PMC associated with the namespace:
$P6 = $P0.'get_class'()
The add_var
and find_var
methods store and retrieve variables in a namespace in a language-neutral way:
$P0.'add_var'("bee", $P3) $P1 = $P0.'find_var'("bee")
The find_namespace
method looks up a namespace, just like the get_namespace
opcode:
$P1 = $P0.'find_namespace'("Duck")
The add_namespace
method adds a new namespace as a child of the namespace object:
$P0.'add_namespace'($P1)
The make_namespace
method looks up a namespace as a child of the namespace object and returns it. If the requested namespace doesn't exist, make_namespace
creates a new one and adds it under that name:
$P1 = $P0.'make_namespace'("Duck")
Aliasing
Just like regular assignment, the various operations to store a variable in a namespace only store a pointer to the PMC. If you modify the local PMC after storing in a namespace, those changes will also appear in the stored global. To store a true copy of the PMC, clone
it before you store it.
Leaving the global variable as an alias for a local variable has its advantages. If you retrieve a stored global into a register and modify it:
$P1 = get_global "feather" inc $P1
... you modify the value of the stored global, so you don't need to call set_global
again.