json-spec-2.0.0.0: src/Data/JsonSpec/Language/QQ.hs
{-# LANGUAGE TemplateHaskellQuotes #-}
{-|
Description : Quasi-quoters for the JsonSpec language
Quasi-quoters for the JsonSpec textual language.
> type Person =
> [jsonspec|
> module Person = {
> "name": string,
> "age": int
> }
> |]
produces a type of kind 'Module'.
See @docs\/language-spec.md@.
-}
module Data.JsonSpec.Language.QQ (
jsonspec,
) where
import Data.JsonSpec.Language.Parser
( Binding(ModuleBinding, TypeBinding)
, Field(Field, fieldName, fieldOptional, fieldSpec)
, Program(Program, programSpec)
, Spec
( ArraySpec, BoolSpec, DateTimeSpec, DictSpec, EitherSpec, IntSpec, LetSpec
, NullSpec, NumberSpec, ObjectSpec, RawSpec, RefSpec, StringSpec, TagSpec
)
, parseProgram
)
import Data.JsonSpec.Spec
( BindingSpec(ModuleBind, TypeBind), FieldSpec(Optional, Required)
, Module(Module)
, Specification
( JsonArray, JsonBool, JsonDateTime, JsonDict, JsonEither, JsonInt, JsonLet
, JsonNullable, JsonNum, JsonObject, JsonRaw, JsonRef, JsonString, JsonTag
)
)
import Data.Text (Text)
import Language.Haskell.TH (Q, Type, TypeQ, appT, litT, promotedT, strTyLit)
import Language.Haskell.TH.Quote
( QuasiQuoter(QuasiQuoter, quoteDec, quoteExp, quotePat, quoteType)
)
import Prelude
( Bool(False, True), Either(Left, Right), Foldable(foldr), Functor(fmap)
, MonadFail(fail), Semigroup((<>)), String
)
import qualified Data.Text as T
{-|
Quasi-quoter for a JsonSpec program.
The quoted text must be a full program
(@module Name = \<spec\>@). Use in type context; the result has
kind 'Module'.
-}
jsonspec :: QuasiQuoter
jsonspec =
QuasiQuoter
{ quoteExp = unsupported "expression"
, quotePat = unsupported "pattern"
, quoteType = quoteJsonSpecType
, quoteDec = unsupported "declaration"
}
unsupported :: String -> String -> Q a
unsupported kind _ =
fail ("jsonspec: " <> kind <> " contexts are not supported; use as a type")
quoteJsonSpecType :: String -> Q Type
quoteJsonSpecType input =
case parseProgram "jsonspec" (T.pack input) of
Left err ->
fail err
Right Program { programSpec = body } ->
promotedT 'Module `appT` specType body
specType :: Spec -> TypeQ
specType StringSpec =
promotedT 'JsonString
specType NumberSpec =
promotedT 'JsonNum
specType IntSpec =
promotedT 'JsonInt
specType BoolSpec =
promotedT 'JsonBool
specType DateTimeSpec =
promotedT 'JsonDateTime
specType RawSpec =
promotedT 'JsonRaw
specType (TagSpec t) =
promotedT 'JsonTag `appT` symbolType t
specType (RefSpec n) =
promotedT 'JsonRef `appT` symbolType n
specType (DictSpec s) =
promotedT 'JsonDict `appT` specType s
specType (NullSpec s) =
promotedT 'JsonNullable `appT` specType s
specType (ArraySpec s) =
promotedT 'JsonArray `appT` specType s
specType (EitherSpec ss) =
promotedT 'JsonEither `appT` listType (fmap specType ss)
specType (ObjectSpec fields) =
promotedT 'JsonObject `appT` listType (fmap fieldType fields)
specType (LetSpec binds body) =
(promotedT 'JsonLet `appT` listType (fmap bindingType binds))
`appT` specType body
fieldType :: Field -> TypeQ
fieldType Field { fieldName = name, fieldOptional = True, fieldSpec = s } =
(promotedT 'Optional `appT` symbolType name) `appT` specType s
fieldType Field { fieldName = name, fieldOptional = False, fieldSpec = s } =
(promotedT 'Required `appT` symbolType name) `appT` specType s
bindingType :: Binding -> TypeQ
bindingType (TypeBinding name s) =
(promotedT 'TypeBind `appT` symbolType name) `appT` specType s
bindingType (ModuleBinding name s) =
(promotedT 'ModuleBind `appT` symbolType name)
`appT` (promotedT 'Module `appT` specType s)
listType :: [TypeQ] -> TypeQ
listType =
foldr
(\t acc -> promotedT '(:) `appT` t `appT` acc)
(promotedT '[])
symbolType :: Text -> TypeQ
symbolType t =
litT (strTyLit (T.unpack t))