🔬13. Let 1 + 2 * 3 = 9

Is it easy to break the behaviour of Perl 6? Well, the answer probably depends on what exactly you want to break.

Playing with operator precedence, I wanted to change the rules of arithmetical operators + and * so that they are executed in different order, namely, multiplication first, addition second.

Sounds like an easy task. Go to src/Perl6/Grammar.nqp and change a couple of lines that set the precedence of the + and * infixes:

- token infix:sym<*>    { <sym> <O(|%multiplicative)> }
+ token infix:sym<*>    { <sym> <O(|%additive)> }
. . .
- token infix:sym<+>    { <sym> <O(|%additive)> }
+ token infix:sym<+>    { <sym> <O(|%multiplicative)> }

Ready? Compile!

Recompiling the grammar takes a long time, so at first it looks promising, but after a few seconds, the compilation stops with an error:

Month out of range. Is: -935111296, should be in 1..12

Makefile:517: recipe for target 'perl6-m' failed
make: *** [perl6-m] Error 1

Month out of range?? Oh, we changed the rules of the Universe and before Perl 6 is even compiled, the new rules of arithmetics are already applied.

OK, let’s add some anaesthesia and suppress the error message. The code that checks for the correct month value is located in src/core/DateTime.pm, namely, inside the DateTime constructor. Comment that line out:

method !new-from-positional(DateTime:
    Int() $year,
    Int() $month,
    Int() $day,
    Int() $hour,
    Int() $minute,
        $second,
        %extra,
    :$timezone = 0,
    :&formatter,
) {
    # (1..12).in-range($month,'Month');
    (1 .. self.DAYS-IN-MONTH($year,$month)).in-range($day,'Day');
    (0..23).in-range($hour,'Hour');
    (0..59).in-range($minute,'Minute');
    (^61).in-range($second,'Second');
    . . .

This time, the month range check doesn’t stop us from going further but another error breaks in:

MVMArray: Index out of bounds

Makefile:517: recipe for target 'perl6-m' failed
make: *** [perl6-m] Error 1

Looks cryptic. MVMArray is a MoarVM array, obviously. So, we not only broke Perl 6 but MoarVM, too. Let’s go fix it.

The sources of MoarVM are located in a separate git repository at nqp/MoarVM. The message we saw can be found in nqp/MoarVM/src/6model/reprs/VMArray.c:

if (index < 0)
    MVM_exception_throw_adhoc(tc, "MVMArray: Index out of bounds");

There are two places like that, so let’s not guess which of them we need and preventatively change both of them to the following:

if (index < 0)
    index = 0;
    // MVM_exception_throw_adhoc(tc, "MVMArray: Index out of bounds");

(This is C, not Perl.)

From nqp/MoarVM, compile and re-install MoarVM and later try compiling Rakudo:

~/rakudo/nqp/MoarVM$ make
~/rakudo/nqp/MoarVM$ make install

~/rakudo/nqp/MoarVM$ cd ../..
~/rakudo$ make

This time, the error pops up immediately (as no NQP files are compiled):

Use of Nil in numeric context

Use of Nil in numeric context

Day out of range. Is: -51, should be in 1..0

Makefile:517: recipe for target 'perl6-m' failed
make: *** [perl6-m] Error 1

It looks like we can ignore Nils at the moment, but the DateTime hurts us again. We know the remedy:

# (1..12).in-range($month,'Month');
# (1 .. self.DAYS-IN-MONTH($year,$month)).in-range($day,'Day');

Yahoo! This time, the compilation process was calm and we got a new perl6 executable, which works as we wanted:

$ ./perl6 -e'say 1+2*3'
9

Don’t forget to restore the files before further experiments with Perl 6 🙂

Update

In the comment to this blog post, you can see a reference to the commit, which changes the way Rakudo checks the validity of the DateTime object. Instead of using the in-range method, simpler checks are used now, for example:

1 <= $month <= 12
    || X::OutOfRange.new(:what<Month>,:got($month),:range<1..12>).throw;

Here are the time measures of the two runs of a loop creating DateTime objects before and after the update:

time ./perl6 -e'DateTime.new(2018,1,5,12,30,0) for ^500000'
real 0m7.261s
user 0m7.276s
sys 0m0.020s

. . .

$ time ./perl6 -e'DateTime.new(2018,1,5,12,30,0) for ^500000'
real 0m4.457s
user 0m4.476s
sys 0m0.012s

3 thoughts on “🔬13. Let 1 + 2 * 3 = 9”

Leave a Reply to gfldex Cancel reply

Your email address will not be published. Required fields are marked *

Retype the CAPTCHA code from the image
Change the CAPTCHA codeSpeak the CAPTCHA code