nanopass-0.0.3.0: src/Nanopass/Internal/Validate.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedRecordDot #-}
module Nanopass.Internal.Validate
( validateLanguage
, validateParams
, validateNonterm
, validateProd
, validateType
) where
import Nanopass.Internal.Representation
import Control.Monad (forM,when)
import Data.Functor ((<&>))
import Data.List (nub, (\\))
import Data.Map (Map)
import Data.Set (Set)
import Nanopass.Internal.Error (Error(..))
import qualified Data.Map as Map
import qualified Data.Set as Set
import qualified Language.Haskell.TH as TH
validateLanguage :: Language 'Unvalidated UpName -> Either Error (Language 'Valid UpName)
validateLanguage lang = do
langParams <- validateParams lang.langInfo.langParams
let tvs = Map.fromList $ langParams <&> \n -> (n.name, n)
nts = Set.fromList $ Map.keys lang.langInfo.nonterms
nonterms <- validateNonterm nts tvs `mapM` lang.langInfo.nonterms
pure Language
{ langName = ValidName lang.langName.name (TH.mkName $ fromUpName lang.langName.name)
, langInfo = LanguageInfo
{ langParams
, nonterms
, originalProgram = lang.langInfo.originalProgram
, baseDefdLang = lang.langInfo.baseDefdLang
}
}
validateParams :: [Name 'Unvalidated LowName] -> Either Error [Name 'Valid LowName]
validateParams tvs = do
let duplicates = tvs \\ nub tvs
when (not $ null duplicates) $ Left $ DuplicateLanguageParams (duplicates <&> (.name))
forM tvs $ \n -> pure $ ValidName n.name (TH.mkName $ fromLowName n.name)
validateNonterm :: Set UpName -- ^ known non-terminals
-> Map LowName (Name 'Valid LowName) -- ^ known type variables
-> Nonterm v
-> Either Error (Nonterm 'Valid)
validateNonterm nts tvs nt = do
let nontermName = ValidName nt.nontermName.name (TH.mkName $ fromUpName nt.nontermName.name)
productions <- validateProd nts tvs `mapM` nt.productions
pure Nonterm
{ nontermName
, productions
}
validateProd :: Set UpName -- ^ known non-terminals
-> Map LowName (Name 'Valid LowName) -- ^ known type variables
-> Production v
-> Either Error (Production 'Valid)
validateProd nts tvs prod = do
let prodName = ValidName prod.prodName.name (TH.mkName $ fromUpName prod.prodName.name)
subterms <- validateType nts tvs `mapM` prod.subterms
pure Production
{ prodName
, subterms
}
validateType :: Set UpName -- ^ known non-terminals
-> Map LowName (Name 'Valid LowName) -- ^ known type variables
-> TypeDesc v
-> Either Error (TypeDesc 'Valid)
validateType nts tvs = \case
RecursiveType n -> case Set.member n nts of
True -> pure $ RecursiveType n
False -> Left $ UnrecognizedNonterm n
VarType n -> case Map.lookup n.name tvs of
Just validName -> pure $ VarType validName
Nothing -> Left $ UnrecognizedTypeVariable n.name
CtorType n ts
| ([], n') <- splitUpDotName n.name
, n' `Set.member` nts
-> case ts of
[] -> pure $ RecursiveType n'
_ -> Left $ UnexpectedTypeApplicationstoRecursiveType n'
CtorType (SourceName n) ts -> do
let ctor = ValidName n (TH.mkName $ fromUpDotName n)
CtorType ctor <$> loop `mapM` ts
CtorType ctor@(ValidName _ _) ts -> do
CtorType ctor <$> loop `mapM` ts
ListType t -> ListType <$> loop t
MaybeType t -> MaybeType <$> loop t
NonEmptyType t -> NonEmptyType <$> loop t
UnitType -> pure UnitType
TupleType t1 t2 ts -> TupleType <$> loop t1 <*> loop t2 <*> loop `mapM` ts
where loop = validateType nts tvs