packages feed

oughta-0.1.0.0: src/Oughta/Extract.hs

{-# LANGUAGE OverloadedStrings #-}

-- | Extract Lua programs embedded in other files
module Oughta.Extract
  ( SourceMap
  , sourceMapFile
  , lookupSourceMap
  , LuaProgram
  , programText
  , sourceMap
  , addPrefix
  , plainLuaProgram
  , fromLines
  , fromLineComments
  ) where

import Data.Foldable qualified as Foldable
import Data.IntMap.Strict (IntMap)
import Data.IntMap.Strict qualified as IntMap
import Data.Maybe qualified as Maybe
import Data.Text (Text)
import Data.Text qualified as Text

-- | Map from Lua line numbers to source line numbers
data SourceMap
  = SourceMap
    { sourceMapFile :: !Text
    , sourceMapLines :: !(IntMap Int)
    }

empty :: FilePath -> SourceMap
empty path = SourceMap (Text.pack path) IntMap.empty

lookupSourceMap ::
  -- | File path
  Text ->
  -- | Lua line number
  Int ->
  SourceMap ->
  Int
lookupSourceMap path luaLine (SourceMap f m) =
  if path == f
  then Maybe.fromMaybe luaLine (IntMap.lookup luaLine m)
  else luaLine

-- | Lua program paired with a 'SourceMap'
data LuaProgram
  = LuaProgram
    { programText :: !Text
    , sourceMap :: !SourceMap
    }

-- | Add a prefix (i.e., prelude).
--
-- Bumps all subsequent line numbers
addPrefix :: Text -> LuaProgram -> LuaProgram
addPrefix pfx prog =
  let pfx' = pfx <> "\n" in
  prog
  { programText = pfx' <> programText prog
  , sourceMap =
      let sm = sourceMap prog in
      sm { sourceMapLines = IntMap.mapKeys (+ Text.count "\n" pfx') (sourceMapLines sm) }
  }

-- | A standalone Lua program
plainLuaProgram :: FilePath -> Text -> LuaProgram
plainLuaProgram path txt = LuaProgram txt (empty path)

-- | Extract a Lua program embedded in certain lines of another file
fromLines :: FilePath -> (Text -> Maybe Text) -> Text -> LuaProgram
fromLines path filt txt =
  -- luaLineNo starts at 2 becaus we add a newline before each line
  let (_, t, m) = Foldable.foldl' go (2, "", empty path) (zip [1..] (Text.lines txt)) in
  LuaProgram t m
  where
    go (luaLineNo, t, sm) (sourceLineNo, line) =
      case filt line of
        Just line' ->
          -- trace ("LINE '" ++ Text.unpack line' ++ " at " ++ show luaLineNo ++ " is " ++ show sourceLineNo) $
          ( luaLineNo + 1
          , t <> "\n" <> line'
          , sm { sourceMapLines = IntMap.insert luaLineNo sourceLineNo (sourceMapLines sm) }
          )
        Nothing ->
          ( luaLineNo
          , t
          , sm
          )

-- | Extract a Lua program embedded in the line comments of another file
fromLineComments ::
  FilePath ->
  -- | Start of comment marker, e.g., @"# "@
  Text ->
  -- | Whole file
  Text ->
  LuaProgram
fromLineComments path c = fromLines path (Text.stripPrefix c)