From f97bd84050489c0da47876d1482c1e532d0365be Mon Sep 17 00:00:00 2001 From: James Eversole Date: Mon, 27 Jan 2025 16:03:03 -0600 Subject: [PATCH] Adds basic tests for modules Basic tests for modules. Fixes for parsing multiple import statements. --- README.md | 3 ++- src/Parser.hs | 6 +++++- test/Spec.hs | 42 ++++++++++++++++++++++++++++++++++++++++-- test/lambda-A.tri | 2 ++ test/multi-level-A.tri | 3 +++ test/multi-level-B.tri | 3 +++ test/multi-level-C.tri | 2 ++ test/namespace-A.tri | 3 +++ test/namespace-B.tri | 2 ++ test/unresolved-A.tri | 2 ++ test/vars-A.tri | 7 +++++++ test/vars-B.tri | 2 ++ test/vars-C.tri | 2 ++ 13 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 test/lambda-A.tri create mode 100644 test/multi-level-A.tri create mode 100644 test/multi-level-B.tri create mode 100644 test/multi-level-C.tri create mode 100644 test/namespace-A.tri create mode 100644 test/namespace-B.tri create mode 100644 test/unresolved-A.tri create mode 100644 test/vars-A.tri create mode 100644 test/vars-B.tri create mode 100644 test/vars-C.tri diff --git a/README.md b/README.md index 720326f..2bc3594 100644 --- a/README.md +++ b/README.md @@ -10,12 +10,13 @@ tricu is the word for "tree" in Lojban: `(x1) is a tree of species/cultivar (x2) - Tree Calculus operator: `t` - Assignments: `x = t t` +- Immutabile definitions - Lambda abstraction syntax: `id = (\a : a)` - List, Number, and String literals: `[(2) ("Hello")]` - Function application: `not (not false)` - Higher order/first-class functions: `map (\a : lconcat a "!") [("Hello")]` - Intensionality blurs the distinction between functions and data (see REPL examples) -- Immutability +- Simple module system for code organization ## REPL examples diff --git a/src/Parser.hs b/src/Parser.hs index ca68488..dd8dd85 100644 --- a/src/Parser.hs +++ b/src/Parser.hs @@ -76,12 +76,16 @@ parseProgramM = do skipMany topLevelNewline moduleNode <- optional parseModuleM skipMany topLevelNewline - importNodes <- many parseImportM + importNodes <- many (do + node <- parseImportM + skipMany topLevelNewline + return node) skipMany topLevelNewline exprs <- sepEndBy parseOneExpression (some topLevelNewline) skipMany topLevelNewline return (maybe [] (: []) moduleNode ++ importNodes ++ exprs) + parseModuleM :: ParserM TricuAST parseModuleM = do LModule moduleName <- satisfyM isModule diff --git a/test/Spec.hs b/test/Spec.hs index d062b1c..7c8f102 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -7,12 +7,13 @@ import Parser import REPL import Research -import Control.Exception (evaluate, try, SomeException) +import Control.Exception (evaluate, try, SomeException) import Control.Monad.IO.Class (liftIO) +import Data.List (isInfixOf) import Test.Tasty import Test.Tasty.HUnit import Test.Tasty.QuickCheck -import Text.Megaparsec (runParser) +import Text.Megaparsec (runParser) import qualified Data.Map as Map import qualified Data.Set as Set @@ -31,6 +32,7 @@ tests = testGroup "Tricu Tests" , lambdas , baseLibrary , fileEval + , modules , demos ] @@ -498,6 +500,42 @@ fileEval = testGroup "File evaluation tests" decodeResult (result res) @?= "\"String test!\"" ] +modules :: TestTree +modules = testGroup "Test modules" + [ testCase "Detect cyclic dependencies" $ do + result <- try (liftIO $ evaluateFileResult "./test/cycle-1.tri") :: IO (Either SomeException T) + case result of + Left e -> do + let errorMsg = show e + if "Encountered cyclic import" `isInfixOf` errorMsg + then return () + else assertFailure $ "Unexpected error: " ++ errorMsg + Right _ -> assertFailure "Expected cyclic dependencies" + , testCase "Module imports and namespacing" $ do + res <- liftIO $ evaluateFileResult "./test/namespace-A.tri" + res @?= Leaf + , testCase "Multiple imports" $ do + res <- liftIO $ evaluateFileResult "./test/vars-A.tri" + res @?= Leaf + , testCase "Error on unresolved variable" $ do + result <- try (liftIO $ evaluateFileResult "./test/unresolved-A.tri") :: IO (Either SomeException T) + case result of + Left e -> do + let errorMsg = show e + if "undefinedVar" `isInfixOf` errorMsg + then return () + else assertFailure $ "Unexpected error: " ++ errorMsg + Right _ -> assertFailure "Expected unresolved variable error" + , testCase "Multi-level imports" $ do + res <- liftIO $ evaluateFileResult "./test/multi-level-A.tri" + res @?= Leaf + , testCase "Lambda expression namespaces" $ do + res <- liftIO $ evaluateFileResult "./test/lambda-A.tri" + res @?= Leaf + ] + + +-- All of our demo tests are also module tests demos :: TestTree demos = testGroup "Test provided demo functionality" [ testCase "Structural equality demo" $ do diff --git a/test/lambda-A.tri b/test/lambda-A.tri new file mode 100644 index 0000000..844f615 --- /dev/null +++ b/test/lambda-A.tri @@ -0,0 +1,2 @@ +!module A +main = (\x : x) t diff --git a/test/multi-level-A.tri b/test/multi-level-A.tri new file mode 100644 index 0000000..34a85f2 --- /dev/null +++ b/test/multi-level-A.tri @@ -0,0 +1,3 @@ +!module A +!import "./test/multi-level-B.tri" B +main = B.main diff --git a/test/multi-level-B.tri b/test/multi-level-B.tri new file mode 100644 index 0000000..0d92637 --- /dev/null +++ b/test/multi-level-B.tri @@ -0,0 +1,3 @@ +!module B +!import "./test/multi-level-C.tri" C +main = C.val diff --git a/test/multi-level-C.tri b/test/multi-level-C.tri new file mode 100644 index 0000000..a4005fb --- /dev/null +++ b/test/multi-level-C.tri @@ -0,0 +1,2 @@ +!module C +val = t diff --git a/test/namespace-A.tri b/test/namespace-A.tri new file mode 100644 index 0000000..6185d67 --- /dev/null +++ b/test/namespace-A.tri @@ -0,0 +1,3 @@ +!module A +!import "./test/namespace-B.tri" B +main = B.x diff --git a/test/namespace-B.tri b/test/namespace-B.tri new file mode 100644 index 0000000..2cda9a7 --- /dev/null +++ b/test/namespace-B.tri @@ -0,0 +1,2 @@ +!module B +x = t diff --git a/test/unresolved-A.tri b/test/unresolved-A.tri new file mode 100644 index 0000000..ca19217 --- /dev/null +++ b/test/unresolved-A.tri @@ -0,0 +1,2 @@ +!module A +main = undefinedVar diff --git a/test/vars-A.tri b/test/vars-A.tri new file mode 100644 index 0000000..962def7 --- /dev/null +++ b/test/vars-A.tri @@ -0,0 +1,7 @@ +!module A + +!import "./test/vars-B.tri" B + +!import "./test/vars-C.tri" C + +main = B.y (C.z) diff --git a/test/vars-B.tri b/test/vars-B.tri new file mode 100644 index 0000000..b72ee50 --- /dev/null +++ b/test/vars-B.tri @@ -0,0 +1,2 @@ +!module B +y = \x : x diff --git a/test/vars-C.tri b/test/vars-C.tri new file mode 100644 index 0000000..78d36ce --- /dev/null +++ b/test/vars-C.tri @@ -0,0 +1,2 @@ +!module C +z = t