packages feed

tala-compiler-0.1.0: test/PatternTests.hs

-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1

module PatternTests (patternTests) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Compiler
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.CorePrep
import Progmasoft.Tala.Core.CorePrep.Wire
import Progmasoft.Tala.Core.Verifier
import Progmasoft.Tala.Core.Wire
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.Lexer
import Progmasoft.Tala.Parser

-- Pattern coverage is deliberately organized by compiler boundary. Successful
-- parsing alone would not catch a lost annotation, repeated subject evaluation,
-- a broken Core wire tag, or a CorePrep operation that never reaches native code.
patternTests :: [(String, Bool)]
patternTests =
    [ ("wildcard pattern reaches typed Core", wildcardLowersToTrue)
    , ("literal pattern reaches equality Core", literalLowersToEquality)
    , ("all relational pattern spellings compile", relationalSpellingsCompile)
    , ("pattern and binds more tightly than or", combinatorPrecedence)
    , ("pattern not negates only its nested pattern", notPatternShape)
    , ("null pattern accepts a reference subject", compilesReference "value is null")
    , ("null pattern rejects a numeric subject", rejectedWith "TAT0020" (numericSource "value is null"))
    , ("type pattern accepts reference-to-reference tests", compilesReference "value is Payload")
    , ("type pattern rejects a numeric target", rejectedWith "TAT0022" (numericSource "value is String"))
    , ("literal pattern rejects an incompatible subject", rejectedWith "TAT0021" (referenceSource "value is 1"))
    , ("relational pattern rejects an incompatible subject", rejectedWith "TAT0023" (referenceSource "value is > 1"))
    , ("pattern subject call appears exactly once in Core", effectfulSubjectEvaluatedOnce)
    , ("pattern-local symbol is reused by every test", patternSubjectIsShared)
    , ("pattern Core remains verifier-valid", patternCoreVerifies)
    , ("Core wire round-trips pattern sequencing", patternCoreWireRoundTrip)
    , ("CorePrep wire round-trips pattern control flow", patternCorePrepWireRoundTrip)
    , ("pattern compilation reaches nonempty CorePrep", patternReachesCorePrep)
    ]

compileSource :: String -> Either [Diagnostic] FrontendArtifacts
compileSource source = compileToCorePrep (CompilerInput "pattern-test.tala" source)

numericSource :: String -> String
numericSource expression =
    "class Program { bool Match(_ int value) { return " ++ expression ++ "; } }"

referenceSource :: String -> String
referenceSource expression =
    unlines
        [ "class Payload {}"
        , "class Program { bool Match(_ Payload value) { return " ++ expression ++ "; } }"
        ]

compilesReference :: String -> Bool
compilesReference expression = accepted (compileSource (referenceSource expression))

accepted :: Either problems value -> Bool
accepted result = case result of
    Right _ -> True
    Left _ -> False

rejectedWith :: String -> String -> Bool
rejectedWith code source = case compileSource source of
    Left diagnostics -> any ((== code) . diagnosticCode) diagnostics
    Right _ -> False

singleReturn :: String -> Maybe CoreExpression
singleReturn source = do
    artifacts <- either (const Nothing) Just (compileSource source)
    function <- case coreModuleFunctions (artifactCore artifacts) of
        [value] -> Just value
        values -> case reverse values of
            value : _ -> Just value
            [] -> Nothing
    case coreFunctionBody function of
        [CoreReturn expression] -> Just expression
        _ -> Nothing

wildcardLowersToTrue :: Bool
wildcardLowersToTrue = case singleReturn (numericSource "value is _") of
    Just (CoreLet _ intBinding (CoreVariable _ intSubject) (CoreLiteral (CoreBoolean True) resultType) letType) ->
        intBinding == intType && intSubject == intType && resultType == boolType && letType == boolType
    _ -> False

literalLowersToEquality :: Bool
literalLowersToEquality = case singleReturn (numericSource "value is 42") of
    Just
        (CoreLet subject _ _ (CorePrimitive CoreEqual [CoreVariable used _, CoreLiteral (CoreInteger 42) _] result) letType) ->
            resolvedSymbol subject == resolvedSymbol used && result == boolType && letType == boolType
    _ -> False

relationalSpellingsCompile :: Bool
relationalSpellingsCompile =
    all
        (accepted . compileSource . numericSource . ("value is " ++))
        ["< 1", "<= 1", "> 1", ">= 1", "== 1", "\\= 1"]

combinatorPrecedence :: Bool
combinatorPrecedence = case parsePatternSource (referenceSource "value is null or Payload and not null") of
    Just (OrPattern _ _ (AndPattern _ _ _ _) _) -> True
    _ -> False

parsePatternSource :: String -> Maybe (Pattern Identifier ())
parsePatternSource source = do
    tokens <- either (const Nothing) Just (runLexer defaultLexer (LexerInput "pattern-test.tala" source))
    ParsedAST tree <- either (const Nothing) Just (runParser defaultParser (ParserInput "pattern-test.tala" tokens))
    expression <- case [ value
                       | TypeDeclaration {typeMembers = members} <- syntaxDeclarations tree
                       , FunctionDeclaration {declarationName = Identifier "Match", declarationBody = Block statements} <- members
                       , ReturnStatement _ (Just value) <- statements
                       ] of
        [value] -> Just value
        _ -> Nothing
    case expression of
        IsPatternExpression _ _ patternValue _ -> Just patternValue
        _ -> Nothing

notPatternShape :: Bool
notPatternShape = case singleReturn (numericSource "value is not > 0") of
    Just (CoreLet _ _ _ (CorePrimitive CoreLogicalNot [CorePrimitive CoreGreaterThan _ _] result) letType) ->
        result == boolType && letType == boolType
    _ -> False

effectfulSource :: String
effectfulSource =
    unlines
        [ "class Program {"
        , "  String Source() { return \"value\"; }"
        , "  bool Match() { return Source() is not null and String; }"
        , "}"
        ]

effectfulSubjectEvaluatedOnce :: Bool
effectfulSubjectEvaluatedOnce = case singleReturn effectfulSource of
    Just expression@CoreLet {} -> countCalls expression == 1
    _ -> False

patternSubjectIsShared :: Bool
patternSubjectIsShared = case singleReturn effectfulSource of
    Just (CoreLet subject _ _ body _) ->
        let uses = expressionSymbols body
         in length uses >= 2 && all (== resolvedSymbol subject) uses
    _ -> False

countCalls :: CoreExpression -> Int
countCalls expression = case expression of
    CoreVariable {} -> 0
    CoreLiteral {} -> 0
    CoreApply callee arguments _ -> 1 + countCalls callee + sum (map countCalls arguments)
    CorePrimitive _ arguments _ -> sum (map countCalls arguments)
    CoreLet _ _ value body _ -> countCalls value + countCalls body
    CoreConditional condition whenTrue whenFalse _ -> sum (map countCalls [condition, whenTrue, whenFalse])
    CoreClosure captures _ _ body _ ->
        sum (map (countCalls . coreCaptureValue) captures) + sum (map statementCalls body)

statementCalls :: CoreStatement -> Int
statementCalls statement = case statement of
    CoreBind binding -> countCalls (coreBindingValue binding)
    CoreAssign _ value -> countCalls value
    CoreReturn value -> countCalls value
    CoreIf condition whenTrue whenFalse ->
        countCalls condition + sum (map statementCalls whenTrue) + sum (map statementCalls whenFalse)
    CoreEvaluate value -> countCalls value
    CoreWhile condition body -> countCalls condition + sum (map statementCalls body)
    CoreDoWhile body condition -> sum (map statementCalls body) + countCalls condition
    CoreFor condition updates body ->
        countCalls condition + sum (map statementCalls updates) + sum (map statementCalls body)
    CoreBreak -> 0
    CoreContinue -> 0

expressionSymbols :: CoreExpression -> [SymbolId]
expressionSymbols expression = case expression of
    CoreVariable name _ -> [resolvedSymbol name]
    CoreLiteral {} -> []
    CoreApply callee arguments _ -> expressionSymbols callee ++ concatMap expressionSymbols arguments
    CorePrimitive _ arguments _ -> concatMap expressionSymbols arguments
    CoreLet _ _ value body _ -> expressionSymbols value ++ expressionSymbols body
    CoreConditional condition whenTrue whenFalse _ -> concatMap expressionSymbols [condition, whenTrue, whenFalse]
    CoreClosure captures _ _ body _ ->
        concatMap (expressionSymbols . coreCaptureValue) captures ++ concatMap statementSymbols body

statementSymbols :: CoreStatement -> [SymbolId]
statementSymbols statement = case statement of
    CoreBind binding -> expressionSymbols (coreBindingValue binding)
    CoreAssign _ value -> expressionSymbols value
    CoreReturn value -> expressionSymbols value
    CoreIf condition whenTrue whenFalse ->
        expressionSymbols condition ++ concatMap statementSymbols whenTrue ++ concatMap statementSymbols whenFalse
    CoreEvaluate value -> expressionSymbols value
    CoreWhile condition body -> expressionSymbols condition ++ concatMap statementSymbols body
    CoreDoWhile body condition -> concatMap statementSymbols body ++ expressionSymbols condition
    CoreFor condition updates body ->
        expressionSymbols condition ++ concatMap statementSymbols updates ++ concatMap statementSymbols body
    CoreBreak -> []
    CoreContinue -> []

patternArtifacts :: Maybe FrontendArtifacts
patternArtifacts = either (const Nothing) Just (compileSource effectfulSource)

patternCoreVerifies :: Bool
patternCoreVerifies = case patternArtifacts of
    Just artifacts -> verifyCore (artifactCore artifacts) == Right (artifactCore artifacts)
    Nothing -> False

patternCoreWireRoundTrip :: Bool
patternCoreWireRoundTrip = case patternArtifacts of
    Just artifacts ->
        case encodeCore defaultCoreWireLimits (artifactCore artifacts) >>= decodeCore defaultCoreWireLimits of
            Right decoded -> decoded == artifactCore artifacts
            Left _ -> False
    Nothing -> False

patternCorePrepWireRoundTrip :: Bool
patternCorePrepWireRoundTrip = case patternArtifacts of
    Just artifacts -> case encodeCorePrep (artifactCorePrep artifacts) >>= decodeCorePrep of
        Right decoded -> decoded == artifactCorePrep artifacts
        Left _ -> False
    Nothing -> False

patternReachesCorePrep :: Bool
patternReachesCorePrep = case patternArtifacts of
    Just artifacts -> not (null (corePrepModuleFunctions (artifactCorePrep artifacts)))
    Nothing -> False