haste-compiler-0.5.1.0: src/Data/JSTarget/Print.hs
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE FlexibleInstances, GADTs, OverloadedStrings, CPP #-}
module Data.JSTarget.Print () where
import Prelude hiding (LT, GT)
import Data.JSTarget.AST
import Data.JSTarget.Op
import Data.JSTarget.PP as PP
import Data.ByteString.Builder
import Control.Monad
import Data.Char
import Numeric (showHex)
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.UTF8 as BS
instance Pretty Var where
pp (Foreign name) = do
put name
doComment <- getOpt externalAnnotation
when doComment . put $ byteString "/* EXTERNAL */"
pp (Internal name@(Name _ _) comment _) = do
hsnames <- getOpt preserveNames
if hsnames
then put $ buildStgName name
else do
pp name
doComment <- getOpt nameComments
when doComment $ do
when (not $ BS.null comment) $ do
put $ byteString "/* "
put comment
put $ byteString " */"
instance Pretty Name where
pp name = finalNameFor name >>= put . buildFinalName
instance Pretty LHS where
pp (NewVar _ v) = "var " .+. pp v
pp (LhsExp _ ex) = pp ex
instance Pretty Lit where
pp (LNum d) = put d
pp (LStr s) = "\"" .+. put (fixQuotes $ BS.toString s) .+. "\""
where
fixQuotes ('\\':xs) = "\\\\" ++ fixQuotes xs
fixQuotes ('"':xs) = '\\':'"' : fixQuotes xs
fixQuotes ('\'':xs) = '\\':'\'' : fixQuotes xs
fixQuotes ('\r':xs) = '\\':'r' : fixQuotes xs
fixQuotes ('\n':xs) = '\\':'n' : fixQuotes xs
fixQuotes (x:xs)
| ord x <= 127 = x : fixQuotes xs
| otherwise = toHex x ++ fixQuotes xs
fixQuotes _ = []
pp (LBool b) = put b
pp (LInt n) = put n
pp (LNull) = "null"
-- | Generate a Haskell \uXXXX escape sequence for a char if it's >127.
toHex :: Char -> String
toHex c =
case ord c of
n | n < 127 -> [c]
| otherwise -> "\\u" ++ exactlyFour (showHex (n `rem` 65536) "")
-- | Truncate and pad a string to exactly four characters. '0' is used for padding.
exactlyFour :: String -> String
exactlyFour s =
pad (4-len) $ drop (len-4) s
where
len = length s
pad 0 cs = cs
pad n cs = '0' : pad (n-1) cs
-- | Default separator; comma followed by space, if spaces are enabled.
sep :: PP ()
sep = "," .+. sp
instance Pretty Exp where
pp (Var v) =
pp v
pp (Lit l) =
pp l
pp (JSLit l) =
put l
pp (Not ex) = do
case neg ex of
Just ex' -> pp ex'
_ -> if expPrec (Not ex) > expPrec ex
then "!(" .+. pp ex .+. ")"
else "!" .+. pp ex
pp bop@(BinOp _ _ _) =
case norm bop of
BinOp op a b -> opParens op a b
ex -> pp ex
pp (Fun args body) = do
"function(" .+. ppList sep args .+. "){" .+. newl
indent $ pp body
ind .+. "}"
pp (Call _ call f args) = do
case call of
Normal True -> "B(" .+. normalCall .+. ")"
Normal False -> normalCall
Fast True -> "B(" .+. fastCall .+. ")"
Fast False -> fastCall
Method m ->
pp f .+. put (BS.cons '.' m) .+. "(" .+. ppList sep args .+. ")"
where
normalCall = "A(" .+. pp f .+. ",[" .+. ppList sep args .+. "])"
fastCall = ppCallFun f .+. "(" .+. ppList sep args .+. ")"
ppCallFun fun@(Fun _ _) = "(" .+. pp fun .+. ")"
ppCallFun fun = pp fun
pp e@(Index arr ix) = do
if expPrec e > expPrec arr
then "(" .+. pp arr .+. ")"
else pp arr
"[" .+. pp ix .+. "]"
pp (Arr exs) = do
"[" .+. ppList sep exs .+. "]"
pp (AssignEx l r) = do
pp l .+. sp .+. "=" .+. sp .+. pp r
pp e@(IfEx c th el) = do
if expPrec e > expPrec c
then "(" .+. pp c .+. ")"
else pp c
sp .+. "?" .+. sp .+. pp th .+. sp .+. ":" .+. sp .+. pp el
pp (Eval x) = do
"E(" .+. pp x .+. ")"
pp (Thunk True x) = do
"new T(function(){" .+. newl .+. indent (pp x) .+. "})"
pp (Thunk False x) = do
"new T(function(){" .+. newl .+. indent (pp x) .+. "},1)"
instance Pretty (Var, Exp) where
pp (v, ex) = pp v .+. sp .+. "=" .+. sp .+. pp ex
instance Pretty Bool where
pp True = "true"
pp False = "false"
-- | Print a series of NewVars at once, to avoid unnecessary "var" keywords.
ppAssigns :: Stm -> PP ()
ppAssigns stm = do
line $ "var " .+. ppList sep assigns .+. ";"
pp next
where
(assigns, next) = gather [] stm
gather as (Assign (NewVar _ v) ex nxt) = gather ((v, ex):as) nxt
gather as nxt = (reverse as, nxt)
-- | Returns the final statement in a case branch.
finalStm :: Stm -> PP Stm
finalStm s =
case s of
Assign _ _ s' -> finalStm s'
Case _ _ _ next -> finalStm next
Forever s' -> finalStm s'
_ -> return s
instance Pretty Stm where
pp (Case cond def alts next) = do
prettyCase cond def alts
pp next
pp (Forever stm) = do
line "while(1){"
indent $ pp stm
line "}"
pp s@(Assign lhs ex next) = do
case lhs of
_ | lhs == blackHole ->
line (pp ex .+. ";") >> pp next
NewVar _ _ ->
ppAssigns s
LhsExp _ _ ->
line (pp lhs .+. sp .+. "=" .+. sp .+. pp ex .+. ";") >> pp next
pp (Return ex) = do
line $ "return " .+. pp ex .+. ";"
pp (Cont) = do
line "continue;"
pp (Stop) = do
return ()
pp (Tailcall call) = do
line $ "return new F(function(){return " .+. pp call .+. ";});"
pp (ThunkRet ex) = do
line $ "return " .+. pp ex .+. ";"
neg :: Exp -> Maybe Exp
neg (BinOp Eq a b) = Just $ BinOp Neq a b
neg (BinOp Neq a b) = Just $ BinOp Eq a b
neg (BinOp GT a b) = Just $ BinOp LTE a b
neg (BinOp LT a b) = Just $ BinOp GTE a b
neg (BinOp GTE a b) = Just $ BinOp LT a b
neg (BinOp LTE a b) = Just $ BinOp GT a b
neg _ = Nothing
-- | Turn eligible case statements into if statements.
prettyCase :: Exp -> Stm -> [Alt] -> PP ()
prettyCase cond def [(con, branch)] = do
case (def, branch) of
(_, Stop) -> do
line $ "if(" .+. pp (neg' (test con)) .+. "){"
indent $ pp def
line "}"
(Stop, _) -> do
line $ "if(" .+. pp (test con) .+. "){"
indent $ pp branch
line "}"
_ -> do
line $ "if(" .+. pp (test con) .+."){"
indent $ pp branch
line "}else{"
indent $ pp def
line "}"
where
test (Lit (LBool True)) = cond
test (Lit (LBool False)) = Not cond
test (Lit (LNum 0)) = Not cond
test c = BinOp Eq cond c
neg' c = maybe (Not c) id (neg c)
prettyCase _ def [] = do
pp def
prettyCase cond def alts = do
line $ "switch(" .+. pp cond .+. "){"
indent $ do
mapM_ pp alts
line $ "default:"
indent $ pp def
line "}"
instance Pretty Alt where
pp (con, branch) = do
line $ "case " .+. pp con .+. ":"
indent $ do
pp branch
s <- finalStm branch
case s of
Return _ -> return ()
Cont -> return ()
_ -> line "break;";
opParens :: BinOp -> Exp -> Exp -> PP ()
opParens Sub a (BinOp Sub (Lit (LNum 0)) b) =
opParens Add a b
opParens Sub a (Lit (LNum n)) | n < 0 =
opParens Add a (Lit (LNum (-n)))
opParens Sub (Lit (LNum 0)) b =
case b of
BinOp _ _ _ -> " -(" .+. pp b .+. ")"
_ -> " -" .+. pp b
opParens op a b = do
let bparens = case b of
Lit (LNum n) | n < 0 -> \x -> "(".+. pp x .+. ")"
_ -> parensR
parensL a .+. put (stringUtf8 $ show op) .+. bparens b
where
parensL x = if expPrec x < opPrec op
then "(" .+. pp x .+. ")"
else pp x
parensR x = if expPrec x <= opPrec op
then "(" .+. pp x .+. ")"
else pp x
-- | Normalize an operator expression by shifting parentheses to the left for
-- all associative operators and eliminating comparisons with true/false.
norm :: Exp -> Exp
norm (BinOp op a (BinOp op' b c)) | op == op' && opIsAssoc op =
norm (BinOp op (BinOp op a b) c)
norm (BinOp Eq a (Lit (LBool True))) = norm a
norm (BinOp Eq (Lit (LBool True)) b) = norm b
norm (BinOp Eq a (Lit (LBool False))) = Not (norm a)
norm (BinOp Eq (Lit (LBool False)) b) = Not (norm b)
norm (BinOp Neq a (Lit (LBool True))) = Not (norm a)
norm (BinOp Neq (Lit (LBool True)) b) = Not (norm b)
norm (BinOp Neq a (Lit (LBool False))) = norm a
norm (BinOp Neq (Lit (LBool False)) b) = norm b
norm e = e