diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d3757a8..f87c3da 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,7 @@ jobs: - name: Install fourmolu run: | - FOURMOLU_VERSION="0.18.0.0" + FOURMOLU_VERSION="0.14.0.0" mkdir -p "$HOME/.local/bin" curl -sL "https://github.com/fourmolu/fourmolu/releases/download/v${FOURMOLU_VERSION}/fourmolu-${FOURMOLU_VERSION}-linux-x86_64" -o "$HOME/.local/bin/fourmolu" chmod a+x "$HOME/.local/bin/fourmolu" diff --git a/cabal.project b/cabal.project index 81a442a..a5b6932 100644 --- a/cabal.project +++ b/cabal.project @@ -2,3 +2,4 @@ packages: . test-show-details: streaming +tests: True diff --git a/src/Codec/CBOR/Cuddle/CBOR/Gen.hs b/src/Codec/CBOR/Cuddle/CBOR/Gen.hs index c00b0db..97991b3 100644 --- a/src/Codec/CBOR/Cuddle/CBOR/Gen.hs +++ b/src/Codec/CBOR/Cuddle/CBOR/Gen.hs @@ -20,12 +20,23 @@ import Capability.Sink (HasSink) import Capability.Source (HasSource, MonadState (..)) import Capability.State (HasState, get, modify) import Codec.CBOR.Cuddle.CDDL ( + CBORGenerator (..), Name (..), OccurrenceIndicator (..), Value (..), ValueVariant (..), + WrappedTerm, + flattenWrappedList, + pairTermList, + singleTermList, + pattern G, + pattern P, + pattern S, + ) +import Codec.CBOR.Cuddle.CDDL.CTree ( + CTree, + CTreeRoot' (..), ) -import Codec.CBOR.Cuddle.CDDL.CTree (CTree, CTreeRoot' (..)) import Codec.CBOR.Cuddle.CDDL.CTree qualified as CTree import Codec.CBOR.Cuddle.CDDL.CtlOp qualified as CtlOp import Codec.CBOR.Cuddle.CDDL.Postlude (PTerm (..)) @@ -207,53 +218,11 @@ genPostlude pt = case pt of PTNil -> pure TNull PTUndefined -> pure $ TSimple 23 --------------------------------------------------------------------------------- --- Kinds of terms --------------------------------------------------------------------------------- - -data WrappedTerm - = SingleTerm Term - | PairTerm Term Term - | GroupTerm [WrappedTerm] - deriving (Eq, Show) - --- | Recursively flatten wrapped list. That is, expand any groups out to their --- individual entries. -flattenWrappedList :: [WrappedTerm] -> [WrappedTerm] -flattenWrappedList [] = [] -flattenWrappedList (GroupTerm xxs : xs) = - flattenWrappedList xxs <> flattenWrappedList xs -flattenWrappedList (y : xs) = y : flattenWrappedList xs - -pattern S :: Term -> WrappedTerm -pattern S t = SingleTerm t - --- | Convert a list of wrapped terms to a list of terms. If any 'PairTerm's are --- present, we just take their "value" part. -singleTermList :: [WrappedTerm] -> Maybe [Term] -singleTermList [] = Just [] -singleTermList (S x : xs) = (x :) <$> singleTermList xs -singleTermList (P _ y : xs) = (y :) <$> singleTermList xs -singleTermList _ = Nothing - -pattern P :: Term -> Term -> WrappedTerm -pattern P t1 t2 = PairTerm t1 t2 - --- | Convert a list of wrapped terms to a list of pairs of terms, or fail if any --- 'SingleTerm's are present. -pairTermList :: [WrappedTerm] -> Maybe [(Term, Term)] -pairTermList [] = Just [] -pairTermList (P x y : xs) = ((x, y) :) <$> pairTermList xs -pairTermList _ = Nothing - -pattern G :: [WrappedTerm] -> WrappedTerm -pattern G xs = GroupTerm xs - -------------------------------------------------------------------------------- -- Generator functions -------------------------------------------------------------------------------- -genForCTree :: RandomGen g => CTree MonoRef -> M g WrappedTerm +genForCTree :: forall g. RandomGen g => CTree MonoRef -> M g WrappedTerm genForCTree (CTree.Literal v) = S <$> genValue v genForCTree (CTree.Postlude pt) = S <$> genPostlude pt genForCTree (CTree.Map nodes) = do @@ -362,6 +331,7 @@ genForCTree (CTree.Tag tag node) = do case enc of S x -> pure $ S $ TTagged tag x _ -> error "Tag controller does not correspond to a single term" +genForCTree (CTree.WithGen (CBORGenerator gen) _) = gen StateGenM genForNode :: RandomGen g => CTree.Node MonoRef -> M g WrappedTerm genForNode = genForCTree <=< resolveIfRef @@ -446,7 +416,8 @@ generateCBORTerm cddl n stdGen = genState = GenState {randomSeed = stdGen, depth = 1} in evalGen (genForName n) genEnv genState -generateCBORTerm' :: RandomGen g => CTreeRoot' Identity MonoRef -> Name -> g -> (Term, g) +generateCBORTerm' :: + RandomGen g => CTreeRoot' Identity MonoRef -> Name -> g -> (Term, g) generateCBORTerm' cddl n stdGen = let genEnv = GenEnv {cddl} genState = GenState {randomSeed = stdGen, depth = 1} diff --git a/src/Codec/CBOR/Cuddle/CBOR/Validator.hs b/src/Codec/CBOR/Cuddle/CBOR/Validator.hs index f80884a..44e4679 100644 --- a/src/Codec/CBOR/Cuddle/CBOR/Validator.hs +++ b/src/Codec/CBOR/Cuddle/CBOR/Validator.hs @@ -145,7 +145,9 @@ validateCBOR' bs rule cddl@(CTreeRoot tree) = -- spec validateTerm :: MonadReader CDDL m => - Term -> Rule -> m CBORTermResult + Term -> + Rule -> + m CBORTermResult validateTerm term rule = let f = case term of TInt i -> validateInteger (fromIntegral i) @@ -183,7 +185,9 @@ validateTerm term rule = -- Ints, so we convert everything to Integer. validateInteger :: MonadReader CDDL m => - Integer -> Rule -> m CDDLResult + Integer -> + Rule -> + m CDDLResult validateInteger i rule = ($ rule) <$> do getRule rule >>= \case @@ -308,7 +312,9 @@ controlInteger i Ne ctrl = -- | Validating a `Float16` validateHalf :: MonadReader CDDL m => - Float -> Rule -> m CDDLResult + Float -> + Rule -> + m CDDLResult validateHalf f rule = ($ rule) <$> do getRule rule >>= \case @@ -343,7 +349,9 @@ controlHalf f Ne ctrl = -- | Validating a `Float32` validateFloat :: MonadReader CDDL m => - Float -> Rule -> m CDDLResult + Float -> + Rule -> + m CDDLResult validateFloat f rule = ($ rule) <$> do getRule rule >>= \case @@ -383,7 +391,9 @@ controlFloat f Ne ctrl = -- | Validating a `Float64` validateDouble :: MonadReader CDDL m => - Double -> Rule -> m CDDLResult + Double -> + Rule -> + m CDDLResult validateDouble f rule = ($ rule) <$> do getRule rule >>= \case @@ -430,7 +440,9 @@ controlDouble f Ne ctrl = -- | Validating a boolean validateBool :: MonadReader CDDL m => - Bool -> Rule -> m CDDLResult + Bool -> + Rule -> + m CDDLResult validateBool b rule = ($ rule) <$> do getRule rule >>= \case @@ -463,7 +475,9 @@ controlBool b Ne ctrl = -- | Validating a `TSimple`. It is unclear if this is used for anything else than undefined. validateSimple :: MonadReader CDDL m => - Word8 -> Rule -> m CDDLResult + Word8 -> + Rule -> + m CDDLResult validateSimple 23 rule = ($ rule) <$> do getRule rule >>= \case @@ -498,7 +512,9 @@ validateNull rule = -- | Validating a byte sequence validateBytes :: MonadReader CDDL m => - BS.ByteString -> Rule -> m CDDLResult + BS.ByteString -> + Rule -> + m CDDLResult validateBytes bs rule = ($ rule) <$> do getRule rule >>= \case @@ -517,7 +533,11 @@ validateBytes bs rule = -- | Controls for byte strings controlBytes :: forall m. - MonadReader CDDL m => BS.ByteString -> CtlOp -> Rule -> m (Either (Maybe CBORTermResult) ()) + MonadReader CDDL m => + BS.ByteString -> + CtlOp -> + Rule -> + m (Either (Maybe CBORTermResult) ()) controlBytes bs Size ctrl = getRule ctrl >>= \case Literal (Value (VUInt (fromIntegral -> sz)) _) -> pure $ boolCtrl $ BS.length bs == sz @@ -568,7 +588,9 @@ controlBytes bs Cborseq ctrl = -- | Validating text strings validateText :: MonadReader CDDL m => - T.Text -> Rule -> m CDDLResult + T.Text -> + Rule -> + m CDDLResult validateText txt rule = ($ rule) <$> do getRule rule >>= \case @@ -607,7 +629,10 @@ controlText s Regexp ctrl = -- | Validating a `TTagged` validateTagged :: MonadReader CDDL m => - Word64 -> Term -> Rule -> m CDDLResult + Word64 -> + Term -> + Rule -> + m CDDLResult validateTagged tag term rule = ($ rule) <$> do getRule rule >>= \case @@ -633,25 +658,25 @@ flattenGroup :: CDDL -> [Rule] -> [Rule] flattenGroup cddl nodes = mconcat [ case resolveIfRef cddl rule of - Literal {} -> [rule] - Postlude {} -> [rule] - Map {} -> [rule] - Array {} -> [rule] - Choice {} -> [rule] - KV {} -> [rule] - Occur {} -> [rule] - Range {} -> [rule] - Control {} -> [rule] - Enum e -> case resolveIfRef cddl e of - Group g -> flattenGroup cddl g - _ -> error "Malformed cddl" - Unwrap g -> case resolveIfRef cddl g of - Map n -> flattenGroup cddl n - Array n -> flattenGroup cddl n - Tag _ n -> [n] - _ -> error "Malformed cddl" - Tag {} -> [rule] + Literal {} -> [rule] + Postlude {} -> [rule] + Map {} -> [rule] + Array {} -> [rule] + Choice {} -> [rule] + KV {} -> [rule] + Occur {} -> [rule] + Range {} -> [rule] + Control {} -> [rule] + Enum e -> case resolveIfRef cddl e of Group g -> flattenGroup cddl g + _ -> error "Malformed cddl" + Unwrap g -> case resolveIfRef cddl g of + Map n -> flattenGroup cddl n + Array n -> flattenGroup cddl n + Tag _ n -> [n] + _ -> error "Malformed cddl" + Tag {} -> [rule] + Group g -> flattenGroup cddl g | rule <- nodes ] @@ -725,7 +750,9 @@ isOptional rule = validateListWithExpandedRules :: forall m. MonadReader CDDL m => - [Term] -> [Rule] -> m [(Rule, CBORTermResult)] + [Term] -> + [Rule] -> + m [(Rule, CBORTermResult)] validateListWithExpandedRules terms rules = go (zip terms rules) where @@ -795,7 +822,9 @@ validateList terms rule = validateMapWithExpandedRules :: forall m. MonadReader CDDL m => - [(Term, Term)] -> [Rule] -> m ([AMatchedItem], Maybe ANonMatchedItem) + [(Term, Term)] -> + [Rule] -> + m ([AMatchedItem], Maybe ANonMatchedItem) validateMapWithExpandedRules = go where @@ -853,7 +882,9 @@ validateExpandedMap terms rules = go rules validateMap :: MonadReader CDDL m => - [(Term, Term)] -> Rule -> m CDDLResult + [(Term, Term)] -> + Rule -> + m CDDLResult validateMap terms rule = ($ rule) <$> do getRule rule >>= \case @@ -899,7 +930,10 @@ dummyRule = MRuleRef (Name "dummy" mempty) -- | Validate both rules ctrlAnd :: Monad m => - (Rule -> m CDDLResult) -> Rule -> Rule -> m (Rule -> CDDLResult) + (Rule -> m CDDLResult) -> + Rule -> + Rule -> + m (Rule -> CDDLResult) ctrlAnd v tgt ctrl = v tgt >>= \case Valid _ -> diff --git a/src/Codec/CBOR/Cuddle/CDDL.hs b/src/Codec/CBOR/Cuddle/CDDL.hs index 173dc23..965d308 100644 --- a/src/Codec/CBOR/Cuddle/CDDL.hs +++ b/src/Codec/CBOR/Cuddle/CDDL.hs @@ -1,10 +1,13 @@ {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE RecordWildCards #-} -- | This module defined the data structure of CDDL as specified in -- https://datatracker.ietf.org/doc/rfc8610/ module Codec.CBOR.Cuddle.CDDL ( CDDL (..), + CBORGenerator (..), sortCDDL, cddlTopLevel, cddlRules, @@ -33,10 +36,18 @@ module Codec.CBOR.Cuddle.CDDL ( GrpChoice (..), unwrap, compareRuleName, + WrappedTerm (..), + flattenWrappedList, + singleTermList, + pairTermList, + pattern S, + pattern G, + pattern P, ) where import Codec.CBOR.Cuddle.CDDL.CtlOp (CtlOp) import Codec.CBOR.Cuddle.Comments (CollectComments (..), Comment, HasComment (..)) +import Codec.CBOR.Term (Term) import Data.ByteString qualified as B import Data.Default.Class (Default (..)) import Data.Function (on, (&)) @@ -45,12 +56,59 @@ import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty qualified as NE import Data.String (IsString (..)) import Data.Text qualified as T -import Data.TreeDiff (ToExpr) +import Data.TreeDiff (ToExpr (..)) +import Data.TreeDiff.Expr qualified as Expr import Data.Word (Word64, Word8) import GHC.Generics (Generic) import Optics.Core ((%), (.~)) import Optics.Getter (view) import Optics.Lens (lens) +import System.Random.Stateful (StatefulGen) + +-------------------------------------------------------------------------------- +-- Kinds of terms +-------------------------------------------------------------------------------- + +data WrappedTerm + = SingleTerm Term + | PairTerm Term Term + | GroupTerm [WrappedTerm] + deriving (Eq, Show, Generic) + +-- | Recursively flatten wrapped list. That is, expand any groups out to their +-- individual entries. +flattenWrappedList :: [WrappedTerm] -> [WrappedTerm] +flattenWrappedList [] = [] +flattenWrappedList (GroupTerm xxs : xs) = + flattenWrappedList xxs <> flattenWrappedList xs +flattenWrappedList (y : xs) = y : flattenWrappedList xs + +pattern S :: Term -> WrappedTerm +pattern S t = SingleTerm t + +-- | Convert a list of wrapped terms to a list of terms. If any 'PairTerm's are +-- present, we just take their "value" part. +singleTermList :: [WrappedTerm] -> Maybe [Term] +singleTermList [] = Just [] +singleTermList (S x : xs) = (x :) <$> singleTermList xs +singleTermList (P _ y : xs) = (y :) <$> singleTermList xs +singleTermList _ = Nothing + +pattern P :: Term -> Term -> WrappedTerm +pattern P t1 t2 = PairTerm t1 t2 + +-- | Convert a list of wrapped terms to a list of pairs of terms, or fail if any +-- 'SingleTerm's are present. +pairTermList :: [WrappedTerm] -> Maybe [(Term, Term)] +pairTermList [] = Just [] +pairTermList (P x y : xs) = ((x, y) :) <$> pairTermList xs +pairTermList _ = Nothing + +pattern G :: [WrappedTerm] -> WrappedTerm +pattern G xs = GroupTerm xs + +newtype CBORGenerator + = CBORGenerator (forall g m. StatefulGen g m => g -> m WrappedTerm) -- | The CDDL constructor takes three arguments: -- 1. Top level comments that precede the first definition @@ -58,7 +116,8 @@ import Optics.Lens (lens) -- 3. All the other top level comments and definitions -- This ensures that `CDDL` is correct by construction. data CDDL = CDDL [Comment] Rule [TopLevel] - deriving (Eq, Generic, Show, ToExpr) + deriving (Generic, Show) + deriving anyclass (ToExpr) -- | Sort the CDDL Rules on the basis of their names -- Top level comments will be removed! @@ -92,7 +151,8 @@ instance Semigroup CDDL where data TopLevel = TopLevelRule Rule | TopLevelComment Comment - deriving (Eq, Generic, Show, ToExpr) + deriving (Generic, Show) + deriving anyclass (ToExpr) -- | -- A name can consist of any of the characters from the set {"A" to @@ -209,13 +269,37 @@ data Rule = Rule , ruleAssign :: Assign , ruleTerm :: TypeOrGroup , ruleComment :: Comment + , ruleGenerator :: Maybe CBORGenerator } - deriving (Eq, Generic, Show) - deriving anyclass (ToExpr) + deriving (Generic) instance HasComment Rule where commentL = lens ruleComment (\x y -> x {ruleComment = y}) +instance ToExpr Rule where + toExpr r@(Rule _ _ _ _ _ _) = + let Rule {..} = r + in Expr.App + "Rule" + [ toExpr ruleName + , toExpr ruleGenParam + , toExpr ruleAssign + , toExpr ruleTerm + , toExpr ruleComment + , toExpr $ const "" <$> ruleGenerator + ] + +instance Show Rule where + show r@(Rule _ _ _ _ _ _) = + let Rule {..} = r + in unwords + [ show ruleName + , show ruleGenParam + , show ruleAssign + , show ruleTerm + , show ruleComment + ] + compareRuleName :: Rule -> Rule -> Ordering compareRuleName = compare `on` ruleName diff --git a/src/Codec/CBOR/Cuddle/CDDL/CTree.hs b/src/Codec/CBOR/Cuddle/CDDL/CTree.hs index 176c147..5198a1b 100644 --- a/src/Codec/CBOR/Cuddle/CDDL/CTree.hs +++ b/src/Codec/CBOR/Cuddle/CDDL/CTree.hs @@ -3,6 +3,7 @@ module Codec.CBOR.Cuddle.CDDL.CTree where import Codec.CBOR.Cuddle.CDDL ( + CBORGenerator, Name, OccurrenceIndicator, RangeBound, @@ -44,7 +45,7 @@ data CTree f | Enum (Node f) | Unwrap (Node f) | Tag Word64 (Node f) - deriving (Generic) + | WithGen CBORGenerator (Node f) -- | Traverse the CTree, carrying out the given operation at each node traverseCTree :: Monad m => (Node f -> m (Node g)) -> CTree f -> m (CTree g) @@ -70,6 +71,7 @@ traverseCTree atNode (Control o t c) = do traverseCTree atNode (Enum ref) = Enum <$> atNode ref traverseCTree atNode (Unwrap ref) = Unwrap <$> atNode ref traverseCTree atNode (Tag i ref) = Tag i <$> atNode ref +traverseCTree atNode (WithGen g ref) = WithGen g <$> atNode ref type Node f = f (CTree f) diff --git a/src/Codec/CBOR/Cuddle/CDDL/Resolve.hs b/src/Codec/CBOR/Cuddle/CDDL/Resolve.hs index 96a0f9a..b4029b7 100644 --- a/src/Codec/CBOR/Cuddle/CDDL/Resolve.hs +++ b/src/Codec/CBOR/Cuddle/CDDL/Resolve.hs @@ -60,6 +60,7 @@ import Data.Hashable #if __GLASGOW_HASKELL__ < 910 import Data.List (foldl') #endif +import Data.Bits (Bits (..)) import Data.List.NonEmpty qualified as NE import Data.Map.Strict qualified as Map import Data.Text qualified as T @@ -90,7 +91,7 @@ asMap cddl = foldl' go Map.empty rules go x (TopLevelRule r) = assignOrExtend x r assignOrExtend :: CDDLMap -> Rule -> CDDLMap - assignOrExtend m (Rule n gps assign tog _) = case assign of + assignOrExtend m (Rule n gps assign tog _ _) = case assign of -- Equals assignment AssignEq -> Map.insert n (toParametrised tog gps) m AssignExt -> Map.alter (extend tog gps) n m @@ -132,7 +133,8 @@ data OrRef a type RefCTree = CTreeRoot OrRef -deriving instance Show (CTree OrRef) +instance Show (CTree OrRef) where + show = showCTree deriving instance Show (CTreeRoot OrRef) @@ -340,11 +342,34 @@ data DistRef a instance Hashable a => Hashable (DistRef a) -deriving instance Show (CTree DistRef) - -deriving instance Eq (CTree DistRef) - -instance Hashable (CTree DistRef) +instance Show (CTree DistRef) where + show = showCTree + +instance Eq (CTree DistRef) where + (==) = eqCTree + +instance Hashable (CTree DistRef) where + hashWithSalt salt = \case + CTree.Literal x -> hashWithSalt salt $ hashWithSalt salt x + CTree.Postlude x -> hashWithSalt (salt `xor` 1) $ hashWithSalt salt x + CTree.Map x -> hashWithSalt (salt `xor` 2) $ hashWithSalt salt x + CTree.Array x -> hashWithSalt (salt `xor` 3) $ hashWithSalt salt x + CTree.Choice x -> hashWithSalt (salt `xor` 4) $ hashWithSalt salt x + CTree.Group x -> hashWithSalt (salt `xor` 5) $ hashWithSalt salt x + CTree.Enum x -> hashWithSalt (salt `xor` 6) $ hashWithSalt salt x + CTree.Unwrap x -> hashWithSalt (salt `xor` 7) $ hashWithSalt salt x + CTree.Occur x y -> hashWithSalt (salt `xor` 8) $ hashWithSalt salt x `xor` hashWithSalt (salt `xor` 1) y + CTree.Tag x y -> hashWithSalt (salt `xor` 9) $ hashWithSalt salt x `xor` hashWithSalt (salt `xor` 1) y + CTree.WithGen _ y -> hashWithSalt (salt `xor` 10) $ hashWithSalt (salt `xor` 1) y + CTree.KV x y z -> + hashWithSalt (salt `xor` 11) $ + hashWithSalt salt x `xor` hashWithSalt (salt `xor` 1) y `xor` hashWithSalt (salt `xor` 2) z + CTree.Range x y z -> + hashWithSalt (salt `xor` 12) $ + hashWithSalt salt x `xor` hashWithSalt (salt `xor` 1) y `xor` hashWithSalt (salt `xor` 2) z + CTree.Control x y z -> + hashWithSalt (salt `xor` 13) $ + hashWithSalt salt x `xor` hashWithSalt (salt `xor` 1) y `xor` hashWithSalt (salt `xor` 2) z deriving instance Show (CTreeRoot DistRef) @@ -400,7 +425,41 @@ data MonoRef a | MRuleRef Name deriving (Functor, Show) -deriving instance Show (CTree MonoRef) +showCTree :: Show (f (CTree f)) => CTree f -> String +showCTree (CTree.Literal x) = "Literal " <> show x +showCTree (CTree.Postlude x) = "Postlude " <> show x +showCTree (CTree.Map x) = "Map " <> show x +showCTree (CTree.Array x) = "Array " <> show x +showCTree (CTree.Choice x) = "Choice " <> show x +showCTree (CTree.Group x) = "Group " <> show x +showCTree (CTree.KV x y z) = "KV " <> show x <> " " <> show y <> " " <> show z +showCTree (CTree.Occur x y) = "Occur " <> show x <> " " <> show y +showCTree (CTree.Range x y z) = "Range " <> show x <> " " <> show y <> " " <> show z +showCTree (CTree.Control x y z) = "Control " <> show x <> " " <> show y <> " " <> show z +showCTree (CTree.Enum x) = "Enum " <> show x +showCTree (CTree.Unwrap x) = "Unwrap " <> show x +showCTree (CTree.Tag x y) = "Tag " <> show x <> " " <> show y +showCTree (CTree.WithGen _ y) = "WithGen " <> show y + +eqCTree :: Eq (f (CTree f)) => CTree f -> CTree f -> Bool +eqCTree (CTree.Literal x) (CTree.Literal x') = x == x' +eqCTree (CTree.Postlude x) (CTree.Postlude x') = x == x' +eqCTree (CTree.Map x) (CTree.Map x') = x == x' +eqCTree (CTree.Array x) (CTree.Array x') = x == x' +eqCTree (CTree.Choice x) (CTree.Choice x') = x == x' +eqCTree (CTree.Group x) (CTree.Group x') = x == x' +eqCTree (CTree.KV x y z) (CTree.KV x' y' z') = x == x' && y == y' && z == z' +eqCTree (CTree.Occur x y) (CTree.Occur x' y') = x == x' && y == y' +eqCTree (CTree.Range x y z) (CTree.Range x' y' z') = x == x' && y == y' && z == z' +eqCTree (CTree.Control x y z) (CTree.Control x' y' z') = x == x' && y == y' && z == z' +eqCTree (CTree.Enum x) (CTree.Enum x') = x == x' +eqCTree (CTree.Unwrap x) (CTree.Unwrap x') = x == x' +eqCTree (CTree.Tag x y) (CTree.Tag x' y') = x == x' && y == y' +eqCTree (CTree.WithGen _ y) (CTree.WithGen _ y') = y == y' +eqCTree _ _ = False + +instance Show (CTree MonoRef) where + show = showCTree deriving instance Show (poly (CTree.Node MonoRef)) => diff --git a/src/Codec/CBOR/Cuddle/Huddle.hs b/src/Codec/CBOR/Cuddle/Huddle.hs index eb4669b..6278456 100644 --- a/src/Codec/CBOR/Cuddle/Huddle.hs +++ b/src/Codec/CBOR/Cuddle/Huddle.hs @@ -77,6 +77,9 @@ module Codec.CBOR.Cuddle.Huddle ( binding2, callToDef, + -- * Generators + withGenerator, + -- * Conversion to CDDL collectFrom, collectFromInit, @@ -85,7 +88,7 @@ module Codec.CBOR.Cuddle.Huddle ( ) where -import Codec.CBOR.Cuddle.CDDL (CDDL) +import Codec.CBOR.Cuddle.CDDL (CBORGenerator, CDDL) import Codec.CBOR.Cuddle.CDDL qualified as C import Codec.CBOR.Cuddle.CDDL.CtlOp qualified as CtlOp import Codec.CBOR.Cuddle.Comments qualified as C @@ -113,6 +116,7 @@ data Named a = Named { name :: T.Text , value :: a , description :: Maybe T.Text + , generator :: Maybe CBORGenerator } deriving (Functor, Generic) @@ -121,7 +125,7 @@ comment :: HasField' "description" a (Maybe T.Text) => T.Text -> a -> a comment desc n = n & field' @"description" .~ Just desc instance Show (Named a) where - show (Named n _ _) = T.unpack n + show (Named n _ _ _) = T.unpack n type Rule = Named Type0 @@ -516,7 +520,7 @@ instance IsCborable (AnyRef a) instance IsCborable GRef cbor :: (IsCborable b, IsConstrainable c b) => c -> Rule -> Constrained -cbor v r@(Named n _ _) = +cbor v r@(Named n _ _ _) = Constrained (toConstrainable v) ValueConstraint @@ -720,12 +724,12 @@ infixl 8 ==> -- | Assign a rule (=:=) :: IsType0 a => T.Text -> a -> Rule -n =:= b = Named n (toType0 b) Nothing +n =:= b = Named n (toType0 b) Nothing Nothing infixl 1 =:= (=:~) :: T.Text -> Group -> Named Group -n =:~ b = Named n b Nothing +n =:~ b = Named n b Nothing Nothing infixl 1 =:~ @@ -946,6 +950,7 @@ binding fRule t0 = , body = getField @"value" rule } Nothing + Nothing where rule = fRule (freshName 0) t2 = case toType0 t0 of @@ -962,6 +967,7 @@ binding2 fRule t0 t1 = , body = getField @"value" rule } Nothing + Nothing where rule = fRule (freshName 0) (freshName 1) t02 = case toType0 t0 of @@ -980,9 +986,9 @@ hiRule (HIRule r) = [r] hiRule _ = [] hiName :: HuddleItem -> T.Text -hiName (HIRule (Named n _ _)) = n -hiName (HIGroup (Named n _ _)) = n -hiName (HIGRule (Named n _ _)) = n +hiName (HIRule (Named n _ _ _)) = n +hiName (HIGroup (Named n _ _ _)) = n +hiName (HIGRule (Named n _ _ _)) = n -- | Collect all rules starting from a given point. This will also insert a -- single pseudo-rule as the first element which references the specified @@ -1001,8 +1007,8 @@ collectFrom topRs = } goHuddleItem (HIRule r) = goRule r goHuddleItem (HIGroup g) = goNamedGroup g - goHuddleItem (HIGRule (Named _ (GRule _ t0) _)) = goT0 t0 - goRule r@(Named n t0 _) = do + goHuddleItem (HIGRule (Named _ (GRule _ t0) _ _)) = goT0 t0 + goRule r@(Named n t0 _ _) = do items <- get when (OMap.notMember n items) $ do modify (OMap.|> (n, HIRule r)) @@ -1010,12 +1016,12 @@ collectFrom topRs = goChoice f (NoChoice x) = f x goChoice f (ChoiceOf x xs) = f x >> goChoice f xs goT0 = goChoice goT2 - goNamedGroup r@(Named n g _) = do + goNamedGroup r@(Named n g _ _) = do items <- get when (OMap.notMember n items) $ do modify (OMap.|> (n, HIGroup r)) goGroup g - goGRule r@(Named n g _) = do + goGRule r@(Named n g _ _) = do items <- get when (OMap.notMember n items) $ do modify (OMap.|> (n, HIGRule $ fmap callToDef r)) @@ -1088,8 +1094,8 @@ toCDDL' mkPseudoRoot hdl = comment "Pseudo-rule introduced by Cuddle to collect root elements" $ "huddle_root_defs" =:= arr (fromList (fmap a topRs)) toCDDLRule :: Rule -> C.Rule - toCDDLRule (Named n t0 c) = - (\x -> C.Rule (C.Name n mempty) Nothing C.AssignEq x (foldMap C.Comment c)) + toCDDLRule (Named n t0 c gen) = + (\x -> C.Rule (C.Name n mempty) Nothing C.AssignEq x (foldMap C.Comment c) gen) . C.TOGType . C.Type0 $ toCDDLType1 <$> choiceToNE t0 @@ -1137,8 +1143,8 @@ toCDDL' mkPseudoRoot hdl = T2Array x -> C.Type1 (C.T2Array $ arrayToCDDLGroup x) Nothing mempty T2Tagged (Tagged mmin x) -> C.Type1 (C.T2Tag mmin $ toCDDLType0 x) Nothing mempty - T2Ref (Named n _ _) -> C.Type1 (C.T2Name (C.Name n mempty) Nothing) Nothing mempty - T2Group (Named n _ _) -> C.Type1 (C.T2Name (C.Name n mempty) Nothing) Nothing mempty + T2Ref (Named n _ _ _) -> C.Type1 (C.T2Name (C.Name n mempty) Nothing) Nothing mempty + T2Group (Named n _ _ _) -> C.Type1 (C.T2Name (C.Name n mempty) Nothing) Nothing mempty T2Generic g -> C.Type1 (toGenericCall g) Nothing mempty T2GenericRef (GRef n) -> C.Type1 (C.T2Name (C.Name n mempty) Nothing) Nothing mempty @@ -1192,10 +1198,10 @@ toCDDL' mkPseudoRoot hdl = toCDDLRangeBound :: RangeBound -> C.Type2 toCDDLRangeBound (RangeBoundLiteral l) = C.T2Value $ toCDDLValue l - toCDDLRangeBound (RangeBoundRef (Named n _ _)) = C.T2Name (C.Name n mempty) Nothing + toCDDLRangeBound (RangeBoundRef (Named n _ _ _)) = C.T2Name (C.Name n mempty) Nothing toCDDLGroup :: Named Group -> C.Rule - toCDDLGroup (Named n (Group t0s) c) = + toCDDLGroup (Named n (Group t0s) c gen) = C.Rule (C.Name n mempty) Nothing @@ -1211,15 +1217,16 @@ toCDDL' mkPseudoRoot hdl = t0s ) (foldMap C.Comment c) + gen toGenericCall :: GRuleCall -> C.Type2 - toGenericCall (Named n gr _) = + toGenericCall (Named n gr _ _) = C.T2Name (C.Name n mempty) (Just . C.GenericArg $ fmap toCDDLType1 (args gr)) toGenRuleDef :: GRuleDef -> C.Rule - toGenRuleDef (Named n gr c) = + toGenRuleDef (Named n gr c gen) = C.Rule (C.Name n mempty) (Just gps) @@ -1229,6 +1236,10 @@ toCDDL' mkPseudoRoot hdl = $ toCDDLType1 <$> choiceToNE (body gr) ) (foldMap C.Comment c) + gen where gps = C.GenericParam $ fmap (\(GRef t) -> C.Name t mempty) (args gr) + +withGenerator :: HasField' "generator" a CBORGenerator => CBORGenerator -> a -> a +withGenerator gen = field' @"generator" .~ gen diff --git a/src/Codec/CBOR/Cuddle/Parser.hs b/src/Codec/CBOR/Cuddle/Parser.hs index f21f3f3..2637978 100644 --- a/src/Codec/CBOR/Cuddle/Parser.hs +++ b/src/Codec/CBOR/Cuddle/Parser.hs @@ -59,7 +59,7 @@ pRule = do <*> (TOGType <$> pType0 <* notFollowedBy (space >> (":" <|> "=>"))) , (,) <$> pAssignG <* space <*> (TOGGroup <$> pGrpEntry) ] - pure $ Rule name genericParam assign typeOrGrp cmt + pure $ Rule name genericParam assign typeOrGrp cmt Nothing pName :: Parser Name pName = label "name" $ do diff --git a/src/Codec/CBOR/Cuddle/Pretty.hs b/src/Codec/CBOR/Cuddle/Pretty.hs index 0e8b1ba..2cc9e9a 100644 --- a/src/Codec/CBOR/Cuddle/Pretty.hs +++ b/src/Codec/CBOR/Cuddle/Pretty.hs @@ -58,7 +58,7 @@ type0Def :: Type0 -> Doc ann type0Def t = nest 2 $ line' <> pretty t instance Pretty Rule where - pretty (Rule n mgen assign tog cmt) = + pretty (Rule n mgen assign tog cmt _) = pretty cmt <> groupIfNoComments tog diff --git a/test/Test/Codec/CBOR/Cuddle/CDDL/Gen.hs b/test/Test/Codec/CBOR/Cuddle/CDDL/Gen.hs index d907b3f..a715518 100644 --- a/test/Test/Codec/CBOR/Cuddle/CDDL/Gen.hs +++ b/test/Test/Codec/CBOR/Cuddle/CDDL/Gen.hs @@ -89,7 +89,11 @@ instance Arbitrary Rule where <*> arbitrary <*> arbitrary <*> arbitrary - shrink = genericShrink + <*> pure Nothing + shrink (Rule a b c d e _) = + [ Rule a' b' c' d' e' Nothing + | (a', b', c', d', e') <- shrink (a, b, c, d, e) + ] instance Arbitrary RangeBound where arbitrary = Gen.elements [ClOpen, Closed] diff --git a/test/Test/Codec/CBOR/Cuddle/CDDL/Parser.hs b/test/Test/Codec/CBOR/Cuddle/CDDL/Parser.hs index 53d777e..7fff349 100644 --- a/test/Test/Codec/CBOR/Cuddle/CDDL/Parser.hs +++ b/test/Test/Codec/CBOR/Cuddle/CDDL/Parser.hs @@ -11,11 +11,12 @@ import Codec.CBOR.Cuddle.Pretty () import Data.Default.Class (Default (..)) import Data.List.NonEmpty (NonEmpty (..)) import Data.Text qualified as T -import Data.TreeDiff (ToExpr (..), ansiWlBgEditExprCompact, exprDiff) +import Data.TreeDiff (ToExpr (..), ansiWlBgEditExprCompact, exprDiff, prettyExpr) import Prettyprinter (Pretty, defaultLayoutOptions, layoutPretty, pretty) import Prettyprinter.Render.String (renderString) import Prettyprinter.Render.Text (renderStrict) import Test.Codec.CBOR.Cuddle.CDDL.Gen qualified as Gen () +import Test.Codec.CBOR.Cuddle.Huddle (ruleMatches, shouldMatchParseRule, shouldMatchParseWith) import Test.Hspec import Test.Hspec.Megaparsec import Test.QuickCheck @@ -40,14 +41,13 @@ roundtripSpec = describe "Roundtripping should be id" $ do xit "Trip Value" $ trip pValue xit "Trip Type0" $ trip pType0 xit "Trip GroupEntry" $ trip pGrpEntry - xit "Trip Rule" $ trip pRule where -- We show that, for a printed CDDL document p, print (parse p) == p. Note -- that we do not show that parse (print p) is p for a given generated -- 'CDDL' doc, since CDDL contains some statements that allow multiple -- parsings. - trip :: forall a. (Eq a, ToExpr a, Show a, Pretty a, Arbitrary a) => Parser a -> Property - trip pa = property $ \(x :: a) -> within 1000000 $ do + trip :: forall a. (ToExpr a, Arbitrary a, Pretty a) => Parser a -> Property + trip pa = forAllShow arbitrary (show . prettyExpr . toExpr) $ \(x :: a) -> within 1000000 $ do let printed = printText x case parse (pa <* eof) "" printed of Left e -> @@ -106,63 +106,65 @@ nameSpec = describe "pName" $ do genericSpec :: Spec genericSpec = describe "generics" $ do it "Parses a simple value generic" $ - parse pRule "" "a = b<0>" - `shouldParse` Rule - (Name "a" mempty) - Nothing - AssignEq - ( TOGType - ( Type0 - ( Type1 - ( T2Name - (Name "b" mempty) - ( Just - ( GenericArg - ( Type1 - (T2Value (value $ VUInt 0)) - Nothing - mempty - :| [] - ) - ) - ) - ) - Nothing - mempty - :| [] - ) - ) - ) - mempty + Rule + (Name "a" mempty) + Nothing + AssignEq + ( TOGType + ( Type0 + ( Type1 + ( T2Name + (Name "b" mempty) + ( Just + ( GenericArg + ( Type1 + (T2Value (value $ VUInt 0)) + Nothing + mempty + :| [] + ) + ) + ) + ) + Nothing + mempty + :| [] + ) + ) + ) + mempty + Nothing + `shouldMatchParseRule` "a = b<0>" it "Parses a range as a generic" $ - parse pRule "" "a = b<0 ... 1>" - `shouldParse` Rule - (Name "a" mempty) - Nothing - AssignEq - ( TOGType - ( Type0 - ( Type1 - ( T2Name - (Name "b" mempty) - ( Just - ( GenericArg - ( Type1 - (T2Value (value $ VUInt 0)) - (Just (RangeOp ClOpen, T2Value (value $ VUInt 1))) - mempty - :| [] - ) - ) - ) - ) - Nothing - mempty - :| [] - ) - ) - ) - mempty + Rule + (Name "a" mempty) + Nothing + AssignEq + ( TOGType + ( Type0 + ( Type1 + ( T2Name + (Name "b" mempty) + ( Just + ( GenericArg + ( Type1 + (T2Value (value $ VUInt 0)) + (Just (RangeOp ClOpen, T2Value (value $ VUInt 1))) + mempty + :| [] + ) + ) + ) + ) + Nothing + mempty + :| [] + ) + ) + ) + mempty + Nothing + `shouldMatchParseRule` "a = b<0 ... 1>" type2Spec :: SpecWith () type2Spec = describe "type2" $ do @@ -347,27 +349,27 @@ type2Spec = describe "type2" $ do , gcComment = Comment mempty } :| [ GrpChoice - { gcGroupEntries = - [ GroupEntry - { geOccurrenceIndicator = Nothing - , geComment = Comment mempty - , geVariant = - GEType - Nothing - ( Type0 - { t0Type1 = - Type1 - { t1Main = T2Name (Name {name = "string", nameComment = Comment mempty}) Nothing - , t1TyOp = Nothing - , t1Comment = Comment mempty - } - :| [] - } - ) - } - ] - , gcComment = Comment mempty - } + { gcGroupEntries = + [ GroupEntry + { geOccurrenceIndicator = Nothing + , geComment = Comment mempty + , geVariant = + GEType + Nothing + ( Type0 + { t0Type1 = + Type1 + { t1Main = T2Name (Name {name = "string", nameComment = Comment mempty}) Nothing + , t1TyOp = Nothing + , t1Comment = Comment mempty + } + :| [] + } + ) + } + ] + , gcComment = Comment mempty + } ] } ) @@ -395,22 +397,22 @@ type2Spec = describe "type2" $ do , gcComment = Comment mempty } :| [ GrpChoice - { gcGroupEntries = - [ GroupEntry - { geOccurrenceIndicator = Nothing - , geComment = Comment mempty - , geVariant = - GEType - Nothing - ( Type0 - { t0Type1 = - Type1 {t1Main = T2Value (value $ VUInt 1), t1TyOp = Nothing, t1Comment = Comment mempty} :| [] - } - ) - } - ] - , gcComment = Comment mempty - } + { gcGroupEntries = + [ GroupEntry + { geOccurrenceIndicator = Nothing + , geComment = Comment mempty + , geVariant = + GEType + Nothing + ( Type0 + { t0Type1 = + Type1 {t1Main = T2Value (value $ VUInt 1), t1TyOp = Nothing, t1Comment = Comment mempty} :| [] + } + ) + } + ] + , gcComment = Comment mempty + } ] } ) @@ -616,10 +618,14 @@ type1Spec = describe "Type1" $ do (Just (RangeOp ClOpen, T2Value (value $ VUInt 3))) mempty -parseExample :: (Show a, Eq a) => T.Text -> Parser a -> a -> Spec -parseExample str parser val = +parseExampleWith :: ToExpr a => (a -> a -> Bool) -> T.Text -> Parser a -> a -> Spec +parseExampleWith matches str parser val = it (T.unpack str) $ - parse (parser <* eof) "" str `shouldParse` val + shouldMatchParseWith matches val parser $ + T.unpack str + +parseExample :: (Show a, ToExpr a, Eq a) => T.Text -> Parser a -> a -> Spec +parseExample = parseExampleWith (==) -- | A bunch of cases found by hedgehog/QC qcFoundSpec :: Spec @@ -651,7 +657,7 @@ qcFoundSpec = ) , t1Comment = Comment mempty } - parseExample "S = 0* ()" pRule $ + parseExampleWith ruleMatches "S = 0* ()" pRule $ Rule (Name "S" mempty) Nothing @@ -662,7 +668,9 @@ qcFoundSpec = ) ) mempty - parseExample + Nothing + parseExampleWith + ruleMatches "W = \"6 ybe2ddl8frq0vqa8zgrk07khrljq7p plrufpd1sff3p95\" : \"u\"" pRule ( Rule @@ -679,4 +687,5 @@ qcFoundSpec = ) ) mempty + Nothing ) diff --git a/test/Test/Codec/CBOR/Cuddle/CDDL/Pretty.hs b/test/Test/Codec/CBOR/Cuddle/CDDL/Pretty.hs index 3a5a354..9c0352e 100644 --- a/test/Test/Codec/CBOR/Cuddle/CDDL/Pretty.hs +++ b/test/Test/Codec/CBOR/Cuddle/CDDL/Pretty.hs @@ -86,30 +86,30 @@ drep = ] mempty :| [ GrpChoice - [ GroupEntry - Nothing - mempty - (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 1) Nothing mempty :| [])) - , GroupEntry - Nothing - mempty - (GEType Nothing (Type0 $ Type1 (T2Name "script_hash" Nothing) Nothing mempty :| [])) - ] - mempty + [ GroupEntry + Nothing + mempty + (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 1) Nothing mempty :| [])) + , GroupEntry + Nothing + mempty + (GEType Nothing (Type0 $ Type1 (T2Name "script_hash" Nothing) Nothing mempty :| [])) + ] + mempty , GrpChoice - [ GroupEntry - Nothing - mempty - (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 2) Nothing mempty :| [])) - ] - mempty + [ GroupEntry + Nothing + mempty + (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 2) Nothing mempty :| [])) + ] + mempty , GrpChoice - [ GroupEntry - Nothing - mempty - (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 3) Nothing mempty :| [])) - ] - mempty + [ GroupEntry + Nothing + mempty + (GEType Nothing (Type0 $ Type1 (T2Value . value $ VUInt 3) Nothing mempty :| [])) + ] + mempty ] ) ) @@ -121,6 +121,7 @@ drep = ) ) mempty + Nothing unitSpec :: Spec unitSpec = describe "HUnit" $ do @@ -181,6 +182,7 @@ unitSpec = describe "HUnit" $ do AssignEq (TOGType (Type0 (Type1 (T2Name (Name "b" mempty) mempty) Nothing mempty :| []))) mempty + Nothing `prettyPrintsTo` "a = b" xit "drep" $ drep diff --git a/test/Test/Codec/CBOR/Cuddle/Huddle.hs b/test/Test/Codec/CBOR/Cuddle/Huddle.hs index 06a0dbf..7d02d37 100644 --- a/test/Test/Codec/CBOR/Cuddle/Huddle.hs +++ b/test/Test/Codec/CBOR/Cuddle/Huddle.hs @@ -5,13 +5,13 @@ module Test.Codec.CBOR.Cuddle.Huddle where -import Codec.CBOR.Cuddle.CDDL (CDDL, sortCDDL) -import Codec.CBOR.Cuddle.Huddle -import Codec.CBOR.Cuddle.Parser +import Codec.CBOR.Cuddle.CDDL (CDDL (..), Rule (..), TopLevel (..), sortCDDL) +import Codec.CBOR.Cuddle.Huddle hiding (Rule) +import Codec.CBOR.Cuddle.Parser (pCDDL, pRule) import Data.Text qualified as T +import Data.TreeDiff (ToExpr, ediff, prettyEditExpr) import Test.Codec.CBOR.Cuddle.CDDL.Pretty qualified as Pretty import Test.Hspec -import Test.Hspec.Megaparsec import Text.Megaparsec import Prelude hiding ((/)) @@ -146,19 +146,49 @@ constraintSpec = -- Helper functions -------------------------------------------------------------------------------- -shouldMatchParse :: - (Text.Megaparsec.ShowErrorComponent e, Show a, Eq a) => +shouldMatchParseWith :: + (Text.Megaparsec.ShowErrorComponent e, Show e, ToExpr a) => + (a -> a -> Bool) -> a -> Text.Megaparsec.Parsec e T.Text a -> String -> Expectation -shouldMatchParse x parseFun input = parse parseFun "" (T.pack input) `shouldParse` x +shouldMatchParseWith matches expected parseFun input = do + case parse parseFun "" $ T.pack input of + Right parsed + | parsed `matches` expected -> pure () + | otherwise -> + expectationFailure $ + unlines + [ "Mismatch between parsed and expected" + , show . prettyEditExpr $ expected `ediff` parsed + ] + Left e -> expectationFailure $ show e -shouldMatchParseCDDL :: - CDDL -> +shouldMatchParse :: + (ShowErrorComponent e, Show e, ToExpr a, Eq a) => + a -> + Text.Megaparsec.Parsec e T.Text a -> String -> Expectation -shouldMatchParseCDDL x = shouldMatchParse x pCDDL +shouldMatchParse = shouldMatchParseWith (==) + +shouldMatchParseCDDL :: CDDL -> String -> Expectation +shouldMatchParseCDDL x = shouldMatchParseWith cddlMatches x pCDDL + +shouldMatchParseRule :: Rule -> String -> Expectation +shouldMatchParseRule x = shouldMatchParseWith ruleMatches x pRule + +cddlMatches :: CDDL -> CDDL -> Bool +cddlMatches (CDDL c r t) (CDDL c' r' t') = c == c' && ruleMatches r r' && and (zipWith topLevelMatches t t') && length t == length t' + +ruleMatches :: Codec.CBOR.Cuddle.CDDL.Rule -> Codec.CBOR.Cuddle.CDDL.Rule -> Bool +ruleMatches (Rule n b c d e _) (Rule n' b' c' d' e' _) = n == n' && b == b' && c == c' && d == d' && e == e' + +topLevelMatches :: TopLevel -> TopLevel -> Bool +topLevelMatches (TopLevelComment c) (TopLevelComment c') = c == c' +topLevelMatches (TopLevelRule r) (TopLevelRule r') = ruleMatches r r' +topLevelMatches _ _ = False toSortedCDDL :: Huddle -> CDDL toSortedCDDL = sortCDDL . toCDDLNoRoot