blanks-0.5.0: test/Test/Blanks/Exp.hs
{-# LANGUAGE DeriveAnyClass #-}
module Test.Blanks.Exp
( Ident (..)
, CExp (..)
, cexpLoc
, CDecl (..)
, declKeywords
, expKeywords
, cexpParser
, runCExpParser
, cdeclParser
, runCDeclParser
, Exp (..)
, ExpScope
, DeclScope
, ExpLocScope
, DeclLocScope
, declLocScopeForget
, declScopeAnno
, expToNameless
, expToNamed
, declToNameless
, declToNamed
) where
import Blanks (LocScope, pattern LocScopeBinder, pattern LocScopeBound, pattern LocScopeEmbed, pattern LocScopeFree,
Located (..), NameOnly, pattern NameOnly, Scope, locScopeAbstract1, locScopeForget, locScopeUnAbstract1,
runColocated, scopeAnno)
import Control.DeepSeq (NFData)
import Control.Monad (when)
import Data.Set (Set)
import qualified Data.Set as Set
import GHC.Generics (Generic)
import Test.Blanks.Parsing
-- A newtype indicating an identifier in our language
newtype Ident = Ident { unIdent :: String } deriving newtype (Eq, Show, Ord, NFData)
-- The type of concrete expressions, labeled with source location
data CExp l =
CExpBool !l !Bool
| CExpInt !l !Int
| CExpApp !l !(CExp l) !(CExp l)
| CExpAdd !l !(CExp l) !(CExp l)
| CExpIf !l !(CExp l) !(CExp l) !(CExp l)
| CExpIsZero !l !(CExp l)
| CExpVar !l !Ident
| CExpAbs !l !Ident !(CExp l)
| CExpAsc !l !(CExp l) !(CExp l)
| CExpTyInt !l
| CExpTyBool !l
| CExpTyFun !l !(CExp l) !(CExp l)
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
-- Extracts the location from a concrete expression
cexpLoc :: CExp l -> l
cexpLoc ce =
case ce of
CExpBool l _ -> l
CExpInt l _ -> l
CExpApp l _ _ -> l
CExpAdd l _ _ -> l
CExpIf l _ _ _ -> l
CExpIsZero l _ -> l
CExpVar l _ -> l
CExpAbs l _ _ -> l
CExpAsc l _ _ -> l
CExpTyInt l -> l
CExpTyBool l -> l
CExpTyFun l _ _ -> l
expKeywords :: Set Ident
expKeywords = Set.fromList $ fmap Ident
[ "#t"
, "#f"
, "+"
, "if"
, "zero?"
, ":"
, "lambda"
, "int"
, "bool"
, "->"
]
declKeywords :: Set Ident
declKeywords = Set.fromList $ fmap Ident
[ "declare"
, "define"
]
nonKeywordParser :: Parser Ident
nonKeywordParser = do
rawIdent <- identifier
let ident = Ident rawIdent
when (Set.member ident expKeywords) (fail ("Parsed exp keyword: " <> rawIdent))
when (Set.member ident declKeywords) (fail ("Parsed decl keyword: " <> rawIdent))
pure ident
-- Parses a concrete expression from a string
cexpParser :: Parser (CExp SourceSpan)
cexpParser = result where
result = branch
[ trueParser
, falseParser
, intParser
, addParser
, ifParser
, isZeroParser
, absParser
, appParser
, ascParser
, tyBoolParser
, tyIntParser
, tyFunParser
, varParser
]
trueParser = around (const . flip CExpBool True) (symbol "#t")
falseParser = around (const . flip CExpBool False) (symbol "#f")
intParser = around CExpInt signed
addParser = around2 CExpAdd (parens (symbol "+" *> double cexpParser))
ifParser = around3 CExpIf (parens (symbol "if" *> triple cexpParser))
isZeroParser = around CExpIsZero (parens (symbol "zero?" *> cexpParser))
absParser = around2 CExpAbs $ parens $ do
_ <- symbol "lambda"
n <- parens nonKeywordParser
b <- cexpParser
pure (n, b)
appParser = around2 CExpApp (parens (double cexpParser))
ascParser = around2 CExpAsc (parens (symbol ":" *> double cexpParser))
tyBoolParser = around (const . CExpTyBool) (symbol "bool")
tyIntParser = around (const . CExpTyInt) (symbol "int")
tyFunParser = around2 (CExpTyFun) (parens (symbol "->" *> double cexpParser))
varParser = around CExpVar nonKeywordParser
runCExpParser :: String -> IO (CExp SourceSpan)
runCExpParser = runParserIO cexpParser
data Level =
LevelTerm
| LevelType
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
data CDecl l = CDecl !l !Level !Ident !(CExp l)
deriving stock (Eq, Show, Generic)
deriving anyclass (NFData)
-- Parses a concrete declaration from a string
cdeclParser :: Parser (CDecl SourceSpan)
cdeclParser = result where
result = branch
[ parser "declare" LevelType
, parser "define" LevelTerm
]
parser name lvl = around2 (flip CDecl lvl) (parens (symbol name *> ((,) <$> nonKeywordParser <*> cexpParser)))
runCDeclParser :: String -> IO (CExp SourceSpan)
runCDeclParser = runParserIO cexpParser
-- Just the expressions of our language that have nothing to do with naming
data Exp a =
ExpBool !Bool
| ExpInt !Int
| ExpApp a a
| ExpAdd a a
| ExpIf a a a
| ExpIsZero a
| ExpAsc a a
| ExpTyBool
| ExpTyInt
| ExpTyFun a a
deriving stock (Eq, Show, Functor, Foldable, Traversable, Generic)
deriving anyclass (NFData)
data Decl a = Decl !Level !Ident a
deriving stock (Eq, Show, Functor, Foldable, Traversable, Generic)
deriving anyclass (NFData)
-- An ExpScope without locations
type ExpScope a = Scope (NameOnly Ident) Exp a
type DeclScope a = Decl (ExpScope a)
-- A nameless equivalent to 'CExp'
type ExpLocScope l a = LocScope l (NameOnly Ident) Exp a
type DeclLocScope l a = Located l (Decl (ExpLocScope l a))
declLocScopeForget :: DeclLocScope l a -> DeclScope a
declLocScopeForget = fmap locScopeForget . locatedVal
declScopeAnno :: l -> DeclScope a -> DeclLocScope l a
declScopeAnno l = Located l . fmap (scopeAnno l)
-- Convert to nameless representation
expToNameless :: CExp l -> ExpLocScope l Ident
expToNameless ce =
case ce of
CExpBool l b -> LocScopeEmbed l (ExpBool b)
CExpInt l i -> LocScopeEmbed l (ExpInt i)
CExpApp l a b -> LocScopeEmbed l (ExpApp (expToNameless a) (expToNameless b))
CExpAdd l a b -> LocScopeEmbed l (ExpAdd (expToNameless a) (expToNameless b))
CExpIf l a b c -> LocScopeEmbed l (ExpIf (expToNameless a) (expToNameless b) (expToNameless c))
CExpIsZero l a -> LocScopeEmbed l (ExpIsZero (expToNameless a))
CExpVar l x -> LocScopeFree l x
CExpAbs l x a -> runColocated (locScopeAbstract1 (NameOnly x) x (expToNameless a)) l
CExpAsc l a b -> LocScopeEmbed l (ExpAsc (expToNameless a) (expToNameless b))
CExpTyInt l -> LocScopeEmbed l ExpTyInt
CExpTyBool l -> LocScopeEmbed l ExpTyBool
CExpTyFun l a b -> LocScopeEmbed l (ExpTyFun (expToNameless a) (expToNameless b))
-- Convert back to named representation. Usually this isn't a necessary operation,
-- but we want to do round-trip testing
expToNamed :: ExpLocScope l Ident -> Maybe (CExp l)
expToNamed e =
case e of
LocScopeBound _ _ -> Nothing
LocScopeFree l a -> pure (CExpVar l a)
LocScopeBinder l _ (NameOnly x) b -> CExpAbs l x <$> expToNamed (locScopeUnAbstract1 x b)
LocScopeEmbed l fe ->
case fe of
ExpBool b -> pure (CExpBool l b)
ExpInt i -> pure (CExpInt l i)
ExpApp a b -> CExpApp l <$> expToNamed a <*> expToNamed b
ExpAdd a b -> CExpAdd l <$> expToNamed a <*> expToNamed b
ExpIf a b c -> CExpIf l <$> expToNamed a <*> expToNamed b <*> expToNamed c
ExpIsZero a -> CExpIsZero l <$> expToNamed a
ExpAsc a b -> CExpAsc l <$> expToNamed a <*> expToNamed b
ExpTyInt -> pure (CExpTyInt l)
ExpTyBool -> pure (CExpTyBool l)
ExpTyFun a b -> CExpTyFun l <$> expToNamed a <*> expToNamed b
declToNameless :: CDecl l -> DeclLocScope l Ident
declToNameless (CDecl l lvl i e) = Located l (Decl lvl i (expToNameless e))
declToNamed :: DeclLocScope l Ident -> Maybe (CDecl l)
declToNamed (Located l (Decl lvl i e)) = fmap (CDecl l lvl i) (expToNamed e)