[TT #1019] Make ok and nok use a PMC argument in test_more.pir and add tests for...
[parrot.git] / runtime / parrot / library / Test / More.pir
blobdeca795c09d45111d592d4a0d8d55a46af98d166
1 # $Id$
3 =head1 NAME
5 Test::More - Parrot extension for testing modules
7 =head1 SYNOPSIS
9     # load this library
10     load_bytecode 'Test/More.pbc'
12     # get the testing functions
13     .local pmc exports, curr_namespace, test_namespace
14     curr_namespace = get_namespace
15     test_namespace = get_namespace [ 'Test'; 'More' ]
16     exports        = split ' ', 'plan diag ok nok is is_deeply like isa_ok skip isnt todo throws_like'
18     test_namespace.'export_to'(curr_namespace, exports)
20     # set a test plan
21     plan( 12 )
23     # run your tests
24     ok( 1 )
25     ok( 0, 'failing test with diagnostic' )
27     is( 100, 100 )
28     is( 200, 100, 'failing integer compare with diagnostic' )
30     is( 1.001, 1.001, 'passing float compare with diagnostic' )
31     is( 8.008, 4.004 )
33     is( 'foo', 'foo', 'passing string compare with diagnostic' )
34     is( 'foo', 'bar', 'failing string compare with diagnostic' )
36     is( some_pmc, another_pmc, 'pmc comparison uses "eq" op' )
38     diag( 'this may take a while' )
39     is_deeply( some_deep_pmc, another_deep_pmc, 'deep structure comparison' )
41     like( 'foo', 'f o**{2}', 'passing regex compare with diagnostic' )
42     skip(1, 'reason for skipping')
43     todo(0, 'this is a failed test', 'reason for todo')
45     $P0 = get_class "Squirrel"
46     $P0.new()
48     isa_ok($P0, "Squirrel", "new Squirrel")
50 =head1 DESCRIPTION
52 C<Test::More> is a pure-Parrot library for testing modules.  It provides the
53 C<ok()>, C<is()>, C<isnt()>, C<is_deeply()>, and C<like()> comparison functions
54 for you.  It also provides the C<plan()> and C<diag()> helper functions. It
55 uses C<Test::Builder>, a simple, single backend for multiple test modules
56 to use within your tests.
58 =head1 FUNCTIONS
60 This class defines the following functions:
62 =over 4
64 =cut
66 .namespace [ 'Test'; 'More' ]
68 .sub _initialize :load
69     load_bytecode 'Test/Builder.pbc'
71     .local pmc test
72     test = new [ 'Test'; 'Builder' ]
74     set_hll_global [ 'Test'; 'More' ], '_test', test
75 .end
77 =item C<plan( number_or_no_plan )>
79 Declares the number of tests you plan to run, either an integer greater than
80 zero or the string C<no_plan>.  This will throw an exception if you have
81 already declared a plan or if you pass an invalid argument.
83 =cut
85 .sub plan
86     .param string tests
88     .local pmc test
89     get_hll_global test, [ 'Test'; 'More' ], '_test'
90     test.'plan'( tests )
91 .end
93 =item C<ok( passed, description )>
95 Records a test as pass or fail depending on the truth of the PMC C<passed>,
96 recording it with the optional test description in C<description>.
98 =cut
100 .sub ok
101     .param pmc    passed
102     .param string description     :optional
104     .local pmc test
105     get_hll_global test, [ 'Test'; 'More' ], '_test'
107     $I0 = istrue passed
108     test.'ok'( $I0, description )
109 .end
111 =item C<nok( passed, description )>
113 Records a test as pass or fail depending on the falsehood of the integer
114 C<passed>, recording it with the optional test description in C<description>.
116 =cut
118 .sub nok
119     .param pmc passed
120     .param string description :optional
122     .local pmc test
123     get_hll_global test, [ 'Test'; 'More' ], '_test'
125     .local int reverse_passed
126     reverse_passed = isfalse passed
128     test.'ok'( reverse_passed, description )
129 .end
131 =item C<is( left, right, description )>
133 Compares the parameters passed as C<left> and C<right>, passing if they are
134 equal and failing otherwise.  This will report the results with the optional
135 test description in C<description>.
137 This is a multi-method, with separate implementations for int-int, float-float,
138 string-string, and PMC-PMC comparisons.  The latter uses the C<eq> opcode for
139 comparison.
141 If there is a mismatch, the current implementation takes the type of C<left> as
142 the proper type for the comparison, converting any numeric arguments to floats.
143 Note that there is a hard-coded precision check to avoid certain rounding
144 errors.  It's not entirely robust, but it's not completely awful either.
146 Patches very welcome.  Multi-dispatch is a bit tricky here.
148 This probably doesn't handle all of the comparisons you want, but it's easy to
149 add more.
151 =cut
153 .sub is :multi(PMC, Integer)
154     .param pmc left
155     .param pmc right
156     .param pmc description :optional
157     .param int have_desc   :opt_flag
159     .local pmc test
160     get_hll_global test, [ 'Test'; 'More' ], '_test'
162     .local int l, r, pass
163     l    = left
164     r    = right
165     pass = iseq l, r
167     test.'ok'( pass, description )
168     if pass goto done
170     .local string diagnostic
171     .local string l_string
172     .local string r_string
174     l_string    = left
175     r_string    = right
177     diagnostic = _make_diagnostic( l_string, r_string )
178     test.'diag'( diagnostic )
179   done:
180 .end
182 .sub is :multi(PMC, Float)
183     .param pmc left
184     .param pmc right
185     .param pmc description :optional
186     .param int have_desc   :opt_flag
187     .param pmc precision   :optional
188     .param int have_prec   :opt_flag
190     .local pmc test
191     get_hll_global test, [ 'Test'; 'More' ], '_test'
193     .local num l, r
194     .local int pass
195     l    = left
196     r    = right
197     pass = iseq l, r
199     if     pass      goto report
200     unless have_prec goto report
202     .local num diff, prec_num
203     prec_num = precision
204     diff     = l - r
205     diff     = abs diff
206     pass     = isle diff, prec_num
208   report:
209     test.'ok'( pass, description )
210     if pass goto done
212     .local string diagnostic
213     .local string l_string
214     .local string r_string
216     l_string    = left
217     r_string    = right
219     diagnostic = _make_diagnostic( l_string, r_string )
220     test.'diag'( diagnostic )
221   done:
222 .end
224 .sub is :multi(PMC, String)
225     .param pmc left
226     .param pmc right
227     .param pmc description :optional
228     .param int have_desc   :opt_flag
230     .local pmc test
231     get_hll_global test, [ 'Test'; 'More' ], '_test'
233     .local string l, r
234     .local int pass
235     l    = left
236     r    = right
237     pass = iseq l, r
239     test.'ok'( pass, description )
240     if pass goto done
242     .local string diagnostic
243     .local string l_string
244     .local string r_string
246     l_string    = left
247     r_string    = right
249     diagnostic = _make_diagnostic( l_string, r_string )
250     test.'diag'( diagnostic )
251   done:
252 .end
254 .sub is :multi(PMC, PMC)
255     .param pmc left
256     .param pmc right
257     .param pmc description :optional
258     .param int have_desc   :opt_flag
260     .local pmc test
261     get_hll_global test, [ 'Test'; 'More' ], '_test'
263     .local int pass
264     .local int does_type
266     does_type = does right, 'String'
267     if does_type goto check_string
269     does_type = does right, 'Float'
270     if does_type goto check_float
272     does_type = does right, 'Integer'
273     if does_type goto check_integer
275   check_string:
276     pass = iseq left, right
277     goto result
279   check_float:
280     .local num ln, rn
281     ln   = left
282     rn   = right
283     pass = iseq ln, rn
284     goto result
286   check_integer:
287     .local int li, ri
288     li   = left
289     ri   = right
290     pass = iseq li, ri
291     goto result
293   result:
294     test.'ok'( pass, description )
295     if pass goto done
297     .local string diagnostic
298     .local string l_string
299     .local string r_string
301     l_string    = left
302     r_string    = right
304     diagnostic = _make_diagnostic( l_string, r_string )
305     test.'diag'( diagnostic )
306   done:
307 .end
309 =item C<isnt( left, right, description )>
311 Like C<is>, but succeeds if the arguments I<don't> match.
313 =cut
315 .sub isnt :multi(Integer, Integer)
316     .param pmc left
317     .param pmc right
318     .param pmc description :optional
319     .param int have_desc   :opt_flag
321     .local pmc test
322     get_hll_global test, [ 'Test'; 'More' ], '_test'
324     .local int pass
325     pass       = 0
327     if left != right goto pass_it
328     goto report
330   pass_it:
331     pass = 1
333   report:
334     test.'ok'( pass, description )
335     if pass goto done
337     .local string diagnostic
338     .local string l_string
339     .local string r_string
341     l_string = left
342     r_string = right
343     r_string = 'not ' . r_string
345     diagnostic = _make_diagnostic( l_string, r_string )
346     test.'diag'( diagnostic )
347   done:
348 .end
350 .sub isnt :multi(Float, Float)
351     .param pmc left
352     .param pmc right
353     .param pmc description :optional
354     .param int have_desc   :opt_flag
356     .local pmc test
357     get_hll_global test, [ 'Test'; 'More' ], '_test'
359     .local int pass
360     pass = 0
362     ne left, right, pass_it
363     goto report
365   pass_it:
366     pass = 1
368   report:
369     test.'ok'( pass, description )
370     if pass goto done
372     .local string diagnostic
373     .local string l_string
374     .local string r_string
376     l_string = left
377     r_string = right
378     r_string = 'not ' . r_string
380     diagnostic = _make_diagnostic( l_string, r_string )
381     test.'diag'( diagnostic )
382   done:
383 .end
385 .sub isnt :multi(String, String)
386     .param pmc left
387     .param pmc right
388     .param pmc description :optional
389     .param int have_desc   :opt_flag
391     .local pmc test
392     get_hll_global test, [ 'Test'; 'More' ], '_test'
394     .local int pass
395     pass = 0
397     ne left, right, pass_it
398     goto report
400   pass_it:
401     pass = 1
403   report:
404     test.'ok'( pass, description )
405     if pass goto done
407     .local string diagnostic
408     .local string l_string
409     .local string r_string
411     l_string = left
412     r_string = right
413     r_string = 'not ' . r_string
415     diagnostic = _make_diagnostic( l_string, r_string )
416     test.'diag'( diagnostic )
417   done:
418 .end
420 .sub isnt :multi(PMC, PMC)
421     .param pmc left
422     .param pmc right
423     .param pmc description :optional
424     .param int have_desc   :opt_flag
426     .local pmc test
427     get_hll_global test, [ 'Test'; 'More' ], '_test'
429     # this comparison may not work in general, but it's worth trying
430     .local int pass
431     pass = isne left, right
433   report:
434     test.'ok'( pass, description )
435     if pass goto done
437     .local string diagnostic
438     .local string l_string
439     .local string r_string
441     l_string = left
442     r_string = right
443     r_string = 'not ' . r_string
445     diagnostic = _make_diagnostic( l_string, r_string )
446     test.'diag'( diagnostic )
447   done:
448 .end
450 =item C<diag( diagnostic )>
452 Prints C<diagnostic> to the screen, without affecting test comparisons.
454 =cut
456 .sub diag
457     .param string diagnostic
459     .local pmc test
460     get_hll_global test, [ 'Test'; 'More' ], '_test'
461     test.'diag'( diagnostic )
462 .end
465 =item C<is_deeply( left, right, description )>
467 Compares the data structures passed as C<left> and C<right>.  If data
468 structures are passed, C<is_deeply> does a deep comparison by walking each
469 structure.  It passes if they are equal and fails otherwise.  This will
470 report the results with the optional test description in C<description>.
472 This handles comparisons of array-like and hash-like structures.
474 =cut
476 .sub is_deeply :multi(PMC, PMC)
477     .param pmc left
478     .param pmc right
479     .param pmc description :optional
480     .param int have_desc   :opt_flag
482     .local int    result
483     .local string diagnosis
485     .local pmc position
486     position = new 'ResizablePMCArray'
488     .local pmc test
489     get_hll_global test, [ 'Test'; 'More' ], '_test'
491     .local int does_flag
492     does_flag = does left, 'array'
493     if does_flag goto compare_array
495     does_flag = does left, 'hash'
496     if does_flag goto compare_hash
498     diagnosis  = typeof left
499     diagnosis .= ' is not a nested data structure'
500     result     = 0
501     goto report_result
503   compare_array:
504     ( result, diagnosis ) = compare_array( left, right, position )
505     goto report_result
507   compare_hash:
508     (result, diagnosis ) = compare_hash( left, right, position )
509     goto report_result
511   report_result:
512     test.'ok'( result, description )
514     unless result goto report_diagnostic
515     .return( result )
517   report_diagnostic:
518     ne diagnosis, '', return_it
520     .local string left_value
521     .local string right_value
522     right_value = pop position
523     left_value  = pop position
525     .local string nested_path
526     nested_path = join '][', position
528     diagnosis   = 'Mismatch'
529     unless nested_path goto show_expected
531     diagnosis  .= ' at ['
532     diagnosis  .= nested_path
533     diagnosis  .= ']'
535   show_expected:
536     diagnosis  .= ': expected '
537     diagnosis  .= left_value
538     diagnosis  .= ', received '
539     diagnosis  .= right_value
541   return_it:
542     test.'diag'( diagnosis )
543     .return( result )
544 .end
546 .sub compare_array
547     .param pmc l_array
548     .param pmc r_array
549     .param pmc position
551     .local pmc test
552     get_hll_global test, [ 'Test'; 'More' ], '_test'
554     .local int l_count
555     .local int r_count
556     l_count = l_array
557     r_count = r_array
558     if l_count == r_count goto compare_contents
560     .local string l_count_string
561     .local string r_count_string
562     l_count_string  = l_count
563     l_count_string .= ' element'
565     if l_count == 1 goto pluralization_done
566     l_count_string .= 's'
568   pluralization_done:
569     r_count_string  = r_count
571     push position, l_count_string
572     push position, r_count_string
574     .return( 0 )
576   compare_contents:
577     .local pmc l_iter
578     .local pmc r_iter
579     .local int count
581     l_iter = iter l_array
582     r_iter = iter r_array
583     l_iter = 0
584     r_iter = 0
585     count  = 0
587     .local pmc l_elem
588     .local pmc r_elem
589     .local int elems_equal
591   iter_start:
592     unless l_iter goto iter_end
593     l_elem = shift l_iter
594     r_elem = shift r_iter
596     $S0 = typeof l_elem
597     elems_equal = compare_elements( l_elem, r_elem, position )
598     unless elems_equal goto elems_not_equal
600     inc count
601     goto iter_start
603   elems_not_equal:
604     unshift position, count
605     .return( 0 )
607   iter_end:
608     .return( 1 )
609 .end
611 .sub compare_hash
612     .param pmc l_hash
613     .param pmc r_hash
614     .param pmc position
616     .local pmc test
617     get_hll_global test, [ 'Test'; 'More' ], '_test'
619     .local int l_count
620     .local int r_count
621     l_count = l_hash
622     r_count = r_hash
623     if l_count == r_count goto compare_contents
625     .local string l_count_string
626     .local string r_count_string
627     l_count_string  = l_count
628     l_count_string .= ' element'
630     if l_count == 1 goto pluralization_done
631     l_count_string .= 's'
633   pluralization_done:
634     r_count_string  = r_count
636     push position, l_count_string
637     push position, r_count_string
639     .return( 0 )
641   compare_contents:
642     .local pmc l_iter
643     .local int count
645     l_iter = iter l_hash
646     l_iter = 0
647     count  = 0
649     .local pmc key
650     .local pmc l_elem
651     .local pmc r_elem
652     .local int elems_equal
654   iter_start:
655     unless l_iter goto iter_end
656     key  = shift l_iter
657     l_elem = l_hash[ key ]
658     r_elem = r_hash[ key ]
660     elems_equal = compare_elements( l_elem, r_elem, position )
661     unless elems_equal goto elems_not_equal
663     inc count
664     goto iter_start
666   elems_not_equal:
667     unshift position, key
668     .return( 0 )
670   iter_end:
671     .return( 1 )
672 .end
674 .sub compare_elements :multi(String, String, PMC)
675     .param pmc left
676     .param pmc right
677     .param pmc position
679     .local int equal
681     eq left, right, are_equal
683   are_not_equal:
684     push position, left
685     push position, right
686     .return( 0 )
688   are_equal:
689     .return( 1 )
690 .end
692 .sub compare_elements :multi(Integer, Integer, PMC)
693     .param pmc left
694     .param pmc right
695     .param pmc position
697     .local int equal
698     eq left, right, are_equal
700   are_not_equal:
701     push position, left
702     push position, right
703     .return( 0 )
705   are_equal:
706     .return( 1 )
707 .end
709 .sub compare_elements :multi(String, String, PMC)
710     .param pmc left
711     .param pmc right
712     .param pmc position
714     eq left, right, are_equal
716   are_not_equal:
717     push position, left
718     push position, right
719     .return( 0 )
721   are_equal:
722     .return( 1 )
723 .end
725 .sub compare_elements :multi(Integer, Integer, PMC)
726     .param pmc left
727     .param pmc right
728     .param pmc position
730     .local int equal
731     eq left, right, are_equal
733   are_not_equal:
734     push position, left
735     push position, right
736     .return( 0 )
738   are_equal:
739     .return( 1 )
740 .end
742 .sub compare_elements :multi(Array, Array, PMC)
743     .param pmc left
744     .param pmc right
745     .param pmc position
747     .local int equal
748     equal = compare_array( left, right, position )
749     .return( equal )
750 .end
752 .sub compare_elements :multi(Hash, Hash, PMC)
753     .param pmc left
754     .param pmc right
755     .param pmc position
757     .local int equal
758     equal = compare_hash( left, right, position )
759     .return( equal )
760 .end
762 .sub compare_elements :multi(Undef, Undef, PMC)
763     .param pmc left
764     .param pmc right
765     .param pmc position
767     .return( 1 )
768 .end
770 .sub compare_elements :multi(Undef, PMC, PMC)
771     .param pmc left
772     .param pmc right
773     .param pmc position
775     .local string l_undef
776     l_undef = '(undef)'
777     push position, l_undef
778     push position, right
779     .return( 0 )
780 .end
782 .sub compare_elements :multi(PMC, Undef, PMC)
783     .param pmc left
784     .param pmc right
785     .param pmc position
787     .local string r_undef
788     r_undef = '(undef)'
789     push position, left
790     push position, r_undef
791     .return( 0 )
792 .end
794 .sub compare_elements :multi(PMC, PMC, PMC)
795     .param pmc left
796     .param pmc right
797     .param pmc position
799     .local int does_flag
800     .local int equal
802   check_array:
803     does_flag = does left, 'array'
804     unless does_flag goto check_hash
805     equal = compare_array( left, right, position )
806     .return( equal )
808   check_hash:
809     does_flag = does left, 'hash'
810     if does_flag goto compare_hash
811     .return( 0 )
813   compare_hash:
814     equal = compare_hash( left, right, position )
815     .return( equal )
816 .end
818 =item C<throws_like( codestring, pattern, description )>
820 Takes PIR code in C<codestring> and a PGE pattern to match in C<pattern>, as
821 well as an optional message in C<description>. Passes a test if the PIR throws
822 an exception that matches the pattern, fails the test otherwise.
824 =cut
826 .sub throws_like
827     .param string target
828     .param string pattern
829     .param string description :optional
831     .local pmc test
832     get_hll_global test, [ 'Test'; 'More' ], '_test'
834     .local pmc comp
835     .local pmc compfun
836     .local pmc compiler
837     compiler = compreg 'PIR'
839     .local pmc eh
840     eh = new 'ExceptionHandler'
841     set_addr eh, handler            # set handler label for exceptions
842     push_eh eh
844     compfun = compiler(target)
845     compfun()                       # eval the target code
847     pop_eh
849     # if it doesn't throw an exception, fail
850     test.'ok'( 0, description )
851     test.'diag'( 'no error thrown' )
853     goto done
855   handler:
856     .local pmc ex
857     .local string error_msg
858     .get_results (ex)
859     pop_eh
860     error_msg = ex
861     like(error_msg, pattern, description)
863   done:
864 .end
866 =item C<like( target, pattern, description )>
868 Similar to is, but using the Parrot Grammar Engine to compare the string
869 passed as C<target> to the pattern passed as C<pattern>.  It passes if the
870 pattern matches and fails otherwise.  This will report the results with the
871 optional test description in C<description>.
873 =cut
875 .sub like
876     .param string target
877     .param string pattern
878     .param string description :optional
880     .local pmc test
881     get_hll_global test, [ 'Test'; 'More' ], '_test'
883     .local pmc p6rule_compile
884     load_bytecode "PGE.pbc"
885     load_bytecode "PGE/Dumper.pbc"
886     load_bytecode "PGE/Text.pbc"
887     load_bytecode "PGE/Util.pbc"
888     p6rule_compile = compreg "PGE::Perl6Regex"
890     .local string diagnostic
891     .local int pass
892     pass = 0
894   match_pattern:
895     .local pmc rulesub
896     .local pmc match
897     .local pmc code
898     .local pmc exp
899     (rulesub, code, exp) = p6rule_compile(pattern)
900     if_null rulesub, rule_fail
901     match = rulesub(target)
902     unless match goto match_fail
903   match_success:
904     goto pass_it
905   match_fail:
906     diagnostic = "match failed: target '"
907     diagnostic .= target
908     diagnostic .= "' does not match pattern '"
909     diagnostic .= pattern
910     diagnostic .= "'"
911     goto report
912   rule_fail:
913     diagnostic = "rule error"
914     goto report
916   pass_it:
917     pass = 1
919   report:
920     test.'ok'( pass, description )
921     if pass goto done
923     test.'diag'( diagnostic )
924   done:
925 .end
927 =item C<skip( how_many, why )>
929 Pass a number of tests, but with a comment that marks the test was
930 actually skipped.  Arguments are optional.
932 =cut
934 .sub skip :multi(Integer, String)
935     .param int how_many
936     .param string description
938     .local pmc test
939     get_hll_global test, [ 'Test'; 'More' ], '_test'
940     test.'skip'(how_many, description)
941 .end
943 .sub skip :multi(Integer)
944     .param int how_many
946     .local pmc test
947     get_hll_global test, [ 'Test'; 'More' ], '_test'
948     test.'skip'(how_many)
949 .end
951 .sub skip :multi(String)
952     .param string description
954     .local pmc test
955     get_hll_global test, [ 'Test'; 'More' ], '_test'
956     test.'skip'(1, description)
957 .end
959 .sub skip :multi()
960     .local pmc test
961     get_hll_global test, [ 'Test'; 'More' ], '_test'
962     test.'skip'()
963 .end
965 =item C<todo( passed, description, reason )>
967 Records a test as pass or fail (like C<ok>, but marks it as TODO so it always
968 appears as a success. This also records the optional C<description> of the test
969 and the C<reason> you have marked it as TODO.
971 =cut
973 .sub todo
974     .param pmc args :slurpy
976     .local pmc test
977     get_hll_global test, [ 'Test'; 'More' ], '_test'
979     test.'todo'( args :flat )
980 .end
982 =item C<isa_ok( object, class_name, object_name )>
984 Pass if the object C<isa> class of the given class name.  The object
985 name passed in is not a full description, but a name to be included in
986 the description. The description is presented as "<object_name> isa
987 <class>".
989 Good input: "C<new MyObject>", "C<return from bar()>"
991 Bad input: "C<test that the return from Foo is correct type>"
993 =cut
995 .sub isa_ok
996     .param pmc thingy
997     .param pmc class_name
998     .param pmc object_name :optional
999     .param int got_name :opt_flag
1001     .local pmc test
1002     get_hll_global test, [ 'Test'; 'More' ], '_test'
1004     .local string description, diagnostic
1005     description = "The object"
1006     unless got_name goto keep_default
1007     description = object_name
1008   keep_default:
1009     diagnostic = description
1010     description .= " isa "
1011     $S0 = class_name
1012     description .= $S0
1014     $I0 = isa thingy, class_name
1015     test.'ok'($I0, description)
1016     if $I0 goto out
1017     diagnostic .= " isn't a "
1018     $S1 = class_name
1019     diagnostic .= $S1
1020     diagnostic .= " it's a "
1021     $S2 = typeof thingy
1022     diagnostic .= $S2
1023     test.'diag'(diagnostic)
1024   out:
1025 .end
1027 .sub _make_diagnostic
1028     .param string received
1029     .param string expected
1030     .local string diagnostic
1032     diagnostic  = 'Have: '
1033     diagnostic .= received
1034     diagnostic .= "\nWant: "
1035     diagnostic .= expected
1037     .return( diagnostic )
1038 .end
1040 =back
1042 =head1 AUTHOR
1044 Written and maintained by chromatic, C<< chromatic at wgz dot org >>, based on
1045 the Perl 6 port he wrote, based on the original Perl 5 version he wrote with
1046 ideas from Michael G. Schwern.  Please send patches, feedback, and suggestions
1047 to the Perl 6 internals mailing list.
1049 =head1 COPYRIGHT
1051 Copyright (C) 2005-2009, Parrot Foundation.
1053 =cut
1055 # Local Variables:
1056 #   mode: pir
1057 #   fill-column: 100
1058 # End:
1059 # vim: expandtab shiftwidth=4 ft=pir: