packages feed

moonlight-pale-0.1.0.0: src-ghc-surface/Moonlight/Pale/Ghc/Expr/Convert/Source.hs

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

module Moonlight.Pale.Ghc.Expr.Convert.Source
  ( SourceSliceIndex (..),
    sourceSliceIndex,
    sourceSliceForRegion,
    sourcePositionOffset,
    sourceColumnOffset,
    normalizedTypeText
  )
where

import Data.Kind (Type)
import Data.Vector.Unboxed qualified as U
import GHC.Utils.Outputable (Outputable, ppr, showSDocUnsafe)
import Moonlight.Pale.Ghc.Expr.Syntax
-- The fields deliberately remain lazy: value-only modules never pay to index
-- source text, while every opaque declaration shares the same forced vectors.
type SourceSliceIndex :: Type
data SourceSliceIndex = SourceSliceIndex
  { ssiSourceCharacters :: U.Vector Char,
    ssiLineStartOffsets :: U.Vector Int
  }

sourceSliceIndex :: String -> SourceSliceIndex
sourceSliceIndex sourceText =
  SourceSliceIndex
    { ssiSourceCharacters = sourceCharacters,
      ssiLineStartOffsets =
        U.cons
          0
          (U.map (+ 1) (U.findIndices (== '\n') sourceCharacters))
    }
  where
    sourceCharacters = U.fromList sourceText

sourceSliceForRegion :: SourceSliceIndex -> SourceRegion -> Maybe (SourceRegion, String)
sourceSliceForRegion moduleSourceIndex regionValue = do
  startOffset <-
    sourcePositionOffset
      moduleSourceIndex
      (srStartLine regionValue)
      (srStartCol regionValue)
  endOffset <-
    sourcePositionOffset
      moduleSourceIndex
      (srEndLine regionValue)
      (srEndCol regionValue)
  if startOffset <= endOffset
    then
      Just
        ( regionValue,
          U.toList
            ( U.take
                (endOffset - startOffset)
                (U.drop startOffset (ssiSourceCharacters moduleSourceIndex))
            )
        )
    else Nothing

sourcePositionOffset :: SourceSliceIndex -> Int -> Int -> Maybe Int
sourcePositionOffset moduleSourceIndex targetLine targetColumn
  | targetLine < 1 =
      Nothing
  | otherwise = do
      lineStartOffset <-
        ssiLineStartOffsets moduleSourceIndex U.!? (targetLine - 1)
      sourceColumnOffset
        (ssiSourceCharacters moduleSourceIndex)
        lineStartOffset
        targetColumn

sourceColumnOffset :: U.Vector Char -> Int -> Int -> Maybe Int
sourceColumnOffset sourceCharacters lineStartOffset targetColumn
  | targetColumn < 1 || lineStartOffset < 0 =
      Nothing
  | otherwise =
      go 1 lineStartOffset
  where
    go !currentColumn !currentOffset
      | currentColumn == targetColumn =
          Just currentOffset
      | currentColumn > targetColumn =
          Nothing
      | otherwise =
          case sourceCharacters U.!? currentOffset of
            Nothing ->
              Nothing
            Just sourceCharacter
              | sourceCharacter == '\n' ->
                  Nothing
              | sourceCharacter == '\t' ->
                  go
                    (currentColumn + 8 - ((currentColumn - 1) `mod` 8))
                    (currentOffset + 1)
              | otherwise ->
                  go
                    (currentColumn + 1)
                    (currentOffset + 1)

normalizedTypeText :: Outputable a => a -> NormalizedTypeText
normalizedTypeText =
  NormalizedTypeText . unwords . words . showSDocUnsafe . ppr