packages feed

clash-lib-1.10.3: src/Clash/Rewrite/StrategyDSL.hs

{-|
  Copyright  :  (C) 2026, QBayLogic B.V.
  License    :  BSD2 (see the file LICENSE)
  Maintainer :  QBayLogic B.V. <devops@qbaylogic.com>

  A pure description language for rewrite strategies. A strategy written in
  this DSL is compiled by "Clash.Rewrite.StrategyDSL.TH" into a fused
  traversal that matches each node's constructor once and runs only the
  transformations registered for that constructor.

  The DSL has two layers:

  * 'Step': what runs at a single node inside a fused traversal. An ordered
    'chain' of transformations, sequencing and success/failure conditionals
    between chain segments, or a whole 'nested' strategy embedded as one node
    action.

  * 'Strat': a complete strategy. Fused traversals over a 'Step', whole-term
    passes, and the usual combinators ('>->', '!->', '>-!', '>-!->',
    'repeatR') between them.
-}

{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoFieldSelectors #-}
{-# LANGUAGE OverloadedRecordDot #-}

module Clash.Rewrite.StrategyDSL
  ( -- * Transformation metadata
    Constructor (..)
  , TransformationWorker (..)
  , Transformation (..)
  , toTransformation
  , anyConstructor
  , named
    -- ** Worker builders: workers receiving the constructor's fields
  , onVar
  , onData
  , onLiteral
  , onPrim
  , onLam
  , onTyLam
  , onApp
  , onTyApp
  , onLet
  , onCase
  , onCast
  , onTick
    -- ** Worker builders: workers receiving the node itself
  , onVarNode
  , onPrimNode
  , onAppNode
  , onTyAppNode
  , onLetNode
  , onTickNode
    -- * Node layer
  , Step (..)
  , chain
  , one
  , nested
    -- * Strategy layer
  , Strat (..)
  , topdown
  , bottomup
  , topdownFix
  , topdownSuc
  , innerMost
  , pass
  , callStrategy
  , repeatR
    -- * Combinators
  , Strategic (..)
  , (>-!->)
  , ToStep (..)
  ) where

import Language.Haskell.TH.Syntax (Name)

-- | One 'Clash.Core.Term.Term' constructor.
data Constructor
  = CVar | CData | CLiteral | CPrim | CLam | CTyLam
  | CApp | CTyApp | CLet | CCase | CCast | CTick
  deriving (Eq, Ord, Show, Enum, Bounded)

-- | A single function separated by the constructor it fires on. Note that this
-- isn't a single transformation per se, as one transformation might fire on
-- multiple constructors. 'Transformation' ties together multiple workers into
-- a single transformation.
--
-- The 'Name' must refer to an exported top-level worker so the generated
-- dispatch code can reference it from the splice site.
--
-- A worker that returns a term other than the one it was given must say so
-- with 'Clash.Rewrite.Util.changed': a 'chain' hands the fields it matched to
-- the next member as long as no change was signaled.
data TransformationWorker
  = FieldWorker !Constructor !Name
  -- ^ @worker :: TransformContext -> Term -> \<fields of the constructor\>
  -- -> RewriteMonad extra Term@. The worker receives the matched node (to
  -- return on the unchanged path with sharing intact) followed by the
  -- constructor's fields.
  | Worker !Constructor !Name
  -- ^ @worker :: Rewrite extra@. A worker that fires only at the given
  -- constructor, like the per-constructor worker above, but receives the
  -- whole node instead of its deconstructed fields. For transformations that
  -- decompose the node themselves, e.g. by collecting the entire application
  -- spine rather than one @App@ layer.
  | AnyWorker !Name
  -- ^ @worker :: Rewrite extra@, applied at all constructors. For a
  -- transformation whose firing condition lives in the
  -- 'Clash.Rewrite.Types.TransformContext' rather than the constructor
  -- (@topLet@).
  deriving (Eq, Show)

-- | Single, named transformation. Can consist of multiple workers (see
-- 'TransformationWorker').
data Transformation = Transformation
  { name :: !String
  -- ^ Canonical name, used for debug tracing, transformation counters, and
  -- the rewrite history.
  , workers :: ![TransformationWorker]
  } deriving (Eq, Show)

-- | Declare a transformation's workers: at most one per constructor, and
-- either an 'AnyWorker' or per-constructor workers, never both. Checked when
-- the transformation is compiled.
toTransformation :: String -> [TransformationWorker] -> Transformation
toTransformation = Transformation

-- | Declare a transformation that fires at any constructor. See 'AnyWorker'.
anyConstructor :: String -> Name -> Transformation
anyConstructor name worker = Transformation name [AnyWorker worker]

-- | Give a transformation a different name; used for deliberate debug
-- aliases of a transformation that runs at several strategy positions (the
-- transformation counters and rewrite history key on the name).
named :: String -> Transformation -> Transformation
named alias transformation = transformation { name = alias }

onVar, onData, onLiteral, onPrim, onLam, onTyLam, onApp, onTyApp, onLet,
  onCase, onCast, onTick :: Name -> [TransformationWorker]
onVar     = pure . FieldWorker CVar
onData    = pure . FieldWorker CData
onLiteral = pure . FieldWorker CLiteral
onPrim    = pure . FieldWorker CPrim
onLam     = pure . FieldWorker CLam
onTyLam   = pure . FieldWorker CTyLam
onApp     = pure . FieldWorker CApp
onTyApp   = pure . FieldWorker CTyApp
onLet     = pure . FieldWorker CLet
onCase    = pure . FieldWorker CCase
onCast    = pure . FieldWorker CCast
onTick    = pure . FieldWorker CTick

onVarNode, onPrimNode, onAppNode, onTyAppNode, onLetNode,
  onTickNode :: Name -> [TransformationWorker]
onVarNode   = pure . Worker CVar
onPrimNode  = pure . Worker CPrim
onAppNode   = pure . Worker CApp
onTyAppNode = pure . Worker CTyApp
onLetNode   = pure . Worker CLet
onTickNode  = pure . Worker CTick

-- | What runs at a single node inside a fused traversal.
data Step
  = Chain [Transformation]
  -- ^ One constructor match dispatches the members registered for that
  -- constructor, in list order. Equivalent to the sequential
  -- @apply n1 t1 '>->' apply n2 t2 …@ chain over all members.
  | SeqS Step Step
  -- ^ '>->' at the node: the right side re-dispatches on the left side's
  -- result.
  | OnChangeS Step Step
  -- ^ '!->' at the node: the right side runs only when the left side
  -- signaled change.
  | OnNoChangeS Step Step
  -- ^ '>-!' at the node: the right side runs only when the left side did
  -- not signal change.
  | EmbedS Strat
  -- ^ A whole strategy as one node action.
  deriving (Eq, Show)

-- | A complete rewriting strategy.
data Strat
  = TopDown Step
  -- ^ Fused 'Clash.Rewrite.Combinators.topdownR': repeat the step at each
  -- node until it no longer fires, then descend.
  | BottomUp Step
  -- ^ Fused 'Clash.Rewrite.Combinators.bottomupR': descend first, run the
  -- step once on the rebuilt node.
  | TopDownFix Step
  -- ^ Fused 'Clash.Rewrite.Combinators.topdownFixR'. Mind its soundness
  -- precondition (Note [topdownFixR] in "Clash.Rewrite.Combinators").
  | TopDownSuc Step
  -- ^ Fused 'Clash.Rewrite.Combinators.topdownSucR': stop at the first
  -- success, neither repeating nor descending below it.
  | InnerMost Step
  -- ^ Fused 'Clash.Rewrite.Combinators.innerMost': bottom-up; when the step
  -- fires, re-traverse the result until the innermost fixpoint is reached.
  | Pass String Name
  -- ^ A whole-term pass: @apply \<name\> \<rewrite\>@, for transformations
  -- that traverse the term themselves (@makeANF@, @etaExpansionTL@, …).
  | CallStrategy Name
  -- ^ Reference to another compiled strategy binding.
  | Seq Strat Strat            -- ^ '>->'
  | DeepSeqS Strat Strat       -- ^ '>-!->'
  | OnChange Strat Strat       -- ^ '!->'
  | OnNoChange Strat Strat     -- ^ '>-!'
  | Repeat Strat               -- ^ 'repeatR'
  deriving (Eq, Show)

-- | Things that can be used where a 'Step' is expected; a bare
-- 'Transformation' lifts to a singleton 'chain'.
class ToStep s where
  toStep :: s -> Step

instance ToStep Step where
  toStep = id

instance ToStep Transformation where
  toStep = one

-- | An ordered chain of transformations, dispatched per constructor. See
-- 'Chain'.
chain :: [Transformation] -> Step
chain = Chain

-- | A singleton 'chain', for use as an operand of the node-level operators.
one :: Transformation -> Step
one = Chain . pure

-- | Embed a whole strategy as a single node action.
nested :: Strat -> Step
nested = EmbedS

topdown, bottomup, topdownFix, topdownSuc, innerMost :: ToStep s => s -> Strat
topdown    = TopDown . toStep
bottomup   = BottomUp . toStep
topdownFix = TopDownFix . toStep
topdownSuc = TopDownSuc . toStep
innerMost  = InnerMost . toStep

-- | A whole-term pass, run as @apply \<name\> \<rewrite\>@.
pass :: String -> Name -> Strat
pass = Pass

-- | Reference another compiled strategy by the name of its binding.
callStrategy :: Name -> Strat
callStrategy = CallStrategy

-- | Keep applying a strategy until it no longer fires.
repeatR :: Strat -> Strat
repeatR = Repeat

infixr 6 >->
infixr 5 !->
infixr 5 >-!

-- | The combinators shared by both DSL layers. Fixities match
-- "Clash.Rewrite.Combinators"; a strategy spec module should import only
-- this module, so the names are unambiguous there.
class Strategic a where
  -- | Apply two strategies in succession.
  (>->) :: a -> a -> a
  -- | Only apply the second strategy if the first one succeeds.
  (!->) :: a -> a -> a
  -- | Only apply the second strategy if the first one fails.
  (>-!) :: a -> a -> a

instance Strategic Strat where
  (>->) = Seq
  (!->) = OnChange
  (>-!) = OnNoChange

instance Strategic Step where
  (>->) = SeqS
  (!->) = OnChangeS
  (>-!) = OnNoChangeS

infixr 6 >-!->

-- | Apply two strategies in succession, with a @deepseq@ of the term in
-- between. Strategy layer only.
(>-!->) :: Strat -> Strat -> Strat
(>-!->) = DeepSeqS