module SourceMap (generate) where
import SourceMap.Types
import qualified VLQ as VLQ
import Control.Monad hiding (forM_)
import Control.Monad.ST
import Data.Aeson hiding ((.=))
import Data.ByteString.Lazy (ByteString)
import qualified Data.ByteString.Lazy as Bytes
import Data.ByteString.Lazy.UTF8 (fromString)
import Data.ByteString.Builder (Builder(), lazyByteString, toLazyByteString)
import Data.Foldable (forM_)
import qualified Data.HashMap.Lazy as Map
import Data.Monoid
import Data.List
import Data.Maybe
import Data.Ord
import Data.STRef
import Data.Text (Text)
import Data.Text.Lazy.Encoding (decodeUtf8)
generate :: SourceMapping -> Value
generate SourceMapping{..} = Object (Map.fromList obj) where
obj = [("version",toJSON version)
,("file",toJSON smFile)
,("sources",toJSON sources)
,("names",toJSON names)
,("mappings",toJSON (decodeUtf8 (encodeMappings sources names smMappings)))] ++
[("sourceRoot",toJSON root) | Just root <- [smSourceRoot]]
names = nub $ mapMaybe mapName smMappings
sources = symbols mapSourceFile
symbols f = sort (nub (mapMaybe f smMappings))
encodeMappings :: [FilePath] -> [Text] -> [Mapping] -> ByteString
encodeMappings sources names = go . sortBy (comparing mapGenerated) where
go mappings = runST $ do
prevGenCol <- newSTRef 0
prevGenLine <- newSTRef 1
prevOrigCol <- newSTRef 0
prevOrigLine <- newSTRef 0
prevName <- newSTRef 0
prevSource <- newSTRef 0
result <- newSTRef (mempty :: Builder)
forM_ (zip [0::Integer ..] mappings) $ \(i,Mapping{..}) -> do
updating prevGenLine $ \previousGeneratedLine ->
if posLine mapGenerated /= previousGeneratedLine
then do prevGenCol .= 0
result += Bytes.replicate (fromIntegral (posLine mapGenerated previousGeneratedLine))
(fromIntegral (fromEnum ';'))
return (posLine mapGenerated)
else do when (i > 0)
(result += (fromString ","))
return previousGeneratedLine
updating prevGenCol $ \previousGeneratedColumn -> do
result += VLQ.encode (posColumn mapGenerated previousGeneratedColumn)
return (posColumn mapGenerated)
case liftM2 (,) mapSourceFile mapOriginal of
Nothing -> return ()
Just (source,original) -> do
updating prevSource $ \previousSource -> do
result += VLQ.encode (indexOf source sources previousSource)
return (indexOf source sources)
updating prevOrigLine $ \previousOriginalLine -> do
result += VLQ.encode (posLine original 1 previousOriginalLine)
return (posLine original 1)
updating prevOrigCol $ \previousOriginalColumn -> do
result += VLQ.encode (posColumn original previousOriginalColumn)
return (posColumn original)
forM_ mapName $ \name -> do
updating prevName $ \previousName -> do
result += VLQ.encode (indexOf name names previousName)
return (indexOf name names)
fmap toLazyByteString $ readSTRef result
updating r f = readSTRef r >>= \x -> f x >>= writeSTRef r
r += y = modifySTRef r (<> lazyByteString y)
x .= y = writeSTRef x y; infixr 1 .=
indexOf e xs = fromIntegral (fromMaybe 0 (elemIndex e xs))
version :: Integer
version = 3