@@ -1,5 +1,4 @@
1−{-# LANGUAGE UndecidableInstances #-}
1+{-# LANGUAGE NoImplicitPrelude #-}
2−{-# LANGUAGE FunctionalDependencies #-}
@@ -23,7 +22,7 @@ wrapper around the field type:
26−> "Color" =:= enum
25+> "Color" =:= enum Both
@@ -41,7 +40,7 @@ to be either @R@, @G@ or @B@.
44−> "Message" =:= record
43+> "Message" =:= record Both
45> ! "author"
44> ! "author"
46> + "recipient"
45> + "recipient"
47> ? "message" [t|Text|]
46> ? "message" [t|Text|]
@@ -68,10 +67,10 @@ enumeration is exhaustive (contains only the listed constructors) or
68non-exhaustive (also contains a constructor for unknown values; this
67non-exhaustive (also contains a constructor for unknown values; this
69is the default):
68is the default):
71−> "Enum1" Exhaustive =:= enum
70+> "Enum1" Exhaustive =:= enum Generator
74−> "Enum2" NonExhaustive =:= enum
73+> "Enum2" NonExhaustive =:= enum Generator
77To define a record, use the `record` function followed by the name of
76To define a record, use the `record` function followed by the name of
@@ -79,7 +78,7 @@ the data type to be generated. The prefix for the record fields is
79inferred automatically by taking all of the uppercase letters in the
78inferred automatically by taking all of the uppercase letters in the
80name. You can override it manually like so:
79name. You can override it manually like so:
82−> "Reference" "ref" =:= record
81+> "Reference" "ref" =:= record Parser
85To describe a record field you must supply its name as it appears
84To describe a record field you must supply its name as it appears
@@ -94,10 +93,14 @@ The type of the field is inferred automatically from its name, so
94if the field is called @"author"@ its type will be @Author@. You can
93if the field is called @"author"@ its type will be @Author@. You can
95override the type by specifying it in quasiquotes like so:
94override the type by specifying it in quasiquotes like so:
97−> "Message" =:= record
96+> "Message" =:= record Both
98−> ! "author" [t|Person|]
97+> ! "author" [t|Person|].
100+Both @record@ and @enum@ take a @GenType@ argument which tells it to generate
101+generator, parser or both. It's being used to optimize compilation time by
102+skipping the unnecessary TH instance generation.
@@ -105,6 +108,7 @@ module Data.THGen.XML
105 ( Exhaustiveness(..)
108 ( Exhaustiveness(..)
106 , PrefixName(..)
109 , PrefixName(..)
107 , ExhaustivenessName(..)
110 , ExhaustivenessName(..)
@@ -114,348 +118,15 @@ module Data.THGen.XML
118 -- Re-exports
123 -- Re-exports
121− , Integer
126+ , P.Integer
124−import Control.DeepSeq
125−import Control.Lens hiding (repeated, enum, (&))
126−import Control.Lens.Internal.FieldTH (makeFieldOpticsForDec)
127−import qualified Data.Char as C
128−import Data.List.NonEmpty (NonEmpty)
129−import Data.Maybe (maybeToList, mapMaybe)
131−import Data.THGen.Compat
132import Data.THGen.Enum
129import Data.THGen.Enum
133−import qualified Data.Text as T
130+import Data.THGen.XML.Internal
134−import GHC.Generics (Generic)
131+import Data.Text
135−import qualified Language.Haskell.TH as TH
136−import qualified Language.Haskell.TH.Syntax as TH
132+import qualified Prelude as P
137−import Prelude hiding ((+), (*))
138−import qualified Text.XML as X
139−import Text.XML.DOM.Parser
140−import Text.XML.ParentAttributes
141−import qualified Text.XML.Writer as XW
145− = XmlFieldPluralMandatory -- Occurs exactly 1 time (Identity)
146− | XmlFieldPluralOptional -- Occurs 0 or 1 times (Maybe)
147− | XmlFieldPluralRepeated -- Occurs 0 or more times (List)
148− | XmlFieldPluralMultiplied -- Occurs 1 or more times (NonEmpty)
150−data XmlAttributePlural
151− = XmlAttributePluralMandatory -- Occurs exactly 1 time (Identity)
152− | XmlAttributePluralOptional -- Occurs 0 or 1 times (Maybe)
154−data PrefixName = PrefixName String String
156−data IsoXmlDescPreField = IsoXmlDescPreField String TH.TypeQ
158−data IsoXmlDescPreAttribute = IsoXmlDescPreAttribute String TH.TypeQ
160−data IsoXmlDescField = IsoXmlDescField XmlFieldPlural String TH.TypeQ
162−data IsoXmlDescAttribute = IsoXmlDescAttribute XmlAttributePlural String TH.TypeQ
164−data IsoXmlDescRecordPart
165− = IsoXmlDescRecordField IsoXmlDescField
166− | IsoXmlDescRecordAttribute IsoXmlDescAttribute
168−newtype IsoXmlDescRecord = IsoXmlDescRecord [IsoXmlDescRecordPart]
170−makePrisms ''IsoXmlDescRecord
172−data ExhaustivenessName = ExhaustivenessName String Exhaustiveness
174−newtype IsoXmlDescEnumCon
175− = IsoXmlDescEnumCon { unIsoXmlDescEnumCon :: String }
177−instance IsString IsoXmlDescEnumCon where
178− fromString = IsoXmlDescEnumCon
180−data IsoXmlDescEnum = IsoXmlDescEnum [IsoXmlDescEnumCon]
182−makePrisms ''IsoXmlDescEnum
187− -> IsoXmlDescPreField
189−appendField plural xrec (IsoXmlDescPreField name ty) =
190− let xfield = IsoXmlDescRecordField $ IsoXmlDescField plural name ty
191− in over _IsoXmlDescRecord (xfield:) xrec
194− :: XmlAttributePlural
196− -> IsoXmlDescPreAttribute
198−appendAttribute plural xrec (IsoXmlDescPreAttribute name ty) =
199− let xattribute = IsoXmlDescRecordAttribute $ IsoXmlDescAttribute plural name ty
200− in over _IsoXmlDescRecord (xattribute:) xrec
202−(!), (?), (*), (+) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
203−(!) = appendField XmlFieldPluralMandatory
204−(?) = appendField XmlFieldPluralOptional
205−(*) = appendField XmlFieldPluralRepeated
206−(+) = appendField XmlFieldPluralMultiplied
208−(!%), (?%) :: IsoXmlDescRecord -> IsoXmlDescPreAttribute -> IsoXmlDescRecord
209−(!%) = appendAttribute XmlAttributePluralMandatory
210−(?%) = appendAttribute XmlAttributePluralOptional
219−appendEnumCon :: IsoXmlDescEnum -> IsoXmlDescEnumCon -> IsoXmlDescEnum
220−appendEnumCon xenum xenumcon =
221− over _IsoXmlDescEnum (xenumcon:) xenum
223−(&) :: IsoXmlDescEnum -> IsoXmlDescEnumCon -> IsoXmlDescEnum
228−class Description name desc | desc -> name where
229− (=:=) :: name -> desc -> TH.DecsQ
233−instance Description PrefixName IsoXmlDescRecord where
234− prefixName =:= descRecord =
235− let descRecordParts = descRecord ^. _IsoXmlDescRecord
236− in isoXmlGenerateDatatype prefixName (reverse descRecordParts)
238−record :: IsoXmlDescRecord
239−record = IsoXmlDescRecord []
241−enum :: IsoXmlDescEnum
242−enum = IsoXmlDescEnum []
244−instance Description ExhaustivenessName IsoXmlDescEnum where
245− exhaustivenessName =:= descEnum =
246− let descEnumCons = descEnum ^. _IsoXmlDescEnum
247− in isoXmlGenerateEnum exhaustivenessName (reverse descEnumCons)
249−instance IsString (TH.TypeQ -> IsoXmlDescPreField) where
250− fromString = IsoXmlDescPreField
252−instance IsString IsoXmlDescPreField where
253− fromString name = IsoXmlDescPreField name ty
255− ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper (xmlLocalName name))
257−instance IsString (TH.TypeQ -> IsoXmlDescPreAttribute) where
258− fromString = IsoXmlDescPreAttribute
260−instance IsString IsoXmlDescPreAttribute where
261− fromString name = IsoXmlDescPreAttribute name ty
263− ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper name)
265−instance s ~ String => IsString (s -> PrefixName) where
266− fromString = PrefixName
268−instance IsString PrefixName where
269− fromString strName = PrefixName strName (makeNamePrefix strName)
271−instance e ~ Exhaustiveness => IsString (e -> ExhaustivenessName) where
272− fromString = ExhaustivenessName
274−instance IsString ExhaustivenessName where
275− fromString strName = ExhaustivenessName strName NonExhaustive
277−makeNamePrefix :: String -> String
278−makeNamePrefix = map C.toLower . filter C.isUpper
280−funSimple :: TH.Name -> TH.ExpQ -> TH.DecQ
281−funSimple name body = TH.funD name [ TH.clause [] (TH.normalB body) [] ]
284− :: ExhaustivenessName -> [IsoXmlDescEnumCon] -> TH.DecsQ
285−isoXmlGenerateEnum (ExhaustivenessName strName' exh) enumCons = do
287− strName = "Xml" ++ strName'
288− strVals = map unIsoXmlDescEnumCon enumCons
289− enumDesc = EnumDesc exh strName strVals
290− name = TH.mkName strName
291− enumDecls <- enumGenerate enumDesc
295− [t|XW.ToXML $(TH.conT name)|]
296− [funSimple 'XW.toXML [e|XW.toXML . T.pack . show|]]
297− toXmlAttributeInst <- do
300− [t|ToXmlAttribute $(TH.conT name)|]
301− [funSimple 'toXmlAttribute [e|T.pack . show|]]
305− [t|FromDom $(TH.conT name)|]
306− [funSimple 'fromDom [e|parseContent readContent|]]
307− fromAttributeInst <- do
310− [t|FromAttribute $(TH.conT name)|]
311− [funSimple 'fromAttribute [e|readContent|]]
312− return $ enumDecls ++ [toXmlInst, toXmlAttributeInst,
313− fromDomInst, fromAttributeInst]
315−isoXmlGenerateDatatype :: PrefixName -> [IsoXmlDescRecordPart] -> TH.DecsQ
316−isoXmlGenerateDatatype (PrefixName strName' strPrefix') descRecordParts = do
318− isNewtype = length descRecordParts == 1
319− strName = "Xml" ++ strName'
320− strPrefix = "x" ++ strPrefix'
321− name = TH.mkName strName
322− fieldName str = "_" ++ strPrefix ++ over _head C.toUpper str
326− descRecordPart <- descRecordParts
327− return $ case descRecordPart of
328− IsoXmlDescRecordField descField ->
330− IsoXmlDescField fieldPlural rawName fieldType = descField
331− fieldStrName = xmlLocalName rawName
332− fName = TH.mkName (fieldName fieldStrName)
333− fType = case fieldPlural of
334− XmlFieldPluralMandatory -> fieldType
335− XmlFieldPluralOptional -> [t| Maybe $fieldType |]
336− XmlFieldPluralRepeated -> [t| [$fieldType] |]
337− XmlFieldPluralMultiplied -> [t| NonEmpty $fieldType |]
339− then varStrictType fName (nonStrictType fType)
340− else varStrictType fName (strictType fType)
341− IsoXmlDescRecordAttribute descAttribute ->
344− attributePlural attributeStrName attributeType = descAttribute
345− fName = TH.mkName (fieldName attributeStrName)
346− fType = case attributePlural of
347− XmlAttributePluralMandatory -> attributeType
348− XmlAttributePluralOptional -> [t| Maybe $attributeType |]
350− then varStrictType fName (nonStrictType fType)
351− else varStrictType fName (strictType fType)
353− -- generate a newtype instead to do less allocations later
354− then newtypeD name (TH.recC name fields) [''Eq, ''Show, ''Generic]
355− else dataD name [TH.recC name fields] [''Eq, ''Show, ''Generic]
356− lensDecls <- makeFieldOpticsForDec lensRules termDecl
360− [t|NFData $(TH.conT name)|]
364− exprHeader = [e|pure $(TH.conE name)|]
365− exprRecordParts = do
366− descRecordPart <- descRecordParts
367− return $ case descRecordPart of
368− IsoXmlDescRecordField descField ->
370− IsoXmlDescField fieldPlural rawName _ = descField
371− fieldStrName = xmlLocalName rawName
372− exprFieldStrName = TH.litE (TH.stringL fieldStrName)
373− fieldParse = case fieldPlural of
374− XmlFieldPluralMandatory -> [e|inElem|]
375− _ -> [e|inElemTrav|]
377− [e|$fieldParse $exprFieldStrName fromDom|]
378− IsoXmlDescRecordAttribute descAttribute ->
380− IsoXmlDescAttribute attributePlural attributeStrName _ = descAttribute
381− exprAttributeStrName = TH.litE (TH.stringL attributeStrName)
382− attributeParse = case attributePlural of
383− XmlAttributePluralMandatory -> [e|parseAttribute|]
384− XmlAttributePluralOptional -> [e|parseAttributeMaybe|]
386− [e|$attributeParse $exprAttributeStrName fromAttribute|]
387− fromDomExpr = foldl (\e fe -> [e| $e <*> $fe |]) exprHeader exprRecordParts
390− [t|FromDom $(TH.conT name)|]
391− [ funSimple 'fromDom fromDomExpr ]
394− objName <- TH.newName strPrefix
397− descRecordPart <- descRecordParts
398− IsoXmlDescField fieldPlural rawName _ <-
399− maybeToList $ case descRecordPart of
400− IsoXmlDescRecordField descField -> Just descField
403− fieldStrName = xmlLocalName rawName
404− fName = TH.mkName (fieldName fieldStrName)
405− exprFieldStrName = TH.litE (TH.stringL rawName)
406− exprForField = case fieldPlural of
407− XmlFieldPluralMandatory -> [e|id|]
409− exprFieldValue = [e|$(TH.varE fName) $(TH.varE objName)|]
411− [e|(\a -> XW.elementA $exprFieldStrName (toXmlParentAttributes a) a)|]
412− return [e|$exprForField $exprFieldRender $exprFieldValue|]
414− = TH.lamE [if null exprFields then TH.wildP else TH.varP objName]
415− $ foldr (\fe e -> [e|$fe *> $e|]) [e|return ()|] exprFields
418− [t|XW.ToXML $(TH.conT name)|]
419− [funSimple 'XW.toXML toXmlExpr]
421− toXmlParentAttributesInst <- do
422− objName <- TH.newName strPrefix
425− descRecordPart <- descRecordParts
426− IsoXmlDescAttribute attributePlural attributeStrName _ <-
427− maybeToList $ case descRecordPart of
428− IsoXmlDescRecordAttribute descAttribute -> Just descAttribute
431− fName = TH.mkName (fieldName attributeStrName)
432− exprAttrStrName = TH.litE (TH.stringL attributeStrName)
433− exprAttrValue = [e|$(TH.varE fName) $(TH.varE objName)|]
434− exprAttrWrap = case attributePlural of
435− XmlAttributePluralMandatory -> [e|Just . toXmlAttribute|]
436− XmlAttributePluralOptional -> [e|fmap toXmlAttribute|]
437− return [e|($exprAttrStrName, $exprAttrWrap $exprAttrValue)|]
438− toXmlParentAttributesExpr
439− = TH.lamE [if null exprAttributes then TH.wildP else TH.varP objName]
440− $ [e|mapMaybe distribPair $(TH.listE exprAttributes)|]
441−#if __GLASGOW_HASKELL__ < 800
444− TH.instanceWithOverlapD (Just TH.Overlapping)
447− [t|ToXmlParentAttributes $(TH.conT name)|]
448− [funSimple 'toXmlParentAttributes toXmlParentAttributesExpr]
450− return $ [termDecl] ++ lensDecls ++
451− [fromDomInst, toXmlInst, toXmlParentAttributesInst, nfDataInst]
453−distribPair :: Functor f => (a, f b) -> f (a, b)
454−distribPair (a, fb) = (a,) <$> fb
456−-- | Get a local part of (possibly) fully qualified 'X.Name':
458−-- >>> xmlLocalName "{http://example.com/ns/my-namespace}my-name"
460−xmlLocalName :: String -> String
461−xmlLocalName = T.unpack . X.nameLocalName . fromString