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

30.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 ')'