{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UndecidableInstances #-}
module Dino.AST
(
Field (..)
, Mapping (..)
, NameType (..)
, Constr (..)
, Importance (..)
, AST (..)
, record
, prettyNamed
, GInspectableArgs (..)
, GInspectableFields (..)
, GInspectable (..)
, Inspectable (..)
, inspectListAsRec
, toTree
, showTree
, drawTree
, htmlTree
) where
import Prelude
import Data.Hashable (Hashable (..))
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HM
import Data.Proxy (Proxy (..))
import Data.String (IsString(..))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Tree (Tree(..))
import Data.Tree.View (Behavior(..), NodeInfo(..))
import qualified Data.Tree.View as View
import GHC.Generics
( (:+:)(..)
, (:*:)(..)
, C1
, D1
, Generic(..)
, K1(..)
, M1(..)
, Meta(..)
, Rec0
, Rep
, S1
, U1
)
import GHC.Stack (HasCallStack)
import GHC.TypeLits (KnownSymbol, symbolVal)
import Text.PrettyPrint.ANSI.Leijen (Doc, Pretty (..))
import qualified Text.PrettyPrint.ANSI.Leijen as PP
import Dino.Pretty
data Mapping k v = Mapping Importance !(HashMap k v)
deriving (Eq, Show, Foldable, Functor, Traversable, Generic)
instance (Hashable k, Hashable v) => Hashable (Mapping k v) where
hashWithSalt s (Mapping i m) = hashWithSalt s (i, m)
data NameType
= Constructor
| LocalVar
| Annotation
deriving (Eq, Show, Generic, Enum, Bounded)
instance Hashable NameType
data Constr
= Tuple
| List
| Named NameType Text
deriving (Eq, Show, Generic)
instance IsString Constr where
fromString = Named Constructor . Text.pack
instance Hashable Constr
data AST n
= Number n
| Text Text
| App Constr [AST n]
| Let Text (AST n) (AST n)
| Record (Mapping Field (AST n))
deriving (Eq, Show, Foldable, Functor, Traversable, Generic)
instance Hashable n => Hashable (AST n)
record :: HasCallStack => Importance -> [(Field, AST n)] -> AST n
record imp = Record . Mapping imp . HM.fromList
prettyNamed :: NameType -> Text -> Doc
prettyNamed Constructor c = PP.string $ Text.unpack c
prettyNamed LocalVar v = PP.string $ Text.unpack v
prettyNamed Annotation a = PP.string $ Text.unpack $ "ANN: " <> a
instance {-# OVERLAPPABLE #-}
(Pretty a, Show k, Ord k) => Pretty (Mapping k a) where
pretty (Mapping imp m) = prettyRecord imp $ pretty <$> m
instance Show a => Pretty (AST a) where
pretty (Number a) = PP.string $ show a
pretty (Text a) = PP.string $ show a
pretty (App Tuple []) = PP.parens PP.empty
pretty (App Tuple vs) =
verticalList PP.lparen PP.comma PP.rparen $ map pretty vs
pretty (App List []) = PP.brackets PP.empty
pretty (App List vs) =
verticalList PP.lbracket PP.comma PP.rbracket $ map pretty vs
pretty (App (Named t c) []) = prettyNamed t c
pretty (App (Named t c) vs) =
underHeader (prettyNamed t c) $ foldr1 (PP.<$>) $ map pretty vs
pretty (Let v a b) =
underHeader (PP.string "let" PP.<+> var PP.<+> "=") (pretty a)
PP.<$>
underHeader (PP.string " in") (pretty b)
where
var = PP.string $ Text.unpack v
pretty (Record rec) = pretty rec
showSym :: forall sym str. (KnownSymbol sym, IsString str) => str
showSym = fromString $ symbolVal (Proxy @sym)
class GInspectableArgs rep where
gInspectArgs :: rep x -> [AST Rational]
instance GInspectableArgs U1 where
gInspectArgs _ = []
instance Inspectable a =>
GInspectableArgs (S1 ('MetaSel 'Nothing x y z) (Rec0 a)) where
gInspectArgs = pure . inspect . unK1 . unM1
instance (GInspectableArgs rep1, GInspectableArgs rep2) =>
GInspectableArgs (rep1 :*: rep2) where
gInspectArgs (rep1 :*: rep2) = gInspectArgs rep1 ++ gInspectArgs rep2
class GInspectableFields rep where
gInspectFields :: rep x -> [(Field, AST Rational)]
instance GInspectableFields U1 where
gInspectFields _ = []
instance (Inspectable a, KnownSymbol fld) =>
GInspectableFields (S1 ('MetaSel ('Just fld) x y z) (Rec0 a)) where
gInspectFields = pure . (showSym @fld, ) . inspect . unK1 . unM1
instance (GInspectableFields rep1, GInspectableFields rep2) =>
GInspectableFields (rep1 :*: rep2) where
gInspectFields (rep1 :*: rep2) = gInspectFields rep1 ++ gInspectFields rep2
class GInspectable rep where
gInspect :: rep x -> AST Rational
instance (GInspectable rep1, GInspectable rep2) =>
GInspectable (rep1 :+: rep2) where
gInspect (L1 rep) = gInspect rep
gInspect (R1 rep) = gInspect rep
instance GInspectable rep => GInspectable (D1 meta rep) where
gInspect = gInspect . unM1
instance (GInspectableArgs rep, KnownSymbol con) =>
GInspectable (C1 ('MetaCons con x 'False) rep) where
gInspect = App (showSym @con) . gInspectArgs . unM1
instance (GInspectableFields rep, KnownSymbol con) =>
GInspectable (C1 ('MetaCons con x 'True) rep) where
gInspect =
App (showSym @con) .
pure . Record . Mapping Unimportant . HM.fromList . gInspectFields . unM1
class Inspectable a where
inspect :: a -> AST Rational
default inspect :: (Generic a, GInspectable (Rep a)) => a -> AST Rational
inspect = gInspect . from
instance Inspectable Rational where inspect = Number
instance Inspectable Int where inspect = Number . toRational
instance Inspectable Integer where inspect = Number . toRational
instance Inspectable Float where inspect = Number . toRational
instance Inspectable Double where inspect = Number . toRational
instance Real n => Inspectable (AST n) where
inspect = fmap toRational
instance Inspectable () where
inspect () = App "()" []
instance Inspectable Bool where
inspect b = App (fromString $ show b) []
instance {-# OVERLAPS #-} Inspectable String where
inspect = Text . Text.pack
instance Inspectable Text where
inspect = Text
instance Inspectable a => Inspectable (Maybe a) where
inspect Nothing = App "Nothing" []
inspect (Just a) = App "Just" [inspect a]
instance {-# OVERLAPPABLE #-} Inspectable a => Inspectable [a] where
inspect = App List . map inspect
instance Inspectable a => Inspectable (Mapping Field a) where
inspect (Mapping i m) = Record $ Mapping i $ fmap inspect m
instance (Inspectable a, Inspectable b) => Inspectable (a, b) where
inspect (a, b) = App Tuple [inspect a, inspect b]
instance (Inspectable a, Inspectable b, Inspectable c) =>
Inspectable (a, b, c) where
inspect (a, b, c) = App Tuple [inspect a, inspect b, inspect c]
instance (Inspectable a, Inspectable b, Inspectable c, Inspectable d) =>
Inspectable (a, b, c, d) where
inspect (a, b, c, d) = App Tuple [inspect a, inspect b, inspect c, inspect d]
inspectListAsRec ::
Inspectable a
=> Importance
-> (a -> Field)
-> [a]
-> AST Rational
inspectListAsRec imp getKey as =
Record $ Mapping imp $ HM.fromList [(getKey a, inspect a) | a <- as]
renderCon :: Constr -> Text
renderCon Tuple = "#Tuple"
renderCon List = "#List"
renderCon (Named t n) = case t of
Constructor -> n
LocalVar -> "*" <> n
Annotation -> "ANN: " <> n
tagTree :: Text -> Tree Text -> Tree Text
tagTree tag (Node n ts) = Node (tag <> n) ts
toTreeRec :: Show n => Mapping Field (AST n) -> [Tree Text]
toTreeRec (Mapping _ fs) =
[tagTree (Text.pack (unField f) <> ": ") $ toTree a | (f, a) <- HM.toList fs]
toTree :: Show n => AST n -> Tree Text
toTree (App c [Record rec]) = Node (renderCon c) $ toTreeRec rec
toTree (Number n) = Node (Text.pack $ show n) []
toTree (Text t) = Node (Text.pack $ show t) []
toTree (App c as) = Node (renderCon c) $ map toTree as
toTree (Let v a body) = Node ("Let *" <> v) [toTree a, toTree body]
toTree (Record fs) = Node "Record" $ toTreeRec fs
showTree :: Show n => AST n -> String
showTree = View.showTree . fmap Text.unpack . toTree
drawTree :: Show n => AST n -> IO ()
drawTree = View.drawTree . fmap Text.unpack . toTree
htmlTree :: Show n => FilePath -> AST n -> IO ()
htmlTree file =
View.writeHtmlTree Nothing file . fmap mkInfo . fmap Text.unpack . toTree
where
mkInfo n = NodeInfo InitiallyExpanded n ""