{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE DeriveGeneric #-}
module Language.PureScript.Docs.RenderedCode.Types
( RenderedCodeElement(..)
, asRenderedCodeElement
, ContainingModule(..)
, asContainingModule
, containingModuleToMaybe
, maybeToContainingModule
, fromContainingModule
, fromQualified
, Namespace(..)
, Link(..)
, FixityAlias
, RenderedCode
, asRenderedCode
, outputWith
, sp
, parens
, syntax
, keyword
, keywordForall
, keywordData
, keywordNewtype
, keywordType
, keywordClass
, keywordInstance
, keywordWhere
, keywordFixity
, keywordKind
, keywordAs
, ident
, dataCtor
, typeCtor
, typeOp
, typeVar
, kind
, alias
, aliasName
) where
import Prelude.Compat
import GHC.Generics (Generic)
import Control.DeepSeq (NFData)
import Control.Monad.Error.Class (MonadError(..))
import Data.Aeson.BetterErrors (Parse, nth, withText, withValue, toAesonParser, perhaps, asText, eachInArray)
import qualified Data.Aeson as A
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.ByteString.Lazy as BS
import qualified Data.Text.Encoding as TE
import Language.PureScript.Names
import Language.PureScript.AST (Associativity(..))
import Language.PureScript.Crash (internalError)
tryAll :: MonadError e m => m a -> [m a] -> m a
tryAll = foldr $ \x y -> catchError x (const y)
firstEq :: Text -> Parse Text a -> Parse Text a
firstEq str p = nth 0 (withText (eq str)) *> p
where
eq s s' = if s == s' then Right () else Left ""
tryParse :: Text -> [Parse Text a] -> Parse Text a
tryParse msg =
tryAll (withValue (Left . (fullMsg <>) . showJSON))
where
fullMsg = "Invalid " <> msg <> ": "
showJSON :: A.Value -> Text
showJSON = TE.decodeUtf8 . BS.toStrict . A.encode
data ContainingModule
= ThisModule
| OtherModule ModuleName
deriving (Show, Eq, Ord)
instance A.ToJSON ContainingModule where
toJSON = A.toJSON . go
where
go = \case
ThisModule -> ["ThisModule"]
OtherModule mn -> ["OtherModule", runModuleName mn]
instance A.FromJSON ContainingModule where
parseJSON = toAesonParser id asContainingModule
asContainingModule :: Parse Text ContainingModule
asContainingModule =
tryParse "containing module" $
current ++ backwardsCompat
where
current =
[ firstEq "ThisModule" (pure ThisModule)
, firstEq "OtherModule" (OtherModule <$> nth 1 asModuleName)
]
backwardsCompat =
[ maybeToContainingModule <$> perhaps asModuleName
]
asModuleName = moduleNameFromString <$> asText
maybeToContainingModule :: Maybe ModuleName -> ContainingModule
maybeToContainingModule Nothing = ThisModule
maybeToContainingModule (Just mn) = OtherModule mn
containingModuleToMaybe :: ContainingModule -> Maybe ModuleName
containingModuleToMaybe ThisModule = Nothing
containingModuleToMaybe (OtherModule mn) = Just mn
fromContainingModule :: ModuleName -> ContainingModule -> ModuleName
fromContainingModule def ThisModule = def
fromContainingModule _ (OtherModule mn) = mn
fromQualified :: Qualified a -> (ContainingModule, a)
fromQualified (Qualified mn x) =
(maybeToContainingModule mn, x)
data Link
= NoLink
| Link ContainingModule
deriving (Show, Eq, Ord)
instance A.ToJSON Link where
toJSON = \case
NoLink -> A.toJSON ["NoLink" :: Text]
Link mn -> A.toJSON ["Link", A.toJSON mn]
asLink :: Parse Text Link
asLink =
tryParse "link"
[ firstEq "NoLink" (pure NoLink)
, firstEq "Link" (Link <$> nth 1 asContainingModule)
]
instance A.FromJSON Link where
parseJSON = toAesonParser id asLink
data Namespace
= ValueLevel
| TypeLevel
| KindLevel
deriving (Show, Eq, Ord, Generic)
instance NFData Namespace
instance A.ToJSON Namespace where
toJSON = A.toJSON . show
asNamespace :: Parse Text Namespace
asNamespace =
tryParse "namespace"
[ withText $ \case
"ValueLevel" -> Right ValueLevel
"TypeLevel" -> Right TypeLevel
"KindLevel" -> Right KindLevel
_ -> Left ""
]
instance A.FromJSON Namespace where
parseJSON = toAesonParser id asNamespace
data RenderedCodeElement
= Syntax Text
| Keyword Text
| Space
| Symbol Namespace Text Link
deriving (Show, Eq, Ord)
instance A.ToJSON RenderedCodeElement where
toJSON (Syntax str) =
A.toJSON ["syntax", str]
toJSON (Keyword str) =
A.toJSON ["keyword", str]
toJSON Space =
A.toJSON ["space" :: Text]
toJSON (Symbol ns str link) =
A.toJSON ["symbol", A.toJSON ns, A.toJSON str, A.toJSON link]
asRenderedCodeElement :: Parse Text RenderedCodeElement
asRenderedCodeElement =
tryParse "RenderedCodeElement" $
[ a Syntax "syntax"
, a Keyword "keyword"
, asSpace
, asSymbol
] ++ backwardsCompat
where
a ctor' ctorStr = firstEq ctorStr (ctor' <$> nth 1 asText)
asSymbol = firstEq "symbol" (Symbol <$> nth 1 asNamespace <*> nth 2 asText <*> nth 3 asLink)
asSpace = firstEq "space" (pure Space)
backwardsCompat =
[ oldAsIdent
, oldAsCtor
, oldAsKind
]
oldAsIdent = firstEq "ident" (Symbol ValueLevel <$> nth 1 asText <*> nth 2 (Link <$> asContainingModule))
oldAsCtor = firstEq "ctor" (Symbol TypeLevel <$> nth 1 asText <*> nth 2 (Link <$> asContainingModule))
oldAsKind = firstEq "kind" (Symbol KindLevel <$> nth 1 asText <*> pure (Link ThisModule))
newtype RenderedCode
= RC { unRC :: [RenderedCodeElement] }
deriving (Show, Eq, Ord, Semigroup, Monoid)
instance A.ToJSON RenderedCode where
toJSON (RC elems) = A.toJSON elems
asRenderedCode :: Parse Text RenderedCode
asRenderedCode = RC <$> eachInArray asRenderedCodeElement
outputWith :: Monoid a => (RenderedCodeElement -> a) -> RenderedCode -> a
outputWith f = foldMap f . unRC
sp :: RenderedCode
sp = RC [Space]
parens :: RenderedCode -> RenderedCode
parens x = syntax "(" <> x <> syntax ")"
syntax :: Text -> RenderedCode
syntax x = RC [Syntax x]
keyword :: Text -> RenderedCode
keyword kw = RC [Keyword kw]
keywordForall :: RenderedCode
keywordForall = keyword "forall"
keywordData :: RenderedCode
keywordData = keyword "data"
keywordNewtype :: RenderedCode
keywordNewtype = keyword "newtype"
keywordType :: RenderedCode
keywordType = keyword "type"
keywordClass :: RenderedCode
keywordClass = keyword "class"
keywordInstance :: RenderedCode
keywordInstance = keyword "instance"
keywordWhere :: RenderedCode
keywordWhere = keyword "where"
keywordFixity :: Associativity -> RenderedCode
keywordFixity Infixl = keyword "infixl"
keywordFixity Infixr = keyword "infixr"
keywordFixity Infix = keyword "infix"
keywordKind :: RenderedCode
keywordKind = keyword "kind"
keywordAs :: RenderedCode
keywordAs = keyword "as"
ident :: Qualified Ident -> RenderedCode
ident (fromQualified -> (mn, name)) =
RC [Symbol ValueLevel (runIdent name) (Link mn)]
dataCtor :: Qualified (ProperName 'ConstructorName) -> RenderedCode
dataCtor (fromQualified -> (mn, name)) =
RC [Symbol ValueLevel (runProperName name) (Link mn)]
typeCtor :: Qualified (ProperName 'TypeName) -> RenderedCode
typeCtor (fromQualified -> (mn, name)) =
RC [Symbol TypeLevel (runProperName name) (Link mn)]
typeOp :: Qualified (OpName 'TypeOpName) -> RenderedCode
typeOp (fromQualified -> (mn, name)) =
RC [Symbol TypeLevel (runOpName name) (Link mn)]
typeVar :: Text -> RenderedCode
typeVar x = RC [Symbol TypeLevel x NoLink]
kind :: Qualified (ProperName 'KindName) -> RenderedCode
kind (fromQualified -> (mn, name)) =
RC [Symbol KindLevel (runProperName name) (Link mn)]
type FixityAlias = Qualified (Either (ProperName 'TypeName) (Either Ident (ProperName 'ConstructorName)))
alias :: FixityAlias -> RenderedCode
alias for =
prefix <> RC [Symbol ns name (Link mn)]
where
(ns, name, mn) = unpackFixityAlias for
prefix = case ns of
TypeLevel ->
keywordType <> sp
_ ->
mempty
aliasName :: FixityAlias -> Text -> RenderedCode
aliasName for name' =
let
(ns, _, _) = unpackFixityAlias for
unParen = T.tail . T.init
name = unParen name'
in
case ns of
ValueLevel ->
ident (Qualified Nothing (Ident name))
TypeLevel ->
typeCtor (Qualified Nothing (ProperName name))
KindLevel ->
internalError "Kind aliases are not supported"
unpackFixityAlias :: FixityAlias -> (Namespace, Text, ContainingModule)
unpackFixityAlias (fromQualified -> (mn, x)) =
case x of
Left (n :: ProperName 'TypeName) ->
(TypeLevel, runProperName n, mn)
Right (Left n) ->
(ValueLevel, runIdent n, mn)
Right (Right (n :: ProperName 'ConstructorName)) ->
(ValueLevel, runProperName n, mn)