@@ -1,4 +1,5 @@
1−{-# LANGUAGE NoImplicitPrelude #-}
1+{-# LANGUAGE UndecidableInstances #-}
2+{-# LANGUAGE FunctionalDependencies #-}
@@ -22,7 +23,7 @@ wrapper around the field type:
25−> "Color" =:= enum Both
26+> "Color" =:= enum
@@ -40,7 +41,7 @@ to be either @R@, @G@ or @B@.
43−> "Message" =:= record Both
44+> "Message" =:= record
44> ! "author"
45> ! "author"
45> + "recipient"
46> + "recipient"
46> ? "message" [t|Text|]
47> ? "message" [t|Text|]
@@ -67,10 +68,10 @@ enumeration is exhaustive (contains only the listed constructors) or
67non-exhaustive (also contains a constructor for unknown values; this
68non-exhaustive (also contains a constructor for unknown values; this
68is the default):
69is the default):
70−> "Enum1" Exhaustive =:= enum Generator
71+> "Enum1" Exhaustive =:= enum
73−> "Enum2" NonExhaustive =:= enum Generator
74+> "Enum2" NonExhaustive =:= enum
76To define a record, use the `record` function followed by the name of
77To define a record, use the `record` function followed by the name of
@@ -78,7 +79,7 @@ the data type to be generated. The prefix for the record fields is
78inferred automatically by taking all of the uppercase letters in the
79inferred automatically by taking all of the uppercase letters in the
79name. You can override it manually like so:
80name. You can override it manually like so:
81−> "Reference" "ref" =:= record Parser
82+> "Reference" "ref" =:= record
84To describe a record field you must supply its name as it appears
85To describe a record field you must supply its name as it appears
@@ -93,14 +94,10 @@ The type of the field is inferred automatically from its name, so
93if the field is called @"author"@ its type will be @Author@. You can
94if the field is called @"author"@ its type will be @Author@. You can
94override the type by specifying it in quasiquotes like so:
95override the type by specifying it in quasiquotes like so:
96−> "Message" =:= record Both
97+> "Message" =:= record
97−> ! "author" [t|Person|].
98+> ! "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.
@@ -108,7 +105,6 @@ module Data.THGen.XML
108 ( Exhaustiveness(..)
105 ( Exhaustiveness(..)
109 , PrefixName(..)
106 , PrefixName(..)
110 , ExhaustivenessName(..)
107 , ExhaustivenessName(..)
@@ -118,15 +114,325 @@ module Data.THGen.XML
123 -- Re-exports
118 -- Re-exports
126− , P.Integer
121+ , Integer
124+import Control.Lens hiding (repeated, enum, (&))
125+import Control.Lens.Internal.FieldTH (makeFieldOpticsForDec)
126+import qualified Data.Char as C
127+import Data.List.NonEmpty (NonEmpty)
128+import Data.Maybe (maybeToList, mapMaybe)
129import Data.THGen.Enum
130import Data.THGen.Enum
130−import Data.THGen.XML.Internal
131−import Data.Text
131+import qualified Data.Text as T
132−import qualified Prelude as P
132+import qualified Language.Haskell.TH as TH
133+import Prelude hiding ((+), (*))
134+import Text.XML.DOM.Parser
135+import Text.XML.ParentAttributes
136+import qualified Text.XML.Writer as XW
137+import qualified Text.XML as X
140+ = XmlFieldPluralMandatory -- Occurs exactly 1 time (Identity)
141+ | XmlFieldPluralOptional -- Occurs 0 or 1 times (Maybe)
142+ | XmlFieldPluralRepeated -- Occurs 0 or more times (List)
143+ | XmlFieldPluralMultiplied -- Occurs 1 or more times (NonEmpty)
145+data XmlAttributePlural
146+ = XmlAttributePluralMandatory -- Occurs exactly 1 time (Identity)
147+ | XmlAttributePluralOptional -- Occurs 0 or 1 times (Maybe)
149+data PrefixName = PrefixName String String
151+data IsoXmlDescPreField = IsoXmlDescPreField String TH.TypeQ
153+data IsoXmlDescPreAttribute = IsoXmlDescPreAttribute String TH.TypeQ
155+data IsoXmlDescField = IsoXmlDescField XmlFieldPlural String TH.TypeQ
157+data IsoXmlDescAttribute = IsoXmlDescAttribute XmlAttributePlural String TH.TypeQ
159+data IsoXmlDescRecordPart
160+ = IsoXmlDescRecordField IsoXmlDescField
161+ | IsoXmlDescRecordAttribute IsoXmlDescAttribute
163+newtype IsoXmlDescRecord = IsoXmlDescRecord [IsoXmlDescRecordPart]
165+makePrisms ''IsoXmlDescRecord
167+data ExhaustivenessName = ExhaustivenessName String Exhaustiveness
169+newtype IsoXmlDescEnumCon
170+ = IsoXmlDescEnumCon { unIsoXmlDescEnumCon :: String }
172+instance IsString IsoXmlDescEnumCon where
173+ fromString = IsoXmlDescEnumCon
175+data IsoXmlDescEnum = IsoXmlDescEnum [IsoXmlDescEnumCon]
177+makePrisms ''IsoXmlDescEnum
182+ -> IsoXmlDescPreField
184+appendField plural xrec (IsoXmlDescPreField name ty) =
185+ let xfield = IsoXmlDescRecordField $ IsoXmlDescField plural name ty
186+ in over _IsoXmlDescRecord (xfield:) xrec
189+ :: XmlAttributePlural
191+ -> IsoXmlDescPreAttribute
193+appendAttribute plural xrec (IsoXmlDescPreAttribute name ty) =
194+ let xattribute = IsoXmlDescRecordAttribute $ IsoXmlDescAttribute plural name ty
195+ in over _IsoXmlDescRecord (xattribute:) xrec
197+(!), (?), (*), (+) :: IsoXmlDescRecord -> IsoXmlDescPreField -> IsoXmlDescRecord
198+(!) = appendField XmlFieldPluralMandatory
199+(?) = appendField XmlFieldPluralOptional
200+(*) = appendField XmlFieldPluralRepeated
201+(+) = appendField XmlFieldPluralMultiplied
203+(!%), (?%) :: IsoXmlDescRecord -> IsoXmlDescPreAttribute -> IsoXmlDescRecord
204+(!%) = appendAttribute XmlAttributePluralMandatory
205+(?%) = appendAttribute XmlAttributePluralOptional
214+appendEnumCon :: IsoXmlDescEnum -> IsoXmlDescEnumCon -> IsoXmlDescEnum
215+appendEnumCon xenum xenumcon =
216+ over _IsoXmlDescEnum (xenumcon:) xenum
218+(&) :: IsoXmlDescEnum -> IsoXmlDescEnumCon -> IsoXmlDescEnum
223+class Description name desc | desc -> name where
224+ (=:=) :: name -> desc -> TH.DecsQ
228+instance Description PrefixName IsoXmlDescRecord where
229+ prefixName =:= descRecord =
230+ let descRecordParts = descRecord ^. _IsoXmlDescRecord
231+ in isoXmlGenerateRecord prefixName (reverse descRecordParts)
233+record :: IsoXmlDescRecord
234+record = IsoXmlDescRecord []
236+enum :: IsoXmlDescEnum
237+enum = IsoXmlDescEnum []
239+instance Description ExhaustivenessName IsoXmlDescEnum where
240+ exhaustivenessName =:= descEnum =
241+ let descEnumCons = descEnum ^. _IsoXmlDescEnum
242+ in isoXmlGenerateEnum exhaustivenessName (reverse descEnumCons)
244+instance IsString (TH.TypeQ -> IsoXmlDescPreField) where
245+ fromString = IsoXmlDescPreField
247+instance IsString IsoXmlDescPreField where
248+ fromString name = IsoXmlDescPreField name ty
250+ ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper name)
252+instance IsString (TH.TypeQ -> IsoXmlDescPreAttribute) where
253+ fromString = IsoXmlDescPreAttribute
255+instance IsString IsoXmlDescPreAttribute where
256+ fromString name = IsoXmlDescPreAttribute name ty
258+ ty = (TH.conT . TH.mkName) ("Xml" ++ over _head C.toUpper name)
260+instance s ~ String => IsString (s -> PrefixName) where
261+ fromString = PrefixName
263+instance IsString PrefixName where
264+ fromString strName = PrefixName strName (makeNamePrefix strName)
266+instance e ~ Exhaustiveness => IsString (e -> ExhaustivenessName) where
267+ fromString = ExhaustivenessName
269+instance IsString ExhaustivenessName where
270+ fromString strName = ExhaustivenessName strName NonExhaustive
272+makeNamePrefix :: String -> String
273+makeNamePrefix = map C.toLower . filter C.isUpper
275+funSimple :: TH.Name -> TH.ExpQ -> TH.DecQ
276+funSimple name body = TH.funD name [ TH.clause [] (TH.normalB body) [] ]
279+ :: ExhaustivenessName -> [IsoXmlDescEnumCon] -> TH.DecsQ
280+isoXmlGenerateEnum (ExhaustivenessName strName' exh) enumCons = do
282+ strName = "Xml" ++ strName'
283+ strVals = map unIsoXmlDescEnumCon enumCons
284+ enumDesc = EnumDesc exh strName strVals
285+ name = TH.mkName strName
286+ enumDecls <- enumGenerate enumDesc
290+ [t|XW.ToXML $(TH.conT name)|]
291+ [funSimple 'XW.toXML [e|XW.toXML . T.pack . show|]]
292+ toXmlAttributeInst <- do
295+ [t|ToXmlAttribute $(TH.conT name)|]
296+ [funSimple 'toXmlAttribute [e|T.pack . show|]]
300+ [t|FromDom $(TH.conT name)|]
301+ [funSimple 'fromDom [e|parseContent readContent|]]
302+ fromAttributeInst <- do
305+ [t|FromAttribute $(TH.conT name)|]
306+ [funSimple 'fromAttribute [e|readContent|]]
307+ return $ enumDecls ++ [toXmlInst, toXmlAttributeInst,
308+ fromDomInst, fromAttributeInst]
310+isoXmlGenerateRecord :: PrefixName -> [IsoXmlDescRecordPart] -> TH.DecsQ
311+isoXmlGenerateRecord (PrefixName strName' strPrefix') descRecordParts = do
313+ strName = "Xml" ++ strName'
314+ strPrefix = "x" ++ strPrefix'
315+ name = TH.mkName strName
316+ fieldName str = "_" ++ strPrefix ++ over _head C.toUpper str
320+ descRecordPart <- descRecordParts
321+ return $ case descRecordPart of
322+ IsoXmlDescRecordField descField ->
324+ IsoXmlDescField fieldPlural fieldStrName fieldType = descField
325+ fName = TH.mkName (fieldName fieldStrName)
326+ fType = case fieldPlural of
327+ XmlFieldPluralMandatory -> fieldType
328+ XmlFieldPluralOptional -> [t| Maybe $fieldType |]
329+ XmlFieldPluralRepeated -> [t| [$fieldType] |]
330+ XmlFieldPluralMultiplied -> [t| NonEmpty $fieldType |]
332+ TH.varStrictType fName (TH.strictType TH.isStrict fType)
333+ IsoXmlDescRecordAttribute descAttribute ->
336+ attributePlural attributeStrName attributeType = descAttribute
337+ fName = TH.mkName (fieldName attributeStrName)
338+ fType = case attributePlural of
339+ XmlAttributePluralMandatory -> attributeType
340+ XmlAttributePluralOptional -> [t| Maybe $attributeType |]
342+ TH.varStrictType fName (TH.strictType TH.isStrict fType)
347+ [TH.recC name fields]
349+ lensDecls <- makeFieldOpticsForDec lensRules dataDecl
352+ exprHeader = [e|pure $(TH.conE name)|]
353+ exprRecordParts = do
354+ descRecordPart <- descRecordParts
355+ return $ case descRecordPart of
356+ IsoXmlDescRecordField descField ->
358+ IsoXmlDescField fieldPlural fieldStrName _ = descField
359+ exprFieldStrName = TH.litE (TH.stringL fieldStrName)
360+ fieldParse = case fieldPlural of
361+ XmlFieldPluralMandatory -> [e|inElem|]
362+ _ -> [e|inElemTrav|]
364+ [e|$fieldParse $exprFieldStrName fromDom|]
365+ IsoXmlDescRecordAttribute descAttribute ->
367+ IsoXmlDescAttribute attributePlural attributeStrName _ = descAttribute
368+ exprAttributeStrName = TH.litE (TH.stringL attributeStrName)
369+ attributeParse = case attributePlural of
370+ XmlAttributePluralMandatory -> [e|parseAttribute|]
371+ XmlAttributePluralOptional -> [e|parseAttributeMaybe|]
373+ [e|$attributeParse $exprAttributeStrName fromAttribute|]
374+ fromDomExpr = foldl (\e fe -> [e| $e <*> $fe |]) exprHeader exprRecordParts
377+ [t|FromDom $(TH.conT name)|]
378+ [ funSimple 'fromDom fromDomExpr ]
381+ objName <- TH.newName strPrefix
384+ descRecordPart <- descRecordParts
385+ IsoXmlDescField fieldPlural fieldStrName _ <-
386+ maybeToList $ case descRecordPart of
387+ IsoXmlDescRecordField descField -> Just descField
390+ fName = TH.mkName (fieldName fieldStrName)
391+ exprFieldStrName = TH.litE (TH.stringL fieldStrName)
392+ exprForField = case fieldPlural of
393+ XmlFieldPluralMandatory -> [e|id|]
395+ exprFieldValue = [e|$(TH.varE fName) $(TH.varE objName)|]
396+ exprFieldRender = [e|mkElement $exprFieldStrName|]
397+ return [e|$exprForField $exprFieldRender $exprFieldValue|]
399+ = TH.lamE [if null exprFields then TH.wildP else TH.varP objName]
400+ $ foldr (\fe e -> [e|$fe *> $e|]) [e|return ()|] exprFields
403+ [t|XW.ToXML $(TH.conT name)|]
404+ [funSimple 'XW.toXML toXmlExpr]
406+ toXmlParentAttributesInst <- do
407+ objName <- TH.newName strPrefix
410+ descRecordPart <- descRecordParts
411+ IsoXmlDescAttribute attributePlural attributeStrName _ <-
412+ maybeToList $ case descRecordPart of
413+ IsoXmlDescRecordAttribute descAttribute -> Just descAttribute
416+ fName = TH.mkName (fieldName attributeStrName)
417+ exprAttrStrName = TH.litE (TH.stringL attributeStrName)
418+ exprAttrValue = [e|$(TH.varE fName) $(TH.varE objName)|]
419+ exprAttrWrap = case attributePlural of
420+ XmlAttributePluralMandatory -> [e|Just . toXmlAttribute|]
421+ XmlAttributePluralOptional -> [e|fmap toXmlAttribute|]
422+ return [e|($exprAttrStrName, $exprAttrWrap $exprAttrValue)|]
423+ toXmlParentAttributesExpr
424+ = TH.lamE [if null exprAttributes then TH.wildP else TH.varP objName]
425+ $ [e|mapMaybe distribPair $(TH.listE exprAttributes)|]
428+ [t|ToXmlParentAttributes $(TH.conT name)|]
429+ [funSimple 'toXmlParentAttributes toXmlParentAttributesExpr]
431+ return $ [dataDecl] ++ lensDecls ++
432+ [fromDomInst, toXmlInst, toXmlParentAttributesInst]
434+mkElement :: (XW.ToXML a, ToXmlParentAttributes a) => X.Name -> a -> XW.XML
435+mkElement name a = XW.elementA name (toXmlParentAttributes a) (XW.toXML a)
437+distribPair :: Functor f => (a, f b) -> f (a, b)
438+distribPair (a, fb) = (a,) <$> fb