A recent discussion on interviewing programmers (in hopes of finding clueful ones) brought up the FizzBuzz challenge. Can you write a program to print the numbers from one to one hundred, printing also “Fizz” for multiples of three, “Buzz” for multiples of five, and “FizzBuzz” for multiples of three and five?
This ought to take no more than a few minutes for a developer with any proficiency in a language. I decided it would be fun to write it in Parrot’s PIR. There’s the straightforward procedural way, the array overloading way, an object-oriented way, the coroutine approach, and the generator technique.
I chose the first two, but I also decided to work entirely with test-driven development, even though this is normally the realm of a SpikeSolution–I thought that might be more interesting for everyone.
To make TDD work well, I created a test file,
t/examples/fizzbuzz.t. It’s a pure-PIR test script that uses the
Parrot version of
Test::More and produces Test::Harness
The program starts out simply:
1 #!parrot 2 3 .include 'hllmacros.pir' 4 5 .const string TESTS = 18
.include directive works like
C; it inserts the contents of another source file into the current
compilation unit. hllmacros.pir contains some useful macros. PIR is
powerful, but it’s still an assembly language at heart. We’ve developed a
few shortcuts for making PIR easier to write.
Line 5 declares a constant–the number of tests to run. PIR has typed variables (actually four distinct types of registers), so all named variable declarations need a type declaration.
7 .sub 'main' :main 8 load_bytecode 'Test/More.pir' 9 load_bytecode 'examples/pir/fizzbuzz.pir' 10 11 .local pmc import_sub 12 .IMPORT( 'Test::More', 'plan', import_sub ) 13 .IMPORT( 'Test::More', 'is', import_sub ) 14 .IMPORT( 'Test::More', 'diag', import_sub ) 15 16 plan( TESTS ) 17 18 test_function( 'procedural' ) 19 test_function( 'keyed_array' ) 20 .end
Lines 7 and 20 bracket the main entry point to this test file–a
main. The name is immaterial to everything
but programmers; only the
:main attribute tells Parrot that,
when invoking this file directly, this is the entry point.
Lines 8 and 9 load two PIR libraries. One is the pure-PIR testing system
and the other is the file of FizzBuzz subroutines to test. Yes,
load_bytecode is an inopportune name.
Line 11 contains another variable declaration. It only exists to make
.IMPORT() macro work correctly. This time, the variable is
a local variable–that is, it persists only through this compilation unit
(the subroutine). It holds a PMC–a Parrot Magic Cookie, Parrot’s single
Lines 12 through 14 import three testing functions from
Test::More. Though they come from a separate namespace,
they’re available within this test file as if I had declared them locally.
(Now you see why I used
Line 16 starts the testing by telling the test library that I plan to
TESTS number of tests.
Lines 18 and 19 call a the remaining function in this file with the name of the various FizzBuzz functions in examples/pir/fizzbuzz.pir.
22 .sub 'test_function'
Note the lack of
:main or any other attribute on line
23 .param string func_name
Line 23 demonstrates how to access subroutine arguments in PIR. The
.param declaration is like
.local in its scope,
but beyond merely associating a name to a particular type of register
within this compilation unit, it also accesses the appropriate value passed
into the subroutine.
25 .local pmc test_sub 26 test_sub = find_global [ 'FizzBuzz' ], func_name
With the function name passed in as an argument, this function needs to
be able to access the function in my example PIR file. The
find_global opcode takes a namespace key and a string and
returns the PMC stored in that namespace under that name. If I have the
test_sub will contain a Subroutine PMC that I
Parrot has first-class functions; this is more than just a function pointer.
27 diag( func_name )
Line 27 calls a
Test::More function to show the name of the
function being tested. It won’t interfere with the TAP output in any way.
It’s just nice when running the tests directly for diagnostic purposes.
29 .local pmc results 30 results = test_sub( 10 ) 31 32 .local int count 33 count = results
Lines 29 and 32 declare two variables which I used throughout the
remainder of this function.
results contains an Array PMC
returned from the subroutine being tested. I use
count to hold
the number of elements in the
results PMC. (If it’s not clear, the
count = results line ends up calling the
get_integer() vtable method on the PMC. That returns the number of elements for an Array-like PMC. How does Parrot know to call that method?
count is an integer, and the assignment operation with an integer variable as an lvalue on a PMC rvalue turns into the
All of my FizzBuzz subroutines currently use the same interface. They take an integer for the number of elements to create and return an Array PMC containing the strings for each element. This makes them all easier to test.
34 is( count, 10, 'test function should return the correct number of results' )
The first test is that calling the subroutine being tested and asking for ten elements should produce an Array PMC containing ten elements.
36 results = test_sub( 100 ) 37 count = results 38 is( count, 100, '... based on start and end' )
That didn’t quite meet the FizzBuzz challenge though, so I tried it
100 elements. (I also worked in a very small step
when developing the previous step; I hardcoded the tested subroutine to
return an Array of 10 null elements.)
40 .local string element 41 element = results 42 is( element, '', '... with nothing for the first element' ) 43 44 element = results 45 is( element, 'Fizz', '... with Fizz for the third element' ) 46 47 element = results 48 is( element, 'Buzz', '... and Buzz for the fifth element' ) 49 50 element = results 51 is( element, 'FizzBuzz', '... and FizzBuzz for the fifteenth element' ) 52 53 element = results 54 is( element, 'Fizz', '... and Fizz for the eighteenth element' ) 55 56 element = results 57 is( element, 'Buzz', '... and Buzz for the twentieth element' ) 58 59 element = results 60 is( element, 'FizzBuzz', '... and FizzBuzz for the thirtieth element' )
This is the heart of the test now. With the returned
results array, accessing individual elements should give the
right answers for the FizzBuzz challenge. You’ve probably noticed, however,
that all of the numbers are off by one. This is because Parrot’s array
indices start at 0, not 1. I could have padded the array by one element if
this were important, but noting it explicitly seemed sufficient.
Finally, this test function needs to end.
That’s the test code. It doesn’t yet meet the FizzBuzz challenge, and that’s not only because I haven’t shown the code for the tested subroutines yet. This doesn’t print anything, unless you consider test output.
Here’s the first part of examples/pir/fizzbuzz.pir:
1 .namespace [ 'FizzBuzz' ]
.namespace directive tells Parrot that all subsequent
declarations should take place into a separate namespace. That’s
unnecessary for simple example code, but it’s a good habit to avoid name
clashes. (I wanted to write as real a program as possible.
3 .sub 'main' :main 4 .param pmc argv
Like the test file, this code has its own
main function as
well. What happens when Parrot loads it from the test file? Absolutely
nothing. Only the first
:main is the main.
However, this means that you can run this program on its own, because in
that case this
main function will execute. Line
4 gives access to the command-line arguments provided in that case.
6 .local string sub_name 7 sub_name = argv 8 9 if sub_name goto load_sub 10 sub_name = 'procedural'
The first element of
argv is the program name. That’s
immaterial here. The real arguments start at index
1. I expect
this program to take one argument, the name of the testable subroutine to
run. If there’s no subroutine provided, default to
Line 9 might look a little awkward. Yes,
goto is the main
form of intrafunction control flow in Parrot. Remember that I called it an
12 load_sub: 13 .local pmc sub_pmc 14 sub_pmc = find_global sub_name
This code ought to look familiar; it uses the string name of a
subroutine to (attempt to) fetch the associated Sub PMC. I say
attempt because someone may provide an invalid name. I didn’t add
any error handling to check for this condition, but the best approach is to
make sure that
sub_pmc contains a PMC that implements the Sub
Note that this use of
find_global is different; I’m calling
it here from the same namespace where I’ve declared the testable
subroutines, so I don’t need the namespace key that I used in the test
16 .local pmc results 17 results = sub_pmc( 100 ) 18 19 .local pmc iter 20 iter = new .Iterator, results 21 22 .local string elem 23 .local int count 24 count = 1
Lines 16 and 17 should look familiar.
Lines 19 and 20 create an iterator. This is another type of PMC, used to iterate through an aggregate.
Lines 22 through 24 declare a couple of variables I want to use in the upcoming loop.
26 iter_start: 27 unless iter goto iter_end 28 elem = shift iter 29 30 print count 31 print ": " 32 print elem 33 print "\n" 34 35 inc count 36 goto iter_start
Lines 26 through 28 are boilerplate for every use of a Parrot iterator that I’ve ever seen. 26 is the start of loop label, 27 is the end of loop condition, and 28 gets the current element out of the iterator into a PMC variable.
Lines 30 through 33 print the number of the current element and the
current element. If it’s blank, it’s blank. Otherwise, it contains
Line 35 increments the count variable, used for display purposes only (it started at one). Line 36 restarts the loop.
38 iter_end: 39 end 40 .end
The loop and the function end here.
The rest of the code actually does the important FizzBuzz work. I’ll
show that tomorrow. In the meantime, I wonder how many people can build
Parrot (or just read the documentation) and write their own version of
procedural is easy, but
keyed_array took a few minutes.