summaryrefslogtreecommitdiff
path: root/lambda-calcul/haskell/test
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud@pankzsoft.com>2025-10-28 20:14:16 +0100
committerArnaud Bailly <arnaud@pankzsoft.com>2025-10-28 20:14:16 +0100
commit1d1f0efc7de1eaa0c8a6b503892c2f950008fde6 (patch)
treebd2e2ea5963d8a208d302854502f580d8ffd73cb /lambda-calcul/haskell/test
parenta210419fd60c00d011f70fb1b968031621e4c01a (diff)
downloadlambda-nantes-1d1f0efc7de1eaa0c8a6b503892c2f950008fde6.tar.gz
wip: port REPL from minilang
Diffstat (limited to 'lambda-calcul/haskell/test')
-rw-r--r--lambda-calcul/haskell/test/Minilang/IOSpec.hs2
-rw-r--r--lambda-calcul/haskell/test/Minilang/Lambda/DeBruijnSpec.hs8
-rw-r--r--lambda-calcul/haskell/test/Minilang/Lambda/EvalSpec.hs87
-rw-r--r--lambda-calcul/haskell/test/Minilang/REPL/HaskelineSpec.hs33
-rw-r--r--lambda-calcul/haskell/test/Minilang/REPLSpec.hs74
5 files changed, 157 insertions, 47 deletions
diff --git a/lambda-calcul/haskell/test/Minilang/IOSpec.hs b/lambda-calcul/haskell/test/Minilang/IOSpec.hs
index 80cda6c..f000f0a 100644
--- a/lambda-calcul/haskell/test/Minilang/IOSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/IOSpec.hs
@@ -27,7 +27,7 @@ spec = parallel $
out <- readFile outputFileName
- out `shouldBe` "V \"z\"\n"
+ out `shouldBe` "z"
withTempFile :: (String -> IO ()) -> IO ()
withTempFile =
diff --git a/lambda-calcul/haskell/test/Minilang/Lambda/DeBruijnSpec.hs b/lambda-calcul/haskell/test/Minilang/Lambda/DeBruijnSpec.hs
index ef8903c..cff047d 100644
--- a/lambda-calcul/haskell/test/Minilang/Lambda/DeBruijnSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/Lambda/DeBruijnSpec.hs
@@ -1,8 +1,10 @@
+{-# LANGUAGE TypeApplications #-}
module Minilang.Lambda.DeBruijnSpec where
import Minilang.Lambda.DeBruijn (DeBruijn (..), eval, toDeBruijn)
import qualified Minilang.Lambda.Eval as Eval
import Test.Hspec (Spec, it, shouldBe)
+import Data.Text (Text)
spec :: Spec
spec = do
@@ -10,9 +12,9 @@ spec = do
let term = Eval.Lam "x" (Eval.Lam "y" (Eval.Var "x"))
deBruijn = toDeBruijn term
- deBruijn `shouldBe` Lam (Lam (Var 1))
+ deBruijn `shouldBe` Lam "x" (Lam "y" (Var "x" 1))
it "evaluates deBruijn term" $ do
- let term = App (App (Lam (Lam (Var 1))) (Var 12)) (Var 34)
+ let term = App (App (Lam "x" (Lam "y" (Var "x" 1))) (FV "z")) (FV "t")
- eval term `shouldBe` Var 12
+ eval @Text term `shouldBe` FV "z"
diff --git a/lambda-calcul/haskell/test/Minilang/Lambda/EvalSpec.hs b/lambda-calcul/haskell/test/Minilang/Lambda/EvalSpec.hs
index 718b46d..89e86b9 100644
--- a/lambda-calcul/haskell/test/Minilang/Lambda/EvalSpec.hs
+++ b/lambda-calcul/haskell/test/Minilang/Lambda/EvalSpec.hs
@@ -2,51 +2,52 @@ module Minilang.Lambda.EvalSpec (spec) where
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (race_)
-import Minilang.Lambda.Eval (Term (..), Value (..), eval, evalNeed)
-import Test.Hspec (Spec, describe, it, shouldBe)
+import Minilang.Lambda.Eval (Term (..), eval, evalNeed)
+import Test.Hspec (Spec, describe, it, pending, shouldBe)
spec :: Spec
-spec = do
- let fix =
- Lam
- "f"
- ( App
- (Lam "x" (App (Var "f") (App (Var "x") (Var "x"))))
- (Lam "x" (App (Var "f") (App (Var "x") (Var "x"))))
- )
- let omega = App fix fix
- let term = App (App (Lam "x" (Lam "y" (Var "y"))) omega) (Var "z")
- let fst' = Lam "x" (Lam "y" (Var "x"))
+spec = it "evaluates Term" pending
- describe "call-by-value eval" $ do
- it "evaluates 'x' as itself" $ do
- let term = Var "x"
- eval term [] `shouldBe` V "x"
- it "evaluates '((λ x. x) y)' as 'y'" $ do
- let term = App (Lam "x" (Var "x")) (Var "y")
- eval term [] `shouldBe` V "y"
- it "evaluates '((λ x y. y) z t y)' as '(t y)'" $ do
- let snd' = Lam "x" (Lam "y" (Var "y"))
- let term = App (App (App snd' (Var "z")) (Var "t")) (Var "y")
- eval term [] `shouldBe` Ap (V "t") (V "y")
- it "evaluates '((λ x y. x) z t)' as 'z'" $ do
- let term = App (App fst' (Var "z")) (Var "t")
- eval term [] `shouldBe` V "z"
- it "diverges on evaluating argument bound to ω" $
- shouldDiverge $
- eval term []
+-- let fix =
+-- Lam
+-- "f"
+-- ( App
+-- (Lam "x" (App (Var "f") (App (Var "x") (Var "x"))))
+-- (Lam "x" (App (Var "f") (App (Var "x") (Var "x"))))
+-- )
+-- let omega = App fix fix
+-- let term = App (App (Lam "x" (Lam "y" (Var "y"))) omega) (Var "z")
+-- let fst' = Lam "x" (Lam "y" (Var "x"))
- describe "call-by-need eval" $ do
- it "evaluates 'x' as itself" $ do
- evalNeed (Var "x") [] `shouldBe` V "x"
- it "evaluates '(λ x y . y) ⊥ z' as 'z'" $ do
- evalNeed term [] `shouldBe` V "z"
- it "diverges on evaluating ω" $
- shouldDiverge $
- evalNeed omega []
+-- describe "call-by-value eval" $ do
+-- it "evaluates 'x' as itself" $ do
+-- let term = Var "x"
+-- eval term [] `shouldBe` V "x"
+-- it "evaluates '((λ x. x) y)' as 'y'" $ do
+-- let term = App (Lam "x" (Var "x")) (Var "y")
+-- eval term [] `shouldBe` V "y"
+-- it "evaluates '((λ x y. y) z t y)' as '(t y)'" $ do
+-- let snd' = Lam "x" (Lam "y" (Var "y"))
+-- let term = App (App (App snd' (Var "z")) (Var "t")) (Var "y")
+-- eval term [] `shouldBe` Ap (V "t") (V "y")
+-- it "evaluates '((λ x y. x) z t)' as 'z'" $ do
+-- let term = App (App fst' (Var "z")) (Var "t")
+-- eval term [] `shouldBe` V "z"
+-- it "diverges on evaluating argument bound to ω" $
+-- shouldDiverge $
+-- eval term []
-shouldDiverge :: Value -> IO ()
-shouldDiverge t =
- race_
- (t `seq` fail "Did not diverge")
- (threadDelay 1000000)
+-- describe "call-by-need eval" $ do
+-- it "evaluates 'x' as itself" $ do
+-- evalNeed (Var "x") [] `shouldBe` V "x"
+-- it "evaluates '(λ x y . y) ⊥ z' as 'z'" $ do
+-- evalNeed term [] `shouldBe` V "z"
+-- it "diverges on evaluating ω" $
+-- shouldDiverge $
+-- evalNeed omega []
+
+-- shouldDiverge :: Value -> IO ()
+-- shouldDiverge t =
+-- race_
+-- (t `seq` fail "Did not diverge")
+-- (threadDelay 1000000)
diff --git a/lambda-calcul/haskell/test/Minilang/REPL/HaskelineSpec.hs b/lambda-calcul/haskell/test/Minilang/REPL/HaskelineSpec.hs
new file mode 100644
index 0000000..b7ca5c9
--- /dev/null
+++ b/lambda-calcul/haskell/test/Minilang/REPL/HaskelineSpec.hs
@@ -0,0 +1,33 @@
+module Minilang.REPL.HaskelineSpec where
+
+import Minilang.REPL.Haskeline
+import System.Console.Haskeline.Completion
+import Test.Hspec
+import Prelude hiding
+ ( lines,
+ readFile,
+ writeFile,
+ )
+
+spec :: Spec
+spec = parallel $ describe "MiniLang Haskeline REPL" $ do
+ describe "Completion" $ do
+ it "returns all commands when input ':' " $ do
+ (fmap replacement) . snd <$> completion (":", "")
+ `shouldReturn` [":quit", ":env", ":clear", ":load", ":set", ":unset", ":help"]
+
+ it "returns sub-commands when input ':set' " $ do
+ (fmap replacement) . snd <$> completion ("tes:", "")
+ `shouldReturn` [":set step", ":set debug"]
+
+ it "returns sub-commands when input ':unset' " $ do
+ (fmap replacement) . snd <$> completion ("tesnu:", "")
+ `shouldReturn` [":unset step", ":unset debug"]
+
+ it "returns list of files when input ':load' " $ do
+ (fmap replacement) . snd <$> completion ("daol:", "")
+ `shouldReturn` [":load bool.mtt", ":load nelist.mtt", ":load vector.mtt", ":load indrec.mtt"]
+
+ it "returns filtered list of files when input ':load xxx' " $ do
+ (fmap replacement) . snd <$> completion ("v daol:", "")
+ `shouldReturn` [":load vector.mtt"]
diff --git a/lambda-calcul/haskell/test/Minilang/REPLSpec.hs b/lambda-calcul/haskell/test/Minilang/REPLSpec.hs
new file mode 100644
index 0000000..e134810
--- /dev/null
+++ b/lambda-calcul/haskell/test/Minilang/REPLSpec.hs
@@ -0,0 +1,74 @@
+module Minilang.REPLSpec where
+
+import Control.Exception (bracket)
+import Data.Text.IO
+import Minilang.Lambda.Eval (Term (Var))
+import Minilang.REPL
+import Minilang.REPL.Purer
+import Minilang.REPL.Types
+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 MiniLang terms read from pure Monad" $ do
+ let out =
+ withInput
+ [ "rec Nat : U = Sum (zero | succ Nat);",
+ "id : Π A : U . Π _ : A . A = λ A . λ x . x;",
+ "id Nat (succ (succ (succ zero)))"
+ ]
+
+ out
+ `shouldBe` [ "Bye!",
+ "$succ $succ $succ $zero : Sum(zero| succ Nat)",
+ "defined id : Π A : U . A → A",
+ "defined Nat : U"
+ ]
+
+ 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 "rec Nat : U = Sum (zero | succ Nat);\nid : Π A : U . Π _ : A . A = λ A . λ x . x;\nid Nat (succ (succ (succ zero)))"
+
+ _ <- withFile fileName ReadMode $ \hin ->
+ withFile outputFileName AppendMode $ \hout ->
+ withHandles hin hout
+
+ out <- readFile outputFileName
+
+ out
+ `shouldBe` "\955\928> defined Nat : U\n\955\928> defined id : \928 A : U . A \8594 A\n\955\928> succ succ succ zero : Sum(zero| succ Nat)\n\955\928> Bye!\n"
+
+ describe "Purer REPL" $ do
+ it "keeps handling Inputs even when they fail" $ do
+ let inputs =
+ [ In "Unit : U",
+ In "Unit : U = Sum(tt)",
+ Com DumpEnv
+ ]
+
+ env (purerREPL (withInputs initialREPL inputs))
+ `shouldBe` [("Unit", Var "x")]
+
+ outputs (withInputs initialREPL inputs)
+ `shouldContain` [Msg "cannot find \"Unit\" in empty context"]
+
+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")