{-# LANGUAGE OverloadedStrings #-} {-# OPTIONS_GHC -Wno-deprecations #-} module Wingman.Metaprogramming.Parser.Documentation where import Data.Functor ((<&>)) import Data.List (sortOn) import Data.String (IsString) import Data.Text (Text) import Data.Text.Prettyprint.Doc hiding (parens) import Data.Text.Prettyprint.Doc.Render.String (renderString) import Development.IDE.GHC.Compat (OccName) import qualified Text.Megaparsec as P import Wingman.Metaprogramming.Lexer (Parser, identifier, variable, parens) import Wingman.Types (TacticsM) import {-# SOURCE #-} Wingman.Metaprogramming.Parser (tactic) ------------------------------------------------------------------------------ -- | Is a tactic deterministic or not? data Determinism = Deterministic | Nondeterministic prettyDeterminism :: Determinism -> Doc b prettyDeterminism Deterministic = "deterministic" prettyDeterminism Nondeterministic = "non-deterministic" ------------------------------------------------------------------------------ -- | How many arguments does the tactic take? data Count a where One :: Count OccName Many :: Count [OccName] prettyCount :: Count a -> Doc b prettyCount One = "single" prettyCount Many = "varadic" ------------------------------------------------------------------------------ -- | What sorts of arguments does the tactic take? Currently there is no -- distincion between 'Ref' and 'Bind', other than documentation. -- -- The type index here is used for the shape of the function the parser should -- take. data Syntax a where Nullary :: Syntax (Parser (TacticsM ())) Ref :: Count a -> Syntax (a -> Parser (TacticsM ())) Bind :: Count a -> Syntax (a -> Parser (TacticsM ())) Tactic :: Syntax (TacticsM () -> Parser (TacticsM ())) prettySyntax :: Syntax a -> Doc b prettySyntax Nullary = "none" prettySyntax (Ref co) = prettyCount co <+> "reference" prettySyntax (Bind co) = prettyCount co <+> "binding" prettySyntax Tactic = "tactic" ------------------------------------------------------------------------------ -- | An example for the documentation. data Example = Example { ex_ctx :: Maybe Text -- ^ Specific context information about when the tactic is applicable , ex_args :: [Var] -- ^ Arguments the tactic was called with , ex_hyp :: [ExampleHyInfo] -- ^ The hypothesis , ex_goal :: Maybe ExampleType -- ^ Current goal. Nothing indicates it's uninteresting. , ex_result :: Text -- ^ Resulting extract. } ------------------------------------------------------------------------------ -- | An example 'HyInfo'. data ExampleHyInfo = EHI { ehi_name :: Var -- ^ Name of the variable , ehi_type :: ExampleType -- ^ Type of the variable } ------------------------------------------------------------------------------ -- | A variable newtype Var = Var { getVar :: Text } deriving newtype (IsString, Pretty) ------------------------------------------------------------------------------ -- | A type newtype ExampleType = ExampleType { getExampleType :: Text } deriving newtype (IsString, Pretty) ------------------------------------------------------------------------------ -- | A command to expose to the parser data MetaprogramCommand a = MC { mpc_name :: Text -- ^ Name of the command. This is the token necessary to run the command. , mpc_syntax :: Syntax a -- ^ The command's arguments , mpc_det :: Determinism -- ^ Determinism of the command , mpc_description :: Text -- ^ User-facing description , mpc_tactic :: a -- ^ Tactic to run , mpc_examples :: [Example] -- ^ Collection of documentation examples } ------------------------------------------------------------------------------ -- | Existentialize the pain away data SomeMetaprogramCommand where SMC :: MetaprogramCommand a -> SomeMetaprogramCommand ------------------------------------------------------------------------------ -- | Run the 'Parser' of a 'MetaprogramCommand' makeMPParser :: MetaprogramCommand a -> Parser (TacticsM ()) makeMPParser (MC name Nullary _ _ t _) = do identifier name t makeMPParser (MC name (Ref One) _ _ t _) = do identifier name variable >>= t makeMPParser (MC name (Ref Many) _ _ t _) = do identifier name P.many variable >>= t makeMPParser (MC name (Bind One) _ _ t _) = do identifier name variable >>= t makeMPParser (MC name (Bind Many) _ _ t _) = do identifier name P.many variable >>= t makeMPParser (MC name Tactic _ _ t _) = do identifier name parens tactic >>= t ------------------------------------------------------------------------------ -- | Compile a collection of metaprogram commands into a parser. makeParser :: [SomeMetaprogramCommand] -> Parser (TacticsM ()) makeParser ps = P.choice $ ps <&> \(SMC mp) -> makeMPParser mp ------------------------------------------------------------------------------ -- | Pretty print a command. prettyCommand :: MetaprogramCommand a -> Doc b prettyCommand (MC name syn det desc _ exs) = vsep [ "##" <+> pretty name , mempty , "arguments:" <+> prettySyntax syn <> ". " , prettyDeterminism det <> "." , mempty , ">" <+> align (pretty desc) , mempty , vsep $ fmap (prettyExample name) exs , mempty ] ------------------------------------------------------------------------------ -- | Pretty print a hypothesis. prettyHyInfo :: ExampleHyInfo -> Doc a prettyHyInfo hi = pretty (ehi_name hi) <+> "::" <+> pretty (ehi_type hi) ------------------------------------------------------------------------------ -- | Append the given term only if the first argument has elements. mappendIfNotNull :: [a] -> a -> [a] mappendIfNotNull [] _ = [] mappendIfNotNull as a = as <> [a] ------------------------------------------------------------------------------ -- | Pretty print an example. prettyExample :: Text -> Example -> Doc a prettyExample name (Example m_txt args hys goal res) = align $ vsep [ mempty , "### Example" , maybe mempty ((line <>) . (<> line) . (">" <+>) . align . pretty) m_txt , "Given:" , mempty , codeFence $ vsep $ mappendIfNotNull (fmap prettyHyInfo hys) mempty <> [ "_" <+> maybe mempty (("::" <+>). pretty) goal ] , mempty , hsep [ "running " , enclose "`" "`" $ pretty name <> hsep (mempty : fmap pretty args) , "will produce:" ] , mempty , codeFence $ align $ pretty res ] ------------------------------------------------------------------------------ -- | Make a haskell code fence. codeFence :: Doc a -> Doc a codeFence d = align $ vsep [ "```haskell" , d , "```" ] ------------------------------------------------------------------------------ -- | Render all of the commands. prettyReadme :: [SomeMetaprogramCommand] -> String prettyReadme = renderString . layoutPretty defaultLayoutOptions . vsep . fmap (\case SMC c -> prettyCommand c) . sortOn (\case SMC c -> mpc_name c) ------------------------------------------------------------------------------ -- | Helper function to build a 'SomeMetaprogramCommand'. command :: Text -> Determinism -> Syntax a -> Text -> a -> [Example] -> SomeMetaprogramCommand command txt det syn txt' a exs = SMC $ MC { mpc_name = txt , mpc_det = det , mpc_syntax = syn , mpc_description = txt' , mpc_tactic = a , mpc_examples = exs }