📘 Basic calculator written in Perl 6

Create a program that calculates mathematical operations with two operands, for example: 4 + 5.3 or 7.8 / 3.

In this task, we will only limit the solution for the simplest case with only one operation so that there are no issues with the precedence order or parentheses.

Let’s first make a solution with regexes and then transform it so it uses Perl 6’s grammars.

my $expression = "14 * 16.4";
$expression ~~ /
    (<[-\d\.]>+) \s* 
    ('+' | '-' | '*' | '/') \s* 
    (<[-\d\.]>+)
/;
given $1 {
    when '+' {say $0 + $2}
    when '-' {say $0 - $2}
    when '*' {say $0 * $2}
    when '/' {say $0 / $2};
}    

The regular expression in this example has three capturing parentheses. The first and the third of them match numbers with an optional minus sign and a floating-point dot. We do not check the number format too strictly to keep the program simple, however.

The construction <[...]> is a character class in Perl 6 regexes. It lists accepted characters. In our case, those are the digits represented by \d, dots \., and minus -. Optional spaces around the operator are matched by \s*.

The operator character is an alternative: '+' | '-' | '*' | '/'.

After the successful match, the parts of $expression appear in the variables $0, $1, and $2. These are the shortcuts for the elements of the match objects: $/[0]$/[1], and $/[2]. Each element is also an object of the Match type.

The given statement chooses one of the when branches depending on the value in $1. Here, an implicit type conversion happens. First, the match object from $1 is coerced to a string before comparing it with one of the operator characters. Second, the $0 and $1 objects are converted to numbers, which are used in actual calculations. Run the program a few times with different expressions and see how it works.

Now, let us solve the problem differently, using grammars. Here is the grammar:

grammar calc {
    rule TOP {
        <value> <operator> <value> {
            given $<operator> {
                when '+' {say $<value>[0] + $<value>[1]}
                when '-' {say $<value>[0] - $<value>[1]}
                when '*' {say $<value>[0] * $<value>[1]}
                when '/' {say $<value>[0] / $<value>[1]}
            }
        }
    }
    token value {
        '-'? \d+ [ '.' \d+ ]?
    }
   token operator {
        '+' | '-' | '*' | '/'
    }
}

And, here is how you use it to parse an expression:

my $expression = "14 * 16.2";
calc.parse($expression); # Prints 226.8

The definition of the grammar contains the TOP rule and two tokens: value and operator. When the parse method is called, the grammar starts parsing the text using the TOP rule, which says that the text should be a value, followed by the operator symbol, followed by another value.

This time, the regex for the values is a bit smarter to make sure the minus sign can appear only before digits, and the decimal point should be seen only once. Still, there is some room to improve the regex.

When using the grammar rules (such as TOP), you don’t need to explicitly care about the optional spaces before parts of the expression. It means that both '14 * 16.2' and '14*16.2' strings are accepted.

The TOP rule has three parts: <value> <operator> <value>.

After they all match, the action code block is triggered. It contains a given-whenblock, similar to the one used earlier in the solution with regexes.

Now, the string values of the expression parts are taken from the match object, which is treated as a hash. For example, the operator character is located in the $<operator> object, which is a shortcut for $/<operator>. However, there are two <value>s in the TOP rule. In this case, the match object contains a list of other objects, which you can access via indices:

say $<value>[0] + $<value>[1]

Again, before using the values, the match objects are converted by Perl 6 to numbers.

📘 Checking balanced parentheses using Perl 6

Check if the parentheses in a given string are balanced, i. e., whether every opening parenthesis has the corresponding closing one.

Let us limit the input strings with the strings containing parentheses () only and no other kinds of brackets {}[], or <>. The text in between contains only letters and spaces. Empty parentheses are not allowed.

Prepare the test suite with different cases—a series of balanced examples and a series of strings with unbalanced parentheses.

my @tests = 'a',         '(a)',      '(a b c)', 'a (b)',
            '(b) a',     '(b (a))',  '( ( c))', 'a(b)c',

            'a (', 'a)', '(a) b c)', 'a b)',    '(b a',
            '((b (a))',  '((c)',    '(((a(((', ')a(';

For such a task, the best tool available in Perl 6 is grammars. Here is a simple grammar description that can recursively parse the above examples.

grammar Balanced {
    rule TOP {
        <expression>+
    }
    rule expression {
        | <:alpha>+ <expression>?        
        | '(' ~ ')' <expression>   
    }
}

The TOP rule says that the sentence is at least one expression. An expression is either a few letters (<:alpha>+) optionally followed by another expression or an expression in parentheses.

Notice the way parentheses are introduced in the expression rule:

'(' ~ ')' <expression>

This syntax allows keeping the opening and closing characters together and is synonymous with the following rule:

'(' <expression> ')'

Now we can parse the strings from the test suit with the Balanced grammar.

for @tests -> $test {    
    my $result = Balanced.parse($test);
    printf("%-12s is%s balanced\n", 
           $test,
           $result ?? '' !! ' not');
}

Depending on the success of parsing a string, the $result variable either contains a Match object or Nil. In the Boolean context, it is either True or False, and it defines what message is printed.

a            is balanced
(a)          is balanced
(a b c)      is balanced
a (b)        is balanced
(b) a        is balanced
(b (a))      is balanced
( ( c))      is balanced
a(b)c        is balanced
a)           is not balanced
(a) b c)     is not balanced
a b)         is not balanced
(b a         is not balanced
((b (a))     is not balanced
((c)         is not balanced
(((a(((      is not balanced
a (          is not balanced
)a(          is not balanced

📘 Decoding Roman numerals using Perl 6

Convert a string, with a Roman number, to a decimal number.

The task is opposite to Task 46, Convert to Roman numerals, but let’s use grammars to solve it. The idea is to directly find the sequences of Roman digits that correspond to thousands, hundreds, tens, and ones. For example, as soon as the program sees LXX, it knows that it is equal to 70. We are not analysing which letters go on the left or right of the given one. Instead, the result is achieved directly.

Here is a complete program, the biggest part of which is the grammar class. It uses a global variable $n for accumulating the decimal number during the parsing.

my $n = 0;
grammar Roman {
    token TOP {
        <thousands>? <hundreds>? <tens>? <ones>?
    }

    token thousands {
        | M    { $n += 1000 }   | MM  { $n += 2000 }
        | MMM  { $n += 3000 }   | MMMM { $n += 4000 }
    }

    token hundreds {
        | C    { $n += 100 }    | CC  { $n += 200 }
        | CCC  { $n += 300 }    | CD  { $n += 400 }
        | D    { $n += 500 }    | DC  { $n += 600 }
        | DCC  { $n += 700 }    | DCCC { $n += 800 }
        | CM   { $n += 900 }
    }

    token tens {
        | X    { $n += 10 }     | XX  { $n += 20 }
        | XXX  { $n += 30 }     | XL  { $n += 40 }
        | L    { $n += 50 }     | LX  { $n += 60 }
        | LXX  { $n += 70 }     | LXXX { $n += 80 }
        | XC   { $n += 90 }
    }

    token ones {
        | I    { $n += 1 }      | II  { $n += 2 }
        | III  { $n += 3 }      | IV  { $n += 4 }
        | V    { $n += 5 }      | VI  { $n += 6 }
        | VII  { $n += 7 }      | VIII { $n += 8 }
        | IX   { $n += 9 }
    }
}

my $roman = 'MMXVIII';
Roman.parse($roman);
say $n; # 2018

The TOP token of the grammar describes how the Roman number is built. A Roman number is a sequence of thousands, hundreds, tens, and ones. All these parts are optional: <thousands>? <hundreds>? <tens>? <ones>?.

Then, the grammar defines the tokens for each individual part. Their structure is similar: It is a set of alternatives; examine, for instance, the ones token: I | II | III | IV | V | VI | VII | VIII | IX.

Each branch of alternatives is equipped with a simple code block that updates the value of the global variable $n. To make the grammar reusable, add the { $n = 0 } block at the beginning of TOP. As homework, convert the grammar to use $/.make and $/.made methods of the match object to collect the parts of the value without using a global variable.

📘 %Templating% engine written in Perl 6

Implement a simple templating engine, which substitutes values in placeholders of the form %name%.

The objective is to create a function that takes a template string and a hash with named values and does the substitution. So, let us prepare and pass them to a function. Notice that, in Perl 6, it is possible to pass a hash as easy as you pass a scalar (see also Task 65, Passing arrays to subroutines).

my $template = 'Hello, %name%! Welcome to %city%!';
my %data = (
    name => 'Klara',
    city => 'Karlovy Vary',
);

say process_template($template, %data);

Inside the function, the hash is passed as a single hash variable.

sub process_template($template is copy, %data) {
    $template ~~ s:g/ ‘%’ (\w+) ‘%’ /%data{$0}/;
    return $template;
}

The function modifies the value of the first argument; this is why it is marked with the is copy trait.

The regex is global, which is turned on by the :g regex adverb. A regex is looking for words between the two percentage symbols. In Perl 6, non-alphanumeric characters must be quoted or escaped, so both '%' and \% are accepted. The second part of the replacement is using the matched value as the key for fetching the value from the %data hash.

📘 Simple string compressor written in Perl 6

Convert a string containing repeating characters to a string, where each repetition is represented by the character and the number of its copies.

For example, the original string abccccdefffffggghhi converts to the compressed string abc4def5g3h2i.

my $str = 'abccccdefffffggghhi';

$str ~~ s:g/
        ( (<:alpha>) $0+ )
    /{
        $0[0] ~ $0.chars
    }/;

say $str; abc4def5g3h2i

The global replacement finds the parts of the string with repeated characters. The tricky part in the regex is the way in which capturing parentheses are counted.

The naïve regex <:alpha>+ matches any letter sequence and consumes the whole string. Thus, only one character must be captured: (<:alpha>). Now, the regex should demand repetitions of that character: $0+, but we also need to capture it as we have to know the length of it.

It is not possible to say (<:alpha>)($0+), as $0 is referring to the capturing part in the second parentheses. The final regex contains nested capturing parentheses. The $0 match object keeps the whole repeated sequence and the array with one element that holds the first matched character. The replacement part uses both elements to build the result: $0[0] ~ $0.chars.

📘 Pig Latin using Perl 6

Convert the given text to Pig Latin.

Pig Latin is a pseudo-language, each word of which is derived from the corresponding English word, following a couple of simple rules:

  1. If the word starts with consonant letters (including consonant sounds represented by letter combinations such as qu), move them all to the end of the word.
  2. Append the ayending.

Here is a program that implements this algorithm.

my $text = prompt('English > ');

$text ~~ s:i:g/ << ([qu]? <-[aeiou]>+) (\w*) >> /$1$0/;
$text ~~ s:i:g/ << (\w+) >> /$0ay/;

say $text; # you are welcome ouyay areay elcomeway

For simplicity, both steps are done via their own regex replacements. The first one finds the words that starts with either quor with a character that is not a vowel (in other words, which is neither a,eio, or u). The two captured parts of the word are switched in the replacement part: $1$0.

The second substitution instruction finds all the words (at this point, the words that had initially started with consonants are already modified and the words, starting with vowels, stay original) and appends the ay ending to it. 

The << and >> anchors bind the regexes to word borders.

As an exercise, modify the program to take care of capital letters in the original sentence so that You becomes Ouyay and not ouYay.

📘 Increasing digits by one using Perl 6

In the given integer number, replace all the digits so that 1 becomes 2, 2 becomes 3, etc., and 9 becomes 0.

This task can be approached both mathematically and string-wise. In Perl 6, regexes seem to be the best match. Although a number is an example of the numeric data type, Perl 6 allows seamless changes when you want to start working with them as with strings.

In the proposed solution, a number is matched against a regex.

my $number = 564378901;
$number ~~ s:g/ (\d) /{ ($0 + 1) % 10 }/;
say $number;

Similar to the code of Task 76, Double each character, a global replacement happens. The target is a digit, \d. (We ignore the fact that the \d character class matches 580 different characters in the Unicode space, see Task 39, Unicode digits.)

So, a digit (treated as a character) lands in the $0 variable. In the replacement part of s///, a block of code is placed. It takes the value of $0 and adds 1 to it. As the + operator expects numeric operands, the character is converted to an integer and is incremented after it. The modulo operator keeps the value in the range between 0 and 9 (including), The new value is converted back to a string character, which replaces the original digit that was captured.

In the given example, 675489012 is printed.Notice that it was not possible to use an increment operator in the replacement. An attempt to make it $0++ would lead to an exception as the ++ operator needs a mutable object.