packages feed

tala-core-0.1.0: src/Progmasoft/Tala/Desugarer/Boundary.hs

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

{- | What crosses from the Typed AST into Core without the lowering state:
types, literals, patterns, type identities and the source catalog. Each is a
translation of one typed piece into its Core counterpart.
-}
module Progmasoft.Tala.Desugarer.Boundary
    ( callableFinalType
    , canonicalTypeName
    , declarationSourceFiles
    , expressionAnnotation
    , functionSources
    , lowerBoundaryType
    , lowerLiteral
    , lowerPattern
    , lowerRelationalPattern
    , lowerTemplateArgument
    , portableSourcePath
    , portableSpanSource
    , scalarType
    , typeIdentity
    ) where

import Data.Bits (xor)
import Data.Char (ord)
import Data.Word (Word64)
import Progmasoft.Tala.AST
import Progmasoft.Tala.Core
import Progmasoft.Tala.Core.Scalar (isCoreNumericType)
import Progmasoft.Tala.RuntimeCall (RuntimeFunction (TextEquals), runtimeFunctionIdentity)

-- | Whether a lowered type is one whose values are computed by need.
scalarType :: Type -> Bool
scalarType lowered = lowered == boolType || isCoreNumericType lowered

{- | The source files of a declaration and of its members. The physical file
catalog is kept even for a declaration that does not yet lower to executable
code: the driver uses it to produce stable, one-source-per-artifact output
names after optimization has removed dead functions.
-}
declarationSourceFiles :: Declaration name annotation -> [FilePath]
declarationSourceFiles declaration = case declaration of
    FunctionDeclaration {} -> [portableSpanSource declaration]
    TypeDeclaration {typeMembers = members} ->
        portableSpanSource declaration : concatMap declarationSourceFiles members
    TemplateTypeDeclaration {typeMembers = members} ->
        portableSpanSource declaration : concatMap declarationSourceFiles members
    EnumDeclaration {} -> [portableSpanSource declaration]

{- | A source path with portable separators. Artifact paths always use them,
even when discovery ran on Windows, because these names become stable wire
and output identities.
-}
portableSourcePath :: FilePath -> FilePath
portableSourcePath = map (\character -> if character == '\\' then '/' else character)

-- | The portable path of the file a declaration is written in.
portableSpanSource :: Declaration name annotation -> FilePath
portableSpanSource = portableSourcePath . sourceFile . declarationSpan

-- | The symbol of each function of the declarations, with its portable source path.
functionSources :: [Declaration ResolvedName Type] -> [(Int, FilePath)]
functionSources = concatMap declarationFunctionSources
    where
        declarationFunctionSources declaration = case declaration of
            FunctionDeclaration {} -> owner declaration
            TypeDeclaration {typeMembers = members} -> concatMap declarationFunctionSources members
            TemplateTypeDeclaration {typeMembers = members} -> concatMap declarationFunctionSources members
            EnumDeclaration {} -> []
        owner declaration =
            [
                ( symbolIdValue (resolvedSymbol (declarationName declaration))
                , portableSpanSource declaration
                )
            ]

-- | The type of the final expression of a callable body, when it ends in one.
callableFinalType :: Block ResolvedName Type -> Maybe Type
callableFinalType (Block statements) = case reverse statements of
    ExpressionStatement _ expression False : _ -> Just (expressionAnnotation expression)
    _ -> Nothing

-- | The type the checker annotated an expression with.
expressionAnnotation :: Expression name Type -> Type
expressionAnnotation expression = case expression of
    NameExpression _ _ valueType -> valueType
    LiteralExpression _ _ valueType -> valueType
    MemberAccessExpression _ _ _ valueType -> valueType
    MethodReferenceExpression _ _ _ valueType -> valueType
    CallExpression _ _ _ valueType -> valueType
    UnaryExpression _ _ _ valueType -> valueType
    BinaryExpression _ _ _ _ valueType -> valueType
    IsPatternExpression _ _ _ valueType -> valueType
    ConditionalExpression _ _ _ _ valueType -> valueType
    CoalesceExpression _ _ _ valueType -> valueType
    AssignmentExpression _ _ _ _ valueType -> valueType
    IncrementExpression _ _ _ valueType -> valueType
    LoopExpression _ _ valueType -> valueType
    BlockExpression _ _ valueType -> valueType
    MatchExpression _ _ _ valueType -> valueType
    CallableExpression _ _ _ _ _ valueType -> valueType

-- | The Boolean Core test of a pattern against a subject of the given type.
lowerPattern :: CoreExpression -> Type -> Pattern ResolvedName Type -> CoreExpression
lowerPattern subject subjectType patternValue = case patternValue of
    WildcardPattern {} -> CoreLiteral (CoreBoolean True) boolType
    NullPattern {} -> CorePrimitive CoreEqual [subject, CoreLiteral CoreNull subjectType] boolType
    -- A string is compared by the characters it holds, which the runtime
    -- decides. The equality primitive on two strings would compare the
    -- objects, and a literal is never the object of the subject.
    LiteralPattern _ literal literalType
        | literalType == stringType -> textEquals literal
    RelationalPattern _ PatternEqual literal literalType
        | literalType == stringType -> textEquals literal
    RelationalPattern _ PatternNotEqual literal literalType
        | literalType == stringType -> CorePrimitive CoreLogicalNot [textEquals literal] boolType
    LiteralPattern _ literal literalType ->
        CorePrimitive CoreEqual [subject, CoreLiteral (lowerLiteral literalType literal) literalType] boolType
    TypePattern _ _ targetType ->
        CorePrimitive
            CoreTypeIs
            [subject, CoreLiteral (CoreInteger (toInteger (typeIdentity targetType))) (namedType "uint")]
            boolType
    RelationalPattern _ operator literal literalType ->
        CorePrimitive
            (lowerRelationalPattern operator)
            [subject, CoreLiteral (lowerLiteral literalType literal) literalType]
            boolType
    NotPattern _ nested _ -> CorePrimitive CoreLogicalNot [lowerPattern subject subjectType nested] boolType
    AndPattern _ left right _ ->
        CorePrimitive CoreLogicalAnd [lowerPattern subject subjectType left, lowerPattern subject subjectType right] boolType
    OrPattern _ left right _ ->
        CorePrimitive CoreLogicalOr [lowerPattern subject subjectType left, lowerPattern subject subjectType right] boolType
    where
        textEquals literal =
            CorePrimitive
                CoreRuntimeCall
                [ CoreLiteral (CoreInteger (runtimeFunctionIdentity TextEquals)) intType
                , subject
                , CoreLiteral (lowerLiteral stringType literal) stringType
                ]
                boolType

-- | The Core primitive that compares like a relational pattern operator.
lowerRelationalPattern :: RelationalPatternOperator -> CorePrimitive
lowerRelationalPattern operator = case operator of
    PatternLessThan -> CoreLessThan
    PatternLessEqual -> CoreLessEqual
    PatternGreaterThan -> CoreGreaterThan
    PatternGreaterEqual -> CoreGreaterEqual
    PatternEqual -> CoreEqual
    PatternNotEqual -> CoreNotEqual

{- | The identity of a type in a type test. FNV-1a is specified rather than
delegated to a host hash library: native code embeds the same canonical
identity into AARC type metadata, so a pattern test remains stable across
processes and target platforms.
-}
typeIdentity :: Type -> Word64
typeIdentity = foldl step 14695981039346656037 . map (fromIntegral . ord) . canonicalTypeName
    where
        step hashValue byte = (hashValue `xor` byte) * 1099511628211

-- | The canonical spelling of a type that its identity is computed from.
canonicalTypeName :: Type -> String
canonicalTypeName valueType = case valueType of
    NamedType (QualifiedName parts) arguments ->
        joinWith "." (map identifierText parts)
            ++ if null arguments then "" else "<" ++ joinWith "," (map canonicalArgument arguments) ++ ">"
    FunctionType parameters result ->
        "(" ++ joinWith "," (map canonicalTypeName parameters) ++ ")->" ++ canonicalTypeName result
    TypeVariable name -> "$" ++ show (symbolIdValue (resolvedSymbol name))
    ErrorType -> "<error>"
    where
        canonicalArgument argument = case argument of
            TypeTemplateArgument nested -> canonicalTypeName nested
            ValueTemplateArgument value -> show value

joinWith :: String -> [String] -> String
joinWith _ [] = ""
joinWith _ [value] = value
joinWith separator (value : rest) = value ++ separator ++ joinWith separator rest

-- | The Core literal of a source literal of the given type.
lowerLiteral :: Type -> Literal -> CoreLiteral
lowerLiteral valueType literal = case literal of
    IntegerLiteral value
        | valueType == boolType -> CoreBoolean (value /= 0)
        | otherwise -> CoreInteger value
    FloatingLiteral spelling -> CoreFloating spelling
    CharacterLiteral value -> CoreInteger value
    BooleanLiteral value -> CoreBoolean value
    StringLiteral value -> CoreString value
    UnitLiteral -> CoreUnit

{- | A type as it crosses from the Typed AST into Core. The frontend keeps
source @void@ separate from value-producing @unit@; the native Core contract
predates that distinction and represents no result as unit, so the erasure
happens once, here.
-}
lowerBoundaryType :: Type -> Type
lowerBoundaryType valueType
    | valueType == voidType = unitType
    | FunctionType parameters result <- valueType =
        FunctionType (map lowerBoundaryType parameters) (lowerBoundaryType result)
    -- A value of an enum is a value of its underlying integer type. An enum
    -- must not cross into Core as a named type: named types are references
    -- there, and an enum is not one.
    | Just underlying <- enumUnderlyingType valueType = underlying
    | NamedType name arguments <- valueType = NamedType name (map lowerTemplateArgument arguments)
    | otherwise = valueType

-- | A template argument as it crosses into Core.
lowerTemplateArgument :: TemplateArgument -> TemplateArgument
lowerTemplateArgument argument = case argument of
    TypeTemplateArgument valueType -> TypeTemplateArgument (lowerBoundaryType valueType)
    ValueTemplateArgument value -> ValueTemplateArgument value