module Minilang.REPLSpec where import Control.Exception (bracket) import Data.Text.IO import Minilang.REPL 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\"" ] 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")