The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
use 5.010;
use warnings;
use Test::More 'no_plan';

use List::Util qw< reduce >;

sub translator {
    my ($errormsg, $rulename, $context) = @_;

    if (substr($rulename,0,1) eq '-') {
        $rulename = substr($rulename,1);
        return "You forgot to define $rulename. :-(";
    }

    if ($errormsg eq q{}) {
        if ($rulename) {
            return "<$rulename> failed to match '$context'";
        }
        else {
            return "Main pattern failed to match '$context'";
        }
    }

    if (lc(substr($errormsg,0,6)) eq 'wanted') {
        return "$errormsg, but was given '$context'. What's up with that?";
    }

    return $errormsg;
}

my $calculator = do{
    use Regexp::Grammars;

    qr{ 
        \A 
        <Answer>
        (?:
            \Z
        |
            <warning: (?{ "Extra junk after expression at index $INDEX: '$CONTEXT'" })>
            <warning: Wanted end of input>
            <error:>
        )

        <rule: Answer>  
            <[_Operand=Mult]> ** <[_Op=(\+|\-)]>
                (?{ $MATCH = shift @{$MATCH{_Operand}};
                    for my $term (@{$MATCH{_Operand}}) {
                        my $op = shift @{$MATCH{_Op}};
                        if ($op eq '+') { $MATCH += $term; }
                        else            { $MATCH -= $term; }
                    }
                })
          |
            <Trailing_stuff>

        <rule: Mult> 
        (?:
            <[_Operand=Pow]> ** <[_Op=(\*|/|%)]>
                (?{ $MATCH = reduce { eval($a . shift(@{$MATCH{_Op}}) . $b) }
                                    @{$MATCH{_Operand}};
                })
        )

        <rule: Pow> 
        (?:
            <[_Operand=Term]> ** <_Op=(\^)> 
                (?{ $MATCH = reduce { $b ** $a } reverse @{$MATCH{_Operand}}; })
        )

        <rule: Term>
        (?:
               <MATCH=Literal>
          | \( <MATCH=Answer> \)
        )

        <rule: Trailing_stuff>
            <...>

        <token: Literal>
            <error:>
        |
            <MATCH=( [+-]? \d++ (?: \. \d++ )?+ )>

    }xms
};

local $/ = "";
{
    my $temp = Regexp::Grammars::set_error_translator(\&translator);
    while (my $input = <DATA>) {
        chomp $input;
        my ($text, $expected) = split /\s+/, $input, 2;
        if ($text =~ $calculator) {
            is $/{Answer}, $expected => "Input $.: $text"; 
        }
        else {
            is_deeply \@!, eval($expected), => "Input $.: $text";
        }
    }
}

{
    use Regexp::Grammars;

    if ('foo' =~ m{ <Answer> <rule: Answer> <...> }xms) {
        fail    'Restore default translator';
    }
    else {
        is_deeply \@!, ["Can't match subrule <Answer> (not implemented)"]
            => 'Restore default translator';
    }
}

__DATA__
2           2

2*3+4       10

2zoo        [
             "Extra junk after expression at index 1: 'zoo'",
             "Wanted end of input, but was given 'zoo'. What's up with that?",
             "Main pattern failed to match 'zoo'",
             "You forgot to define Trailing_stuff. :-(",
            ]

zoo         [
             "<Literal> failed to match 'zoo'",
             "You forgot to define Trailing_stuff. :-(",
            ]