Raku Parrot

2022-08-06

One of the programming languages that I have stumbled upon not too long ago is Raku. This language, which was previously known as Perl 6, is chock-full of really cool features. It has support for programming in both object oriented and functional paradigms, optional gradual typing, a nice story for async programming and more.

A really nice feature of Raku is that Grammars are a core part of the language. Grammars allow for a great way to interpret textual information. When programming there are a lot of use cases where a specific set of patterns need to be recognized and used from text. An interpreter for a format such as JSON is a good example. From the text version of a JSON file elements such as arrays, objects, numbers, strings, etc. needs to be recognized. In addition a representation of them needs to be built for manipulation and use within a programming language.

In order to demonstrate this grammar feature of Raku, we are going to implement a small program to parrot phrases back to us. The grammar inside this program describes short three word (sometimes a bit irregular) sentences like "I like sushi." or "They eat pear.". Our program will parrot these phrases back to us twice, first just as text and the second time with certain words being replaced by emojis.

To give a preview of what this code will do, if we call it from the command line given the input:

I like sushi.

we get back:

I like sushi.
I like 🍣.

if we would use an input like:

They eat pear.

we get back:

They eat pear.
They eat 🍐.

In cases where the our parroting program does not understand what we enter, for example for the sentence:

This is too complex!

we get the phrase back:

Squaaawk can not understand!

To show how this is implemented, first we will list the full code in Raku and we will go over its elements piece by piece. If one wants to follow along Replit offers a way to see the Raku language in action without any installation. For a local installation the Rakudo implementation or the Rakudo Star bundle are great starting points.

grammar ParrotHearing {
    rule TOP { <personalPronoun> <parrotVerb> <parrotNoun><ending>}
    token personalPronoun { "I" | "You" | "He" | "She" | "It" | "We" | "They"| "Me" | "Him" | "Her" | "Us" | "Them" }
    token parrotVerb { \w+ }
    token parrotNoun { \w+ }
    token ending { "." | "!" | "?" }
}

sub convertToEmoji($string)
{
    my $parsedValue = uniparse($string.uc);
    if ($parsedValue) {return $parsedValue}
    else {return $string}
}

class EmojiActions {

    method TOP ($/) {
        my $personalPronoun = $<personalPronoun>;
        my $parrotVerb= $<parrotVerb>.made;
        my $parrotNoun= $<parrotNoun>.made;
        my $ending= $<ending>;
        make ( "$personalPronoun $parrotVerb $parrotNoun$ending")
    }

    method parrotVerb ($/) {
       make (convertToEmoji($/))
    }

    method parrotNoun($/) {
       make (convertToEmoji($/))
    }
}

sub MAIN($name) {

    my $parrotParsed =  ParrotHearing.parse($name.Str, actions => EmojiActions);
    if ($parrotParsed) {say $parrotParsed.Str;
                       say $parrotParsed.made.Str}
    else {say "Squaaawk can not understand!"};
}

The first part of the implementation is defining the Grammar of the language that the parrot hears that we call ParrotHearing. Grammars in Raku are a way to organize Regular Expressions (Regexes). Without going into the formal definition of Regular Expressions they can be used to define patterns of characters in text. These patterns can be utilized in various way, such as searching, replacing and transforming text.

The basic elements of the grammar are regex objects. These contain the regex patterns that we use. In Raku these regular expressions are defined with their own domain specific language. For example one regex object our grammar will be using is { \w+ }. The pattern in this object, \w+, matches a single word character, consisting of letters, digits or underscores, one or more times. For example if we have the text "These are all w0rds_ " it will match four times.

Another regex object that we will use is { "I" | "You" | "He" | "She" | "It" | "We" | "They"| "Me" | "Him" | "Her" | "Us" | "Them" } which contains a pattern that will match any of the listed personal pronouns that is capitalized. This means that it will match the text of "You", "They" and "Them", but not "i", "apple" or "them".

Next let's take a look at how the ParrotHearing grammar is organizing the regular expressions. The sentences that that this grammar defines are very basic: they have the form of a personal pronoun, followed by verb, a noun and an ending. In fact, the verbs and nouns that the Parrot accepts are very simplified: they are the pattern \w+ that we described above that would match most words. The personal pronouns are those that the regex object { "I" | "You" | "He" | "She" | "It" | "We" | "They"| "Me" | "Him" | "Her" | "Us" | "Them" } will match while the ending is one of the ., ! and ? characters.

The final piece is a function that replaces the nouns and verbs in a sentence with the emoji equivalent if it can. If no replacement can be found it leaves the text unchanged. When parsing we can use this function as part of an action to perform. This allows us to tie everything together in our MAIN function (also called a subroutine in Raku). This parses in the text from the command line, prints the text twice (can be done by the say function in Raku). The first time it will print the text and the second time it will print the text with the emoji replacements. In the case when it can not parse the input it will print "Squaaawk can not understand!" instead.

To run everything, if the code is in a file called main.raku one can call raku main.raku "I love sushi." or some other sentence to get started. Feel free to give it a try on Replit to see what the code parrots back!