1use 5.010;
2use warnings;
3use Test::More 'no_plan';
4
5use List::Util qw< reduce >;
6
7my $calculator = do{
8    use Regexp::Grammars;
9    qr{
10        \A
11        <Answer>
12        (?:
13            \Z
14        |
15            <warning: (?{ "Extra junk after expression at index $INDEX: '$CONTEXT'" })>
16            <warning: Expected end of input>
17            <error:>
18        )
19
20        <rule: Answer>
21            <[_Operand=Mult]> ** <[_Op=(\+|\-)]>
22                (?{ $MATCH = shift @{$MATCH{_Operand}};
23                    for my $term (@{$MATCH{_Operand}}) {
24                        my $op = shift @{$MATCH{_Op}};
25                        if ($op eq '+') { $MATCH += $term; }
26                        else            { $MATCH -= $term; }
27                    }
28                })
29          |
30            <Trailing_stuff>
31
32        <rule: Mult>
33        (?:
34            <[_Operand=Pow]> ** <[_Op=(\*|/|%)]>
35                (?{ $MATCH = reduce { eval($a . shift(@{$MATCH{_Op}}) . $b) }
36                                    @{$MATCH{_Operand}};
37                })
38        )
39
40        <rule: Pow>
41        (?:
42            <[_Operand=Term]> ** <_Op=(\^)>
43                (?{ $MATCH = reduce { $b ** $a } reverse @{$MATCH{_Operand}}; })
44        )
45
46        <rule: Term>
47        (?:
48               <MATCH=Literal>
49          | \( <MATCH=Answer> \)
50        )
51
52        <rule:  Trailing_stuff>
53            <!!!>
54
55        <token: Literal>
56            <error:>
57        |
58            <MATCH=( [+-]? \d++ (?: \. \d++ )?+ )>
59
60    }xms
61};
62
63local $/ = "";
64while (my $input = <DATA>) {
65    chomp $input;
66    my ($text, $expected) = split /\s+/, $input, 2;
67    if ($text =~ $calculator) {
68        is $/{Answer}, $expected => "Input $.: $text";
69    }
70    else {
71        is_deeply \@!, eval($expected), => "Input $.: $text";
72    }
73}
74
75__DATA__
762           2
77
782*3+4       10
79
802zoo        [
81             "Extra junk after expression at index 1: 'zoo'",
82             "Expected end of input, but found 'zoo' instead",
83             "Expected valid input, but found 'zoo' instead",
84             "Can't match subrule <Trailing_stuff> (not implemented)",
85            ]
86
871+2zoo        [
88             "Extra junk after expression at index 1: '+2zoo'",
89             "Expected end of input, but found '+2zoo' instead",
90             "Expected valid input, but found '+2zoo' instead",
91             "Can't match subrule <Trailing_stuff> (not implemented)",
92            ]
93
94zoo         [
95             "Expected literal, but found 'zoo' instead",
96             "Can't match subrule <Trailing_stuff> (not implemented)",
97            ]
98