package Rpn; use strict; use warnings; sub evaluate { my ($expr) = @_; my @stack; for my $tok (split ' ', $expr) { if ($tok =~ /^-?\d+$/) { push @stack, $tok; next; } my $x = pop @stack; defined $x or die "Stack underflow\n"; my $y = pop @stack; defined $y or die "Stack underflow\n"; if ($tok eq '+') { push @stack, $y + $x; } elsif ($tok eq '-') { push @stack, $y - $x; } elsif ($tok eq '*') { push @stack, $y * $x; } elsif ($tok eq '/') { push @stack, int($y / $x); } else { die "Invalid token:\"$tok\"\n"; } } @stack == 1 or die "Invalid stack:[@stack]\n"; return $stack[0]; } 1; #### use strict; use warnings; use Test::More; use Rpn; my @normal_tests = ( [ '1 2 +', 3 ], [ '1 -2 -', 3 ], [ '-1 2 +', 1 ], [ '1 2 -', -1 ], [ '1 2 + 3 -', 0 ], [ '1 2 - 3 -', -4 ], [ '1 2 - 5 +', 4 ], [ '1 2 - 5 + 2 -', 2 ], [ '1 1 1 1 1 2 + + + + +', 7 ], [ '1 -5 +', -4 ], [ '5 3 *', 15 ], [ '-2 -5 *', 10 ], [ '2 -5 *', -10 ], [ '6 4 /', 1 ], [ '0 1 /', 0 ], [ '1 0 *', 0 ], [ '00 1 +', 1 ], [ '1 00 -', 1 ], [ '00', 0 ], [ '-00', 0 ], [ '1 2 3 * +', 7 ], [ '3 4 * 2 3 * +', 18 ], [ '3 4 * 2 / 3 *', 18 ], [ '3 4 * 5 / 3 *', 6 ], [ '999999 1000 / 67 * 56 80 * 8 * -', 31093 ], [ '42', 42 ], ); my @exception_tests = ( [ '5 4 %', "Invalid token:\"%\"\n" ], [ '5 +', "Stack underflow\n" ], [ '+', "Stack underflow\n" ], [ '5 4 + 42', "Invalid stack:[9 42]\n" ], [ '', "Invalid stack:[]\n" ], ); plan tests => @normal_tests + @exception_tests; for my $t (@normal_tests) { cmp_ok(Rpn::evaluate($t->[0]), '==', $t->[1]); } for my $t (@exception_tests) { eval { Rpn::evaluate($t->[0]) }; is($@, $t->[1]); } #### module Rpn-0.0.1-cpan:ASAVIGE; sub evaluate (Str $expr) returns Int { my @stack; for ($expr.split()) -> $tok { if $tok ~~ rx:Perl5/^-?\d+$/ { @stack.push($tok); next; } my $x = @stack.pop() err die "Stack underflow\n"; my $y = @stack.pop() err die "Stack underflow\n"; given $tok { when '+' { @stack.push($y + $x) } when '-' { @stack.push($y - $x) } when '*' { @stack.push($y * $x) } when '/' { @stack.push(int($y / $x)) } default { die "Invalid token:\"$tok\"\n" } } } @stack.elems == 1 or die "Invalid stack:[@stack[]]\n"; return @stack[0]; } #### #!/usr/bin/pugs use v6; use Test; use Rpn; my @normal_tests = ( [ '1 2 +', 3 ], [ '1 -2 -', 3 ], [ '-1 2 +', 1 ], [ '1 2 -', -1 ], [ '1 2 + 3 -', 0 ], [ '1 2 - 3 -', -4 ], [ '1 2 - 5 +', 4 ], [ '1 2 - 5 + 2 -', 2 ], [ '1 1 1 1 1 2 + + + + +', 7 ], [ '1 -5 +', -4 ], [ '5 3 *', 15 ], [ '-2 -5 *', 10 ], [ '2 -5 *', -10 ], [ '6 4 /', 1 ], [ '0 1 /', 0 ], [ '1 0 *', 0 ], [ '00 1 +', 1 ], [ '1 00 -', 1 ], [ '00', 0 ], [ '-00', 0 ], [ '1 2 3 * +', 7 ], [ '3 4 * 2 3 * +', 18 ], [ '3 4 * 2 / 3 *', 18 ], [ '3 4 * 5 / 3 *', 6 ], [ '999999 1000 / 67 * 56 80 * 8 * -', 31093 ], [ '42', 42 ], ); my @exception_tests = ( [ '5 4 %', "Invalid token:\"%\"\n" ], [ '5 +', "Stack underflow\n" ], [ '+', "Stack underflow\n" ], [ '5 4 + 42', "Invalid stack:[9 42]\n" ], [ '', "Invalid stack:[]\n" ], ); plan @normal_tests.elems + @exception_tests.elems; for @normal_tests -> $t { cmp_ok(Rpn::evaluate($t[0]), &infix:<==>, $t[1]); } for @exception_tests -> $t { try { Rpn::evaluate($t[0]) }; is($!, $t[1]); } #### {-# OPTIONS_GHC -fglasgow-exts -Wall #-} module Rpn (evaluate) where import Char isStrDigit :: String -> Bool isStrDigit = all isDigit -- Check that a string matches regex /^-?\d+$/. isSNum :: String -> Bool isSNum [] = False isSNum "-" = False isSNum ('-':xs) = isStrDigit xs isSNum xs = isStrDigit xs calc :: Int -> String -> Int -> Int calc x "+" y = x+y calc x "-" y = x-y calc x "*" y = x*y calc x "/" y = x`div`y calc _ tok _ = error $ "Invalid token:" ++ show tok evalStack :: [Int] -> String -> [Int] evalStack xs y | isSNum y = (read y):xs | (a:b:cs) <- xs = (calc b y a):cs | otherwise = error "Stack underflow" evaluate :: String -> Int evaluate expr | [e] <- el = e | otherwise = error $ "Invalid stack:" ++ show el where el = foldl evalStack [] $ words expr #### {-# OPTIONS_GHC -fglasgow-exts -Wall #-} -- t1.hs: build with: ghc --make -o t1 t1.hs Rpn.hs module Main where import Test.HUnit import Control.Exception import Rpn type NormalExpected = (String, Int) makeNormalTest :: NormalExpected -> Test makeNormalTest e = TestCase ( assertEqual "" (snd e) (Rpn.evaluate (fst e)) ) normalTests :: Test normalTests = TestList ( map makeNormalTest [ ( "1 2 +", 3 ), ( "1 -2 -", 3 ), ( "-1 2 +", 1 ), ( "1 2 -", -1 ), ( "1 2 + 3 -", 0 ), ( "1 2 - 3 -", -4 ), ( "1 2 - 5 +", 4 ), ( "1 2 - 5 + 2 -", 2 ), ( "1 1 1 1 1 2 + + + + +", 7 ), ( "1 -5 +", -4 ), ( "5 3 *", 15 ), ( "-2 -5 *", 10 ), ( "2 -5 *", -10 ), ( "6 4 /", 1 ), ( "0 1 /", 0 ), ( "1 0 *", 0 ), ( "00 1 +", 1 ), ( "1 00 -", 1 ), ( "00", 0 ), ( "-00", 0 ), ( "1 2 3 * +", 7 ), ( "3 4 * 2 3 * +", 18 ), ( "3 4 * 2 / 3 *", 18 ), ( "3 4 * 5 / 3 *", 6 ), ( "999999 1000 / 67 * 56 80 * 8 * -", 31093 ), ( "42", 42 ) ]) -- Exception wrapper for Rpn.evaluate -- The idea is to catch calls to the error function and verify -- that the expected error string was indeed written. evaluateWrap :: String -> IO String evaluateWrap x = do res <- tryJust errorCalls (Control.Exception.evaluate (Rpn.evaluate x)) case res of Right r -> return (show r) Left r -> return r type ExceptionExpected = (String, String) makeExceptionTest :: ExceptionExpected -> Test makeExceptionTest e = TestCase ( do x <- evaluateWrap (fst e) assertEqual "" (snd e) x ) exceptionTests :: Test exceptionTests = TestList ( map makeExceptionTest [ ( "5 4 %", "Invalid token:\"%\"" ), ( "5 +", "Stack underflow" ), ( "+", "Stack underflow" ), ( "5 4 + 42", "Invalid stack:[42,9]" ), ( "", "Invalid stack:[]" ) ]) main :: IO Counts main = do runTestTT normalTests runTestTT exceptionTests #### (Control.Exception.evaluate (Rpn.evaluate x)) #### f (x:y:zs) "+" = y+x:zs #### import Debug.Trace -- ... f (x:y:zs) "+" = trace ("+" ++ show x ++ ":" ++ show y ++ ":" ++ show zs) (y+x:zs) #### autrijus stares at type Eval x = forall r. ContT r (ReaderT x IO) (ReaderT x IO x) and feels very lost Didn't you write that code? yeah. and it works I just don't know what it means.