In yesterday’s installment (Testing FizzBuzz in Parrot), I explained a test file for testing multiple Parrot implementations of the FizzBuzz problem. I also promised to show two different ways to solve the problem in Parrot. The test framework requires both approaches to take a single integer describing how many FizzBuzz elements to produce and to return an Array-like PMC containing the FizzBuzz strings.
Pre-populating an Array
The most straightforward approach is to create a new Array PMC and fill
it in with the appropriate strings. Despite its procedural
naïveté, it’s also the most straightforward application of the
algorithm. It all takes place in the procedural function:
42 .sub 'procedural'
43 .param int end
The function starts as normal. It takes one parameter; an integer. Right
now the code assumes that it will start at position 0 in the created array
and loop until the integer counter reaches end.
45 .local pmc results
46 results = new .ResizableStringArray
47
48 .local int count
49 count = 1
50
51 .local int remainder
52 .local string element
Lines 45 through 52 declare a couple of interesting variables. I chose
to use a ResizableStringArray for ease of use. It would have
been just as easy to use a FixedStringArray, but I thought
showing the push code later would be clearer. Notice that
count, the loop counter, starts at 1. This is important to
avoid fencepost errors in the algorithm later.
I declared remainder and element outside of
the loop (though there’s no real sense of “outside” in PIR) out of personal
preference. I’m not aware of any real scoping issues in Parrot, except that
declarations must precede use.
54 loop_start:
55 if count > end goto loop_end
56 element = ''
This is the start of the loop. It tests the counter to see if the loop
has run end times. If so, it quits looping. Otherwise, it
initializes element to an empty string.
58 check_fizz:
59 remainder = count % 3
60 if remainder goto check_buzz
61 element .= 'Fizz'
The label in line 58 exists only for clarity. It’s not necessary for the
algorithm. Lines 59 checks that the current element number is a multiple of
three, using the modulus operator. Line 60 skips ahead if there’s a
remainder. Otherwise, line 61 appends the string Fizz to the
current value of element.
63 check_buzz:
64 remainder = count % 5
65 if remainder goto add_element
66 element .= 'Buzz'
No matter what’s in element, line 64 is the next line
executed. It checks that the number of the current element is a multiple of
five. If not, another goto skips line 66. Line 66 appends the
string Buzz to the current element.
I deliberately kept the structure of check_fizz and
check_buzz similar to make the code clearer. There are ways to
reduce this duplication–perhaps turning this code into a parametrized
function–but with only two occurrences, it wasn’t quite worth it.
68 add_element:
69 push results, element
70 inc count
71 goto loop_start
The loop ends by pushing element, which contains an empty
string, Fizz, Buzz, or FizzBuzz,
onto the results array. It increments the loop counter and
restarts the loop.
73 loop_end:
74 .return( results )
75 .end
When the loop ends, the function returns the entire array.
Besides the lack of control flow beyond goto LABEL, the
straightforward algorithm in PIR looks like that in almost any high level
language.
Creating a FizzBuzz Array PMC
My second approach was to subclass FixedStringArray and
override some of its vtable methods to generate the right data instead of
filling up an entire array all at once. This is similar to the
tie mechanism in Perl 5, except that it’s pervasive throughout
Parrot so that the implementation is much, much better.
The public function is keyed_array:
77 .sub 'keyed_array'
78 .param int count
79
80 .local pmc results
81 results = new [ 'FizzBuzz'; 'Array' ]
82 results = count
83
84 .return( results )
85 .end
That’s it; like procedural it takes a single integer
parameter, the number of elements requested.
Line 81 creates a new FizzBuzz;Array object. The strange
bracket syntax names a class by Key. A Key is a
hierarchical name, and it’s useful for nested namespaces.
Line 82 sets the number of elements in the array to count.
Parrot turns the act of assigning an integer to a PMC to a method call on
the PMC; the method is set_integer_native. This will be
important in a moment.
It’s important to show the implementation of
FizzBuzz;Array.
87 .namespace [ 'FizzBuzz'; 'Array' ]
88
89 .sub 'init' :init :load
90 .local pmc fs_array
91 .local pmc fb_array
92
93 fs_array = getclass 'FixedStringArray'
94 fb_array = subclass fs_array, [ 'FizzBuzz'; 'Array' ]
95
96 addattribute fb_array, 'size'
97 .end
Line 87 starts a new namespace for FizzBuzz;Array. Parrot
methods must belong to a namespace with the same name as an object’s
class.
Line 89 begins a compilation unit of initialization code. The presence
of both :init and :load is a bit of a mish-mash.
They execute at different times, depending on whether you invoke this code
directly or load it from another piece of code invoked directly. Using both
here hurts nothing; it supports both execution models I explained in my
previous post.
The most important lines are 93 and 94. Line 93 fetches the
Class PMC associated with the built-in
FixedStringArray. Line 94 creates a new class named
FizzBuzz;Array as a subclass of FixedStringArray.
fb_array then contains the new Class PMC.
Finally, line 96 adds an attribute–an instance variable–to the class,
named size. I’m sure you can imagine what that indicates.
99 .sub '__init' :method
100 .local pmc size
101 size = new .Integer
102
103 setattribute self, 'size', size
104 .end
Parrot classes need initializers. Parrot will call the
__init method when creating a new PMC. Lines 100 and 101
declare and create a new Integer PMC. Line 103 assigns that
PMC to the size attribute.
Why is it an Integer? Attributes can only contain PMCs, not
primitive types such as integers, floats, or strings.
Strictly speaking, I don’t need this initializer, but I find it’s helpful to create a PMC with all of the necessary attributes initialized correctly than to check for their existence whenever I want to access those attributes later.
106 .sub 'set_integer_native' :vtable :method
107 .param int size
108
109 .local pmc size_pmc
110 size_pmc = getattribute self, 'size'
111 size_pmc = size
112 .end
I mentioned earlier that assigning count to a
FizzBuzz;Array ultimately called the
set_integer_native vtable method. Because I decided not to
create a full array with all of the elements precalculated, I needed to
intercept this call and do something different.
The :vtable attribute tells Parrot that this subroutine
should override the parent’s vtable method of the same name. The
:method attribute tells Parrot that this subroutine expects
the invocant in a special register according to Parrot’s calling
conventions. It also makes that invocant available through the body of the
subroutine in the variable named self.
All vtable methods are methods, so, yes, :vtable
should imply :method. It currently doesn’t.
Lines 109 and 110 retrieve the size attribute PMC into a
variable. Line 111 assigns the size passed into this
subroutine to the PMC. Think of the PMC as a container for this integer
value.
It might be tempting to try to assign a primitive integer to the attribute, but that won’t work. As I said before, attributes must be PMCs. You must box a primitive value in a PMC to assign to an attribute slot.
114 .sub 'get_integer' :vtable :method
115 .local pmc size_pmc
116 size_pmc = getattribute self, 'size'
117
118 .local int size
119 size = size_pmc
120 .return( size )
121 .end
Most set_* methods imply a corresponding get_*
method. This get_integer vtable method looks at the
size attribute and returns its value. Parrot will call this
for code like:
.local int num_elements
num_elements = fb_array
That assumes that fb_array is a FizzBuzz;Array
object, of course.
123 .sub 'elements' :vtable :method
124 .local pmc size_pmc
125 size_pmc = getattribute self, 'size'
126
127 .local int size
128 size = size_pmc
129 .return( size )
130 .end
Array-like PMCs have another vtable method called elements.
There is currently no easy way in Parrot, to my knowledge, to use the same
Parrot subroutine for two different vtable methods. There will be.
Finally, the real work comes from accessing an element of the array.
132 .sub 'get_string_keyed' :vtable :method
133 .param int key
134 inc key
The vtable method called is either get_string_keyed or
get_string_keyed_int, depending on some Parrot esoterica. It
takes an integer as the key.
Because Parrot arrays start with an index of 0, line 134
increments the key to make the math easier.
136 .local pmc size_pmc
137 .local int size
138 size_pmc = getattribute self, 'size'
139 size = size_pmc
140
141 unless key > size goto calculate_string
142 .return()
Fixed arrays have fixed sizes in Parrot. I decided to return nothing if
someone tries to access an element beyond the appropriate size of a
FizzBuzz;Array.
Another option is throwing an exception, but I wanted to keep the code simple for this example.
144 calculate_string:
145 .local string result
146 result = ''
147
148 .local int remainder
149
150 check_fizz:
151 remainder = key % 3
152 if remainder goto check_buzz
153 result .= 'Fizz'
154
155 check_buzz:
156 remainder = key % 5
157 if remainder goto return_result
158 result .= 'Buzz'
159
160 return_result:
161 .return( result )
162 .end
The rest of the code looks very much like the procedural
implementation, except that it doesn’t loop and push the results onto an
array. This code calculates the result for the given index and returns it
directly. It could be more complex by adding caching, but that would
complicate the code and it’s unnecessary.
164 .sub 'get_string_keyed_int' :vtable :method
194 .end
Due to not being able to share implementation of vtable methods defined
as PIR subroutines, get_string_keyed_int uses the worst
possible method of code sharing–copy and paste–to handle the other way of
accessing an array value by index. It works though.
Final Thoughts
There are plenty of other ways to solve this problem in Parrot. A fun one is to create separate iterators and access them all simultaneously. Perhaps I’ll do that–or perhaps you will. Despite being an assembly language for a virtual machine still under active development, PIR is reasonably straightforward and very powerful.
