packages feed

b9-0.5.67: src/tests/B9/Content/ErlTermsSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module B9.Content.ErlTermsSpec
   ( spec
   )
where

import           Data.List
import           Test.Hspec
import           Test.QuickCheck
import           Data.Maybe
import           B9.Artifact.Content.ErlTerms
import           B9.Text

spec :: Spec
spec = do
   describe "parseErlTerm" $ do
      it
         "parses a non-empty string"
         (          parseErlTerm "test" "\"hello world\"."
         `shouldBe` Right (ErlString "hello world")
         )

      it
         "parses a string with escaped characters"
         (          parseErlTerm "test" "\"\\b\\^A\"."
         `shouldBe` Right (ErlString "\b\^A")
         )

      it "parses a string with escaped octals: \\X"
         (parseErlTerm "test" "\"\\7\"." `shouldBe` Right (ErlString "\o7"))

      it
         "parses a string with escaped octals: \\XY"
         (parseErlTerm "test" "\"\\73\"." `shouldBe` Right (ErlString "\o73"))

      it
         "parses a string with escaped octals: \\XYZ"
         (parseErlTerm "test" "\"\\431\"." `shouldBe` Right (ErlString "\o431"))

      it
         "parses a string with escaped hex: \\xNN"
         (parseErlTerm "test" "\"\\xbE\"." `shouldBe` Right (ErlString "\xbe"))

      it
         "parses a string with escaped hex: \\x{N} (1)"
         (parseErlTerm "test" "\"\\x{a}\"." `shouldBe` Right (ErlString "\xa"))

      it
         "parses a string with escaped hex: \\x{N} (2)"
         (parseErlTerm "test" "\"\\x{2}\"." `shouldBe` Right (ErlString "\x2"))

      it
         "parses a two digit octal followed by a non-octal digit"
         (          parseErlTerm "test" "\"\\779\"."
         `shouldBe` Right (ErlString "\o77\&9")
         )

      it
         "parses a string with escaped hex: \\x{NNNNNN...}"
         (          parseErlTerm "test" "\"\\x{000000Fa}\"."
         `shouldBe` Right (ErlString "\xfa")
         )

      it
         "parses decimal literals"
         (property
            (do
               decimal <- arbitrary `suchThat` (>= 0)
               let decimalStr =
                      unsafeRenderToText (show (decimal :: Integer) ++ ".")
               parsedTerm <- case parseErlTerm "test" decimalStr of
                  (Left  e         ) -> fail e
                  (Right parsedTerm) -> return parsedTerm
               return (ErlNatural decimal == parsedTerm)
            )
         )

      it "parses a negative signed decimal"
         (parseErlTerm "test" "-1." `shouldBe` Right (ErlNatural (-1)))

      it "parses a positive signed decimal"
         (parseErlTerm "test" "+1." `shouldBe` Right (ErlNatural 1))

      it
         "parses decimal literals with radix notation"
         (property
            (do
               radix         <- choose (2, 36)
               digitsInRadix <- listOf1 (choose (0, radix - 1))
               let (Right parsedTerm) = parseErlTerm "test" erlNumber
                   erlNumber = unsafeRenderToText (show radix ++ "#" ++ digitChars ++ ".")
                   expected   = convertStrToDecimal radix digitChars
                   digitChars = (naturals !!) <$> digitsInRadix
               return (ErlNatural expected == parsedTerm)
            )
         )

      it
         "parses a floating point literal with exponent and sign"
         (parseErlTerm "test" "-10.40E02." `shouldBe` Right (ErlFloat (-10.4e2))
         )

      it "parses a simple erlang character literal"
         (parseErlTerm "test" "$ ." `shouldBe` Right (ErlChar (toEnum 32)))

      it
         "parses an erlang character literal with escape sequence"
         (          parseErlTerm "test" "$\\x{Fe}."
         `shouldBe` Right (ErlChar (toEnum 254))
         )

      it "parses an unquoted atom with @ and _"
         (parseErlTerm "test" "a@0_T." `shouldBe` Right (ErlAtom "a@0_T"))

      it
         "parses a quoted atom with letters, spaces and special characters"
         (          parseErlTerm "test" "' $s<\\\\.0_=@\\e\\''."
         `shouldBe` Right (ErlAtom " $s<\\.0_=@\ESC'")
         )

      it
         "parses a binary literal containing a string"
         (          parseErlTerm "test" "<<\"1 ok!\">>."
         `shouldBe` Right (ErlBinary "1 ok!")
         )

      it "parses an empty binary"
         (parseErlTerm "test" "<<>>." `shouldBe` Right (ErlBinary ""))

      it "parses an empty list"
         (parseErlTerm "test" "[]." `shouldBe` Right (ErlList []))

      it
         "parses a list of atoms"
         (          parseErlTerm "test" " [ hello, 'world'        ] ."
         `shouldBe` Right (ErlList [ErlAtom "hello", ErlAtom "world"])
         )

      it "parses an empty tuple"
         (parseErlTerm "test" " {  } ." `shouldBe` Right (ErlTuple []))

      it
         "parses a tuple of atoms"
         (          parseErlTerm "test" " { hello, 'world' } ."
         `shouldBe` Right (ErlTuple [ErlAtom "hello", ErlAtom "world"])
         )

   describe "renderErlTerm" $ do
      it "renders an empty binary as \"<<>>\"."
         (renderErlTerm (ErlBinary "") `shouldBe` "<<>>.")

      it "renders an erlang character"
         (renderErlTerm (ErlChar 'a') `shouldBe` "$a.")

      it
         "renders a quoted atom and escapes special characters"
         (          renderErlTerm (ErlAtom " $s\"<\\.0_=@\ESC'")
         `shouldBe` "' $s\"<\\\\.0_=@\\x{1b}\\''."
         )

      it "renders _ correctly as '_'"
         (renderErlTerm (ErlAtom "_") `shouldBe` "'_'.")

      it "renders an empty string correctly as ''"
         (renderErlTerm (ErlAtom "") `shouldBe` "''.")

      it
         "renders a string and escapes special characters"
         (          renderErlTerm (ErlString "' $s\"<\\.0_=@\ESC''")
         `shouldBe` "\"' $s\\\"<\\\\.0_=@\\x{1b}''\"."
         )

      it "renders an empty list"  (renderErlTerm (ErlList []) `shouldBe` "[].")

      it "renders an empty tuple" (renderErlTerm (ErlTuple []) `shouldBe` "{}.")

   describe "renderErlTerm and parseErlTerm" $ it
      "parseErlTerm parses all terms rendered by renderErlTerm"
      (property parsesRenderedTerms)

parsesRenderedTerms :: SimpleErlangTerm -> Bool
parsesRenderedTerms term =
   either error (term ==) (parseErlTerm "test" (renderErlTerm term))

naturals :: String
naturals = ['0' .. '9'] ++ ['a' .. 'z']

convertStrToDecimal :: Int -> String -> Integer
convertStrToDecimal radix digitChars =
   let hornersMethod acc d = acc * radixHighPrecision + digitCharToInteger d
       digitCharToInteger d = toInteger $ fromJust $ elemIndex d naturals
       radixHighPrecision = toInteger radix
   in  foldl hornersMethod 0 digitChars