packages feed

tala-compiler-0.1.0: test/SourceTextTests.hs

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

module SourceTextTests (sourceTextTests) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Diagnostic
import Progmasoft.Tala.SourceText

sourceTextTests :: [(String, Bool)]
sourceTextTests =
    [ ("lossless source fragments reconstruct mixed source", reconstructsMixedSource)
    , ("source fragments classify every protected spelling", classifiesProtectedSpelling)
    , ("source fragment spans cross CRLF exactly once", spansTrackCrLf)
    , ("source mask hides structural characters in protected text", masksProtectedBraces)
    , ("source mask retains physical line endings", maskRetainsLineEndings)
    , ("multi-line raw strings protect every crossed line", rawStringProtectedLines)
    , ("multi-line long comments protect every crossed line", longCommentProtectedLines)
    , ("single-line protected fragments do not protect indentation", singleLineFragmentsRemainIndentable)
    , ("an attached double dash begins a line comment", attachedDoubleDashIsComment)
    , ("a double dash after a literal begins a line comment", literalDoubleDashIsComment)
    , ("spaced double dash begins a line comment", spacedDoubleDashIsComment)
    , ("unterminated normal strings have a lossless-scan diagnostic", rejectsUnterminatedString)
    , ("unterminated character literals have a lossless-scan diagnostic", rejectsUnterminatedCharacter)
    , ("unterminated raw strings have a lossless-scan diagnostic", rejectsUnterminatedRawString)
    , ("unterminated long comments have a lossless-scan diagnostic", rejectsUnterminatedLongComment)
    ]

mixedSource :: String
mixedSource =
    "namespace Demo;\r\n"
        ++ "-- ordinary { comment }\n"
        ++ "class Program {\n"
        ++ "  String text = \"escaped \\\" }\";\n"
        ++ "  char marker = '}';\n"
        ++ "  auto raw = [=[\n{ untouched }\n]=];\n"
        ++ "  --[==[ long { comment ]=] still open ]==]\n"
        ++ "}\n"

fragmentsOf :: String -> Either [Diagnostic] [SourceFragment]
fragmentsOf = scanSourceFragments "source-text.tala"

reconstructsMixedSource :: Bool
reconstructsMixedSource = (reconstructSource <$> fragmentsOf mixedSource) == Right mixedSource

classifiesProtectedSpelling :: Bool
classifiesProtectedSpelling = case fragmentsOf mixedSource of
    Right fragments ->
        all
            (`elem` map sourceFragmentKind fragments)
            [ CodeFragment
            , LineCommentFragment
            , LongCommentFragment
            , StringLiteralFragment
            , CharacterLiteralFragment
            , RawStringLiteralFragment
            ]
    Left _ -> False

spansTrackCrLf :: Bool
spansTrackCrLf = case fragmentsOf "code\r\n\"text\"" of
    Right [code, literal] ->
        sourceFragmentSpan code == SourceSpan "source-text.tala" (SourcePosition 1 1) (SourcePosition 2 1)
            && sourceFragmentSpan literal == SourceSpan "source-text.tala" (SourcePosition 2 1) (SourcePosition 2 7)
    _ -> False

masksProtectedBraces :: Bool
masksProtectedBraces = case fragmentsOf "class A { String x = \"}\"; -- {\n}" of
    Right fragments -> maskedSource fragments == "class A { String x =    ;     \n}"
    Left _ -> False

maskRetainsLineEndings :: Bool
maskRetainsLineEndings = case fragmentsOf "-- a\r\n\"b\nc\"\r\n" of
    Right fragments -> filter (`elem` "\r\n") (maskedSource fragments) == "\r\n\n\r\n"
    Left _ -> False

rawStringProtectedLines :: Bool
rawStringProtectedLines = case fragmentsOf "auto value = [=[\n one \n two \n]=];" of
    Right fragments -> protectedLineNumbers fragments == [1, 2, 3, 4]
    Left _ -> False

longCommentProtectedLines :: Bool
longCommentProtectedLines = case fragmentsOf "--[=[ first\n second\n]=]\nclass A {}" of
    Right fragments -> protectedLineNumbers fragments == [1, 2, 3]
    Left _ -> False

singleLineFragmentsRemainIndentable :: Bool
singleLineFragmentsRemainIndentable = case fragmentsOf "\"text\" -- comment\n" of
    Right fragments -> null (protectedLineNumbers fragments)
    Left _ -> False

attachedDoubleDashIsComment :: Bool
attachedDoubleDashIsComment = case fragmentsOf "value--;" of
    Right [code, comment] ->
        sourceFragmentKind code == CodeFragment
            && sourceFragmentText code == "value"
            && sourceFragmentKind comment == LineCommentFragment
            && sourceFragmentText comment == "--;"
    _ -> False

literalDoubleDashIsComment :: Bool
literalDoubleDashIsComment = case fragmentsOf "\"value\"--;" of
    Right [literal, comment] ->
        sourceFragmentKind literal == StringLiteralFragment
            && sourceFragmentKind comment == LineCommentFragment
            && sourceFragmentText comment == "--;"
    _ -> False

spacedDoubleDashIsComment :: Bool
spacedDoubleDashIsComment = case fragmentsOf "value -- explanation" of
    Right [code, comment] ->
        sourceFragmentKind code == CodeFragment
            && sourceFragmentText code == "value "
            && sourceFragmentKind comment == LineCommentFragment
    _ -> False

rejectsUnterminatedString :: Bool
rejectsUnterminatedString = hasCode "TAL0101" (fragmentsOf "\"open")

rejectsUnterminatedCharacter :: Bool
rejectsUnterminatedCharacter = hasCode "TAL0102" (fragmentsOf "'x")

rejectsUnterminatedRawString :: Bool
rejectsUnterminatedRawString = hasCode "TAL0103" (fragmentsOf "[=[open")

rejectsUnterminatedLongComment :: Bool
rejectsUnterminatedLongComment = hasCode "TAL0104" (fragmentsOf "--[=[open")

hasCode :: String -> Either [Diagnostic] value -> Bool
hasCode code result = case result of
    Left problems -> any ((== code) . diagnosticCode) problems
    Right _ -> False