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. When used with two empty arrays, +
will coerce both to strings and concatenate them, which results in an empty string. +
doesn’t behave like this in Raku, so we’ll need to use 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 in Raku! It’s time to start getting creative.
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
We can’t generate the characters we need very easily with the ~+
, ~|
, and ~^
operators alone though. There is a way to do this using that 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
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 $translation = $x.&translate;
$translation ~= ' + ' ~ $remainder.&from-uint if $remainder;
$translation
} elsif $x.is-prime {
from-uint $x - 1, $remainder + 1
} else {
my Str:D $translation = $x.&prime-factors».&from-uint.fmt: '(%s)', ' * ';
$translation ~= ' + ' ~ $remainder.&from-uint if $remainder;
$translation
}
}
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 $translation = $x.&translate;
$translation ~= ' + ' ~ $remainder.&from-uint if $remainder;
$translation
} elsif $x.is-prime {
from-uint $x - 1, $remainder + 1
} else {
my Str:D $translation = $x.&prime-factors».&from-uint.fmt: '(%s)', ' * ';
$translation ~= ' + ' ~ $remainder.&from-uint if $remainder;
$translation
}
}
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! This 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.
One thought on “Day 24: The Grinch of Raku, Part 2: Hold Your Horses”