packages feed

tala-frontend-0.1.0: src/Progmasoft/Tala/TypeChecker/Loops.hs

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

{- | The loops around the place the type checker is at.

A @break@ or a @continue@ is checked against the innermost loop, and what a
@break@ may carry depends on how that loop is used. Blocks used as values and
loop headers are not loops, but a transfer has to cross them to reach one, so
they are entries of the same stack.
-}
module Progmasoft.Tala.TypeChecker.Loops
    ( LoopKind (..)
    , LoopContext (..)
    , outsideLoops
    , enterLoop
    , loopCondition
    , loopUpdate
    , transferTarget
    ) where

import Progmasoft.Tala.AST (Type)

-- | How a loop is used, which decides what its @break@ statements carry.
data LoopKind
    = -- | A loop statement: @break@ carries no value.
      StatementLoop
    | {- | A loop used as an expression: every @break@ carries the loop's
      value, typed in the context that receives it.
      -}
      ExpressionLoop (Maybe Type)
    | {- | Not a loop: the edge of a block used as a value. A @break@ or
      @continue@ inside it leaves the block without a value and targets the
      loop around the expression the block belongs to.
      -}
      ValueBlockEdge
    | {- | The condition of a loop of the given kind. A @break@ there leaves
      that loop, like one in its body. A @continue@ there abandons the rest
      of the condition and evaluates the condition of that loop again.
      -}
      LoopCondition LoopKind
    | {- | The update clause of a loop of the given kind. A @continue@ there
      ends the update, and the condition of the loop is tested next. A
      @break@ there leaves that loop.
      -}
      LoopUpdate LoopKind

{- | The loops around a statement, innermost first, and the kind of the loop
statement that is about to be checked. A loop expression checks its loop
statement with the pending kind set; every loop moves the pending kind onto
the stack for its own body.
-}
data LoopContext = LoopContext
    { pendingLoop :: LoopKind
    , enclosingLoops :: [LoopKind]
    }

-- | The context of a statement that no loop encloses.
outsideLoops :: LoopContext
outsideLoops = LoopContext StatementLoop []

-- | The context of the body of the loop statement being checked.
enterLoop :: LoopContext -> LoopContext
enterLoop loops = LoopContext StatementLoop (pendingLoop loops : enclosingLoops loops)

-- | The context of the condition of the loop statement about to be checked.
loopCondition :: LoopContext -> LoopContext
loopCondition loops = LoopContext StatementLoop (LoopCondition (pendingLoop loops) : enclosingLoops loops)

-- | The context of the update clause of the loop statement about to be checked.
loopUpdate :: LoopContext -> LoopContext
loopUpdate loops = LoopContext StatementLoop (LoopUpdate (pendingLoop loops) : enclosingLoops loops)

{- | The loop a @break@ or @continue@ targets: the innermost entry that is
not the edge of a block used as a value. A transfer crosses such an edge
freely; the block it leaves simply yields no value.
-}
transferTarget :: LoopContext -> Maybe LoopKind
transferTarget loops = case dropWhile isValueBlockEdge (enclosingLoops loops) of
    kind : _ -> Just kind
    [] -> Nothing
    where
        isValueBlockEdge kind = case kind of
            ValueBlockEdge -> True
            _ -> False