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