packages feed

moonlight-pale-0.1.0.0: src-ghc-surface/Moonlight/Pale/Ghc/Expr/Render/Literal.hs

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}

module Moonlight.Pale.Ghc.Expr.Render.Literal
  ( renderNormalizedLit,
    renderNormalizedOverLit,
    renderExactIntegral,
    renderExactFractional,
    renderExactRational,
    finiteDecimal,
    factorMultiplicity,
    renderScaledDecimal,
    renderExponent,
    renderPrimitiveByte
  )
where

import Data.ByteString qualified as ByteString
import Data.Char qualified as Char
import Data.Ratio (denominator, numerator)
import Data.Word (Word8)
import GHC.Types.SourceText (FractionalExponentBase (..))
import Numeric (showHex)
import Moonlight.Pale.Ghc.Expr.Syntax
renderNormalizedLit :: NormalizedLit -> String
renderNormalizedLit = \case
  NormalizedChar value -> show value
  NormalizedCharPrim value -> show value <> "#"
  NormalizedString value -> show value
  NormalizedMultilineString value -> show value
  NormalizedStringPrim value -> "\"" <> foldMap renderPrimitiveByte (ByteString.unpack value) <> "\"#"
  NormalizedInt value -> renderExactIntegral "" value
  NormalizedIntPrim value -> renderExactIntegral "#" value
  NormalizedWordPrim value -> renderExactIntegral "##" value
  NormalizedInt8Prim value -> renderExactIntegral "#Int8" value
  NormalizedInt16Prim value -> renderExactIntegral "#Int16" value
  NormalizedInt32Prim value -> renderExactIntegral "#Int32" value
  NormalizedInt64Prim value -> renderExactIntegral "#Int64" value
  NormalizedWord8Prim value -> renderExactIntegral "#Word8" value
  NormalizedWord16Prim value -> renderExactIntegral "#Word16" value
  NormalizedWord32Prim value -> renderExactIntegral "#Word32" value
  NormalizedWord64Prim value -> renderExactIntegral "#Word64" value
  NormalizedFloatPrim value -> renderExactFractional "#" value
  NormalizedDoublePrim value -> renderExactFractional "##" value

renderNormalizedOverLit :: NormalizedOverLit -> String
renderNormalizedOverLit = \case
  NormalizedIntegralOverLit value -> renderExactIntegral "" value
  NormalizedFractionalOverLit value -> renderExactFractional "" value
  NormalizedStringOverLit value -> show value

renderExactIntegral :: String -> ExactIntegral -> String
renderExactIntegral suffix exactValue =
  maybe
    ( [ '-' | exactIntegralNegative exactValue ]
        <> show (exactIntegralValue exactValue)
        <> suffix
    )
    id
    (exactIntegralSource exactValue)

renderExactFractional :: String -> ExactFractional -> String
renderExactFractional suffix exactValue =
  maybe
    ( [ '-' | exactFractionalNegative exactValue ]
        <> renderExactRational (exactFractionalSignificand exactValue)
        <> renderExponent (exactFractionalBase exactValue) (exactFractionalExponent exactValue)
        <> suffix
    )
    id
    (exactFractionalSource exactValue)

renderExactRational :: Rational -> String
renderExactRational rationalValue =
  maybe
    ("(" <> show (numerator rationalValue) <> " / " <> show (denominator rationalValue) <> ")")
    id
    (finiteDecimal rationalValue)

finiteDecimal :: Rational -> Maybe String
finiteDecimal rationalValue =
  let denominatorValue = denominator rationalValue
      (twoCount, afterTwos) = factorMultiplicity 2 denominatorValue
      (fiveCount, residualDenominator) = factorMultiplicity 5 afterTwos
      decimalPlaces = max twoCount fiveCount
      scaledNumerator =
        numerator rationalValue
          * (2 ^ (decimalPlaces - twoCount))
          * (5 ^ (decimalPlaces - fiveCount))
   in if residualDenominator /= 1
        then Nothing
        else Just (renderScaledDecimal decimalPlaces scaledNumerator)

factorMultiplicity :: Integer -> Integer -> (Int, Integer)
factorMultiplicity factorValue value
  | value `mod` factorValue == 0 =
      let (remainingCount, residualValue) =
            factorMultiplicity factorValue (value `div` factorValue)
       in (remainingCount + 1, residualValue)
  | otherwise =
      (0, value)

renderScaledDecimal :: Int -> Integer -> String
renderScaledDecimal decimalPlaces scaledNumerator
  | decimalPlaces == 0 =
      show scaledNumerator <> ".0"
  | otherwise =
      let signPrefix = ['-' | scaledNumerator < 0]
          unsignedDigits = show (abs scaledNumerator)
          paddedDigits =
            replicate (max 0 (decimalPlaces + 1 - length unsignedDigits)) '0'
              <> unsignedDigits
          splitIndex = length paddedDigits - decimalPlaces
          (wholeDigits, fractionalDigits) = splitAt splitIndex paddedDigits
       in signPrefix <> wholeDigits <> "." <> fractionalDigits

renderExponent :: FractionalExponentBase -> Integer -> String
renderExponent exponentBase exponentValue =
  case (exponentBase, exponentValue) of
    (_, 0) -> ""
    (Base10, _) -> "e" <> show exponentValue
    (Base2, _) -> "p" <> show exponentValue

renderPrimitiveByte :: Word8 -> String
renderPrimitiveByte byteValue =
  case Char.chr (fromIntegral byteValue) of
    '"' -> "\\\""
    '\\' -> "\\\\"
    characterValue
      | byteValue >= 32 && byteValue <= 126 ->
          [characterValue]
      | otherwise ->
          "\\x" <> showHex byteValue "" <> "\\&"