packages feed

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

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

{- | What a closure takes from the place that creates it.

A callable written without a capture list captures what its body reads and
does not define: the lowering finds those names in the lowered body. A
closure takes the value of each when it is created.
-}
module Progmasoft.Tala.Desugarer.Captures
    ( discoverImplicitCaptures
    , localSymbols
    ) where

import Progmasoft.Tala.AST
import Progmasoft.Tala.Core

{- | The captures of a closure with the given parameters and body: every name
the body reads that is neither a parameter nor a local of the body, once,
in the order of the first read.
-}
discoverImplicitCaptures :: [(ResolvedName, Type)] -> [CoreStatement] -> [CoreCapture]
discoverImplicitCaptures parameters statements =
    let bound = map (resolvedSymbol . fst) parameters ++ localSymbols statements
        free = filter (\(name, _) -> resolvedSymbol name `notElem` bound) (statementReads statements)
     in [CoreCapture StrongCapture name valueType (CoreVariable name valueType) | (name, valueType) <- uniqueReads free]

{- | The symbols the statements bind, in every branch and loop body they
hold. The body of a closure among them is a scope of its own and is not
searched.
-}
localSymbols :: [CoreStatement] -> [SymbolId]
localSymbols = concatMap collect
    where
        collect statement = case statement of
            CoreBind binding -> [resolvedSymbol (coreBindingName binding)]
            CoreIf _ yes no -> localSymbols yes ++ localSymbols no
            CoreWhile _ body -> localSymbols body
            CoreDoWhile body _ -> localSymbols body
            CoreFor _ body update -> localSymbols body ++ localSymbols update
            _ -> []

statementReads :: [CoreStatement] -> [(ResolvedName, Type)]
statementReads = concatMap collect
    where
        collect statement = case statement of
            CoreBind binding -> expressionReads (coreBindingValue binding)
            CoreAssign _ value -> expressionReads value
            CoreReturn value -> expressionReads value
            CoreIf condition yes no -> expressionReads condition ++ statementReads yes ++ statementReads no
            CoreWhile condition body -> expressionReads condition ++ statementReads body
            CoreDoWhile body condition -> statementReads body ++ expressionReads condition
            CoreFor condition body update ->
                expressionReads condition ++ statementReads body ++ statementReads update
            CoreEvaluate value -> expressionReads value
            CoreBreak -> []
            CoreContinue -> []

expressionReads :: CoreExpression -> [(ResolvedName, Type)]
expressionReads expression = case expression of
    CoreVariable name valueType -> [(name, valueType)]
    CoreLiteral _ _ -> []
    CoreApply callee arguments _ -> expressionReads callee ++ concatMap expressionReads arguments
    CorePrimitive _ arguments _ -> concatMap expressionReads arguments
    CoreLet name _ value body _ ->
        expressionReads value ++ filter ((/= resolvedSymbol name) . resolvedSymbol . fst) (expressionReads body)
    CoreConditional condition whenTrue whenFalse _ ->
        expressionReads condition ++ expressionReads whenTrue ++ expressionReads whenFalse
    -- A closure reads, from the place that creates it, its capture
    -- initializers and whatever its body reads that is not its own: its
    -- parameters, its captures and its locals belong to the closure. Without
    -- that, a closure around this one would capture them as if they were
    -- names of its surroundings.
    CoreClosure captures parameters _ body _ ->
        let own =
                map (resolvedSymbol . fst) parameters
                    ++ map (resolvedSymbol . coreCaptureName) captures
                    ++ localSymbols body
         in concatMap (expressionReads . coreCaptureValue) captures
                ++ filter ((`notElem` own) . resolvedSymbol . fst) (statementReads body)

uniqueReads :: [(ResolvedName, Type)] -> [(ResolvedName, Type)]
uniqueReads = foldl append []
    where
        append output value@(name, _)
            | any ((== resolvedSymbol name) . resolvedSymbol . fst) output = output
            | otherwise = output ++ [value]