Action Classes and Building Parsers
Each action method is called when its token successfully matches, with the Match object as $/. make attaches a value (an AST node or computed result), retrieved with .made.
Pattern: grammar = syntax; actions = semantics (build an AST, then evaluate/compile it separately).
grammar Arith {
rule TOP { <expr> }
rule expr { <term>+ % <add-op> }
rule term { <factor>+ % <mul-op> }
rule factor { <number> | '(' <expr> ')' }
token add-op { '+' | '-' }
token mul-op { '*' | '/' }
token number { \d+ ['.' \d+]? }
}
class ArithActions {
method TOP($/) { make $<expr>.made }
method expr($/) { make self!fold($<term>».made, $<add-op>».Str) }
method term($/) { make self!fold($<factor>».made, $<mul-op>».Str) }
method factor($/) { make $<number> ?? $<number>.made !! $<expr>.made }
method number($/) { make +$/ }
method !fold(@values, @ops) {
my $result = @values.head;
for @ops Z @values.skip -> ($op, $v) {
$result = do given $op {
when '+' { $result + $v }
when '-' { $result - $v }
when '*' { $result * $v }
when '/' { $result / $v }
}
}
$result
}
}
for '2 + 3 * 4', '(2 + 3) * 4', '10 / 4 - 1' -> $input {
my $m = Arith.parse($input, actions => ArithActions);
say "$input = {$m.made}";
}
# 2 + 3 * 4 = 14
# (2 + 3) * 4 = 20
# 10 / 4 - 1 = 1.530.1 Error Reporting
grammar Strict is Arith {
method error($msg) {
my $parsed = self.target.substr(0, self.pos).trim-trailing;
my $line = $parsed.lines.elems || 1;
die "Parse error at line $line: $msg";
}
rule factor { <number> | '(' <expr> [ ')' || <.error("expected ')'")> ] }
}
try Strict.parse('(1 + 2', actions => ArithActions);
say $!.message if $!; # Parse error at line 1: expected ')'