typed-peg-0.4.0.0: src/PEG/Key.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE RankNTypes #-}
-- | What a non-terminal reference points at.
--
-- A 'PEG.Syntax.PExp' is indexed by a /key type/ @nt :: Type -> Type@: a
-- value of type @nt a@ names a rule returning @a@, and
-- @'PEG.Syntax.NT' :: nt a -> PExp s nt a@ is a reference to it. Checking a
-- reference is then checking the type of one constructor, which costs the
-- type checker the same whatever the size of the grammar.
--
-- There are two kinds of key.
--
-- * A key type declared for one grammar, one constructor per rule:
--
-- @
-- data ArithEnv s a where
-- ArithEnv_expr :: ArithEnv s Exp
-- ArithEnv_term :: ArithEnv s Exp
-- @
--
-- This is what 'PEG.QQ.pegGrammar' generates in declaration position,
-- together with its 'Tabulate' instance, and it is what a grammar of any
-- size should use.
--
-- * @'InEnv' env@, a membership proof into a type-level list of rules
-- ('PEG.Type.Env'). This is what @'PEG.Syntax.nt' \@"expr"@,
-- 'PEG.QQ.pegRules' and a hand-written environment go through.
--
-- == Why keys
--
-- The environment used to be the only index. A reference carried a unary
-- proof, @There (There ... Here)@, of where its rule sits in a type-level
-- list, and the type checker's evidence for that proof is proportional to how
-- deep the rule is times how much of the list is left. Summed over every
-- reference of a grammar, that was the whole of what compiling a large
-- grammar cost: 2.2 GB of heap at 128 rules and more than 8 GB at 256, even
-- with the proof supplied by the splice rather than searched for. A declared
-- key costs 50 MB at 512 rules. See @bench-compile/@.
module PEG.Key
( Tabulate (..)
, Table (..)
, InEnv (..)
) where
import Data.Kind (Type)
import PEG.Member
import PEG.Type
-- | A total function out of a key type, as a value.
newtype Table (nt :: Type -> Type) (f :: Type -> Type) =
Table { lookupTable :: forall b. nt b -> f b }
-- | A key type whose rules can be enumerated.
--
-- This is what lets a grammar over declared keys be compiled with a knot:
-- 'PEG.Parse.compileGrammar' tabulates the compiled rule bodies once and
-- resolves every reference through the table.
class Tabulate (nt :: Type -> Type) where
-- | Memoise a function out of the key type.
--
-- @'lookupTable' (tabulate f)@ must agree with @f@, and must evaluate
-- @f k@ at most once for each key @k@ however often it is looked up. The
-- instance 'PEG.QQ.pegGrammar' generates binds @f k@ for every constructor
-- in a @let@ outside the lookup.
tabulate :: (forall b. nt b -> f b) -> Table nt f
-- | The name of the rule a key refers to.
ruleName :: nt b -> String
-- | A reference into a type-level environment: a proof that @env@ binds some
-- name to a rule returning @a@.
--
-- The name is not in the type. It does not have to be: a reference is built
-- by 'PEG.Syntax.nt' or 'PEG.Syntax.ntw', which state the name and demand
-- that the environment agrees about the result type.
data InEnv (env :: Env) (a :: Type) where
InEnv :: Member n env a -> InEnv env a