Skip to content

Commit 8c66e35

Browse files
committed
Tools for extending Huddle specifications
We add some tools to allow extending Huddle specifications using the semigroup instance established: - We allow one to reference a HuddleItem (which is a rule, a group, or a generic def) as a Type0. - We then allow one to reference (by name) a HuddleItem from an existing spec. These two combined allow us to (somewhat) "extend" a specification in a nice manner - we can reference items from the previous spec by their name, and then selectively override things using the semigroup instance (but respecting the original ordering). There are two "disappointments" involved in this, however: - Since the items from a previous spec are referenced by name, we lose the type-safety provided by the Haskell compiler. It's quite possible to refer to an item that doesn't exist, and you won't find out until runtime. - The whole thing falls apart for generic rules. When calling a generic rule in the usual way, we do two things: - Apply the actual argument and turn it into a 'GRuleCall' which we return to the call site. - Discard the argument, create an appropriate number of fresh names and insert this into Huddle as a 'GRuleDef'. This crucially ignores any actual arguments, which is why we can pass an error as a fake argument in the Includable instance for HuddleM and have it all work. Unfortunately, what we cannot do is go from the 'GRuleDef' and extract from it the fact that, on the Haskell side, this is a function (with an unknown number of parameters). Which is very annoying. I have some ideas about resolving this second issue in a slightly-less-horrible way; they will follow in a subsequent commit.
1 parent 3358bdd commit 8c66e35

File tree

4 files changed

+89
-27
lines changed

4 files changed

+89
-27
lines changed

example/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@ main = do
6868
putDocW 80 $ pretty cw
6969
putStrLn "--------------------------------------"
7070
putDocW 80 $ pretty (toCDDL Monad.spec)
71+
putStrLn "--------------------------------------"
72+
putDocW 80 $ pretty (toCDDL Monad.spec2)
7173
_ -> putStrLn "Expected filename"
7274

7375
parseFromFile ::

example/Monad.hs

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,52 @@
11
{-# LANGUAGE OverloadedLists #-}
22
{-# LANGUAGE OverloadedStrings #-}
33
{-# LANGUAGE RecursiveDo #-}
4-
module Monad where
4+
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
5+
{-# HLINT ignore "Use camelCase" #-}
56

7+
module Monad where
8+
9+
import Codec.CBOR.Cuddle.Huddle qualified as Huddle
610
import Codec.CBOR.Cuddle.Huddle.HuddleM
711
import Data.Word (Word64)
812

13+
hdl_set :: (IsType0 t0) => t0 -> GRuleCall
14+
hdl_set = Huddle.binding $ \x -> "set" Huddle.=:= arr [0 <+ a x]
15+
916
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+
spec = huddleDef $ mdo
18+
transaction <-
19+
"transaction"
20+
=:= mp
21+
[ idx 0 ==> set txIn,
22+
idx 1 ==> set txOut
23+
]
24+
txIn <- "txIn" =:= arr ["transaction_id" ==> hash32, "index" ==> txId]
25+
txOut <- "txOut" =:= arr [idx 0 ==> address, idx 1 ==> value]
1726
txId <- "txId" =:= VUInt `sized` (2 :: Word64)
1827
address <- "address" =:= VBytes `sized` (32 :: Word64)
1928
hash32 <- "hash32" =:= VBytes `sized` (32 :: Word64)
20-
value <- "value" =:= VUInt
21-
set <- binding $ \x -> "set" =::= arr [0 <+ a x]
29+
value <- "value" =:= VUInt
30+
set <- include hdl_set
2231

2332
setRootRules [transaction]
24-
pure ()
33+
34+
spec2 :: Huddle
35+
spec2 =
36+
spec
37+
<> huddleDef
38+
( mdo
39+
set <- include hdl_set
40+
txIn <- unsafeIncludeFromHuddle spec "txIn"
41+
txOut <- unsafeIncludeFromHuddle spec "txOut"
42+
_transaction <-
43+
"transaction"
44+
=:= mp
45+
[ idx 0 ==> set txIn,
46+
idx 1 ==> set txOut,
47+
idx 2 ==> metadata
48+
]
49+
metadata <- "metadata" =:= VBytes
50+
_value <- "value" =:= mp ["token" ==> VText, "quantity" ==> VUInt]
51+
pure ()
52+
)

src/Codec/CBOR/Cuddle/Huddle.hs

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ import Data.ByteString (ByteString)
9494
import Data.Default.Class (Default (..))
9595
import Data.Generics.Product (field, getField)
9696
import Data.List.NonEmpty qualified as NE
97+
import Data.Map.Ordered.Strict (OMap)
98+
import Data.Map.Ordered.Strict qualified as OMap
9799
import Data.String (IsString (fromString))
98100
import Data.Text qualified as T
99101
import Data.Tuple.Optics (Field2 (..))
@@ -103,8 +105,6 @@ import GHC.Exts (IsList (Item, fromList, toList))
103105
import GHC.Generics (Generic)
104106
import Optics.Core (view, (%~), (&), (.~), (^.))
105107
import Prelude hiding ((/))
106-
import Data.Map.Ordered.Strict qualified as OMap
107-
import Data.Map.Ordered.Strict (OMap)
108108

109109
data Named a = Named
110110
{ name :: T.Text,
@@ -136,32 +136,37 @@ data Huddle = Huddle
136136
}
137137
deriving (Generic, Show)
138138

139-
-- | This semigroup instance takes the roots from the RHS and uses the
140-
-- RHS to override items on the LHS where they share a name.
141-
-- The value from the RHS is taken, but the index from the LHS is used.
139+
-- | This semigroup instance:
140+
-- - Takes takes the roots from the RHS unless they are empty, in which case
141+
-- it takes the roots from the LHS
142+
-- - Uses the RHS to override items on the LHS where they share a name.
143+
-- The value from the RHS is taken, but the index from the LHS is used.
142144
--
143-
-- Note that this allows replacing items in the middle of a tree without
145+
-- Note that this allows replacing items in the middle of a tree without
144146
-- updating higher-level items which make use of them - that is, we do not
145147
-- need to "close over" higher-level terms, since by the time they have been
146148
-- built into a huddle structure, the references have been converted to keys.
147-
instance Semigroup Huddle where
148-
h1 <> h2 = Huddle {
149-
roots = roots h2,
150-
items = OMap.unionWithL (\_ _ v2 -> v2) (items h1) (items h2)
151-
}
149+
instance Semigroup Huddle where
150+
h1 <> h2 =
151+
Huddle
152+
{ roots = case roots h2 of
153+
[] -> roots h1
154+
xs -> xs,
155+
items = OMap.unionWithL (\_ _ v2 -> v2) (items h1) (items h2)
156+
}
152157

153158
-- | This instance is mostly used for testing
154159
instance IsList Huddle where
155160
type Item Huddle = Rule
156-
fromList [] = Huddle mempty OMap.empty
157-
fromList (x : xs) =
158-
(field @"items" %~ (OMap.|> (x ^. field @"name", HIRule x))) $ fromList xs
161+
fromList [] = Huddle mempty OMap.empty
162+
fromList (x : xs) =
163+
(field @"items" %~ (OMap.|> (x ^. field @"name", HIRule x))) $ fromList xs
159164

160165
toList = const []
161166

162167
instance Default Huddle where
163-
def = Huddle [] OMap.empty
164-
168+
def = Huddle [] OMap.empty
169+
165170
data Choice a
166171
= NoChoice a
167172
| ChoiceOf a (Choice a)
@@ -529,6 +534,13 @@ instance IsType0 GRef where
529534
instance (IsType0 a) => IsType0 (Tagged a) where
530535
toType0 = NoChoice . T2Tagged . fmap toType0
531536

537+
instance IsType0 HuddleItem where
538+
toType0 (HIRule r) = toType0 r
539+
toType0 (HIGroup g) = toType0 g
540+
toType0 (HIGRule g) =
541+
error $
542+
"Attempt to reference generic rule from HuddleItem not supported: " <> show g
543+
532544
class CanQuantify a where
533545
-- | Apply a lower bound
534546
(<+) :: Word64 -> a -> a

src/Codec/CBOR/Cuddle/Huddle/HuddleM.hs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ module Codec.CBOR.Cuddle.Huddle.HuddleM
99
huddleDef,
1010
huddleDef',
1111
include,
12+
unsafeIncludeFromHuddle,
1213
)
1314
where
1415

@@ -82,3 +83,22 @@ instance (IsType0 t0) => Includable (t0 -> GRuleCall) where
8283
in do
8384
modify (field @"items" %~ (OMap.|> (n, HIGRule grDef)))
8485
pure gr
86+
87+
instance Includable HuddleItem where
88+
include x@(HIRule r) = include r >> pure x
89+
include x@(HIGroup g) = include g >> pure x
90+
include x@(HIGRule g) =
91+
let n = g ^. field @"name"
92+
in do
93+
modify (field @"items" %~ (OMap.|> (n, x)))
94+
pure x
95+
96+
unsafeIncludeFromHuddle ::
97+
Huddle ->
98+
T.Text ->
99+
HuddleM HuddleItem
100+
unsafeIncludeFromHuddle h name =
101+
let items = h ^. field @"items"
102+
in case OMap.lookup name items of
103+
Just v -> include v
104+
Nothing -> error $ show name <> " was not found in Huddle spec"

0 commit comments

Comments
 (0)