packages feed

ychr-0.1.0.0: src/YCHR/Internal/Desugared.hs

{-# LANGUAGE DuplicateRecordFields #-}

-- | CHR Abstract Syntax Tree
--
-- This module defines the internal AST for CHR programs. This is not
-- the direct output of the parser, but a desugared and semantically
-- precise representation that guides compilation to the VM.
--
-- Desugaring already performed at this stage:
--
--   * Simplification and propagation rules are represented uniformly
--     as simpagation rules. A simplification rule has an empty 'headKept'
--     list; a propagation rule has an empty 'headRemoved' list. The
--     compiler checks whether 'headRemoved' is empty to decide whether
--     propagation history maintenance is needed.
--
--   * Guards and body goals are represented with distinct types
--     ('Guard' and 'BodyGoal') to enforce that only semantically
--     appropriate goals appear in each position.
--
--   * Constraint head names and function names are 'QualifiedName',
--     so the qualification invariant established by the renamer and
--     committed by the resolver is reflected in the type system.
module YCHR.Internal.Desugared
  ( -- * Program structure
    Program (..),
    Rule (..),
    Head (..),
    Function (..),
    Equation (..),

    -- * Goals
    Guard (..),
    BodyGoal (..),
    FunStmt (..),

    -- * Expressions
    Expr (..),

    -- * Re-exports from CHR.Types
    QualifiedConstraint (..),
    QualifiedName (..),
    HeadConstraint (..),
    HeadArg (..),
    Term (..),
    TypeExpr (..),
    TypeDefinition (..),
    BoundSig (..),
  )
where

import Data.Map.Strict (Map)
import Data.Text (Text)
import YCHR.Internal.Parsed (AnnP)
import YCHR.Internal.Resolved (Expr (..))
import YCHR.Internal.Types

data Program = Program
  { rules :: [Rule],
    functions :: [Function],
    constraintTypes :: Map QualifiedName [TypeExpr],
    -- | Bounds declared on each @:- chr_constraint@ that carries a
    -- @requiring@ clause. See 'YCHR.Internal.Resolved.Program' for the
    -- bounded-vs-unbounded convention (constraints without bounds are
    -- absent from the map).
    constraintBounds :: Map QualifiedName [BoundSig],
    typeDefinitions :: [TypeDefinition]
  }
  deriving (Show)

data Rule = Rule
  { name :: Maybe Text,
    head :: AnnP Head,
    guard :: AnnP [Guard],
    body :: AnnP [BodyGoal]
  }
  deriving (Show)

data Head = Head
  { kept :: [HeadConstraint],
    removed :: [HeadConstraint]
  }
  deriving (Show, Eq)

-- | Guards over the typed 'Expr' AST. 'GuardMatch' and 'GuardGetArg'
-- always carry a 'VarExpr' as their operand: HNF only introduces
-- match/getarg guards for fresh variables it has just bound. The
-- operand type is left as 'Expr' rather than 'Text' so the lambda
-- lifter and pretty-printer can walk guards uniformly.
data Guard
  = GuardEqual Expr Expr
  | GuardMatch Expr Name Int
  | GuardGetArg Text Expr Int
  | GuardExpr Expr
  deriving (Show, Eq)

-- | Body goals over the typed 'Expr' AST.
--
-- 'BodyTell' is a tell of a user-declared constraint. Its arguments
-- are 'Expr' and are evaluated at runtime, like every other expression
-- position in the language (function args, constructor args, @is@ RHS,
-- @=@ operands). Head patterns and equation patterns still carry
-- 'Term' (see 'HeadConstraint' / 'Equation'); the call-vs-constructor
-- question is decided structurally for tells but does not arise for
-- patterns.
--
-- 'BodyCall' replaces the legacy @BodyFunctionCall@ for static calls
-- and 'BodyApply' replaces it for dynamic dispatch ('$call').
data BodyGoal
  = BodyTrue
  | BodyTell QualifiedName [Expr]
  | BodyUnify Expr Expr
  | BodyHostStmt Text [Expr]
  | BodyIs Text Expr
  | BodyCall QualifiedName [Expr]
  | BodyApply Expr [Expr]
  deriving (Show, Eq)

data Function = Function
  { name :: QualifiedName,
    arity :: Int,
    signatures :: [([TypeExpr], TypeExpr)],
    -- | Bounds declared on this function via @requiring@. Empty when
    -- the function is unbounded.
    requiring :: [BoundSig],
    equations :: AnnP [Equation]
  }
  deriving (Show)

data Equation = Equation
  { params :: [HeadArg],
    guards :: [Guard],
    -- | Statements executed before evaluating 'rhs'. Empty for
    -- single-expression bodies. The desugarer is responsible for
    -- restricting which 'R.Expr' shapes are accepted in non-final
    -- position; see 'FunStmt'.
    prelude :: [FunStmt],
    rhs :: Expr
  }
  deriving (Show)

-- | A statement appearing in non-final position of a function equation's
-- right-hand side. Strictly a subset of 'BodyGoal' — rule-body-only forms
-- like 'BodyTell', 'BodyUnify', and 'BodyTrue' are intentionally not
-- representable.
data FunStmt
  = -- | @X is E@. The LHS must be a variable; the desugarer rejects
    -- non-variable LHS in this position.
    FunIs Text Expr
  | -- | @host:f(args)@ evaluated for side effects.
    FunHostStmt Text [Expr]
  | -- | Statically resolved user-function call; result discarded.
    FunCall QualifiedName [Expr]
  | -- | Dynamic dispatch (@'$call'(F, args)@); result discarded.
    FunApply Expr [Expr]
  deriving (Show, Eq)