packages feed

large-records-0.1.0.0: src/Data/Record/QQ/CodeGen/Parser.hs

{-# LANGUAGE DataKinds           #-}
{-# LANGUAGE GADTs               #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Data.Record.QQ.CodeGen.Parser (
    ParsedRecordInfo(..)
  , parseRecordExp
  , parseRecordPat
  ) where

import Data.Bifunctor
import Language.Haskell.TH (Exp(RecConE), Pat(RecP))
import Language.Haskell.TH.Syntax (Quasi, NameSpace(..))

import qualified Language.Haskell.TH.Syntax as TH

import Data.Record.Internal.Naming
import Data.Record.Internal.Record
import Data.Record.Internal.Record.Resolution
import Data.Record.QQ.CodeGen.HSE

import qualified Data.Record.Internal.TH.Name as N

data ParsedRecordInfo a =
    -- | The pattern/value we matched against is not a (known) large record
    NotKnownLargeRecord

    -- | The pattern/value has fields not present in the record definition
  | UnknownFields [String]

    -- | Successfully matched against a known large records
    --
    -- Note that not all fields may have a corresponding value/pattern.
    --
    -- In addition to the record info, we also record the qualifier used in
    -- the original name.
  | ParsedRecordInfo N.Qualifier (Record (Maybe a))

parseRecordExp :: Quasi m => Exp -> m (Maybe (ParsedRecordInfo Exp))
parseRecordExp = traverse (uncurry parseRecordInfo) . termExp

parseRecordPat :: Quasi m => Pat -> m (Maybe (ParsedRecordInfo Pat))
parseRecordPat = traverse (uncurry parseRecordInfo) . termPat

-- | Try to match a pattern or expression against a (large) record
parseRecordInfo ::
     forall m a. Quasi m
  => N.Name 'DataName 'N.Dynamic
  -> [(String, a)]
  -> m (ParsedRecordInfo a)
parseRecordInfo userConstr fields = do
    mInternalConstr <- resolveHseName nameRecordInternalConstr userConstr
    case mInternalConstr of
      Nothing ->
        return NotKnownLargeRecord
      Just internalConstr ->
        aux <$> resolveRecord (N.nameBase userConstr) internalConstr
  where
    aux :: Either String (Record ()) -> ParsedRecordInfo a
    aux (Left _err) = NotKnownLargeRecord
    aux (Right r)   =
        case matchRecordFields fields r of
          (r', []) -> ParsedRecordInfo  (N.nameQualifier userConstr) (snd <$> r')
          (_ , un) -> UnknownFields un

{-------------------------------------------------------------------------------
  Match against the syntax tree generated by @haskell-src-exts@

  We ignore any qualifiers for field names.
-------------------------------------------------------------------------------}

termExp :: Exp -> Maybe (N.Name 'DataName 'N.Dynamic, [(String, TH.Exp)])
termExp (RecConE constr fields) = Just (
      fromHseName constr
    , map (first TH.nameBase) fields
    )
termExp _otherwise = Nothing

termPat :: Pat -> Maybe (N.Name 'DataName 'N.Dynamic, [(String, TH.Pat)])
termPat (RecP constr fields) = Just (
      fromHseName constr
    , map (first TH.nameBase) fields
    )
termPat _otherwise = Nothing