233 lines
7.5 KiB
Haskell
233 lines
7.5 KiB
Haskell
module Main where
|
|
|
|
import Eval
|
|
import Lexer
|
|
import Parser
|
|
import Research
|
|
|
|
import Control.Exception (evaluate, try, SomeException)
|
|
import qualified Data.Map as Map
|
|
import Test.Tasty
|
|
import Test.Tasty.HUnit
|
|
import Test.Tasty.QuickCheck
|
|
import Text.Megaparsec (runParser)
|
|
|
|
main :: IO ()
|
|
main = defaultMain tests
|
|
|
|
tests :: TestTree
|
|
tests = testGroup "Sapling Tests"
|
|
[ lexerTests
|
|
, parserTests
|
|
, integrationTests
|
|
, evaluationTests
|
|
, propertyTests
|
|
]
|
|
|
|
lexerTests :: TestTree
|
|
lexerTests = testGroup "Lexer Tests"
|
|
[ testCase "Lex simple identifiers" $ do
|
|
let input = "x a b = a"
|
|
let expected = Right [LIdentifier "x", LIdentifier "a", LIdentifier "b", LAssign, LIdentifier "a"]
|
|
runParser saplingLexer "" input @?= expected
|
|
|
|
, testCase "Lex Tree Calculus terms" $ do
|
|
let input = "t t t"
|
|
let expected = Right [LKeywordT, LKeywordT, LKeywordT]
|
|
runParser saplingLexer "" input @?= expected
|
|
|
|
, testCase "Handle invalid input" $ do
|
|
let input = "x = "
|
|
case runParser saplingLexer "" input of
|
|
Left _ -> return ()
|
|
Right _ -> assertFailure "Expected failure on invalid input"
|
|
|
|
, testCase "Lex escaped characters in strings" $ do
|
|
let input = "\"hello\\nworld\""
|
|
let expected = Right [LStringLiteral "hello\\nworld"]
|
|
runParser saplingLexer "" input @?= expected
|
|
|
|
, testCase "Lex mixed literals" $ do
|
|
let input = "t \"string\" 42"
|
|
let expected = Right [LKeywordT, LStringLiteral "string", LIntegerLiteral 42]
|
|
runParser saplingLexer "" input @?= expected
|
|
|
|
, testCase "Lex invalid token" $ do
|
|
let input = "$invalid"
|
|
case runParser saplingLexer "" input of
|
|
Left _ -> return ()
|
|
Right _ -> assertFailure "Expected lexer to fail on invalid token"
|
|
]
|
|
|
|
parserTests :: TestTree
|
|
parserTests = testGroup "Parser Tests"
|
|
[ testCase "Parse function definitions" $ do
|
|
let input = "x a b = a"
|
|
let expected = SFunc "x" ["a", "b"] (SApp (SVar "a") [])
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse nested Tree Calculus terms" $ do
|
|
let input = "t (t t) t"
|
|
let expected = TFork (TStem TLeaf) TLeaf
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse sequential Tree Calculus terms" $ do
|
|
let input = "t t t"
|
|
let expected = TFork TLeaf TLeaf
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse mixed list literals" $ do
|
|
-- You must put non-list literals in parentheses
|
|
let input = "[t (\"hello\") t]"
|
|
let expected = SList [TLeaf, SStr "hello", TLeaf]
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse function with applications" $ do
|
|
let input = "f x = t x"
|
|
let expected = SFunc "f" ["x"] (SApp TLeaf [SVar "x"])
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse nested lists" $ do
|
|
let input = "[t [(t t)]]"
|
|
let expected = SList [TLeaf, SList [TStem TLeaf]]
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse complex parentheses" $ do
|
|
let input = "t (t t (t t))"
|
|
let expected = TStem (TFork TLeaf (TStem TLeaf))
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse empty list" $ do
|
|
let input = "[]"
|
|
let expected = SList []
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse multiple nested lists" $ do
|
|
let input = "[[t t] [t (t t)]]"
|
|
let expected = SList [SList [TLeaf, TLeaf], SList [TLeaf, TStem TLeaf]]
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse whitespace variance" $ do
|
|
let input1 = "[t t]"
|
|
let input2 = "[ t t ]"
|
|
let expected = SList [TLeaf, TLeaf]
|
|
parseSapling input1 @?= expected
|
|
parseSapling input2 @?= expected
|
|
|
|
, testCase "Parse string in list" $ do
|
|
let input = "[(\"hello\")]"
|
|
let expected = SList [SStr "hello"]
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Parse parentheses inside list" $ do
|
|
let input = "[t (t t)]"
|
|
let expected = SList [TLeaf, TStem TLeaf]
|
|
parseSapling input @?= expected
|
|
|
|
-- Do I want to allow multi-line indentation-sensitive syntax?
|
|
-- Probably not.
|
|
--, testCase "Parse multi-line function definition" $ do
|
|
-- let input = "f x y =\n t t"
|
|
-- let expected = SFunc "f" ["x", "y"] (TStem TLeaf)
|
|
-- parseSapling input @?= expected
|
|
|
|
, testCase "Parse nested parentheses in function body" $ do
|
|
let input = "f = t (t (t t))"
|
|
let expected = SFunc "f" [] (TStem (TStem (TStem TLeaf)))
|
|
parseSapling input @?= expected
|
|
]
|
|
|
|
integrationTests :: TestTree
|
|
integrationTests = testGroup "Integration Tests"
|
|
[ testCase "Combine lexer and parser" $ do
|
|
let input = "x = t t t"
|
|
let expected = SFunc "x" [] (TFork TLeaf TLeaf)
|
|
parseSapling input @?= expected
|
|
|
|
, testCase "Complex Tree Calculus expression" $ do
|
|
let input = "t (t t t) t"
|
|
let expected = TFork (TFork TLeaf TLeaf) TLeaf
|
|
parseSapling input @?= expected
|
|
]
|
|
|
|
evaluationTests :: TestTree
|
|
evaluationTests = testGroup "Evaluation Tests"
|
|
[ testCase "Evaluate single Leaf" $ do
|
|
let input = "t"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= Leaf
|
|
|
|
, testCase "Evaluate single Stem" $ do
|
|
let input = "t t"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= Stem Leaf
|
|
|
|
, testCase "Evaluate single Fork" $ do
|
|
let input = "t t t"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= Fork Leaf Leaf
|
|
|
|
, testCase "Evaluate nested Fork and Stem" $ do
|
|
let input = "t (t t) t"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= Fork (Stem Leaf) Leaf
|
|
|
|
, testCase "Evaluate `not` function" $ do
|
|
let input = "t (t (t t) (t t t)) t)"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?=
|
|
Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf
|
|
|
|
, testCase "Environment updates with definitions" $ do
|
|
let input = "x = t\ny = x"
|
|
let env = evalMulti Map.empty (parseMulti input)
|
|
Map.lookup "x" env @?= Just Leaf
|
|
Map.lookup "y" env @?= Just Leaf
|
|
|
|
, testCase "Variable substitution" $ do
|
|
let input = "x = t t\ny = t x\ny"
|
|
let env = evalMulti Map.empty (parseMulti input)
|
|
(result env) @?= Stem (Stem Leaf)
|
|
|
|
, testCase "Multiline input evaluation" $ do
|
|
let input = "x = t\ny = t t\nx"
|
|
let env = evalMulti Map.empty (parseMulti input)
|
|
(result env) @?= Leaf
|
|
|
|
, testCase "Evaluate string literal" $ do
|
|
let input = "\"hello\""
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= toString "hello"
|
|
|
|
, testCase "Evaluate list literal" $ do
|
|
let input = "[t (t t)]"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= toList [Leaf, Stem Leaf]
|
|
|
|
, testCase "Evaluate empty list" $ do
|
|
let input = "[]"
|
|
let ast = parseSapling input
|
|
(result $ evalSapling Map.empty ast) @?= toList []
|
|
|
|
, testCase "Evaluate variable dependency chain" $ do
|
|
let input = "x = t\ny = t x\nz = t y\nz"
|
|
let env = evalMulti Map.empty (parseMulti input)
|
|
(result env) @?= (Stem (Stem Leaf))
|
|
|
|
, testCase "Evaluate redefinition of variables" $ do
|
|
let input = "x = t t\nx = t\nx"
|
|
let env = evalMulti Map.empty (parseMulti input)
|
|
(result env) @?= Leaf
|
|
]
|
|
|
|
propertyTests :: TestTree
|
|
propertyTests = testGroup "Property Tests"
|
|
[ testProperty "Lexing and parsing round-trip" $ \input ->
|
|
case runParser saplingLexer "" input of
|
|
Left _ -> property True
|
|
Right tokens -> case runParser parseExpression "" tokens of
|
|
Left _ -> property True
|
|
Right ast -> parseSapling input === ast
|
|
]
|
|
|