Skip to content

Commit 6d182e7

Browse files
committed
Introduce HuddleM
This is an attempt at addressing #35. We provide a monad for specifying Huddle definitions, which will collect them in definition order. This allows a fairly convenient way to put together a specification, at the cost of it being trickier to re-use definitions elsewhere, since they need to be returned from the monad.
1 parent fcb9ed7 commit 6d182e7

File tree

6 files changed

+120
-3
lines changed

6 files changed

+120
-3
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,10 @@
2222
## 0.3.2.0 -- 2024-09-11
2323

2424
* Leading rather than trailing commas in the pretty printer.
25+
26+
## 0.3.3.0 -- 2024-11-13
27+
28+
* Introduce HuddleM, another way to define a Huddle spec. This allows total
29+
control over the order that items are presented in the CDDL, at the cost
30+
of making it somewhat harder to re-use items (they need to be returned from
31+
the monad).

cuddle.cabal

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cabal-version: 3.4
22
name: cuddle
3-
version: 0.3.2.1
3+
version: 0.3.3.0
44
synopsis: CDDL Generator and test utilities
55

66
-- description:
@@ -50,6 +50,7 @@ library
5050
Codec.CBOR.Cuddle.CDDL.Postlude
5151
Codec.CBOR.Cuddle.CDDL.Resolve
5252
Codec.CBOR.Cuddle.Huddle
53+
Codec.CBOR.Cuddle.Huddle.HuddleM
5354
Codec.CBOR.Cuddle.Parser
5455
Codec.CBOR.Cuddle.Pretty
5556

@@ -81,7 +82,9 @@ library
8182
executable example
8283
import: warnings, ghc2021
8384
default-language: Haskell2010
84-
other-modules: Conway
85+
other-modules:
86+
Conway
87+
Monad
8588

8689
-- other-extensions:
8790
hs-source-dirs: example

example/Main.hs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Prettyprinter.Util (putDocW)
2222
import System.Environment (getArgs)
2323
import System.Random (getStdGen)
2424
import Text.Megaparsec (ParseErrorBundle, Parsec, errorBundlePretty, runParser)
25+
import qualified Monad
2526

2627
main :: IO ()
2728
main = do
@@ -65,6 +66,8 @@ main = do
6566
[] -> do
6667
let cw = toCDDL conway
6768
putDocW 80 $ pretty cw
69+
putStrLn "--------------------------------------"
70+
putDocW 80 $ pretty (toCDDL Monad.spec)
6871
_ -> putStrLn "Expected filename"
6972

7073
parseFromFile ::

example/Monad.hs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{-# LANGUAGE OverloadedLists #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE RecursiveDo #-}
4+
module Monad where
5+
6+
import Codec.CBOR.Cuddle.Huddle.HuddleM
7+
import Data.Word (Word64)
8+
9+
spec :: Huddle
10+
spec = huddleDef $ mdo
11+
transaction <- "transaction" =:= mp
12+
[ idx 0 ==> set txIn,
13+
idx 1 ==> set txOut
14+
]
15+
txIn <- "txIn" =:= arr [ "transaction_id" ==> hash32, "index" ==> txId]
16+
txOut <- "txOut" =:= arr [ idx 0 ==> address, idx 1 ==> value]
17+
txId <- "txId" =:= VUInt `sized` (2 :: Word64)
18+
address <- "address" =:= VBytes `sized` (32 :: Word64)
19+
hash32 <- "hash32" =:= VBytes `sized` (32 :: Word64)
20+
value <- "value" =:= VUInt
21+
set <- binding $ \x -> "set" =::= arr [0 <+ a x]
22+
23+
setRootRules [transaction]
24+
pure ()

example/cddl-files/basic_assign.cddl

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ header_body = [
1313
]
1414

1515
$kes_signature = bytes .size 32
16-
unit_interval<denominator> = [0..denominator, denominator]
16+
unit_interval<denominator> = [0 .. denominator, denominator]
1717

1818
unit_int = unit_interval<uint>
1919

@@ -28,3 +28,8 @@ usz4 = uint .size 4
2828
usz8 = uint .size 8
2929

3030
group = (usz4, usz8 / mysize, header_body, { * uint => coin })
31+
32+
set<a> = [ * a]
33+
set2<a> = set<a>
34+
35+
coin_bag = set2<coin>
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
-- | Monad for declaring Huddle constructs
2+
module Codec.CBOR.Cuddle.Huddle.HuddleM
3+
( module Huddle,
4+
(=:=),
5+
(=:~),
6+
(=::=),
7+
binding,
8+
setRootRules,
9+
huddleDef,
10+
huddleDef',
11+
include,
12+
)
13+
where
14+
15+
import Codec.CBOR.Cuddle.Huddle hiding (binding, (=:=), (=:~))
16+
import Codec.CBOR.Cuddle.Huddle qualified as Huddle
17+
import Control.Monad.State.Strict (State, modify, runState)
18+
import Data.Default.Class (def)
19+
import Data.Generics.Product (HasField (..))
20+
import Data.Text qualified as T
21+
import Optics.Core (Field2 (..), set, (%), (%~))
22+
23+
type HuddleM = State Huddle
24+
25+
-- | Overridden version of assignment which also adds the rule to the state
26+
(=:=) :: (IsType0 a) => T.Text -> a -> HuddleM Rule
27+
n =:= b = let r = n Huddle.=:= b in include r
28+
29+
infixl 1 =:=
30+
31+
-- | Overridden version of group assignment which adds the rule to the state
32+
(=:~) :: T.Text -> Group -> HuddleM (Named Group)
33+
n =:~ b = let r = n Huddle.=:~ b in include r
34+
35+
infixl 1 =:~
36+
37+
binding ::
38+
forall t0.
39+
(IsType0 t0) =>
40+
(GRef -> Rule) ->
41+
HuddleM (t0 -> GRuleCall)
42+
binding fRule = include (Huddle.binding fRule)
43+
44+
-- | Renamed version of Huddle's underlying '=:=' for use in generic bindings
45+
(=::=) :: (IsType0 a) => T.Text -> a -> Rule
46+
n =::= b = n Huddle.=:= b
47+
48+
infixl 1 =::=
49+
50+
setRootRules :: [Rule] -> HuddleM ()
51+
setRootRules = modify . set (field @"roots")
52+
53+
huddleDef :: HuddleM a -> Huddle
54+
huddleDef = snd . huddleDef'
55+
56+
huddleDef' :: HuddleM a -> (a, Huddle)
57+
huddleDef' mh = (_2 % field @"items") %~ reverse $ runState mh def
58+
59+
class Includable a where
60+
-- | Include a rule, group, or generic rule defined elsewhere
61+
include :: a -> HuddleM a
62+
63+
instance Includable Rule where
64+
include r = modify (field @"items" %~ (HIRule r :)) >> pure r
65+
66+
instance Includable (Named Group) where
67+
include r = modify ((field @"items") %~ (HIGroup r :)) >> pure r
68+
69+
instance (IsType0 t0) => Includable (t0 -> GRuleCall) where
70+
include gr =
71+
let fakeT0 = error "Attempting to unwrap fake value in generic call"
72+
grDef = callToDef <$> gr fakeT0
73+
in do
74+
modify (field @"items" %~ (HIGRule grDef :))
75+
pure gr

0 commit comments

Comments
 (0)