{-# OPTIONS_GHC -fno-warn-orphans #-} {-# LANGUAGE OverloadedStrings, FlexibleInstances, GeneralizedNewtypeDeriving, CPP #-} -- | Haste AST pretty printing machinery. The actual printing happens in -- Haste.AST.Print. module Haste.AST.PP where import Data.Monoid import Data.String import Data.List (foldl') import Data.Array import Control.Monad import Control.Applicative import qualified Data.Map as M import qualified Data.ByteString.Lazy as BS import qualified Data.ByteString.Char8 as BSS import Data.ByteString (ByteString) import Haste.AST.Syntax (Name (..)) import Data.ByteString.Builder import Haste.Config import Haste.AST.PP.Opts type IndentLvl = Int -- | Final name for symbols. This name is what actually appears in the final -- JS dump, albeit "base 62"-encoded. newtype FinalName = FinalName Int deriving (Ord, Eq, Enum, Show) type NameSupply = (FinalName, M.Map Name FinalName) emptyNS :: NameSupply emptyNS = (FinalName 0, M.empty) newtype PP a = PP {unPP :: Config -> IndentLvl -> NameSupply -> Builder -> (NameSupply, Builder, a)} instance Monad PP where PP m >>= f = PP $ \cfg indentlvl ns b -> case m cfg indentlvl ns b of (ns', b', x) -> unPP (f x) cfg indentlvl ns' b' return x = PP $ \_ _ ns b -> (ns, b, x) instance Applicative PP where pure = return (<*>) = ap instance Functor PP where fmap f p = p >>= return . f -- | Convenience operator for using the PP () IsString instance. (.+.) :: PP () -> PP () -> PP () (.+.) = (>>) infixl 1 .+. -- | Generate the final name for a variable. -- Up until this point, internal names may be just about anything. -- The "final name" scheme ensures that all internal names end up with a -- proper, unique JS name. finalNameFor :: Name -> PP FinalName finalNameFor n = PP $ \_ _ ns@(nextN, m) b -> case M.lookup n m of Just n' -> (ns, b, n') _ -> ((succ nextN, M.insert n nextN m), b, nextN) -- | Returns the value of the given pretty-printer option. getOpt :: (PPOpts -> a) -> PP a getOpt f = getCfg (f . ppOpts) -- | Runs the given printer iff the specified PP option is True. whenOpt :: (PPOpts -> Bool) -> PP () -> PP () whenOpt f p = getOpt f >>= \x -> when x p -- | Returns the value of the given pretty-printer option. getCfg :: (Config -> a) -> PP a getCfg f = PP $ \cfg _ ns b -> (ns, b, f cfg) -- | Pretty print an AST. pretty :: Pretty a => Config -> a -> BS.ByteString pretty cfg ast = case runPP cfg (pp ast) of (b, _) -> toLazyByteString b -- | Run a pretty printer. runPP :: Config -> PP a -> (Builder, a) runPP cfg p = case unPP p cfg 0 emptyNS mempty of (_, b, x) -> (b, x) -- | Pretty-print a program and return the final name for its entry point. prettyProg :: Pretty a => Config -> Name -> a -> (Builder, Builder) prettyProg cfg mainSym ast = runPP cfg $ do pp ast hsnames <- getOpt preserveNames if hsnames then return $ buildStgName mainSym else buildFinalName <$> finalNameFor mainSym -- | JS-mangled version of an internal name. buildStgName :: Name -> Builder buildStgName (Name n mq) = byteString "$hs$" <> qual <> byteString (BSS.map mkjs n) where qual = case mq of Just (_, m) -> byteString (BSS.map mkjs m) <> byteString "$" _ -> mempty mkjs c | c >= 'a' && c <= 'z' = c | c >= 'A' && c <= 'Z' = c | c >= '0' && c <= '9' = c | c == '$' = c | otherwise = '_' -- | Turn a FinalName into a Builder. buildFinalName :: FinalName -> Builder buildFinalName (FinalName 0) = fromString "_0" buildFinalName (FinalName fn) = charUtf8 '_' <> go fn mempty where arrLen = 62 chars = listArray (0,arrLen-1) $ "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" go 0 acc = acc go n acc = let (rest, ix) = n `quotRem` arrLen in go rest (charUtf8 (chars ! ix) <> acc) -- | Indent the given builder another step. indent :: PP a -> PP a indent (PP p) = PP $ \cfg indentlvl ns b -> if useIndentation (ppOpts cfg) then p cfg (indentlvl+1) ns b else p cfg 0 ns b class Buildable a where put :: a -> PP () instance Buildable Builder where put x = PP $ \_ _ ns b -> (ns, b <> x, ()) instance Buildable ByteString where put = put . byteString instance Buildable String where put = put . stringUtf8 instance Buildable Char where put = put . charUtf8 instance Buildable Int where put = put . intDec instance Buildable Double where put d = case round d of n | fromIntegral n == d -> put $ intDec n | otherwise -> put $ doubleDec d instance Buildable Integer where put = put . integerDec instance Buildable Bool where put True = "true" put False = "false" -- | Emit indentation up to the current level. ind :: PP () ind = PP $ \cfg indentlvl ns b -> (ns, foldl' (<>) b (replicate indentlvl (indentStr $ ppOpts cfg)), ()) -- | A space character. sp :: PP () sp = whenOpt useSpaces $ put ' ' -- | A newline character. newl :: PP () newl = whenOpt useNewlines $ put '\n' -- | Indent the given builder and terminate it with a newline. line :: PP () -> PP () line p = do ind >> p whenOpt useNewlines $ put '\n' -- | Pretty print a list with the given separator. ppList :: Pretty a => PP () -> [a] -> PP () ppList sep (x:xs) = foldl' (\l r -> l >> sep >> pp r) (pp x) xs ppList _ _ = return () instance IsString (PP ()) where fromString = put . stringUtf8 -- | Pretty-printer class. Each part of the AST needs an instance of this. class Pretty a where pp :: a -> PP ()