Raku Books / Perl 6 at a Glance / Regexes and Grammars
Calculator
When considering language parsers, implementing a calculator is like writing a “Hello, World!” programme. In this section, we will create a grammar for the calculator that can handle the four arithmetical operations and parentheses. The hidden advantage of the calculator example is that you have to teach it to follow the operations priority and nested expressions.
Our calculator grammar will expect the single expression at a top level. The priority of operations will be automatically achieved by the traditional approach to grammar construction, in which the expression consists of terms and factors.
The terms are parts separated by pluses and minuses:
<term>+ %% ['+'|'-']Here, Perl 6’s %% symbol is used. You may also rewrite
the rule using more traditional quantifiers:
<term> [['+'|'-'] <term>]*Each term is, in turn, a list of factors separated by the symbols for multiplication or division:
<factor>+ %% ['*'|'/']CodeBlockPlaceholder4raku-static rule group { ‘(’
This rule refers to the expression rule and thus can start another recursion loop.
It’s time to introduce the enhancement of the value
token so that it accepts the floating point values. This task is easy;
it only requires creating a regex that matches the number in as many
formats as possible. I will skip the negative numbers and the numbers in
scientific notation.
token value {
| \d+['.' \d+]?
| '.' \d+
}Here is the complete grammar of the calculator:
grammar Calc {
rule TOP {
^ <expression> $
}
rule expression {
| <term>+ %% $<op>=(['+'|'-'])
| <group>
}
rule term {
<factor>+ %% $<op>=(['*'|'/'])
}
rule factor {
| <value>
| <group>
}
rule group {
'(' <expression> ')'
}
token value {
| \d+['.' \d+]?
| '.' \d+
}
}Note the $<op>=(...) construction in some of the
rules. This is the named capture. The name simplifies the access to a
value via the $/ variable. In this case, you can reach the
value as $<op>, and you don’t have to worry about the
possible change of the variable name after you update a rule as it
happens with the numbered variables $0, $1,
etc.
Now, create the actions for the compiler. At the TOP
level, the rule returns the calculated value, which it takes from the
ast field of the expression.
class CalcActions {
method TOP($/) {
$/.make: $<expression>.ast
}
. . .
}The actions of the underlying rules groups and
value are as simple as we’ve just seen.
method group($/) {
$/.make: $<expression>.ast
}
method value($/) {
$/.make: +$/
}CodeBlockPlaceholder10raku-static method factor($/) { if $
Move on to the term action. Here, we have to take care
of the list with its variable length. The rule’s regex has the
+ quantifier, which means that it can capture one or more
elements. Also, as the rule handles both the multiplication and the
division operators, the action must distinguish between the two cases.
The $<op> variable contains either the *
or the / character.
This is how the syntax tree looks like for the string with three
terms, 3*4*5:
expression => 「3*4*5」
term => 「3*4*5」
factor => 「3」
value => 「3」
op => 「*」
factor => 「4」
value => 「4」
op => 「*」
factor => 「5」
value => 「5」CodeBlockPlaceholder13raku-static method term($/) { my $result =
$
if $<op> {
my @ops = $<op>.map(~*);
my @vals = $<factor>[1..*].map(*.ast);
for 0..@ops.elems - 1 -> $c {
if @ops[$c] eq '*' {
$result *= @vals[$c];
}
else {
$result /= @vals[$c];
}
}
}
$/.make: $result;
} CodeBlockPlaceholder14raku-static my @ops = $
The values themselves will land in the @vals array. To
ensure that the values of the two arrays, @vals and
@ops, correspond to each other, the slice of
$<factor> is taken, which starts at the second
element:
my @vals = $<factor>[1..*].map(*.ast);CodeBlockPlaceholder17raku-static method expression($/) { if $
if $<op> {
my @ops = $<op>.map(~*);
my @vals = $<term>[1..*].map(*.ast);
for 0..@ops.elems - 1 -> $c {
if @ops[$c] eq '+' {
$result += @vals[$c];
}
else {
$result -= @vals[$c];
}
}
}
$/.make: $result;
}
} CodeBlockPlaceholder18raku-static my $calc = Calc.parse( @*ARGS [0], :actions(CalcActions) ); say $calc.ast; CodeBlockPlaceholder19
Let’s see if it works.
$ perl6 calc.pl '39 + 3.14 * (7 - 18 / (505 - 502)) - .14'
42It does.
CodeBlockPlaceholder21raku-static
x = 40 + 2; print x;
y = x - (5/2); print y;
z = 1 + y * x; print z;
print 14 - 16/3 + x; ```
Course navigation
← AST and attributes | Appendix →