1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
|
module Minilang.REPLSpec where
import Control.Exception (bracket)
import Data.Either (isRight)
import Data.Text.IO
import Minilang.Lambda.Eval (Term (Lam, Var))
import Minilang.Lambda.Parser (AST (..))
import Minilang.Lambda.Unify (Type (TyVar))
import Minilang.REPL
import Minilang.REPL.Purer (PurerEnv (..))
import Minilang.REPL.Run (loadProgramInEnv)
import Minilang.REPL.Types (Out (Evaluated))
import System.Directory (getTemporaryDirectory, removeFile)
import System.FilePath ((<.>), (</>))
import System.IO (IOMode (..), hClose, withFile)
import System.Posix.Temp (mkstemp)
import Test.Hspec
import Prelude hiding (lines, readFile, writeFile)
spec :: Spec
spec = parallel $
describe "MiniLang REPL" $ do
it "evaluates untyped MiniLang terms read from pure Monad" $ do
let out =
withInput
["((lam (x) x) y)"]
out
`shouldBe` [ "Bye!",
"y : <?>"
]
it "evaluates untyped MiniLang terms with definitions" $ do
let out =
withInput
["(def id (lam (x) x))", "(id y)"]
out
`shouldBe` [ "Bye!",
"y : <?>",
"id : <?>"
]
it "evaluates typed MiniLang terms with definitions" $ do
let out =
withInput
[ ":lang Typed",
"(def id (lam (x) x))",
"(id y)"
]
out
`shouldBe` [ "Bye!",
"y : c",
"id : a -> a",
"Language set to Typed"
]
it "defaults untyped terms" $ do
let out =
withInput
[ "(let ((Succ (lam (n z s) (s n))) \
\ (Zero (lam (z s) z))) \
\ (Succ (Succ Zero)))"
]
out
`shouldBe` [ "Bye!",
"(lam (z s) (s ((lam (n z s) (s n)) (lam (z s) z)))) : <?>"
]
it "do typecheck term when language is set to 'Typed'" $ do
let out =
withInput
[ ":lang Typed",
"(let ((Succ (lam (n z s) (s n))) \
\ (Zero (lam (z s) z))) \
\ (Succ (Succ Zero)))"
]
out
`shouldBe` [ "Bye!",
"Cannot unify type variable f with type (e -> f -> e) -> e due to circularity: The variable already occurs in the type.",
"Language set to Typed"
]
it "shows error when trying to load a non-existent file" $ do
withInput [":load foo"]
`shouldBe` [ "Bye!",
"REPLError \"cannot load file foo in Pure interpreter\""
]
it "can load 'bool-prog.lisp'" $ do
boolProg <- readFile "bool-prog.lisp"
let PurerEnv{outputs} = runProgram (loadProgramInEnv boolProg)
outputs `shouldBe` []
it "can load 'bool.lisp'" $ do
boolProg <- readFile "bool.lisp"
let PurerEnv {outputs} = runProgram (loadProgramInEnv boolProg)
outputs
`shouldBe` [ Evaluated
(Abs ["f", "t"] (Sym "f"))
(Lam "f" (Lam "t" (Var "f")))
(TyVar "<?>")
]
around withTempFile $
it "evaluates single MiniLang term read from IO" $ \fileName -> do
let outputFileName = fileName <> ".out"
writeFile
fileName
"(let ((Succ (lam (n z s) (s n))) \
\ (Zero (lam (z s) z))) \
\ (Succ Zero))"
_ <- withFile fileName ReadMode $ \hin ->
withFile outputFileName AppendMode $ \hout ->
withHandles hin hout
out <- readFile outputFileName
out
`shouldBe` "\955\928> (lam (z s) (s (lam (z s) z))) : <?>\n\955\928> Bye!\n"
withTempFile :: (String -> IO ()) -> IO ()
withTempFile =
bracket mkTempFile rmTempFile
where
mkTempFile = getTemporaryDirectory >>= \dir -> mkstemp (dir </> "test-repl") >>= \(fp, h) -> hClose h >> pure fp
rmTempFile fn = removeFile fn >> removeFile (fn <.> "out")
|