tracks/haskell/exercises/forth/test/Tests.hs in trackler-2.0.0.2 vs tracks/haskell/exercises/forth/test/Tests.hs in trackler-2.0.0.3

- old
+ new

@@ -2,77 +2,132 @@ import Control.Monad (foldM) import Test.Hspec (Spec, describe, it, shouldBe) import Test.Hspec.Runner (configFastFail, defaultConfig, hspecWith) -import Forth (ForthError(..), empty, evalText, formatStack) +import Forth (ForthError(..), empty, evalText, toList) main :: IO () main = hspecWith defaultConfig {configFastFail = True} specs specs :: Spec specs = describe "forth" $ do - -- As of 2016-10-02, there was no reference file - -- for the test cases in `exercism/x-common`. + -- Test cases adapted from `exercism/x-common/forth` on 2016-11-06. - let runTexts = fmap formatStack . foldM (flip evalText) empty + let runTexts = fmap toList . foldM (flip evalText) empty - it "no input, no stack" $ - formatStack empty `shouldBe` "" + describe "parsing and numbers" $ do + it "empty input results in empty stack" $ + toList empty `shouldBe` [] - it "numbers just get pushed onto the stack" $ - runTexts ["1 2 3 4 5"] `shouldBe` Right "1 2 3 4 5" + it "numbers just get pushed onto the stack" $ + runTexts ["1 2 3 4 5"] `shouldBe` Right [1, 2, 3, 4, 5] - it "non-word characters are separators" $ - runTexts ["1\NUL2\SOH3\n4\r5 6\t7"] `shouldBe` Right "1 2 3 4 5 6 7" + it "all non-word characters are separators" $ + runTexts ["1\NUL2\SOH3\n4\r5 6\t7"] `shouldBe` Right [1, 2, 3, 4, 5, 6, 7] - it "basic arithmetic" $ do - runTexts ["1 2 + 4 -"] `shouldBe` Right "-1" - runTexts ["2 4 * 3 /"] `shouldBe` Right "2" + describe "addition" $ do + it "can add two numbers" $ + runTexts ["1 2 +"] `shouldBe` Right [3] + it "errors if there is nothing on the stack" $ + runTexts ["+"] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 +"] `shouldBe` Left StackUnderflow - it "division by zero" $ - runTexts ["4 2 2 - /"] `shouldBe` Left DivisionByZero + describe "subtraction" $ do + it "can subtract two numbers" $ + runTexts ["3 4 -"] `shouldBe` Right [-1] + it "errors if there is nothing on the stack" $ + runTexts ["-"] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 -"] `shouldBe` Left StackUnderflow - it "dup" $ do - runTexts ["1 DUP" ] `shouldBe` Right "1 1" - runTexts ["1 2 Dup"] `shouldBe` Right "1 2 2" - runTexts ["dup" ] `shouldBe` Left StackUnderflow + describe "multiplication" $ do + it "can multiply two numbers" $ + runTexts ["2 4 *"] `shouldBe` Right [8] + it "errors if there is nothing on the stack" $ + runTexts ["*"] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 *"] `shouldBe` Left StackUnderflow - it "drop" $ do - runTexts ["1 drop" ] `shouldBe` Right "" - runTexts ["1 2 drop"] `shouldBe` Right "1" - runTexts ["drop" ] `shouldBe` Left StackUnderflow + describe "division" $ do + it "can divide two numbers" $ + runTexts ["12 3 /"] `shouldBe` Right [4] + it "performs integer division" $ + runTexts ["8 3 /"] `shouldBe` Right [2] + it "errors if dividing by zero" $ + runTexts ["4 0 /"] `shouldBe` Left DivisionByZero + it "errors if there is nothing on the stack" $ + runTexts ["/"] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 /"] `shouldBe` Left StackUnderflow - it "swap" $ do - runTexts ["1 2 swap" ] `shouldBe` Right "2 1" - runTexts ["1 2 3 swap"] `shouldBe` Right "1 3 2" - runTexts ["1 swap" ] `shouldBe` Left StackUnderflow - runTexts ["swap" ] `shouldBe` Left StackUnderflow + describe "combined arithmetic" $ do + it "addition and subtraction" $ + runTexts ["1 2 + 4 -"] `shouldBe` Right [-1] - it "over" $ do - runTexts ["1 2 over" ] `shouldBe` Right "1 2 1" - runTexts ["1 2 3 over"] `shouldBe` Right "1 2 3 2" - runTexts ["1 over" ] `shouldBe` Left StackUnderflow - runTexts ["over" ] `shouldBe` Left StackUnderflow + it "multiplication and division" $ + runTexts ["2 4 * 3 /"] `shouldBe` Right [2] - it "defining a new word" $ - runTexts [ ": dup-twice dup dup ;" - , "1 dup-twice" ] `shouldBe` Right "1 1 1" + describe "dup" $ do + it "copies the top value on the stack" $ + runTexts ["1 DUP" ] `shouldBe` Right [1, 1] + it "is case-insensitive" $ + runTexts ["1 2 Dup"] `shouldBe` Right [1, 2, 2] + it "errors if there is nothing on the stack" $ + runTexts ["dup" ] `shouldBe` Left StackUnderflow - it "redefining an existing word" $ - runTexts [ ": foo dup ;" - , ": foo dup dup ;" - , "1 foo" ] `shouldBe` Right "1 1 1" + describe "drop" $ do + it "removes the top value on the stack if it is the only one" $ + runTexts ["1 drop" ] `shouldBe` Right [] + it "removes the top value on the stack if it is not the only one" $ + runTexts ["1 2 drop"] `shouldBe` Right [1] + it "errors if there is nothing on the stack" $ + runTexts ["drop" ] `shouldBe` Left StackUnderflow - it "redefining an existing built-in word" $ - runTexts [ ": swap dup ;" - , "1 swap" ] `shouldBe` Right "1 1" + describe "swap" $ do + it "swaps the top two values on the stack if they are the only ones" $ + runTexts ["1 2 swap" ] `shouldBe` Right [2, 1] + it "swaps the top two values on the stack if they are not the only ones" $ + runTexts ["1 2 3 swap"] `shouldBe` Right [1, 3, 2] + it "errors if there is nothing on the stack" $ + runTexts ["swap" ] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 swap" ] `shouldBe` Left StackUnderflow - it "defining words with odd characters" $ - runTexts [": € 220371 ; €"] `shouldBe` Right "220371" + describe "over" $ do + it "copies the second element if there are only two" $ + runTexts ["1 2 over" ] `shouldBe` Right [1, 2, 1] + it "copies the second element if there are more than two" $ + runTexts ["1 2 3 over"] `shouldBe` Right [1, 2, 3, 2] + it "errors if there is nothing on the stack" $ + runTexts ["over" ] `shouldBe` Left StackUnderflow + it "errors if there is only one value on the stack" $ + runTexts ["1 over" ] `shouldBe` Left StackUnderflow - it "defining a number" $ - runTexts [": 1 2 ;"] `shouldBe` Left InvalidWord + describe "user-defined words" $ do + it "can consist of built-in words" $ + runTexts [ ": dup-twice dup dup ;" + , "1 dup-twice" ] `shouldBe` Right [1, 1, 1] - it "calling a non-existing word" $ - runTexts ["1 foo"] `shouldBe` Left (UnknownWord "foo") + it "execute in the right order" $ + runTexts [ ": countup 1 2 3 ;" + , "countup" ] `shouldBe` Right [1, 2, 3] + + it "can override other user-defined words" $ + runTexts [ ": foo dup ;" + , ": foo dup dup ;" + , "1 foo" ] `shouldBe` Right [1, 1, 1] + + it "can override built-in words" $ + runTexts [ ": swap dup ;" + , "1 swap" ] `shouldBe` Right [1, 1] + + it "can consist of arbitrary characters such as Unicode characters" $ + runTexts [": € 220371 ; €"] `shouldBe` Right [220371] + + it "cannot redefine numbers" $ + runTexts [": 1 2 ;"] `shouldBe` Left InvalidWord + + it "errors if executing a non-existent word" $ + runTexts ["1 foo"] `shouldBe` Left (UnknownWord "foo")