packages feed

ychr-0.1.0.0: src/YCHR/Internal/Compile/Types.hs

{-# LANGUAGE DuplicateRecordFields #-}

-- | Internal types for the CHR-to-VM compiler.
module YCHR.Internal.Compile.Types
  ( -- * Errors
    CompileError (..),

    -- * Semantic newtypes
    OccurrenceNumber (..),
    HeadPosition (..),
    PartnerIndex (..),

    -- * Data types
    Occurrence (..),
    Partner (..),
    IndexCondition (..),
    CompiledGuards (..),

    -- * Partner index conditions
    PartnerCondMap,

    -- * Occurrence map
    OccurrenceMap,
    occMapEmpty,
    occMapAppend,
    occMapMap,
    lookupOccurrences,

    -- * Variable map
    VarMap,
    varMapFromList,
    lookupVar,
    insertVar,
    notMemberVar,
  )
where

import Data.Map.Strict (Map)
import Data.Map.Strict qualified as Map
import Data.Text (Text)
import YCHR.Internal.Desugared qualified as D
import YCHR.Internal.Types (HeadArg, HeadConstraint, Identifier, Name, RuleId)
import YCHR.Internal.Types qualified as Types
import YCHR.Internal.VM (ArgIndex, BoolExpr, ConstraintType, Stmt, ValExpr)

-- | Errors raised by any pass in the CHR-to-VM compiler. Wrapped in
-- 'YCHR.Internal.Parsed.AnnP' at the use site to carry the source location and
-- original parsed expression for diagnostics.
data CompileError
  = -- | A head constraint references a constraint type that is not in
    -- the symbol table. Raised by 'YCHR.Internal.Compile.Occurrences'.
    UnknownConstraintType Types.Name
  | -- | A guard or body term references a variable that is not bound
    -- by the rule head. Raised by 'YCHR.Internal.Compile' while compiling
    -- terms.
    UnboundVariable Text
  deriving (Show)

-- | 1-based occurrence number within a constraint type's occurrence list.
newtype OccurrenceNumber = OccurrenceNumber {unOccurrenceNumber :: Int}
  deriving (Show, Eq, Ord, Num, Enum)

-- | 0-based position in the combined (removed ++ kept) head list.
newtype HeadPosition = HeadPosition {unHeadPosition :: Int}
  deriving (Show, Eq, Ord, Num, Enum)

-- | 0-based index into the partners list of an occurrence.
newtype PartnerIndex = PartnerIndex {unPartnerIndex :: Int}
  deriving (Show, Eq, Ord, Num, Enum)

-- | One occurrence of a single head constraint within a rule. The
-- compiler emits one @occurrence_c_j@ procedure per 'Occurrence'.
data Occurrence = Occurrence
  { -- | Source name of the constraint this occurrence belongs to.
    conName :: Name,
    -- | Arity of the constraint (used for procedure-name encoding).
    conArity :: Int,
    -- | 1-based position within the constraint's occurrence list.
    -- Assigned top-down in 'YCHR.Internal.Compile.collectOccurrences'.
    number :: OccurrenceNumber,
    -- | The full rule this occurrence belongs to. Carried so that
    -- 'genFireStmts' can read its guard, body, and head shape.
    rule :: D.Rule,
    -- | Numeric identifier of the rule, used as the propagation
    -- history key. Assigned by 'YCHR.Internal.Compile.collectOccurrences'
    -- from the rule's program-wide source position.
    ruleId :: RuleId,
    -- | Display name of the rule, used for diagnostics and
    -- 'YCHR.Internal.VM.StackFrame' labels. Equals @rule.name@ when
    -- the rule was explicitly named, otherwise a synthetic
    -- @__rule_N@ fallback.
    ruleDisplay :: Text,
    -- | Position of the active head constraint inside the rule's
    -- combined (removed ++ kept) head list.
    activeIdx :: HeadPosition,
    -- | Whether the active constraint is kept (@True@) or removed
    -- (@False@) when the rule fires.
    isKept :: Bool,
    -- | Arguments of the active head constraint, in source order.
    -- Narrowed to 'HeadArg' so the HNF invariant ("head args are
    -- variables or wildcards") is enforced by the type.
    activeArgs :: [HeadArg],
    -- | The other head constraints, in the order they will be iterated
    -- by nested 'YCHR.Internal.VM.Foreach' loops.
    partners :: [Partner],
    -- | Whether ωr guarantees this occurrence can never fire with the
    -- active constraint in this role. Every occurrence is born active
    -- ('False') in 'YCHR.Internal.Compile.Occurrences.mkOccurrence'; the passivity
    -- pass 'YCHR.Internal.Compile.Passive.markPassive' flips it. A passive
    -- occurrence keeps its ωr 'number' (numbering runs first) but
    -- contributes no @occurrence_c_j@ procedure and no call from
    -- @activate_c@. See "YCHR.Internal.Compile.Passive" and
    -- @dev-docs/passive-occurrences.md@.
    passive :: Bool
  }

-- | One partner constraint of an 'Occurrence' — i.e. a head constraint
-- of the same rule that is /not/ the active one.
data Partner = Partner
  { -- | Position in the rule's combined (removed ++ kept) head list.
    idx :: HeadPosition,
    -- | The original constraint as it appears in the head, narrowed
    -- to 'HeadConstraint' (post-HNF).
    constraint :: HeadConstraint,
    -- | Whether the partner is kept (@True@) or removed (@False@) when
    -- the rule fires. Removed partners are killed before the body runs
    -- and skipped during backjumping (they are guaranteed dead).
    isKept :: Bool,
    -- | Constraint-type index used by 'YCHR.Internal.VM.Foreach' to look the
    -- partner up in the constraint store.
    cType :: ConstraintType
  }

-- | A single index condition lifted out of an equality check guard onto
-- a partner 'YCHR.Internal.VM.Foreach' loop: argument @argIndex@ of the partner
-- must be 'YCHR.Internal.VM.Equal' to @expectedValue@. Produced by
-- 'YCHR.Internal.Compile.classifyEqual' and consumed by
-- 'YCHR.Internal.Compile.wrapInPartnerLoops', which projects it back to the
-- @(ArgIndex, ValExpr)@ pair the VM 'YCHR.Internal.VM.Foreach' instruction expects.
data IndexCondition = IndexCondition
  { argIndex :: ArgIndex,
    expectedValue :: ValExpr
  }

-- | Per-partner index conditions lifted out of check guards by the
-- 'YCHR.Internal.VM.Foreach' index-condition pushdown optimization. Each entry
-- maps a partner index @k@ to the list of conditions that becomes
-- 'YCHR.Internal.VM.Foreach' @k@'s index-conditions argument.
type PartnerCondMap = Map PartnerIndex [IndexCondition]

-- | Result of compiling a guard conjunction. Threaded out of
-- 'YCHR.Internal.Compile.compileGuards' to its two callers (occurrence bodies
-- and function equations).
data CompiledGuards = CompiledGuards
  { -- | Wrapper that nests an inner statement block inside the
    -- structural @if@s and let-bindings introduced by match guards
    -- ('YCHR.Internal.Desugared.GuardMatch', 'YCHR.Internal.Desugared.GuardGetArg').
    matchWrapper :: [Stmt] -> [Stmt],
    -- | Index conditions lifted onto the surrounding partner
    -- 'YCHR.Internal.VM.Foreach' loops by 'YCHR.Internal.Compile.classifyEqual'. Empty
    -- when no occurrence context is available (function equations).
    indexConditions :: PartnerCondMap,
    -- | Residual boolean check that did not lift into the index map.
    -- 'Nothing' when every check guard lifted or when there were no
    -- check guards.
    residualCheck :: Maybe BoolExpr,
    -- | 'VarMap' extended with the bindings introduced by match
    -- guards.
    extendedVarMap :: VarMap
  }

newtype OccurrenceMap = OccurrenceMap (Map.Map Identifier [Occurrence])

occMapEmpty :: OccurrenceMap
occMapEmpty = OccurrenceMap Map.empty

occMapAppend :: Identifier -> Occurrence -> OccurrenceMap -> OccurrenceMap
occMapAppend k occ (OccurrenceMap m) = OccurrenceMap (Map.insertWith (++) k [occ] m)

occMapMap :: ([Occurrence] -> [Occurrence]) -> OccurrenceMap -> OccurrenceMap
occMapMap f (OccurrenceMap m) = OccurrenceMap (Map.map f m)

lookupOccurrences :: Identifier -> OccurrenceMap -> [Occurrence]
lookupOccurrences k (OccurrenceMap m) = Map.findWithDefault [] k m

-- | Map from source-level variable names to the 'ValExpr' that holds
-- their value. The compiler stores only value bindings here; constraint
-- identifiers (partner ids, the active id) are referenced by their
-- generated names directly via 'YCHR.Internal.VM.IdVar' and never enter the map.
newtype VarMap = VarMap (Map.Map Text ValExpr)

varMapFromList :: [(Text, ValExpr)] -> VarMap
varMapFromList = VarMap . Map.fromList

lookupVar :: Text -> VarMap -> Maybe ValExpr
lookupVar k (VarMap m) = Map.lookup k m

insertVar :: Text -> ValExpr -> VarMap -> VarMap
insertVar k v (VarMap m) = VarMap (Map.insert k v m)

notMemberVar :: Text -> VarMap -> Bool
notMemberVar k (VarMap m) = Map.notMember k m