📘 Computing leap years in Perl 6

Tell if the given year is leap or common.

The algorithm for detecting whether the year is leap includes a few divisibility tests. Take an extract in the pseudocode from Wikipedia:

if (year is not divisible by 4) then (it is a common year)
else if (year is not divisible by 100) then (it is a leap year)
else if (year is not divisible by 400) then (it is a common year)
else (it is a leap year)

It is possible to implement the above sequence of ifs and elses in Perl 6, but it is a better idea to join conditions using the logical operators.

my $year = 2018;
say ($year %% 400 or $year % 100 and $year %% 4) ??
    'Leap' !! 'Common';

Notice that both the modulo % and divisibility %% operators are used, which allow avoiding Boolean negations in the sub-conditions.

The following program prints the list of leap years in the range 1800–2400:

for 1800 .. 2400 -> $year {
    say $year if $year %% 400 or $year % 100 and $year %% 4;
}

There may be some considerations regarding the efficiency of the sequence of the checks because each year is first tested against 400, while it may be more optimal to check first if the year is divisible by 4. If this becomes an important argument, then the ifelse chain may be more efficient. To achieve an even higher speed, a pre-calculated array of leap years is better.

📘 Datetime arithmetic in Perl 6

Find the difference between the two dates. Add a given number of days to the date.

The DateTime class in Perl 6 defines the + and  operators, which can be used in combination with either another DateTime object or with the Duration object.

Let us first find the difference between the two given dates:

my $date1 = DateTime.new('2017-12-31T23:59:50');
my $date2 = DateTime.new('2018-01-01T00:00:10');

say $date2 - $date1; # 20

The type of the $date2 - $date1 expression is Duration. In the numeric context, it returns the number of seconds. Therefore, there are 20 seconds between our dates at hand.

Duration object can also be used instead of the second DateTime object.

For example, increase the given date by two minutes:

my $now = DateTime.now();
my $when = $now + Duration.new(120);
say "$now -> $when";

Or learn what were the date and time a week ago:

my $back = $now - Duration.new(3600 * 24 * 7);
say $back;

In the current design of the language, the constructor of the Duration class needs a number of seconds.

📘 Formatted date in Perl 6

Print the current date in an even better format.

In Perl 6, there is a built-in DateTime class. It is equipped with a few useful methods, so there’s no need to use external modules for many standard tasks. Save the current moment in the $now variable:

my $now = DateTime.now;

The easiest thing is to print the proper time using a dedicated method:

say $now.hh-mm-ss; # 22:37:16

To achieve a more granular control over both the date and time parts, use the formatter attribute of the constructor together with methods like day and month to get separate parts of the date and time (see Task 87, Current date and time): 

my $now = DateTime.now(
    formatter => {
        sprintf '%02d.%02d.%04d, %02d:%02d', 
        .day, .month, .year, .hour, .minute
    }
);

say $now; # 14.10.2017, 22:41

The formatting string '%02d.%02d.%04d, %02d:%02d' uses the standard POSIX printf format. The sprintf function forms a string that is returned when a DateTime object (the $now variable, in our case) is stringified. For instance, it is used when the variable is interpolated in a string:

say "Current date and time: $now.";

📘 Current date and time in Perl 6

Print current date and time as an epoch and in a human-readable format.

In Perl 6, the time function returns the current time as the Unix epoch:

say time;

The output is something like this: 1495785518.

For manipulating dates and times, use the built-in DateTime class:

say DateTime.now;

The date is now in a more human-readable format, although still is overloaded with many details: 2017-05-26T10:02:20.500209+02:00.

To access the separate elements of date and time, use the methods on the variable of the DateTime class:

my $dt = DateTime.now;

say $dt.day;   # 26
say $dt.month; # 5
say $dt.year;  # 2017
say $dt.hour;  # 10
say $dt.minute; # 9
say $dt.second; # 5.55802702903748

The meaning of all the elements is quite straightforward.

All the values except seconds are integer. For the seconds, you may want to take the integer part only:

say $dt.second.Int;

📘 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.