clash-shockwaves-1.0.1: src/Clash/Shockwaves/Internal/Translator.hs
{-# LANGUAGE OverloadedStrings #-}
{- |
Copyright : (C) 2025-2026, QBayLogic B.V.
License : BSD2 (see the file LICENSE)
Maintainer : QBayLogic B.V. <devops@qbaylogic.com>
Code for rendering values using the translators specified.
Values are constructed from their subvalues.
-}
module Clash.Shockwaves.Internal.Translator where
import Clash.Prelude hiding (sub)
import qualified Clash.Shockwaves.BitList as BL
import Clash.Shockwaves.Internal.BitList
import Clash.Shockwaves.Internal.Types
import Clash.Shockwaves.Internal.Util
import Data.Bifunctor (first)
import qualified Data.List as L
import Data.List.Extra (chunksOf)
import qualified Data.Map as M
import Data.Maybe (fromMaybe, isJust)
import Data.Tuple.Extra (second)
import Math.NumberTheory.Logarithms (intLog2)
import Numeric (showHex)
-- | Apply a 'WaveStyle' to a t'Translation' value. Only replaces 'WSDefault'.
applyStyle :: WaveStyle -> Translation -> Translation
applyStyle s (Translation r sb) = Translation (applyStyleR s r) sb
-- | Apply a 'WaveStyle' to a 'Render' value. Only replaces 'WSDefault'.
applyStyleR :: WaveStyle -> Render -> Render
applyStyleR s (Just (l, WSDefault, p)) = Just (l, s, p)
applyStyleR _ r = r
{- | Apply a precedence value to a t'Translation'.
If the precedence is higher or equal to that of the current value,
it is wrapped in parentheses.
-}
applyPrec :: Prec -> Translation -> Translation
applyPrec p (Translation r s) = Translation (applyPrecR p r) s
{- | Apply a precedence value to a 'Render'.
If the precedence is higher or equal to that of the current value,
it is wrapped in parentheses.
-}
applyPrecR :: Prec -> Render -> Render
applyPrecR p (Just (v, s, p')) =
if p' > p
then Just (v, s, p')
else Just (parenthesize v, s, 11)
applyPrecR _ Nothing = Nothing
{- | Apply a precedence value to a list of subsignal translations.
If the precedence is higher or equal to that of the current value,
it is wrapped in parentheses.
-}
applyPrecs :: Prec -> [(a, Translation)] -> [(a, Translation)]
applyPrecs p = L.map (second (applyPrec p))
{- | Get the value of a t'Translation'. If the value is not defined,
return @{value missing}@.
-}
getVal :: Translation -> Value
getVal t = case t of
Translation (Just (v, _, _)) _ -> v
_ -> "{value missing}"
-- | Remove subsignal translators that do not have a subsignal name.
filterSignals :: [(SubSignal, Translation)] -> [(SubSignal, Translation)]
filterSignals = L.filter ((/= "") . fst)
{- FOURMOLU_DISABLE -}
-- | Change bits using 'BitPart'.
changeBits :: BitPart -> BitList -> BitList
changeBits (BPConcat bps) bin = L.foldl (<>) "" $ L.map (`changeBits` bin) bps
changeBits (BPLit bl) _bin = bl
changeBits (BPSlice s bp) bin = BL.slice s $ changeBits bp bin
changeBits BPIn bin = bin
{- FOURMOLU_ENABLE -}
{- FOURMOLU_DISABLE -}
-- | Decode a string of bits into an unsigned integer.
decodeUns :: Integer -> String -> Maybe Integer
decodeUns k "" = Just k
decodeUns k ('0':r) = decodeUns (k*2) r
decodeUns k ('1':r) = decodeUns (k*2+1) r
decodeUns _ _ = Nothing
-- | Decode a string of bits into a signed integer.
decodeSig :: String -> Maybe Integer
decodeSig "" = Just 0
decodeSig ('0':r) = decodeUns 0 r
decodeSig ('1':r) = decodeUns (-1) r
decodeSig _ = Nothing
{- FOURMOLU_ENABLE -}
{- | Complete a translation based on already translated subsignals.
The exact behaviour is non-trivial.
Translators that require special translation ('TRef','TLut','TNumber')
cannot be translated. If a single subsignal is provided with label `""`,
this translation is used as if it was the result of the translation.
Otherwise, an error is raised.
'TSum', 'TProduct', 'TArray' and 'TConst' render a value as expected based on
the subtranslations. Note for TSum that this is a list containing only the
translation of the variant used, i.e. it behaves like 'TRef', 'TLut' and 'TNumber'.
Advanced translators are not supported.
The final two variants, 'TStyled' and 'TDuplicate' are considered /wrappers/
and translate the value recursively.
-}
translateFromSubs :: Translator -> [(SubSignal, Translation)] -> Translation
translateFromSubs (Translator _ translator) subs = case translator of
TRef{} -> case subs of
[("", t)] -> t
_ ->
errorX
"Ref should only appear as a nested type that is translated through split; for referenced types, modify Waveform.translate"
TLut _ _ -> case subs of
[("", t)] -> t
_ ->
errorX
"LUT translators require a custom implementation of Waveform.translate that does not call render"
TNumber{} -> case subs of
[("", t)] -> t
_ ->
errorX
"Number translators require a custom implementation of Waveform.translate that does not call render"
TChangeBits{} -> errorX "translator not supported"
TAdvancedSum{} -> errorX "translator not supported"
TAdvancedProduct{} -> errorX "translator not supported"
-- normal
TSum _ -> case subs of
[(_, t)] -> t
_ -> errorT "{invalid variant}"
TProduct
{ start
, sep
, stop
, labels
, preci
, preco
} -> Translation (Just (v, WSDefault, preco)) $ filterSignals subs
where
labels' = L.map Just labels <> L.repeat Nothing
subs' = applyPrecs preci subs
vals = L.map (getVal . snd) subs'
v = start <> joinWith sep fields <> stop
fields = L.zipWith addLabel labels' vals
addLabel = \case
Just l -> (l <>)
Nothing -> id
TConst t -> t
TArray
{ len
, start
, sep
, stop
, preci
, preco
} -> Translation ren subs
where
ren =
if L.length subs == len
then
Just
( start
<> joinWith sep (L.map (getVal . applyPrec preci . snd) subs)
<> stop
, WSDefault
, preco
)
else
errorR "{values missing}"
-- recursive
TStyled sty t -> applyStyle sty $ translateFromSubs t subs
TDuplicate n t -> Translation ren [(n, t')]
where
t' = translateFromSubs t subs
Translation ren _ = t'
-- | Translate a 'BitList' using the provided translator.
translateBinT :: Translator -> BitList -> Translation
translateBinT trans@(Translator width variant) bin''@(BL _ _ blLength)
| width <= blLength
, bin <- BL.take width bin'' = case variant of
TRef _ TypeRef{translateBinRef} -> translateBinRef bin
TLut _ TypeRef{translateBinRef} -> translateBinRef bin
TNumber{format, spacer, prefix, warn} -> Translation (if isJust render then render else Just ("undefined", WSError, 11)) []
where
bin' = show bin
render :: Render
render =
(\(v, s, p) -> (prefix <> applySpacer spacer v, s, p)) <$> case format of
NFBin -> Just (bin', undefStyle, 11)
NFOct -> Just (hexDigit <$> chunksOf 3 (extendBits 3), undefStyle, 11)
NFHex -> Just (hexDigit <$> chunksOf 4 (extendBits 4), undefStyle, 11)
NFUns -> (\i -> (show i, WSDefault, 11)) <$> decodeUns 0 bin'
NFSig -> (\i -> (show i, WSDefault, if i >= 0 then 11 else 6)) <$> decodeSig bin'
undefStyle = if 'x' `elem` bin' then (if warn then WSWarn else WSError) else WSDefault
extendBits k = L.replicate (k - 1 - ((width + k - 1) `rem` k)) '0' <> bin'
hexDigit :: String -> Char
hexDigit b =
fromMaybe
'x'
(((`showHex` "") <$> decodeUns 0 b :: Maybe String) >>= ((fst <$>) . L.uncons))
-- normal
TSum subs -> translation
where
k = intLog2 $ L.length subs * 2 - 1
(b, b') = BL.split k bin
translation =
maybe
(errorT "undefined")
(\v -> translateBinT (subs L.!! fromIntegral v) b') -- translate with selected translator
(BL.toInteger b)
TAdvancedSum{index, defTrans, rangeTrans} -> case BL.toInteger $ BL.slice index bin of
Just i -> translateBinT t bin
where
t = fromMaybe defTrans $ asum $ L.map go rangeTrans
go ((a, b), t')
| a <= i && i < b = Just t'
| otherwise = Nothing
Nothing -> errorT "undefined"
TProduct{subs} ->
translateFromSubs trans subTs
where
subTs = carryFoldl go bin subs
go b (lbl, t@(Translator w _)) = let (b', b'') = BL.split w b in (b'', (lbl, translateBinT t b'))
TConst t -> t
TArray{sub = sub@(Translator w _), len} ->
translateFromSubs trans subTs
where
subTs = enumLabel $ L.map (("",) . translateBinT sub) $ carryFoldl go bin [0 .. len - 1]
go b _ = let (b', b'') = BL.split w b in (b'', b')
TAdvancedProduct{sliceTrans, hierarchy, valueParts, preco} -> Translation ren subs
where
translations = L.map (\(s, translator) -> translateBinT translator $ BL.slice s bin) sliceTrans
ren = Just (L.concatMap getValPart valueParts, WSDefault, preco)
subs = L.map (second (translations L.!!)) hierarchy
getValPart (VPLit s) = s
getValPart (VPRef i p) = getVal $ applyPrec p $ translations L.!! i
-- recursive
TStyled sty t -> applyStyle sty $ translateBinT t bin
TDuplicate n t -> Translation ren' [(n, t')]
where
t' = translateBinT t bin
Translation ren _ = t'
ren' = (\(v, _, p) -> (v, WSInherit 0, p)) <$> ren
TChangeBits{sub, bits} -> translateBinT sub $ changeBits bits bin
| otherwise =
errorX
$ "BitList length ("
<> show blLength
<> ") is smaller than translator length ("
<> show width
<> ")"
-- structure
{- | Return the t'Structure' implied by a t'Translator'. Useful for determining
the structure of a constant translation.
-}
structureT :: Translator -> Structure
structureT (Translator _ t) = case t of
TRef _ TypeRef{structureRef} -> structureRef
TSum ts -> Structure $ mergeDuplicateSubsignals subs
where
subs = L.concatMap (getS . structureT) ts
getS (Structure s) = s
TAdvancedSum{rangeTrans, defTrans} -> structureT $ Translator 0 $ TSum (defTrans : L.map snd rangeTrans)
TProduct{subs} -> Structure $ L.map (second structureT) subs
TConst trans -> fromTranslation trans
TLut _ TypeRef{structureRef} -> structureRef
TNumber{} -> Structure []
TArray{sub, len} ->
Structure
. L.map (first show)
. enumerate
$ L.replicate len
$ structureT sub
where
enumerate = L.zip [(0 :: Int) ..]
TAdvancedProduct{sliceTrans, hierarchy} -> Structure $ L.map (second (structures L.!!)) hierarchy
where
structures = L.map (structureT . snd) sliceTrans
TStyled _ t' -> structureT t'
TDuplicate n t' -> Structure [(n, structureT t')]
TChangeBits{sub} -> structureT sub
-- | Merge duplicate subsignals in a list of subsignal structures.
mergeDuplicateSubsignals :: [(SubSignal, Structure)] -> [(SubSignal, Structure)]
mergeDuplicateSubsignals = L.reverse . L.foldr addSignal [] . L.reverse
where
addSignal ::
(SubSignal, Structure) -> [(SubSignal, Structure)] -> [(SubSignal, Structure)]
addSignal sig signals = case L.mapAccumL mergeOrPass (Just sig) signals of
(Nothing, signals') -> signals'
(Just sig', signals') -> sig' : signals'
where
mergeOrPass ::
Maybe (SubSignal, Structure) ->
(SubSignal, Structure) ->
(Maybe (SubSignal, Structure), (SubSignal, Structure))
mergeOrPass (Just (name, Structure s)) (name', Structure s')
| name == name' =
(Nothing, (name, Structure $ mergeDuplicateSubsignals (s' <> s)))
mergeOrPass newsig oldsig = (newsig, oldsig)
-- | Construct a t'Structure' from a t'Translation'.
fromTranslation :: Translation -> Structure
fromTranslation (Translation _ subs) = Structure $ L.map (second fromTranslation) subs
-- translator based functions
{- FOURMOLU_DISABLE -}
-- | Run a function on a translator's subtranslators, and combine the results.
-- This follows 'TRef' references.
foldTranslator :: (Translator -> a) -> ([a] -> b) -> Translator -> b
foldTranslator m f (Translator _ variant) = case variant of
-- leaf translators
TRef _ TypeRef{translatorRef} -> f [m translatorRef]
TLut _ _ -> f []
TConst _ -> f []
TNumber{} -> f []
-- combining
TSum subs -> f $ L.map m subs
TAdvancedSum{defTrans,rangeTrans} -> f $ L.map (m . snd) rangeTrans <> [m defTrans]
TProduct{subs} -> f $ L.map (m . snd) subs
TArray{sub} -> f [m sub]
TAdvancedProduct{sliceTrans} -> f $ L.map (m . snd) sliceTrans
-- single recursive
TStyled _ t -> f [m t]
TDuplicate _ t -> f [m t]
TChangeBits{sub} -> f [m sub]
{- FOURMOLU_ENABLE -}
-- | Test if there is a LUT translator in a translator (following references).
hasLutT :: Translator -> Bool
hasLutT (Translator _ (TLut _ _)) = True
hasLutT t = foldTranslator hasLutT or t
{- | Add all type references in a translator structure to a type map.
To add the types in a type, run this function on a reference to said type.
-}
addTypesT :: Translator -> (TypeMap -> TypeMap)
addTypesT t
| Translator _ (TRef n TypeRef{translatorRef}) <- t =
addType n translatorRef . addSubTypes
| otherwise = addSubTypes
where
addSubTypes = foldTranslator addTypesT (L.foldl (.) id) t
-- lut stuff
{- | From a translator, create a function that given a binary value
returns a list of functions to add all LUT values to the LUT maps.
-}
addValueT :: Translator -> BitList -> [LUTMap -> LUTMap]
addValueT translator@(Translator _ variant) =
if hasLutT translator
then case variant of
-- leaf translators
TRef _ TypeRef{translatorRef} -> addValueT translatorRef
TLut name TypeRef{translateBinRef} -> go
where
go bin =
let translation = safeValOr (errorT "error") (translateBinRef bin)
in [M.alter (Just . insertIfMissing bin translation . fromMaybe M.empty) name]
TConst _ -> const []
TNumber{} -> const []
-- combining
TSum subs -> go
where
fSubs = L.map addValueT subs
k = intLog2 $ L.length subs * 2 - 1
go bl =
let (b', b'') = BL.split k bl
in case BL.toInteger b' of
Just i -> (fSubs L.!! fromIntegral i) b''
Nothing -> []
TAdvancedSum{index, defTrans, rangeTrans} -> go
where
fDefTrans = addValueT defTrans
fRangeTrans = L.map (second addValueT) rangeTrans
go bin = case BL.toInteger $ BL.slice index bin of
Just i -> fs bin
where
fs = fromMaybe fDefTrans $ asum $ L.map go' fRangeTrans
go' ((a, b), f)
| a <= i && i < b = Just f
| otherwise = Nothing
Nothing -> []
TProduct{subs} -> go'
where
fSubs = L.map (\(_, trans@(Translator w _)) -> (w, addValueT trans)) subs
go' bin = L.concat $ carryFoldl go bin fSubs
go b (w, f) = let (b', b'') = BL.split w b in (b'', f b')
TArray{sub = sub@(Translator w _), len} -> go'
where
fSub = addValueT sub
go' bin = L.concat $ carryFoldl go bin [0 .. len - 1]
go b _ = let (b', b'') = BL.split w b in (b'', fSub b')
TAdvancedProduct{sliceTrans} -> go
where
fSliceTrans = L.map (second addValueT) sliceTrans
go bin = L.concatMap (\(s, f) -> f $ BL.slice s bin) fSliceTrans
-- single recursive
TStyled _ t -> addValueT t
TDuplicate _ t -> addValueT t
TChangeBits{sub, bits} -> go
where
fSub = addValueT sub
go bin = fSub $ changeBits bits bin
else const []