tala-frontend-0.1.0: src/Progmasoft/Tala/Template/Mangling.hs
-- SPDX-FileCopyrightText: 2026 Progmasoft <support@progmasoft.com>
-- SPDX-License-Identifier: MPL-2.0 WITH AdditionRef-Progmasoft-Exception-1.1
{- |
Reversible-by-construction ASCII names for internal template emission.
This is not a public ABI promise. It gives Core/native handoff a deterministic,
collision-free spelling while the public ABI and linker compatibility policy
are still being designed. Every structural boundary is tagged and counted;
Unicode identifiers are encoded as fixed-width scalar values.
-}
module Progmasoft.Tala.Template.Mangling
( TemplateMangleLimits (..)
, defaultTemplateMangleLimits
, TemplateMangleError (..)
, MangledTemplateType (..)
, MangledTemplateMember (..)
, mangleTemplateType
, mangleTemplateMember
, mangleTemplateMembers
, validMangledTemplateSymbol
, renderTemplateMangleError
) where
import Data.Char (isAsciiLower, isAsciiUpper, isDigit, ord)
import Numeric (showHex)
import Progmasoft.Tala.AST
-- | Resource bounds applied while producing deterministic internal names.
data TemplateMangleLimits = TemplateMangleLimits
{ maximumMangledTypeDepth :: Int
-- ^ Maximum nested type-argument depth.
, maximumMangledSymbolLength :: Int
-- ^ Maximum emitted ASCII symbol length.
, maximumMangledNameParts :: Int
-- ^ Maximum qualified-name segment count.
, maximumMangledArguments :: Int
-- ^ Maximum arguments or function parameters.
}
deriving (Eq, Ord, Read, Show)
-- | Conservative finite limits used by normal compiler specialization.
defaultTemplateMangleLimits :: TemplateMangleLimits
defaultTemplateMangleLimits = TemplateMangleLimits 128 65535 1024 4096
-- | Failure while converting a closed template type or member to an ASCII name.
data TemplateMangleError
= -- | One or more configured bounds are invalid.
InvalidTemplateMangleLimits String
| -- | An unresolved template variable remains.
OpenTypeCannotBeMangled ResolvedName
| -- | ErrorType has no stable concrete identity.
ErrorTypeCannotBeMangled
| -- | A nominal type has no name segments.
EmptyQualifiedNameCannotBeMangled
| -- | A name segment has no Unicode scalars.
EmptyIdentifierCannotBeMangled
| -- | A name contains a non-scalar code point.
InvalidIdentifierScalar Integer
| -- | A character argument is not a Unicode scalar.
InvalidCharacterTemplateValue Integer
| -- | Nested type structure exceeded its configured bound.
MangledTypeDepthExceeded Int
| -- | Qualified name exceeded its segment bound.
MangledNamePartLimitExceeded Int Int
| -- | Type argument or parameter list exceeded its bound.
MangledArgumentLimitExceeded Int Int
| -- | Final encoded name exceeded its length bound.
MangledSymbolLengthExceeded Int Int
| -- | The input is not a supported member declaration.
ExpectedMangleableMemberDeclaration
deriving (Eq, Ord, Read, Show)
-- | Encoded nominal template type identity used only at internal compiler boundaries.
newtype MangledTemplateType = MangledTemplateType
{ mangledTemplateTypeText :: String
-- ^ Reversible, deterministic ASCII symbol spelling.
}
deriving (Eq, Ord, Read, Show)
-- | Encoded member identity paired with its original semantic identity.
data MangledTemplateMember = MangledTemplateMember
{ mangledMemberSourceSymbol :: SymbolId
-- ^ Resolved source symbol for diagnostics and mapping.
, mangledMemberSourceName :: Identifier
-- ^ Original source spelling.
, mangledMemberText :: String
-- ^ Reversible internal ASCII name.
}
deriving (Eq, Ord, Read, Show)
-- | Mangle a closed type under explicit recursion and output-size limits.
mangleTemplateType :: TemplateMangleLimits -> Type -> Either TemplateMangleError MangledTemplateType
mangleTemplateType limits valueType = do
validateLimits limits
payload <- encodeType limits 0 valueType
let symbol = "_TAT1_T" ++ payload
enforceLength limits symbol
pure (MangledTemplateType symbol)
-- | Mangle one method or nested type using its owner type as the namespace.
mangleTemplateMember ::
TemplateMangleLimits ->
Type ->
Declaration ResolvedName Type ->
Either TemplateMangleError MangledTemplateMember
mangleTemplateMember limits owner member = do
MangledTemplateType ownerName <- mangleTemplateType limits owner
case member of
FunctionDeclaration {} -> do
name <- encodeIdentifier (resolvedSpelling (declarationName member))
signature <- encodeType limits 0 (declarationAnnotation member)
let staticTag = if declarationIsStatic member then "S1" else "S0"
accessTag = case declarationAccess member of
DefaultAccess -> "A0"
PublicAccess -> "A1"
PrivateAccess -> "A2"
InternalAccess -> "A3"
ProtectedAccess -> "A4"
symbol = ownerName ++ "_M" ++ name ++ "_" ++ staticTag ++ accessTag ++ "_Y" ++ signature
enforceLength limits symbol
pure
( MangledTemplateMember
(resolvedSymbol (declarationName member))
(resolvedSpelling (declarationName member))
symbol
)
TypeDeclaration {} -> nestedTypeMember ownerName member
TemplateTypeDeclaration {} -> Left ExpectedMangleableMemberDeclaration
EnumDeclaration {} -> Left ExpectedMangleableMemberDeclaration
where
nestedTypeMember ownerName declaration = do
name <- encodeIdentifier (resolvedSpelling (declarationName declaration))
annotation <- encodeType limits 0 (declarationAnnotation declaration)
let symbol = ownerName ++ "_N" ++ name ++ "_Y" ++ annotation
enforceLength limits symbol
pure
( MangledTemplateMember
(resolvedSymbol (declarationName declaration))
(resolvedSpelling (declarationName declaration))
symbol
)
-- | Mangle every direct member of a type declaration in source order.
mangleTemplateMembers ::
TemplateMangleLimits ->
Type ->
Declaration ResolvedName Type ->
Either [TemplateMangleError] [MangledTemplateMember]
mangleTemplateMembers limits owner declaration = case declaration of
TypeDeclaration {typeMembers = members} -> collect (map (mangleTemplateMember limits owner) members)
_ -> Left [ExpectedMangleableMemberDeclaration]
validateLimits :: TemplateMangleLimits -> Either TemplateMangleError ()
validateLimits limits
| maximumMangledTypeDepth limits < 0 = invalid "maximum type depth cannot be negative"
| maximumMangledSymbolLength limits <= 0 = invalid "maximum symbol length must be positive"
| maximumMangledNameParts limits <= 0 = invalid "maximum qualified-name part count must be positive"
| maximumMangledArguments limits < 0 = invalid "maximum argument count cannot be negative"
| otherwise = Right ()
where
invalid = Left . InvalidTemplateMangleLimits
encodeType :: TemplateMangleLimits -> Int -> Type -> Either TemplateMangleError String
encodeType limits depth valueType
| depth > maximumMangledTypeDepth limits = Left (MangledTypeDepthExceeded (maximumMangledTypeDepth limits))
| otherwise = case valueType of
NamedType name arguments -> do
encodedName <- encodeQualifiedName limits name
if length arguments > maximumMangledArguments limits
then Left (MangledArgumentLimitExceeded (maximumMangledArguments limits) (length arguments))
else do
encodedArguments <- traverse (encodeArgument limits (depth + 1)) arguments
pure ("N" ++ encodedName ++ "A" ++ count encodedArguments ++ concat encodedArguments)
FunctionType parameters result -> do
if length parameters > maximumMangledArguments limits
then Left (MangledArgumentLimitExceeded (maximumMangledArguments limits) (length parameters))
else do
encodedParameters <- traverse (encodeType limits (depth + 1)) parameters
encodedResult <- encodeType limits (depth + 1) result
pure ("F" ++ count encodedParameters ++ concatMap frame encodedParameters ++ "R" ++ frame encodedResult)
TypeVariable name -> Left (OpenTypeCannotBeMangled name)
ErrorType -> Left ErrorTypeCannotBeMangled
encodeArgument :: TemplateMangleLimits -> Int -> TemplateArgument -> Either TemplateMangleError String
encodeArgument limits depth argument = case argument of
TypeTemplateArgument nested -> ("T" ++) . frame <$> encodeType limits depth nested
ValueTemplateArgument value -> ("V" ++) . frame <$> encodeValue value
encodeValue :: TemplateValue -> Either TemplateMangleError String
encodeValue value = case value of
IntegerTemplateValue integer ->
pure (if integer < 0 then "IN" ++ digits (abs integer) else "IP" ++ digits integer)
BooleanTemplateValue boolean -> pure (if boolean then "B1" else "B0")
CharacterTemplateValue scalar
| validScalar scalar -> pure ("C" ++ scalarHex scalar)
| otherwise -> Left (InvalidCharacterTemplateValue scalar)
TemplateValueParameter name -> Left (OpenTypeCannotBeMangled name)
where
digits integer = show integer ++ "_"
encodeQualifiedName :: TemplateMangleLimits -> QualifiedName -> Either TemplateMangleError String
encodeQualifiedName limits (QualifiedName parts)
| null parts = Left EmptyQualifiedNameCannotBeMangled
| length parts > maximumMangledNameParts limits =
Left (MangledNamePartLimitExceeded (maximumMangledNameParts limits) (length parts))
| otherwise = do
encoded <- traverse encodeIdentifier parts
pure (count encoded ++ concatMap frame encoded)
encodeIdentifier :: Identifier -> Either TemplateMangleError String
encodeIdentifier (Identifier value)
| null value = Left EmptyIdentifierCannotBeMangled
| otherwise = do
encoded <- traverse encodeCharacter value
pure (count value ++ concat encoded)
encodeCharacter :: Char -> Either TemplateMangleError String
encodeCharacter character
| validScalar scalar = Right (scalarHex scalar)
| otherwise = Left (InvalidIdentifierScalar scalar)
where
scalar = toInteger (ord character)
scalarHex :: Integer -> String
scalarHex scalar = replicate (6 - length encoded) '0' ++ encoded
where
encoded = showHex scalar ""
validScalar :: Integer -> Bool
validScalar scalar =
scalar >= 0
&& scalar <= 0x10ffff
&& not (scalar >= 0xd800 && scalar <= 0xdfff)
frame :: String -> String
frame value = show (length value) ++ "_" ++ value
count :: [value] -> String
count values = show (length values) ++ "_"
enforceLength :: TemplateMangleLimits -> String -> Either TemplateMangleError ()
enforceLength limits symbol
| length symbol > maximumMangledSymbolLength limits =
Left (MangledSymbolLengthExceeded (maximumMangledSymbolLength limits) (length symbol))
| otherwise = Right ()
-- | Validate the emitted alphabet and framing invariants of a mangled symbol.
validMangledTemplateSymbol :: String -> Bool
validMangledTemplateSymbol symbol =
"_TAT1_" `prefixOf` symbol
&& all validCharacter symbol
where
validCharacter character =
isAsciiLower character
|| isAsciiUpper character
|| isDigit character
|| character == '_'
-- | Render a mangling failure without exposing internal encoding details.
renderTemplateMangleError :: TemplateMangleError -> String
renderTemplateMangleError issue = case issue of
InvalidTemplateMangleLimits message -> "invalid template mangle limits: " ++ message
OpenTypeCannotBeMangled name -> "open template symbol cannot be mangled: " ++ identifierText (resolvedSpelling name)
ErrorTypeCannotBeMangled -> "ErrorType cannot be part of a mangled template symbol"
EmptyQualifiedNameCannotBeMangled -> "an empty qualified name cannot be mangled"
EmptyIdentifierCannotBeMangled -> "an empty identifier cannot be mangled"
InvalidIdentifierScalar scalar -> "identifier contains invalid Unicode scalar " ++ show scalar
InvalidCharacterTemplateValue scalar -> "character template value is invalid Unicode scalar " ++ show scalar
MangledTypeDepthExceeded limit -> "template mangling exceeded type depth " ++ show limit
MangledNamePartLimitExceeded limit actual -> limitMessage "qualified-name part" limit actual
MangledArgumentLimitExceeded limit actual -> limitMessage "template argument" limit actual
MangledSymbolLengthExceeded limit actual -> limitMessage "mangled symbol length" limit actual
ExpectedMangleableMemberDeclaration -> "template member mangling requires a closed type or function member"
where
limitMessage subject limit actual =
subject ++ " limit " ++ show limit ++ " was exceeded by " ++ show actual
prefixOf :: String -> String -> Bool
prefixOf [] _ = True
prefixOf _ [] = False
prefixOf (left : leftRest) (right : rightRest) = left == right && prefixOf leftRest rightRest
collect :: [Either problem value] -> Either [problem] [value]
collect values = case [problem | Left problem <- values] of
[] -> Right [value | Right value <- values]
problems -> Left problems