blanks-0.4.0: test/Test/Blanks/LocScopeTest.hs
module Test.Blanks.LocScopeTest where
import Blanks
import Data.String (IsString)
import Test.Blanks.Parsing
import Test.Tasty
import Test.Tasty.HUnit
-- A newtype indicating an identifier in our language
newtype Ident = Ident { unIdent :: String } deriving (Eq, Show, Ord, IsString)
-- The type of concrete expressions, labeled with source location
data CExp l =
CExpTrue !l
| CExpFalse !l
| 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)
deriving (Eq, Show)
-- Extracts the location from a concrete expression
cexpLoc :: CExp l -> l
cexpLoc ce =
case ce of
CExpTrue l -> l
CExpFalse l -> l
CExpInt l _ -> l
CExpApp l _ _ -> l
CExpAdd l _ _ -> l
CExpIf l _ _ _ -> l
CExpIsZero l _ -> l
CExpVar l _ -> l
CExpAbs l _ _ -> l
-- Just the expressions of our language that have nothing to do with naming
data Exp a =
ExpTrue
| ExpFalse
| ExpInt !Int
| ExpApp a a
| ExpAdd a a
| ExpIf a a a
| ExpIsZero a
deriving (Eq, Show, Functor, Foldable, Traversable)
-- A nameless equivalent to 'CExp'
type ExpScope l = LocScope l (NameOnly Ident) Exp Ident
-- Parsers a concrete expression from a string
cexpParser :: Parser (CExp SourceSpan)
cexpParser = result where
result = branch
[ trueParser
, falseParser
, intParser
, addParser
, ifParser
, isZeroParser
, absParser
, appParser
, varParser
]
trueParser = around (const . CExpTrue) (symbol "#t")
falseParser = around (const . CExpFalse) (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 (fmap Ident identifier)
b <- cexpParser
pure (n, b)
appParser = around2 CExpApp (parens (double cexpParser))
varParser = around CExpVar (fmap Ident identifier)
-- Convert to nameless representation
nameless :: CExp l -> ExpScope l
nameless ce =
case ce of
CExpTrue l -> LocScopeEmbed l ExpTrue
CExpFalse l -> LocScopeEmbed l ExpFalse
CExpInt l i -> LocScopeEmbed l (ExpInt i)
CExpApp l a b -> LocScopeEmbed l (ExpApp (nameless a) (nameless b))
CExpAdd l a b -> LocScopeEmbed l (ExpAdd (nameless a) (nameless b))
CExpIf l a b c -> LocScopeEmbed l (ExpIf (nameless a) (nameless b) (nameless c))
CExpIsZero l a -> LocScopeEmbed l (ExpIsZero (nameless a))
CExpVar l x -> LocScopeFree l x
CExpAbs l x a -> runColocated (blankAbstract1 (NameOnly x) x (nameless a)) l
-- Convert back to named representation. Usually this isn't a necessary operation,
-- but we want to do round-trip testing
named :: ExpScope l -> Maybe (CExp l)
named e =
case e of
LocScopeBound _ _ -> Nothing
LocScopeFree l a -> pure (CExpVar l a)
LocScopeBinder l _ (NameOnly x) b -> CExpAbs l x <$> named (blankUnAbstract1 x b)
LocScopeEmbed l fe ->
case fe of
ExpTrue -> pure (CExpTrue l)
ExpFalse -> pure (CExpFalse l)
ExpInt i -> pure (CExpInt l i)
ExpApp a b -> CExpApp l <$> named a <*> named b
ExpAdd a b -> CExpAdd l <$> named a <*> named b
ExpIf a b c -> CExpIf l <$> named a <*> named b <*> named c
ExpIsZero a -> CExpIsZero l <$> named a
-- An ExpScope without locations
type ExpSimpleScope = Scope (NameOnly Ident) Exp Ident
testSingle :: TestName -> String -> ExpSimpleScope -> TestTree
testSingle name input expected = testCase name $ do
namedExp <- runParserIO cexpParser input
let namelessExp = nameless namedExp
cexpLoc namedExp @?= locScopeLocation namelessExp
let actual = locScopeForget namelessExp
expected @?= actual
let renamedExp = named namelessExp
Just namedExp @?= renamedExp
testLocScope :: TestTree
testLocScope = testGroup "LocScope" cases where
xIdent = Ident "x"
yIdent = Ident "y"
xExp = ScopeFree xIdent
yExp = ScopeFree yIdent
trueExp = ScopeEmbed ExpTrue
intExp = ScopeEmbed (ExpInt 42)
negIntExp = ScopeEmbed (ExpInt (-42))
cases =
[ testSingle "var" "x" xExp
, testSingle "true" "#t" trueExp
, testSingle "false" "#f" (ScopeEmbed ExpFalse)
, testSingle "int" "42" intExp
, testSingle "neg int" "-42" negIntExp
, testSingle "add" "(+ 42 -42)" (ScopeEmbed (ExpAdd intExp negIntExp))
, testSingle "if" "(if #t 42 -42)" (ScopeEmbed (ExpIf trueExp intExp negIntExp))
, testSingle "add var" "(+ 42 x)" (ScopeEmbed (ExpAdd intExp xExp))
, testSingle "iszero" "(zero? 42)" (ScopeEmbed (ExpIsZero intExp))
, testSingle "app" "(x y)" (ScopeEmbed (ExpApp xExp yExp))
, testSingle "abs yy" "(lambda (y) y)" (ScopeBinder 1 (NameOnly yIdent) (ScopeBound 0))
, testSingle "abs xyy" "(lambda (x) (lambda (y) y))" (ScopeBinder 1 (NameOnly xIdent) (ScopeBinder 1 (NameOnly yIdent) (ScopeBound 0)))
, testSingle "abs xyx" "(lambda (x) (lambda (y) x))" (ScopeBinder 1 (NameOnly xIdent) (ScopeBinder 1 (NameOnly yIdent) (ScopeBound 1)))
, testSingle "app abs" "((lambda (x) x) 42)" (ScopeEmbed (ExpApp (ScopeBinder 1 (NameOnly xIdent) (ScopeBound 0)) intExp))
]