diff --git a/src/Eval.hs b/src/Eval.hs index 25ff5bc..07b7e85 100644 --- a/src/Eval.hs +++ b/src/Eval.hs @@ -2,121 +2,93 @@ module Eval where import Parser import Research -import Data.Set (Set) -import qualified Data.Set as Set -import Data.List (foldl') -import qualified Data.Map as Map -import Data.Map (Map) -evalSingle :: Map.Map String T -> SaplingAST -> Map.Map String T +import Data.Map (Map) +import qualified Data.Map as Map +import Data.List (foldl') +import qualified Data.Set as Set + +evalSingle :: Map String T -> SaplingAST -> Map String T evalSingle env term = case term of - SFunc name [] body -> - let result = evalAST env body - in Map.insert name result env - SApp func arg -> - let result = apply (evalAST env func) (evalAST env arg) - in Map.insert "__result" result env - SVar name -> case Map.lookup name env of - Just value -> Map.insert "__result" value env - Nothing -> error $ "Variable " ++ name ++ " not defined" - _ -> - let result = evalAST env term - in Map.insert "__result" result env + SFunc name [] body -> + let + lineNoLambda = eliminateLambda body + result = evalAST env lineNoLambda + in Map.insert name result env + SLambda _ body -> + let result = evalAST env body + in Map.insert "__result" result env + SApp func arg -> + let result = apply (evalAST env func) (evalAST env arg) + in Map.insert "__result" result env + SVar name -> + case Map.lookup name env of + Just value -> Map.insert "__result" value env + Nothing -> error $ "Variable " ++ name ++ " not defined" + _ -> + let result = evalAST env term + in Map.insert "__result" result env evalSapling :: Map String T -> [SaplingAST] -> Map String T evalSapling env [] = env evalSapling env [lastLine] = - let - lastLineNoLambda = eliminateLambda lastLine + let lastLineNoLambda = eliminateLambda lastLine updatedEnv = evalSingle env lastLineNoLambda in Map.insert "__result" (result updatedEnv) updatedEnv evalSapling env (line:rest) = - let - lineNoLambda = eliminateLambda line + let lineNoLambda = eliminateLambda line updatedEnv = evalSingle env lineNoLambda in evalSapling updatedEnv rest evalAST :: Map String T -> SaplingAST -> T evalAST env term = case term of - SVar name -> - case Map.lookup name env of - Just value -> value - Nothing -> error $ "Variable " ++ name ++ " not defined" + SVar name -> case Map.lookup name env of + Just value -> value + Nothing -> error $ "Variable " ++ name ++ " not defined" TLeaf -> Leaf - TStem t -> - Stem (evalAST env t) - TFork t1 t2 -> - Fork (evalAST env t1) (evalAST env t2) - SApp t1 t2 -> - apply (evalAST env t1) (evalAST env t2) + TStem t -> Stem (evalAST env t) + TFork t1 t2 -> Fork (evalAST env t1) (evalAST env t2) + SApp t1 t2 -> apply (evalAST env t1) (evalAST env t2) SStr str -> toString str SInt num -> toNumber num SList elems -> toList (map (evalAST Map.empty) elems) SFunc name args body -> error $ "Unexpected function definition " ++ name ++ " in evalAST; define via evalSingle." - SLambda {} -> - error "Internal error: SLambda found in evalAST after elimination." - -result :: Map String T -> T -result r = case Map.lookup "__result" r of - Just a -> a - Nothing -> error "No __result field found in provided environment" - + SLambda {} -> error "Internal error: SLambda found in evalAST after elimination." eliminateLambda :: SaplingAST -> SaplingAST eliminateLambda (SLambda (v:vs) body) - | null vs = lambdaToT v (eliminateLambda body) - | otherwise = - eliminateLambda (SLambda [v] (SLambda vs body)) -eliminateLambda (SApp f arg) = - SApp (eliminateLambda f) (eliminateLambda arg) -eliminateLambda (TStem t) = - TStem (eliminateLambda t) -eliminateLambda (TFork l r) = - TFork (eliminateLambda l) (eliminateLambda r) -eliminateLambda (SList xs) = - SList (map eliminateLambda xs) -eliminateLambda (SFunc n vs b) = - SFunc n vs (eliminateLambda b) + | null vs = lambdaToT v (eliminateLambda body) + | otherwise = eliminateLambda (SLambda [v] (SLambda vs body)) +eliminateLambda (SApp f arg) = SApp (eliminateLambda f) (eliminateLambda arg) +eliminateLambda (TStem t) = TStem (eliminateLambda t) +eliminateLambda (TFork l r) = TFork (eliminateLambda l) (eliminateLambda r) +eliminateLambda (SList xs) = SList (map eliminateLambda xs) eliminateLambda other = other lambdaToT :: String -> SaplingAST -> SaplingAST lambdaToT x (SVar y) - | x == y = tI + | x == y = tI lambdaToT x (SVar y) - | x /= y = - SApp tK (SVar y) + | x /= y = SApp tK (SVar y) lambdaToT x t - | not (isFree x t) = - SApp tK t + | not (isFree x t) = SApp tK t lambdaToT x (SApp n u) - | not (isFree x (SApp n u)) = - SApp tK (SApp (eliminateLambda n) (eliminateLambda u)) -lambdaToT x (SApp n u) = - SApp - (SApp tS (lambdaToT x (eliminateLambda n))) - (lambdaToT x (eliminateLambda u)) -lambdaToT x (SApp f args) = lambdaToT x f + | not (isFree x (SApp n u)) = SApp tK (SApp (eliminateLambda n) (eliminateLambda u)) +lambdaToT x (SApp n u) = SApp (SApp tS (lambdaToT x (eliminateLambda n))) (lambdaToT x (eliminateLambda u)) lambdaToT x body - | not (isFree x body) = - SApp tK body - | otherwise = - SApp - (SApp tS (lambdaToT x body)) - tLeaf + | not (isFree x body) = SApp tK body + | otherwise = SApp (SApp tS (lambdaToT x body)) TLeaf -tLeaf :: SaplingAST -tLeaf = TLeaf - -freeVars :: SaplingAST -> Set String +freeVars :: SaplingAST -> Set.Set String freeVars (SVar v) = Set.singleton v freeVars (SInt _) = Set.empty freeVars (SStr _) = Set.empty freeVars (SList xs) = foldMap freeVars xs -freeVars (SFunc _ _ b) = freeVars b freeVars (SApp f arg) = freeVars f <> freeVars arg freeVars TLeaf = Set.empty +freeVars (SFunc _ _ b) = freeVars b freeVars (TStem t) = freeVars t freeVars (TFork l r) = freeVars l <> freeVars r freeVars (SLambda vs b) = foldr Set.delete (freeVars b) vs @@ -130,11 +102,15 @@ toAST (Stem a) = TStem (toAST a) toAST (Fork a b) = TFork (toAST a) (toAST b) tI :: SaplingAST -tI = toAST _I +tI = SApp (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))) TLeaf tK :: SaplingAST -tK = toAST _K +tK = SApp TLeaf TLeaf tS :: SaplingAST -tS = toAST _S +tS = SApp (SApp TLeaf (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf))) TLeaf +result :: Map String T -> T +result r = case Map.lookup "__result" r of + Just a -> a + Nothing -> error "No __result field found in provided environment" diff --git a/src/Lexer.hs b/src/Lexer.hs index 1941a87..19edb05 100644 --- a/src/Lexer.hs +++ b/src/Lexer.hs @@ -7,6 +7,7 @@ import Data.Void import qualified Data.Set as Set type Lexer = Parsec Void String + data LToken = LKeywordT | LIdentifier String @@ -44,7 +45,7 @@ stringLiteral = do if null content then fail "Empty string literals are not allowed" else do - char '"' -- " + char '"' return (LStringLiteral content) assign :: Lexer LToken @@ -92,5 +93,5 @@ saplingLexer = many (sc *> choice lexSapling :: String -> [LToken] lexSapling input = case runParser saplingLexer "" input of - Left err -> error $ "Lexical error:\n" ++ errorBundlePretty err + Left err -> error $ "Lexical error:\n" ++ errorBundlePretty err Right tokens -> tokens diff --git a/src/Main.hs b/src/Main.hs index 7e3bb43..dd6cb51 100644 --- a/src/Main.hs +++ b/src/Main.hs @@ -11,3 +11,6 @@ import Text.Megaparsec (runParser) main :: IO () main = repl Map.empty --(Map.fromList [("__result", Leaf)]) + +runSapling :: String -> String +runSapling s = show $ result (evalSapling Map.empty $ parseSapling s) diff --git a/src/Parser.hs b/src/Parser.hs index 5635242..2f594b3 100644 --- a/src/Parser.hs +++ b/src/Parser.hs @@ -1,10 +1,8 @@ module Parser where import Debug.Trace - import Lexer import Research hiding (toList) - import Control.Exception (throw) import Data.List.NonEmpty (toList) import qualified Data.Set as Set @@ -14,17 +12,18 @@ import Text.Megaparsec.Char import Text.Megaparsec.Error (errorBundlePretty, ParseErrorBundle) type Parser = Parsec Void [LToken] + data SaplingAST - = SVar String - | SInt Int - | SStr String - | SList [SaplingAST] - | SFunc String [String] SaplingAST - | SApp SaplingAST SaplingAST + = SVar String + | SInt Int + | SStr String + | SList [SaplingAST] + | SFunc String [String] SaplingAST + | SApp SaplingAST SaplingAST | TLeaf - | TStem SaplingAST - | TFork SaplingAST SaplingAST - | SLambda [String] SaplingAST + | TStem SaplingAST + | TFork SaplingAST SaplingAST + | SLambda [String] SaplingAST deriving (Show, Eq, Ord) parseSapling :: String -> [SaplingAST] @@ -35,7 +34,7 @@ parseSapling input = parseSingle :: String -> SaplingAST parseSingle "" = error "Empty input provided to parseSingle" parseSingle input = case runParser parseExpression "" (lexSapling input) of - Left err -> error $ handleParseError err + Left err -> error $ handleParseError err Right ast -> ast scnParser :: Parser () @@ -45,6 +44,7 @@ parseExpression :: Parser SaplingAST parseExpression = choice [ try parseFunction , try parseLambda + , try parseLambdaExpression , try parseListLiteral , try parseApplication , try parseTreeTerm @@ -59,6 +59,19 @@ parseFunction = do body <- parseExpression return (SFunc name (map getIdentifier args) body) +parseAtomicBase :: Parser SaplingAST +parseAtomicBase = choice + [ try parseVarWithoutAssignment + , parseTreeLeaf + , parseGrouped + ] +parseVarWithoutAssignment :: Parser SaplingAST +parseVarWithoutAssignment = do + LIdentifier name <- satisfy isIdentifier + if (name == "t" || name == "__result") + then fail $ "Reserved keyword: " ++ name ++ " cannot be assigned." + else notFollowedBy (satisfy (== LAssign)) *> return (SVar name) + parseLambda :: Parser SaplingAST parseLambda = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) $ do satisfy (== LBackslash) @@ -81,6 +94,7 @@ parseAtomicLambda = choice , parseTreeLeaf , parseLiteral , parseListLiteral + , try parseLambda , between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseLambdaExpression ] @@ -92,22 +106,15 @@ parseApplication = do parseLambdaApplication :: Parser SaplingAST parseLambdaApplication = do - func <- parseAtomicLambda - args <- many parseAtomicLambda - return $ foldl (\acc arg -> SApp acc arg) func args + func <- parseAtomicLambda + args <- many parseAtomicLambda + return $ foldl (\acc arg -> SApp acc arg) func args isTreeTerm :: SaplingAST -> Bool -isTreeTerm TLeaf = True -isTreeTerm (TStem _) = True +isTreeTerm TLeaf = True +isTreeTerm (TStem _) = True isTreeTerm (TFork _ _) = True -isTreeTerm _ = False - -parseAtomicBase :: Parser SaplingAST -parseAtomicBase = choice - [ parseVar - , parseTreeLeaf - , parseGrouped - ] +isTreeTerm _ = False parseTreeLeaf :: Parser SaplingAST parseTreeLeaf = satisfy isKeywordT *> notFollowedBy (satisfy (== LAssign)) *> pure TLeaf @@ -123,8 +130,8 @@ parseTreeTerm = do pure $ foldl combine base rest where combine acc next = case acc of - TLeaf -> TStem next - TStem t -> TFork t next + TLeaf -> TStem next + TStem t -> TFork t next TFork _ _ -> TFork acc next parseTreeLeafOrParenthesized :: Parser SaplingAST @@ -147,7 +154,6 @@ parseAtomic = choice , parseLiteral ] - parseGrouped :: Parser SaplingAST parseGrouped = between (satisfy (== LOpenParen)) (satisfy (== LCloseParen)) parseExpression @@ -190,8 +196,8 @@ parseSingleItem = do token <- satisfy isListItem case token of LIdentifier name -> return (SVar name) - LKeywordT -> return TLeaf - _ -> fail "Unexpected token in list item" + LKeywordT -> return TLeaf + _ -> fail "Unexpected token in list item" isListItem :: LToken -> Bool isListItem (LIdentifier _) = True @@ -216,24 +222,19 @@ parseStrLiteral = do return (SStr value) -- Boolean Helpers -isKeywordT (LKeywordT) = True -isKeywordT _ = False - -isIdentifier (LIdentifier _) = True -isIdentifier _ = False - +isKeywordT (LKeywordT) = True +isKeywordT _ = False +isIdentifier (LIdentifier _) = True +isIdentifier _ = False isIntegerLiteral (LIntegerLiteral _) = True -isIntegerLiteral _ = False - -isStringLiteral (LStringLiteral _) = True -isStringLiteral _ = False - -isLiteral (LIntegerLiteral _) = True -isLiteral (LStringLiteral _) = True -isLiteral _ = False - -esNewline (LNewline) = True -isNewline _ = False +isIntegerLiteral _ = False +isStringLiteral (LStringLiteral _) = True +isStringLiteral _ = False +isLiteral (LIntegerLiteral _) = True +isLiteral (LStringLiteral _) = True +isLiteral _ = False +isNewline (LNewline) = True +isNewline _ = False -- Error Handling handleParseError :: ParseErrorBundle [LToken] Void -> String @@ -246,9 +247,10 @@ handleParseError bundle = showError :: ParseError [LToken] Void -> String showError (TrivialError offset (Just (Tokens tokenStream)) expected) = "Parse error at offset " ++ show offset ++ ": unexpected token " - ++ show tokenStream ++ ", expected one of " ++ show (Set.toList expected) + ++ show tokenStream ++ ", expected one of " ++ show (Set.toList expected) showError (FancyError offset fancy) = "Parse error at offset " ++ show offset ++ ":\n " ++ unlines (map show (Set.toList fancy)) showError (TrivialError offset Nothing expected) = "Parse error at offset " ++ show offset ++ ": expected one of " - ++ show (Set.toList expected) + ++ show (Set.toList expected) + diff --git a/test/Spec.hs b/test/Spec.hs index b7e6b67..8c1384c 100644 --- a/test/Spec.hs +++ b/test/Spec.hs @@ -5,237 +5,339 @@ 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) +import qualified Data.Map as Map +import qualified Data.Set as Set + main :: IO () main = defaultMain tests +runSapling :: String -> String +runSapling s = show $ result (evalSapling Map.empty $ parseSapling s) + tests :: TestTree tests = testGroup "Sapling Tests" - [ lexerTests - , parserTests - , integrationTests - , evaluationTests - , propertyTests - ] + [ lexerTests + , parserTests + , integrationTests + , evaluationTests + , propertyTests + , lambdaEvalTests + ] lexerTests :: TestTree lexerTests = testGroup "Lexer Tests" - [ testCase "Lex simple identifiers" $ do - let input = "x a b = a" - expect = Right [LIdentifier "x", LIdentifier "a", LIdentifier "b", LAssign, LIdentifier "a"] - runParser saplingLexer "" input @?= expect - , testCase "Lex Tree Calculus terms" $ do - let input = "t t t" - expect = Right [LKeywordT, LKeywordT, LKeywordT] - runParser saplingLexer "" input @?= expect - , testCase "Lex escaped characters in strings" $ do - let input = "\"hello\\nworld\"" - expect = Right [LStringLiteral "hello\\nworld"] - runParser saplingLexer "" input @?= expect - , testCase "Lex mixed literals" $ do - let input = "t \"string\" 42" - expect = Right [LKeywordT, LStringLiteral "string", LIntegerLiteral 42] - runParser saplingLexer "" input @?= expect - , testCase "Lex invalid token" $ do - let input = "$invalid" - case runParser saplingLexer "" input of - Left _ -> return () - Right _ -> assertFailure "Expected lexer to fail on invalid token" - , testCase "Drop trailing whitespace in definitions" $ do - let input = "x = 5 " - expect = [LIdentifier "x",LAssign,LIntegerLiteral 5] - case (runParser saplingLexer "" input) of - Left _ -> assertFailure "Failed to lex input" - Right i -> i @?= expect - , testCase "Error when using invalid characters in identifiers" $ do + [ testCase "Lex simple identifiers" $ do + let input = "x a b = a" + expect = Right [LIdentifier "x", LIdentifier "a", LIdentifier "b", LAssign, LIdentifier "a"] + runParser saplingLexer "" input @?= expect + , testCase "Lex Tree Calculus terms" $ do + let input = "t t t" + expect = Right [LKeywordT, LKeywordT, LKeywordT] + runParser saplingLexer "" input @?= expect + , testCase "Lex escaped characters in strings" $ do + let input = "\"hello\\nworld\"" + expect = Right [LStringLiteral "hello\\nworld"] + runParser saplingLexer "" input @?= expect + , testCase "Lex mixed literals" $ do + let input = "t \"string\" 42" + expect = Right [LKeywordT, LStringLiteral "string", LIntegerLiteral 42] + runParser saplingLexer "" input @?= expect + , testCase "Lex invalid token" $ do + let input = "$invalid" + case runParser saplingLexer "" input of + Left _ -> return () + Right _ -> assertFailure "Expected lexer to fail on invalid token" + , testCase "Drop trailing whitespace in definitions" $ do + let input = "x = 5 " + expect = [LIdentifier "x",LAssign,LIntegerLiteral 5] + case (runParser saplingLexer "" input) of + Left _ -> assertFailure "Failed to lex input" + Right i -> i @?= expect + , testCase "Error when using invalid characters in identifiers" $ do case (runParser saplingLexer "" "__result = 5") of - Left _ -> return () - Right _ -> assertFailure "Expected failure when trying to assign the value of __result" - ] + Left _ -> return () + Right _ -> assertFailure "Expected failure when trying to assign the value of __result" + ] parserTests :: TestTree parserTests = testGroup "Parser Tests" - [ testCase "Error when parsing incomplete definitions" $ do - let input = lexSapling "x = " - case (runParser parseExpression "" input) of - Left _ -> return () - Right _ -> assertFailure "Expected failure on invalid input" - , testCase "Error when assigning a value to T" $ do - let input = lexSapling "t = x" - case (runParser parseExpression "" input) of - Left _ -> return () - Right _ -> assertFailure "Expected failure when trying to assign the value of T" - , testCase "Error when parsing bodyless definitions with arguments" $ do - let input = lexSapling "x a b = " - case (runParser parseExpression "" input) of - Left _ -> return () - Right _ -> assertFailure "Expected failure on invalid input" - , testCase "Parse function definitions" $ do - let input = "x a b c = a" - let expect = SFunc "x" ["a","b","c"] (SVar "a") - parseSingle input @?= expect - , testCase "Parse nested Tree Calculus terms" $ do - let input = "t (t t) t" - let expect = SApp (SApp TLeaf (SApp TLeaf TLeaf)) TLeaf - parseSingle input @?= expect - , testCase "Parse sequential Tree Calculus terms" $ do - let input = "t t t" - let expect = SApp (SApp TLeaf TLeaf) TLeaf - parseSingle input @?= expect - , testCase "Parse mixed list literals" $ do - let input = "[t (\"hello\") t]" - let expect = SList [TLeaf, SStr "hello", TLeaf] - parseSingle input @?= expect - , testCase "Parse function with applications" $ do - let input = "f x = t x" - let expect = SFunc "f" ["x"] (SApp TLeaf (SVar "x")) - parseSingle input @?= expect - , testCase "Parse nested lists" $ do - let input = "[t [(t t)]]" - let expect = SList [TLeaf,SList [SApp TLeaf TLeaf]] - parseSingle input @?= expect - , testCase "Parse complex parentheses" $ do - let input = "t (t t (t t))" - let expect = SApp TLeaf (SApp (SApp TLeaf TLeaf) (SApp TLeaf TLeaf)) - parseSingle input @?= expect - , testCase "Parse empty list" $ do - let input = "[]" - let expect = SList [] - parseSingle input @?= expect - , testCase "Parse multiple nested lists" $ do - let input = "[[t t] [t (t t)]]" - let expect = SList [SList [TLeaf,TLeaf],SList [TLeaf,SApp TLeaf TLeaf]] - parseSingle input @?= expect - , testCase "Parse whitespace variance" $ do - let input1 = "[t t]" - let input2 = "[ t t ]" - let expect = SList [TLeaf, TLeaf] - parseSingle input1 @?= expect - parseSingle input2 @?= expect - , testCase "Parse string in list" $ do - let input = "[(\"hello\")]" - let expect = SList [SStr "hello"] - parseSingle input @?= expect - , testCase "Parse parentheses inside list" $ do - let input = "[t (t t)]" - let expect = SList [TLeaf,SApp TLeaf TLeaf] - parseSingle input @?= expect - , testCase "Parse nested parentheses in function body" $ do - let input = "f = t (t (t t))" - let expect = SFunc "f" [] (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf))) - parseSingle input @?= expect - , testCase "Parse lambda abstractions" $ do - let input = "(\\a : a)" - let expect = (SLambda ["a"] (SVar "a")) - parseSingle input @?= expect - , testCase "Parse multiple arguments to lambda abstractions" $ do - let input = "x = (\\a b : a)" - let expect = SFunc "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a"))) - parseSingle input @?= expect - , testCase "Grouping T terms with parentheses in function application" $ do - let input = "x = (\\a : a)\n" <> "x (t)" - expect = [SFunc "x" [] (SLambda ["a"] (SVar "a")),SApp (SVar "x") TLeaf] - parseSapling input @?= expect - ] + [ testCase "Error when parsing incomplete definitions" $ do + let input = lexSapling "x = " + case (runParser parseExpression "" input) of + Left _ -> return () + Right _ -> assertFailure "Expected failure on invalid input" + , testCase "Error when assigning a value to T" $ do + let input = lexSapling "t = x" + case (runParser parseExpression "" input) of + Left _ -> return () + Right _ -> assertFailure "Expected failure when trying to assign the value of T" + , testCase "Parse function definitions" $ do + let input = "x = (\\a b c : a)" + expect = SFunc "x" [] (SLambda ["a"] (SLambda ["b"] (SLambda ["c"] (SVar "a")))) + parseSingle input @?= expect + , testCase "Parse nested Tree Calculus terms" $ do + let input = "t (t t) t" + expect = SApp (SApp TLeaf (SApp TLeaf TLeaf)) TLeaf + parseSingle input @?= expect + , testCase "Parse sequential Tree Calculus terms" $ do + let input = "t t t" + expect = SApp (SApp TLeaf TLeaf) TLeaf + parseSingle input @?= expect + , testCase "Parse mixed list literals" $ do + let input = "[t (\"hello\") t]" + expect = SList [TLeaf, SStr "hello", TLeaf] + parseSingle input @?= expect + , testCase "Parse function with applications" $ do + let input = "f = (\\x : t x)" + expect = SFunc "f" [] (SLambda ["x"] (SApp TLeaf (SVar "x"))) + parseSingle input @?= expect + , testCase "Parse nested lists" $ do + let input = "[t [(t t)]]" + expect = SList [TLeaf,SList [SApp TLeaf TLeaf]] + parseSingle input @?= expect + , testCase "Parse complex parentheses" $ do + let input = "t (t t (t t))" + expect = SApp TLeaf (SApp (SApp TLeaf TLeaf) (SApp TLeaf TLeaf)) + parseSingle input @?= expect + , testCase "Parse empty list" $ do + let input = "[]" + expect = SList [] + parseSingle input @?= expect + , testCase "Parse multiple nested lists" $ do + let input = "[[t t] [t (t t)]]" + expect = SList [SList [TLeaf,TLeaf],SList [TLeaf,SApp TLeaf TLeaf]] + parseSingle input @?= expect + , testCase "Parse whitespace variance" $ do + let input1 = "[t t]" + let input2 = "[ t t ]" + expect = SList [TLeaf, TLeaf] + parseSingle input1 @?= expect + parseSingle input2 @?= expect + , testCase "Parse string in list" $ do + let input = "[(\"hello\")]" + expect = SList [SStr "hello"] + parseSingle input @?= expect + , testCase "Parse parentheses inside list" $ do + let input = "[t (t t)]" + expect = SList [TLeaf,SApp TLeaf TLeaf] + parseSingle input @?= expect + , testCase "Parse nested parentheses in function body" $ do + let input = "f = (\\x : t (t (t t)))" + expect = SFunc "f" [] (SLambda ["x"] (SApp TLeaf (SApp TLeaf (SApp TLeaf TLeaf)))) + parseSingle input @?= expect + , testCase "Parse lambda abstractions" $ do + let input = "(\\a : a)" + expect = (SLambda ["a"] (SVar "a")) + parseSingle input @?= expect + , testCase "Parse multiple arguments to lambda abstractions" $ do + let input = "x = (\\a b : a)" + expect = SFunc "x" [] (SLambda ["a"] (SLambda ["b"] (SVar "a"))) + parseSingle input @?= expect + , testCase "Grouping T terms with parentheses in function application" $ do + let input = "x = (\\a : a)\nx (t)" + expect = [SFunc "x" [] (SLambda ["a"] (SVar "a")),SApp (SVar "x") TLeaf] + parseSapling input @?= expect + ] integrationTests :: TestTree integrationTests = testGroup "Integration Tests" - [ testCase "Combine lexer and parser" $ do - let input = "x = t t t" - let expect = SFunc "x" [] (SApp (SApp TLeaf TLeaf) TLeaf) - parseSingle input @?= expect - , testCase "Complex Tree Calculus expression" $ do - let input = "t (t t t) t" - let expect = SApp (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf)) TLeaf - parseSingle input @?= expect - ] + [ testCase "Combine lexer and parser" $ do + let input = "x = t t t" + expect = SApp (SVar "x") (SApp (SApp TLeaf TLeaf) TLeaf) + parseSingle input @?= expect + , testCase "Complex Tree Calculus expression" $ do + let input = "t (t t t) t" + expect = SApp (SApp TLeaf (SApp (SApp TLeaf TLeaf) TLeaf)) TLeaf + parseSingle input @?= expect + ] evaluationTests :: TestTree evaluationTests = testGroup "Evaluation Tests" - [ testCase "Evaluate single Leaf" $ do - let input = "t" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= Leaf - , testCase "Evaluate single Stem" $ do - let input = "t t" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= Stem Leaf - , testCase "Evaluate single Fork" $ do - let input = "t t t" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= Fork Leaf Leaf - , testCase "Evaluate nested Fork and Stem" $ do - let input = "t (t t) t" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= Fork (Stem Leaf) Leaf - , testCase "Evaluate `not` function" $ do - let input = "t (t (t t) (t t t)) t" - let ast = parseSingle input - (result $ evalSingle 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 = evalSapling Map.empty (parseSapling 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 = evalSapling Map.empty (parseSapling input) - (result env) @?= Stem (Stem Leaf) - , testCase "Multiline input evaluation" $ do - let input = "x = t\ny = t t\nx" - let env = evalSapling Map.empty (parseSapling input) - (result env) @?= Leaf - , testCase "Evaluate string literal" $ do - let input = "\"hello\"" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= toString "hello" - , testCase "Evaluate list literal" $ do - let input = "[t (t t)]" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= toList [Leaf, Stem Leaf] - , testCase "Evaluate empty list" $ do - let input = "[]" - let ast = parseSingle input - (result $ evalSingle Map.empty ast) @?= toList [] - , testCase "Evaluate variable dependency chain" $ do - let input = "x = t (t t)\n \ - \ y = x\n \ - \ z = y\n \ - \ variablewithamuchlongername = z\n \ - \ variablewithamuchlongername" - let env = evalSapling Map.empty (parseSapling input) - (result env) @?= (Stem (Stem Leaf)) - , testCase "Evaluate variable shadowing" $ do - let input = "x = t t\nx = t\nx" - let env = evalSapling Map.empty (parseSapling input) - (result env) @?= Leaf - , testCase "Lambda identity" $ do - let input = "(\\a : a)" - env = evalSapling Map.empty (parseSapling input) - result env @?= Fork (Stem (Stem Leaf)) (Stem Leaf) + [ testCase "Evaluate single Leaf" $ do + let input = "t" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= Leaf + , testCase "Evaluate single Stem" $ do + let input = "t t" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= Stem Leaf + , testCase "Evaluate single Fork" $ do + let input = "t t t" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= Fork Leaf Leaf + , testCase "Evaluate nested Fork and Stem" $ do + let input = "t (t t) t" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= Fork (Stem Leaf) Leaf + , testCase "Evaluate `not` function" $ do + let input = "t (t (t t) (t t t)) t" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= + Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf + , testCase "Environment updates with definitions" $ do + let input = "x = t\ny = x" + env = evalSapling Map.empty (parseSapling 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" + env = evalSapling Map.empty (parseSapling input) + (result env) @?= Stem (Stem Leaf) + , testCase "Multiline input evaluation" $ do + let input = "x = t\ny = t t\nx" + env = evalSapling Map.empty (parseSapling input) + (result env) @?= Leaf + , testCase "Evaluate string literal" $ do + let input = "\"hello\"" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= toString "hello" + , testCase "Evaluate list literal" $ do + let input = "[t (t t)]" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= toList [Leaf, Stem Leaf] + , testCase "Evaluate empty list" $ do + let input = "[]" + let ast = parseSingle input + (result $ evalSingle Map.empty ast) @?= toList [] + , testCase "Evaluate variable dependency chain" $ do + let input = "x = t (t t)\n \ + \ y = x\n \ + \ z = y\n \ + \ variablewithamuchlongername = z\n \ + \ variablewithamuchlongername" + env = evalSapling Map.empty (parseSapling input) + (result env) @?= (Stem (Stem Leaf)) + , testCase "Evaluate variable shadowing" $ do + let input = "x = t t\nx = t\nx" + env = evalSapling Map.empty (parseSapling input) + (result env) @?= Leaf , testCase "Apply identity to Boolean Not" $ do - let not = "(t (t (t t) (t t t)) t)" - input = "x = (\\a : a)\nx " ++ not - env = evalSapling Map.empty (parseSapling input) - result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf + let not = "(t (t (t t) (t t t)) t)" + let input = "x = (\\a : a)\nx " ++ not + env = evalSapling Map.empty (parseSapling input) + result env @?= Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) Leaf , testCase "Constant function matches" $ do - let input = "k = (\\a b : a)\nk (t t) t" - env = evalSapling Map.empty (parseSapling input) - result env @?= Stem Leaf - ] + let input = "k = (\\a b : a)\nk (t t) t" + env = evalSapling Map.empty (parseSapling input) + result env @?= Stem Leaf + , testCase "Boolean AND_ TF" $ do + let input = "and (t t) (t)" + env = evalSapling boolEnv (parseSapling input) + result env @?= Leaf + , testCase "Boolean AND_ FT" $ do + let input = "and (t) (t t)" + env = evalSapling boolEnv (parseSapling input) + result env @?= Leaf + , testCase "Boolean AND_ FF" $ do + let input = "and (t) (t)" + env = evalSapling boolEnv (parseSapling input) + result env @?= Leaf + , testCase "Boolean AND_ TT" $ do + let input = "and (t t) (t t)" + env = evalSapling boolEnv (parseSapling input) + result env @?= Stem Leaf + , testCase "Verifying Equality" $ do + let input = "equal (t t t) (t t t)" + env = evalSapling boolEnv (parseSapling input) + result env @?= Stem Leaf + ] + where + boolEnv = evalSapling Map.empty $ parseSapling + "false = t\n \ + \ true = t t\n \ + \ falseL = (\\z : false)\n \ + \ id = (\\a : a)\n \ + \ triage = (\\a b c : (t (t a b) c))\n \ + \ match_bool = (\\ot of : triage of (\\z : ot) t)\n \ + \ and = match_bool id falseL\n \ + \ fix = (\\m wait f : wait m (\\x : f (wait m x))) (\\x : x x) (\\a b c : (t (t a) (t t c) b))\n \ + \ equal = fix ((\\self : triage (triage true (\\z : false) (\\z x : false)) (\\ax : triage false (self ax) (\\z x : false)) (\\ax ay : triage false (\\z : false) (\\bx by : and (self ax bx) (self ay by)))))\ + \ " + 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 -> parseSingle input === ast - ] + [ 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 -> parseSingle input === ast + ] + +lambdaEvalTests :: TestTree +lambdaEvalTests = testGroup "Lambda Evaluation Tests" + [ testCase "Lambda Identity Function" $ do + let input = "id = (\\x : x)\nid t" + runSapling input @?= "Leaf" + + , testCase "Lambda Constant Function (K combinator)" $ do + let input = "k = (\\x y : x)\nk t (t t)" + runSapling input @?= "Leaf" + + , testCase "Lambda Application with Variable" $ do + let input = "id = (\\x : x)\nval = t t\nid val" + runSapling input @?= "Stem Leaf" + + , testCase "Lambda Application with Multiple Arguments" $ do + let input = "apply = (\\f x y : f x y)\nk = (\\a b : a)\napply k t (t t)" + runSapling input @?= "Leaf" + + , testCase "Nested Lambda Application" $ do + let input = "apply = (\\f x y : f x y)\nid = (\\x : x)\napply (\\f x : f x) id t" + runSapling input @?= "Leaf" + + , testCase "Lambda with a complex body" $ do + let input = "f = (\\x : t (t x))\nf t" + runSapling input @?= "Stem (Stem Leaf)" + + , testCase "Lambda returning a function" $ do + let input = "f = (\\x : (\\y : x))\ng = f t\ng (t t)" + runSapling input @?= "Leaf" + + , testCase "Lambda with Shadowing" $ do + let input = "f = (\\x : (\\x : x))\nf t (t t)" + runSapling input @?= "Stem Leaf" + + , testCase "Lambda returning another lambda" $ do + let input = "k = (\\x : (\\y : x))\nk_app = k t\nk_app (t t)" + runSapling input @?= "Leaf" + + , testCase "Lambda with free variables" $ do + let input = "y = t t\nf = (\\x : y)\nf t" + runSapling input @?= "Stem Leaf" + + , testCase "SKI Composition" $ do + let input = "s = (\\x y z : x z (y z))\nk = (\\x y : x)\ni = (\\x : x)\ncomp = s k i\ncomp t (t t)" + runSapling input @?= "Leaf" + , testCase "Lambda with multiple parameters and application" $ do + let input = "f = (\\a b c : t a b c)\nf t (t t) (t t t)" + runSapling input @?= "Fork (Fork Leaf Leaf) Leaf" + + , testCase "Lambda with nested application in the body" $ do + let input = "f = (\\x : t (t (t x)))\nf t" + runSapling input @?= "Stem (Stem (Stem Leaf))" + , testCase "Lambda returning a function and applying it" $ do + let input = "f = (\\x : (\\y : t x y))\ng = f t\ng (t t)" + runSapling input @?= "Fork Leaf (Stem Leaf)" + , testCase "Lambda applying a variable" $ do + let input = "id = (\\x : x)\na = t t\nid a" + runSapling input @?= "Stem Leaf" + , testCase "Multiple lambda abstractions in the same expression" $ do + let input = "f = (\\x : (\\y : x y))\ng = (\\z : z)\nf g t" + runSapling input @?= "Stem Leaf" + , testCase "Lambda with a string literal" $ do + let input = "f = (\\x : x)\nf \"hello\"" + runSapling input @?= "Fork (Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) (Fork (Stem Leaf) (Fork Leaf Leaf))) (Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) (Fork (Stem Leaf) (Fork Leaf Leaf)))" + , testCase "Lambda with an integer literal" $ do + let input = "f = (\\x : x)\nf 42" + runSapling input @?= "Fork (Leaf) (Fork (Stem Leaf) (Fork Leaf Leaf))" + , testCase "Lambda with a list literal" $ do + let input = "f = (\\x : x)\nf [t (t t)]" + runSapling input @?= "Fork Leaf (Fork (Stem Leaf) Leaf)" + ]