sourcemap (empty) → 0.1.0.0
raw patch · 6 files changed
+307/−0 lines, 6 filesdep +aesondep +attoparsecdep +basesetup-changed
Dependencies added: aeson, attoparsec, base, bytestring, process, unordered-containers, utf8-string
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- sourcemap.cabal +27/−0
- src/SourceMap.hs +94/−0
- src/SourceMap/Types.hs +33/−0
- src/VLQ.hs +121/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2012, Chris Done++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Chris Done nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ sourcemap.cabal view
@@ -0,0 +1,27 @@+name: sourcemap+version: 0.1.0.0+synopsis: Implementation of source maps as proposed by Google and Mozilla.+description: Implementation of source maps, revision 3, proposed by Google and Mozilla here+ https://wiki.mozilla.org/DevTools/Features/SourceMap and here+ https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit+license: BSD3+license-file: LICENSE+author: Chris Done+stability: alpha+maintainer: chrisdone@gmail.com+copyright: 2012 Chris Done+category: Development+build-type: Simple+cabal-version: >=1.8++library+ exposed-modules: SourceMap, SourceMap.Types+ other-modules: VLQ+ hs-source-dirs: src+ build-depends: base ==4.5.*,+ bytestring >= 0.10.2.0,+ aeson,+ unordered-containers,+ attoparsec,+ process,+ utf8-string
+ src/SourceMap.hs view
@@ -0,0 +1,94 @@+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS -Wall #-}++module SourceMap 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.Foldable (forM_)+import qualified Data.HashMap.Lazy as Map+import Data.List+import Data.Maybe+import Data.Ord+import Data.STRef++-- | Generate the JSON.+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 (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] -> [String] -> [Mapping] -> ByteString+encodeMappings sources names = go . sortBy (comparing mapGenerated) where+ go mappings = runST $ do+ -- State.+ prevGenCol <- newSTRef 0+ prevGenLine <- newSTRef 1+ prevOrigCol <- newSTRef 0+ prevOrigLine <- newSTRef 0+ prevName <- newSTRef 0+ prevSource <- newSTRef 0+ result <- newSTRef Bytes.empty+ -- Generate the groupings.+ forM_ (zip [0::Integer ..] mappings) $ \(i,Mapping{..}) -> do+ -- Continuations on the same line are separated by “,”, whereas+ -- new lines are separted by “;”.+ 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 += ",")+ return previousGeneratedLine+ -- Original generated column (also offsetted from previous entries).+ updating prevGenCol $ \previousGeneratedColumn -> do+ result += VLQ.encode (posColumn mapGenerated - previousGeneratedColumn)+ return (posColumn mapGenerated)+ -- Optional additional fields.+ case liftM2 (,) mapSourceFile mapOriginal of+ Nothing -> return ()+ Just (source,original) -> do+ -- Source index.+ updating prevSource $ \previousSource -> do+ result += VLQ.encode (indexOf source sources - previousSource)+ return (indexOf source sources)+ -- Original line (also offsetted from previous entries).+ updating prevOrigLine $ \previousOriginalLine -> do+ result += VLQ.encode (posLine original - 1 - previousOriginalLine)+ return (posLine original - 1)+ -- Original column (also offsetted from previous entries).+ updating prevOrigCol $ \previousOriginalColumn -> do+ result += VLQ.encode (posColumn original - previousOriginalColumn)+ return (posColumn original)+ -- Optional name+ forM_ mapName $ \name -> do+ updating prevName $ \previousName -> do+ result += VLQ.encode (indexOf name names - previousName)+ return (indexOf name names)+ -- Return the byte buffer.+ readSTRef result++ updating r f = readSTRef r >>= \x -> f x >>= writeSTRef r+ r += y = modifySTRef r (\x -> Bytes.append x y)+ x .= y = writeSTRef x y; infixr 1 .=+ indexOf e xs = fromIntegral (fromMaybe 0 (elemIndex e xs))++-- | Format version.+version :: Integer+version = 3
+ src/SourceMap/Types.hs view
@@ -0,0 +1,33 @@+{-# OPTIONS -Wall #-}++-- | Types for the source maps.++module SourceMap.Types where++import Data.Int+import Data.Monoid+import Data.Function++-- | The source mapping.+data SourceMapping = SourceMapping+ { smFile :: FilePath+ , smSourceRoot :: Maybe FilePath+ , smMappings :: [Mapping]+ } deriving Show++-- | A mapping.+data Mapping = Mapping+ { mapGenerated :: Pos+ , mapOriginal :: Maybe Pos+ , mapSourceFile :: Maybe FilePath+ , mapName :: Maybe String+ } deriving Show++-- | A source position.+data Pos = Pos+ { posLine :: Int32+ , posColumn :: Int32+ } deriving (Eq,Show)++instance Ord Pos where+ compare a b = on compare posLine a b <> on compare posColumn a b
+ src/VLQ.hs view
@@ -0,0 +1,121 @@+-- | Implements Base 64-encoded VLQ for 32-bit+-- integers. Implementation copied from+-- https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java+--++{-# OPTIONS -Wall #-}++module VLQ+ (encode+ ,decode)+ where++import Data.Bits hiding (shift)+import Data.ByteString.Lazy (ByteString)+import qualified Data.ByteString.Lazy as B+import Data.Int+import Data.List+import Data.Word+import Prelude hiding ((>>))++-- | A Base64 VLQ digit can represent 5 bits, so it is base-32.+baseShift :: Int+baseShift = 5++-- | Base point.+base :: Int32+base = 1 << baseShift++-- | A mask of bits for a VLQ digit (11111), 31 decimal.+baseMask :: Int32+baseMask = base - 1++-- | The continuation bit is the 6th bit.+continuationBit :: Int32+continuationBit = base++-- | Converts from a two-complement value to a value where the sign+-- bit is is placed in the least significant bit. For example, as+-- decimals:+-- 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)+-- 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)+toVlqSigned :: Int32 -> Int32+toVlqSigned value =+ if value < 0+ then ((-value) << 1) + 1+ else (value << 1) + 0++-- | Converts to a two-complement value from a value where the sign+-- bit is is placed in the least significant bit. For example, as+-- decimals:+-- 2 (10 binary) becomes 1, 3 (11 binary) becomes -1+-- 4 (100 binary) becomes 2, 5 (101 binary) becomes -2+fromVlgSigned :: Int32 -> Int32+fromVlgSigned value =+ let value' = value >> 1+ in if (value & 1) == 1+ then -value'+ else value'++-- | Produces a ByteString containing a VLQ-encoded value of the given 32-bit integer.+encode :: Int32 -> ByteString+encode = B.map encodeBase64 . start where+ start 0 = B.singleton (fst (continue 0))+ start n = B.unfoldr go . toVlqSigned $ n++ go value+ | value <= 0 = Nothing+ | otherwise = Just (continue value)++ continue value =+ let digit = value & baseMask+ value' = value >> baseShift+ digit' = if value' > 0+ then digit .|. continuationBit+ else digit+ in (fromIntegral digit',value')+++-- | Decodes the given VLQ-encoded value into a 32-bit integer.+decode :: ByteString -> Int32+decode = fromVlgSigned . go (0,0) . B.map decodeBase64 where+ go (result,shift) bytes =+ case B.uncons bytes of+ Nothing -> result+ Just (c,next) ->+ let digit = fromIntegral c+ continuation = (digit & continuationBit) /= 0+ digit' = digit & baseMask+ result' = result + (digit' << shift)+ shift' = shift + baseShift+ in if continuation+ then go (result',shift') next+ else result'++-- | Base 64 characters.+base64Chars :: [Word8]+base64Chars = map (fromIntegral.fromEnum) "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"++-- | Encode the given number to a base 64 character.+encodeBase64 :: Word8 -> Word8+encodeBase64 i = maybe (error "Base 64 char must be between 0 and 63.")+ id+ (lookup i (zip [0..] base64Chars))++-- | Encode the given base 64 character to a number.+decodeBase64 :: Word8 -> Word8+decodeBase64 i = maybe (error "Not a valid base 65 digit.")+ id+ (lookup i (zip base64Chars [0..]))++-- | Makes the code more familiar to read. Shift-left.+(<<) :: Int32 -> Int -> Int32+(<<) = shiftL++-- | Makes the code more familiar to read. Shift-right.+(>>) :: Int32 -> Int -> Int32+(>>) = shiftR++-- | Makes the code more familiar to read. And.+(&) :: Int32 -> Int32 -> Int32+(&) = (.&.)