{-| Copyright : (C) 2019, QBayLogic B.V. License : BSD2 (see the file LICENSE) Maintainer : QBayLogic B.V. -} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE MagicHash #-} module Clash.Tests.Netlist.Id ( module Clash.Tests.Netlist.Id ) where import qualified Clash.Netlist.Types as Id import qualified Clash.Netlist.Id as Id import Clash.Annotations.Primitive import Control.Monad.Trans.State.Lazy import qualified Data.ByteString as BS import Data.Coerce import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck import qualified Data.Text as Text import Data.Text (Text) import Data.Text.Encoding (decodeUtf8) import Test.QuickCheck.Utf8 import Text.Show.Pretty (ppShow) import Debug.Trace newtype NonEmptyText = NonEmptyText Text deriving (Show) newtype ArbitraryText = ArbitraryText Text deriving (Show) newtype ArbitraryAsciiText = ArbitraryAsciiText Text deriving (Show) instance Arbitrary ArbitraryAsciiText where arbitrary = coerce (decodeUtf8 . BS.concat <$> listOf oneByte) shrink = coerce shrinkValidUtf8 instance Arbitrary ArbitraryText where arbitrary = coerce genValidUtf8 shrink = coerce shrinkValidUtf8 instance Arbitrary NonEmptyText where arbitrary = coerce genValidUtf81 shrink = coerce shrinkValidUtf81 eval :: Bool -> HDL -> State Id.IdentifierSet a -> a eval esc hdl a = evalState a (Id.emptyIdentifierSet esc Id.PreserveCase hdl) eval' :: State Id.IdentifierSet a -> a eval' = eval True VHDL roundTrip :: Bool -> HDL -> Text -> Text roundTrip esc hdl = Id.toText . eval esc hdl . Id.make roundTrip' :: Text -> Text roundTrip' = roundTrip True VHDL roundTripTest :: Text -> TestTree roundTripTest t = testCase (Text.unpack ("roundTrip: " <> t)) (t @=? roundTrip' t) -- | Raw identifiers should always come up the same after 'Id.toText' rawToIdProperty :: NonEmptyText -> Property rawToIdProperty t = coerce t === Id.toText (eval' (Id.addRaw (coerce t))) xor :: Bool -> Bool -> Bool xor True True = False xor True False = True xor False True = True xor False False = False tests :: TestTree tests = testGroup "Clash.Tests.Netlist.Id" [ testCase "roundTrip: empty id" ("clash_internal" @=? roundTrip' "") -- Round trip tests tess whether a "make -> to text" roundtrip ~ id , roundTripTest "foo_bar" , roundTripTest "foo_1" , roundTripTest "foo_1_2" , roundTripTest "foo_1_2_ab" , roundTripTest "foo_1_ab_2" , testGroup "no collisions (one id)" $ flip map [minBound..maxBound] $ \hdl -> testProperty (show hdl) $ \id0 -> eval True hdl $ do id0t <- Id.toText <$> Id.make (coerce @ArbitraryAsciiText id0) id1t <- Id.toText <$> Id.make (coerce @ArbitraryAsciiText id0) pure (id0t /= id1t) , testGroup "no collisions (two ids)" $ flip map [minBound..maxBound] $ \hdl -> testProperty (show hdl) $ \id0 id1 -> eval True hdl $ do id0t <- Id.toText <$> Id.make (coerce @ArbitraryAsciiText id0) id1t <- Id.toText <$> Id.make (coerce @ArbitraryAsciiText id1) pure (id0t /= id1t) , testGroup "make0" $ eval' $ do id0 <- Id.toText <$> Id.make "foo" id1 <- Id.toText <$> Id.make "foo" id2 <- Id.toText <$> Id.make "foo_0" id3 <- Id.toText <$> Id.make "foo" id4 <- Id.toText <$> Id.make "foo_0" pure [ testCase "id0 == foo" $ id0 @?= "foo" , testCase "id1 == foo_0" $ id1 @?= "foo_0" , testCase "id2 == foo_0_0" $ id2 @?= "foo_0_0" , testCase "id3 == foo_0_1" $ id3 @?= "foo_1" , testCase "id4 == foo_0_0" $ id4 @?= "foo_0_1" ] , testGroup "make1" $ eval' $ do id0 <- Id.toText <$> Id.make "foo" id1 <- Id.toText <$> Id.make "foo_37" id2 <- Id.toText <$> Id.make "foo" id3 <- Id.toText <$> Id.make "foo_3" pure [ testCase "id0 == foo" $ id0 @?= "foo" , testCase "id1 == foo_37" $ id1 @?= "foo_37" , testCase "id2 == foo_38" $ id2 @?= "foo_38" , testCase "id3 == foo_3" $ id3 @?= "foo_3" ] , testGroup "Id.add" $ eval' $ do old <- get id0 <- Id.addRaw "LED" put old Id.add id0 traceM . ppShow =<< get id1 <- Id.toText <$> Id.make "led" pure [ testCase "id1 == led_0" $ id1 @?= "led_0" ] -- Some tools/hdls are case insensitive, so we should make sure we are too , testGroup "case sensitivity" $ eval' $ do id0 <- Id.toText <$> Id.make "foobar" id1 <- Id.toText <$> Id.make "fOoBAr" pure [ testCase "id0 == foobar" $ id0 @?= "foobar" , testCase "id1 == fOoBAr_0" $ id1 @?= "fOoBAr_0" ] -- An identifier made with 'mkBasic' should pass the 'isBasic' test , testGroup "mkBasic" $ concat $ flip map [minBound..maxBound] $ \hdl -> [ testProperty (show hdl <> " (ascii)") (Id.isBasic# hdl . roundTrip False hdl . coerce @ArbitraryAsciiText) , testProperty (show hdl <> " (UTF8)") (Id.isBasic# hdl . roundTrip False hdl . coerce @ArbitraryText) ] -- We expect a processed identifier to be either a valid basic xor -- extended identifier. Anything "in between" is an error. , testGroup "Basic XOR Extended" $ flip map [minBound..maxBound] $ \hdl -> testProperty (show hdl) $ \id0 -> let id1 = roundTrip True hdl (coerce @ArbitraryText id0) in Id.isBasic# hdl id1 `xor` Id.isExtended# hdl id1 , testCase "keyword (use => \\use\\)" ("\\use\\" @=? roundTrip' "use") , testCase "keyword (else => \\else\\)" ("\\else\\" @=? roundTrip' "else") , testCase "keyword (record => \\record\\)" ("\\record\\" @=? roundTrip' "record") , testCase "keyword (configuration => \\configuration\\)" ("\\configuration\\" @=? roundTrip' "configuration") , testCase "keyword (cOnFiGUrAtiON => \\cOnFiGUrAtiON\\)" ("\\cOnFiGUrAtiON\\" @=? roundTrip' "cOnFiGUrAtiON") , testCase "Verilog keyword in VHDL (always => always)" ("always" @=? roundTrip' "always") , testGroup "extended identifiers" [ testCase "(1) foo bar => \\foo bar\\" $ "\\foo bar\\" @=? roundTrip' "foo bar" , testCase "(2) foo bar => \\foo bar\\" $ 9 @=? Text.length (roundTrip' "foo bar") , testCase "foo\\bar => foobar" $ "foobar" @=? roundTrip' "foo\\bar" , testCase "\\foobar\\ => foobar" $ "foobar" @=? roundTrip' "\\foobar\\" -- This behavior makes sense, but it results in ugly identifiers, so -- backslashes are stripped -- , testCase "foo\\bar => \\foo\\\\bar\\" $ "\\foo\\\\bar\\" @=? roundTrip' "foo\\bar" -- , testCase "\\foobar\\ => \\\\\\foobar\\\\\\" $ "\\\\\\foobar\\\\\\" @=? roundTrip' "\\foobar\\" ] , testGroup "pretty names" [ testCase "(# #) => Unit" $ "Unit" @=? roundTrip' "(# #)" , testCase "() => Unit" $ "Unit" @=? roundTrip' "()" , testCase "(,,) => Tup3" $ "Tup3" @=? roundTrip' "(,,)" , testCase "(#,,,,#) => Tup5" $ "Tup5" @=? roundTrip' "(,,,,)" ] , testGroup "pretty names (force basic)" [ testCase "(# #) => Unit" $ "Unit" @=? roundTrip False VHDL "(# #)" , testCase "() => Unit" $ "Unit" @=? roundTrip False VHDL "()" , testCase "(,,) => Tup3" $ "Tup3" @=? roundTrip False VHDL "(,,)" , testCase "(#,,,,#) => Tup5" $ "Tup5" @=? roundTrip False VHDL "(,,,,)" ] , testGroup "disallow escaped identifiers" [ testCase "foo bar => foobar" $ "foobar" @=? roundTrip False VHDL "foo bar" , testCase "foo\\bar => foobar" $ "foobar" @=? roundTrip False VHDL "foo\\bar" ] -- Raw identifiers are a bit weird: they're passed in by users and should -- be spliced into the HDL at verbatim. Clash shouldn't generate collisions -- though. , testGroup "raw identifiers" [ testProperty "id" rawToIdProperty , testGroup "Verilog: \\foo bar␣" $ eval True Verilog $ do id0 <- Id.toText <$> Id.addRaw "\\foo bar " id1 <- Id.toText <$> Id.make "foo bar" pure [ testCase "id0 == \\foo bar " $ id0 @?= "\\foo bar " , testCase "id1 == \\foo bar_0 " $ id1 @?= "\\foo bar_0 " ] , testGroup "Verilog: \\foo bar␣␣" $ eval True Verilog $ do id0 <- Id.toText <$> Id.addRaw "\\foo bar " id1 <- Id.toText <$> Id.make "foo bar" pure [ testCase "id0 == \\foo bar " $ id0 @?= "\\foo bar " , testCase "id1 == \\foo bar_0 " $ id1 @?= "\\foo bar_0 " ] , testGroup "VHDL: \\foo bar\\" $ eval True VHDL $ do id0 <- Id.toText <$> Id.addRaw "\\foo bar\\" id1 <- Id.toText <$> Id.make "foo bar" pure [ testCase "id0 == \\foo bar\\" $ id0 @?= "\\foo bar\\" , testCase "id1 == \\foo bar_0\\ " $ id1 @?= "\\foo bar_0\\" ] , testGroup "VHDL: \\foo bar \\" $ eval True VHDL $ do id0 <- Id.toText <$> Id.addRaw "\\foo bar \\" id1 <- Id.toText <$> Id.make "foo bar" -- While 'id1' could strictly be \foo bar\, it's probably best to be -- whitespace insensitive. pure [ testCase "id0 == \\foo bar \\" $ id0 @?= "\\foo bar \\" , testCase "id1 == \\foo bar_0\\ " $ id1 @?= "\\foo bar_0\\" ] ] ]