packages feed

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)