Skip to content

Instantly share code, notes, and snippets.

@Kaiepi Kaiepi/draft.md Secret
Last active Dec 22, 2019

Embed
What would you like to do?
Draft for the Raku Advent Calendar article on the 24th

The Grinch of Raku, Part 2: Hold Your Horses

In 2017, the Grinch ruined Christmas by showing off some of the naughty things you can do with Raku's features. Unfortunately, while his heart grew by three sizes that year, there's more than one Grinch! This Grinch will be doing something extra naughty this year, taking some inspiration from the JavaScript community.

You may have heard of JSFuck, which is a tool that allows you to write any JavaScript code using only the characters [, ], (, ), +, and !. This is something you'd only expect to be possible in a language like JavaScript, right? That's not entirely true! To prove this, let's port it to Raku. Since this can't be implemented using the exact same set of characters, our restrictions will be that only non-alphanumeric ASCII characters may be used in the translated code, and string literals must not be used.

Generating Primitives

The first thing we'll need to do is find a way to generate some primitives. The ones from JavaScript that are of interest to us are booleans, numbers, and strings; any other type of primitive can be represented through other means. These are generated mainly through type coercion on empty arrays, which also happens to be possible to do in Raku.

True and False can be generated in Raku using the ! prefix operator, similarly to how you can in JavaScript:

say ![];  # OUTPUT: True
say !![]: # OUTPUT: False

Using this in combination with the + prefix operator, we can generate any whole number, which is also the case in JavaScript:

say +[];         # OUTPUT: 0
say +![];        # OUTPUT: 1
say +![] + +![]; # OUTPUT: 2

In JavaScript, + also happens to be used to concatenate strings which, when used with two empty arrays, generates an empty string. This isn't the case in Raku, so we'll need to handle this using the ~ operator instead:

say (~[]).perl; # OUTPUT: ""

What about strings that aren't empty, though? In JavaScript, strings are iterable, which allows for certain characters to be used when stringifying values other than empty arrays. This isn't the case at all in Raku! We'll need to start getting creative. All we need to do for now is be able to generate the string "&chr" for reasons explained later.

String bitwise operators allow you to perform the same bitwise operations you can perform on numbers on codepoints in strings. Using the ~^ infix operator, we can generate a null character given 0 and 0:

say ord +[] ~^ +[]; # OUTPUT: 0

But now we have a problem: we can't generate the strings we need very easily with the ~+, ~|, and ~^ operators alone! There is a way to do that using this null character, but we need a lowercase letter of some sort first. We can grab the letter "e" from "True" if we use a regex:

say (~![] ~~ /...(.)/)[+[]]; # OUTPUT: e

Now, using an infinite sequence with these two characters, we can generate most of the characters in ASCII:

my Str:D @chars = (+[] ~^ +[]...(~![] ~~ /...(.)/)[+[]]...*);
say @chars[65..90];  # OUTPUT: (A B C D E F G H I J K L M N O P Q R S T U V W X Y Z)
say @chars[97..122]; # OUTPUT: (a b c d e f g h i j k l m n o p q r s t u v w x y z)

Now that we can generate the characters in the string "&chr", we'll be able to generate any Unicode string after the next step.

Evaluating Code

Most of the JavaScript code that can be generated depends on the Function constructor in order to work. Using it, you can arbitrarily generate a function at runtime. As far as I know, it's not possible to generate code like this in Raku without using &EVAL. There's a problem we need to solve if we are to use it, though.

We can use string literals with &EVAL just fine:

say EVAL "'Hello, world!'"; # OUTPUT: Hello, world!

But if we try to use a value that is unknown at compile-time with it, we'll get an exception warning about the security implications of what we're doing, telling us to use the MONKEY-SEE-NO-EVAL pragma:

say EVAL my $ = "'Hello, world!'"; # Throws X::SecurityPolicy::Eval

That's not good in our case! We can't set this pragma without alphanumeric characters. It's time to get naughty. What happens if we try to use &EVAL using an indirect symbol lookup?

say ::('&EVAL')(my $ = "'Hello world!'"); # OUTPUT: Hello, world!

Perfect! Along with this, using indirect symbol lookup we can also call the &chr routine to generate a string for any Unicode codepoint. In combination, this allows us to translate any valid Raku code.

Hold Your Horses

We're ready to start writing code for our port of JSFuck. This will simply be a script that takes some Raku code as input and outputs its translation. All of the subroutines used (apart from &MAIN) will be pure. Now, let's give this port a bit of a nicer name than the obvious choice and call it Hold Your Horses instead.

Our first subroutine will be &from-uint, which will translate numbers. We could just add 1 to 0 repeatedly until we get the number we're looking for, but this will generate huge amounts of code for larger codepoints. One way we can shorten the code this generates is if we represent numbers as being products of prime numbers. This can be further shortened by representing prime numbers greater than 5 as being a sum of products of prime numbers:

use Prime::Factor;

sub from-uint(UInt:D $x, Int:D $remainder = 0 --> Str:D) is pure {
    proto sub translate(UInt:D --> Str:D) is pure {*}
    multi sub translate(0 --> '+[]')              { }
    multi sub translate(1 --> '+![]')             { }
    multi sub translate(UInt:D $x --> Str:D)      { join ' + ', '+![]' xx $x }

    if $x <= 5 {
        my Str:D $str = $x.&translate;
        $remainder ?? sprintf('%s + %s', $str, $remainder.&from-uint) !! $str
    } elsif $x.is-prime {
        from-uint $x - 1, $remainder + 1
    } else {
        my Str:D $str = $x.&prime-factors».&from-uint.fmt: '(%s)', ' * ';
        $remainder ?? sprintf('%s + %s', $str, $remainder.&from-uint) !! $str
    }
}

Now we can implement &from-str, which will parse code input by the user. This needs to map each codepoint in the given code to a Hold Your Horses number, which can be done by looking up a character in the sequence of characters from earlier if it is within its range, otherwise &chr can be called. Since we're using this sequence every time we see a character that is included by it, this will be stored in $_ by our next subroutine. Since translating a single codepoint can be quite intensive, let's use the experimental is cached trait with our helper subroutine that handles this to avoid having to do it more than once for any given codepoint:

use experimental :cached;

sub from-str(Str:D $code --> Str:D) is pure {
    my Int:D constant LIMIT = 'z'.ord.succ;

    proto sub translate(UInt:D --> Str:D) is pure is cached {*}
    multi sub translate(UInt:D $codepoint where 0..^LIMIT --> Str:D) {
        sprintf '.[%s]', $codepoint.&from-uint
    }
    multi sub translate(UInt:D $codepoint where LIMIT..* --> Str:D) {
        sprintf '::(%s)(%s)', '&chr'.ords».&translate.join(' ~ '), $codepoint.&from-uint
    }

    sprintf '::(%s)(%s)', '&EVAL'.ords».&translate.join(' ~ '), $code.ords».&translate.join(' ~ ')
}

Now we can implement &hold-your-horses, which will handle the full translation of code input by the user. All this needs to do is store the sequence from earlier in $_ before calling &from-str:

sub hold-your-horses(Str:D $code --> Str:D) is pure {
    Qc:to/TRANSLATION/.chomp
    $_ := (+[] ~^ +[]...~(![] ~~ /...(.)/)[+[]]...*);
    {$code.&from-str};
    TRANSLATION
}

With &MAIN added, our script is now complete:

use v6.d;
use experimental :cached;
use Prime::Factor;
unit sub MAIN(Str:D $code) {
    say hold-your-horses $code
}

sub from-uint(UInt:D $x, Int:D $remainder = 0 --> Str:D) is pure {
    proto sub translate(UInt:D --> Str:D) is pure {*}
    multi sub translate(0 --> '+[]')              { }
    multi sub translate(1 --> '+![]')             { }
    multi sub translate(UInt:D $x --> Str:D)      { join ' + ', '+![]' xx $x }

    if $x <= 5 {
        my Str:D $str = $x.&translate;
        $remainder ?? sprintf('%s + %s', $str, $remainder.&from-uint) !! $str
    } elsif $x.is-prime {
        from-uint $x - 1, $remainder + 1
    } else {
        my Str:D $str = $x.&prime-factors».&from-uint.fmt: '(%s)', ' * ';
        $remainder ?? sprintf('%s + %s', $str, $remainder.&from-uint) !! $str
    }
}

sub from-str(Str:D $code --> Str:D) is pure {
    my Int:D constant LIMIT = 'z'.ord.succ;

    proto sub translate(UInt:D --> Str:D) is pure is cached {*}
    multi sub translate(UInt:D $codepoint where 0..^LIMIT --> Str:D) {
        sprintf '.[%s]', $codepoint.&from-uint
    }
    multi sub translate(UInt:D $codepoint where LIMIT..* --> Str:D) {
        sprintf '::(%s)(%s)', '&chr'.ords».&translate.join(' ~ '), $codepoint.&from-uint
    }

    sprintf '::(%s)(%s)', '&EVAL'.ords».&translate.join(' ~ '), $code.ords».&translate.join(' ~ ')
}

sub hold-your-horses(Str:D $code --> Str:D) is pure {
    Qc:to/TRANSLATION/.chomp
    $_ := (+[] ~^ +[]...~(![] ~~ /...(.)/)[+[]]...*);
    {$code.&from-str};
    TRANSLATION
}

Now, does this actually work? For brevity's sake, let's say it works as intended if say "Hello, world! 👋" can be translated and run:

bastille% raku hold-your-horses.raku 'say "Hello, world! 👋"' > hello-world.raku
bastille% raku hello-world.raku
Hello, world! 👋

Perfect! Here is the script's output:

$_ := (+[] ~^ +[]...~(![] ~~ /...(.)/)[+[]]...*);
::(.[(+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![] + +![]) * ((+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![]) + +![])] ~ .[(+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) + +![]) + +![])] ~ .[(+![] + +![] + +![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])])(.[(+![] + +![] + +![] + +![] + +![]) * ((+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) + +![]] ~ .[((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![])] ~ .[(+![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![]] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![])] ~ .[(+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![])] ~ .[((+![] + +![]) * (+![] + +![] + +![]) + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) + +![])] ~ .[(+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) * (+![] + +![] + +![] + +![] + +![])] ~ .[(+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![])] ~ ::(.[(+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![] + +![]) * (+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![]) * (+![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) + +![])] ~ .[(+![] + +![]) * (+![] + +![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])])((+![] + +![] + +![] + +![] + +![]) * (+![] + +![] + +![] + +![] + +![]) * ((+![] + +![]) * ((+![] + +![]) * ((+![] + +![]) * (+![] + +![] + +![] + +![] + +![]) + +![]) + +![]) + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) * (+![] + +![] + +![]) + +![])) ~ .[(+![] + +![]) * ((+![] + +![]) * (+![] + +![]) * (+![] + +![]) * (+![] + +![]) + +![])]);

Wrapping Up

Raku is quite a large language with an extensive set of features. These can be combined in some very interesting ways! Here, using a combination of type coercion, string bitwise operators, regexen, sequences, indirect symbol lookup, and a loophole with &EVAL, we were able to be naughty Grinches again this year and port JSFuck from JavaScript. If you're tempted to say something is impossible to write in Raku, hold your horses; it may very well be possible to do with the right tools.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.