module Language.ECMAScript3.PrettyPrint (Pretty (..)
,javaScript
,renderStatements
,renderExpression
,PP (..)
,unsafeInExprStmt
) where
import qualified Text.PrettyPrint.Leijen as Pretty
import Text.PrettyPrint.Leijen hiding (Pretty, parens)
import Language.ECMAScript3.Syntax
import Prelude hiding (maybe, id)
import qualified Prelude
import Data.Char
import Numeric
parens :: Doc -> Doc
parens = Pretty.parens . align
class Pretty a where
prettyPrint :: a -> Doc
instance Pretty (JavaScript a) where
prettyPrint (Script _ ss) = prettyPrint ss
instance Pretty [Statement a] where
prettyPrint = vcat . map prettyPrint
instance Pretty (Expression a) where
prettyPrint = ppExpression True
parenList :: (a -> Doc) -> [a] -> Doc
parenList ppElem = encloseSep (text "(") (text ")") comma . map ppElem
isIf :: Statement a -> Bool
isIf IfSingleStmt {} = True
isIf IfStmt {} = True
isIf _ = False
instance Pretty (Statement a) where
prettyPrint s = case s of
BlockStmt _ ss -> asBlock ss
EmptyStmt _ -> semi
ExprStmt _ e | unsafeInExprStmt (e) -> parens (nest 4 (ppExpression True e)) <> semi
ExprStmt _ e | otherwise -> nest 4 (ppExpression True e) <> semi
IfSingleStmt _ test cons -> text "if" <+>
parens (ppExpression True test) </>
indented 3 cons
IfStmt _ test cons alt -> text "if" <+> parens (ppExpression True test) </>
indented 3 cons </> text "else"
<+> if isIf alt
then prettyPrint alt
else indented 3 alt
SwitchStmt _ e cases ->
text "switch" <+> parens (ppExpression True e) <> line <>
ppBlock 2 (vcat (map prettyPrint cases))
WhileStmt _ test body -> text "while" <+> parens (ppExpression True test) </>
indented 3 body
ReturnStmt _ Nothing -> text "return" <> semi
ReturnStmt _ (Just e) -> text "return" <+> nest 4 (ppExpression True e) <> semi
DoWhileStmt _ s e ->
text "do" </>
(indented 3 s </> text "while" <+> parens (ppExpression True e)
<> semi)
BreakStmt _ Nothing -> text "break" <> semi
BreakStmt _ (Just label) -> text "break" <+> prettyPrint label <> semi
ContinueStmt _ Nothing -> text "continue" <> semi
ContinueStmt _ (Just label) -> text"continue" <+> prettyPrint label
<> semi
LabelledStmt _ label s -> prettyPrint label <> colon </> prettyPrint s
ForInStmt p init e body ->
text "for" <+>
parens (prettyPrint init <+> text "in" <+> ppExpression True e) </>
indented 3 body
ForStmt _ init incr test body ->
text "for" <+>
parens (prettyPrint init <> semi <+> maybe incr (ppExpression True) <>
semi <+> maybe test (ppExpression True)) </>
indented 3 body
TryStmt _ stmt mcatch mfinally ->
text "try" </> inBlock stmt </> ppCatch </> ppFinally
where ppFinally = case mfinally of
Nothing -> empty
Just stmt -> text "finally" <> inBlock stmt
ppCatch = case mcatch of
Nothing -> empty
Just cc -> prettyPrint cc
ThrowStmt _ e -> text "throw" <+> ppExpression True e <> semi
WithStmt _ e s -> text "with" <+> parens (ppExpression True e)
</> indented 3 s
VarDeclStmt _ decls ->
text "var" <+> cat (punctuate comma (map (ppVarDecl True) decls))
<> semi
FunctionStmt _ name args body ->
text "function" <+> prettyPrint name <>
parenList prettyPrint args <+>
asBlock body
unsafeInExprStmt :: Expression a -> Bool
unsafeInExprStmt = unsafeInExprStmt_ 15
where unsafeInExprStmt_ prec e =
case e of
ObjectLit {} -> True
DotRef _ obj _ | prec >= 1 -> unsafeInExprStmt_ 1 obj
BracketRef _ obj _ | prec > 0 -> unsafeInExprStmt_ 1 obj
UnaryAssignExpr a op lv | (op `elem` [PostfixInc, PostfixDec])
&& (prec > 3) -> unsafeLv 2 lv
InfixExpr _ _ l _ | prec >= 5 -> unsafeInExprStmt_ 5 l
CondExpr _ c _ _ | prec >= 12 -> unsafeInExprStmt_ 12 c
AssignExpr _ _ lv _ | prec >= 13 -> unsafeLv 2 lv
ListExpr _ (e:_) | prec >= 14 -> unsafeInExprStmt_ 14 e
CallExpr _ e _ | prec >= 2 -> unsafeInExprStmt_ 2 e
FuncExpr {} -> True
_ -> False
unsafeLv prec lv = case lv of
LVar {} -> False
LDot _ obj _ -> unsafeInExprStmt_ prec obj
LBracket _ obj _ -> unsafeInExprStmt_ prec obj
instance Pretty (CatchClause a) where
prettyPrint (CatchClause _ id s) =
text "catch" <+> (parens.prettyPrint) id <+> inBlock s
instance Pretty (ForInit a) where
prettyPrint t = case t of
NoInit -> empty
VarInit vs -> text "var"
<+> cat (punctuate comma $ map (ppVarDecl False) vs)
ExprInit e -> ppExpression False e
instance Pretty (ForInInit a) where
prettyPrint t = case t of
ForInVar id -> text "var" <+> prettyPrint id
ForInLVal lv -> prettyPrint lv
instance Pretty (LValue a) where
prettyPrint lv = case lv of
LVar _ x -> printIdentifierName x
LDot _ e x -> ppObjInDotRef e ppMemberExpression <> text "." <> printIdentifierName x
LBracket _ e1 e2 -> ppMemberExpression e1 <>
brackets (ppExpression True e2)
instance Pretty (VarDecl a) where
prettyPrint = ppVarDecl True
instance Pretty (CaseClause a) where
prettyPrint c = case c of
CaseClause _ e ss ->
text "case" <+> ppExpression True e <> suffix ss
CaseDefault _ ss ->
text "default" <> suffix ss
where
suffix :: [Statement a] -> Doc
suffix [] = colon
suffix ss = colon <> nest 2 (linebreak <> prettyPrint ss)
instance Pretty InfixOp where
prettyPrint op = text $ case op of
OpMul -> "*"
OpDiv -> "/"
OpMod -> "%"
OpAdd -> "+"
OpSub -> "-"
OpLShift -> "<<"
OpSpRShift -> ">>"
OpZfRShift -> ">>>"
OpLT -> "<"
OpLEq -> "<="
OpGT -> ">"
OpGEq -> ">="
OpIn -> "in"
OpInstanceof -> "instanceof"
OpEq -> "=="
OpNEq -> "!="
OpStrictEq -> "==="
OpStrictNEq -> "!=="
OpBAnd -> "&"
OpBXor -> "^"
OpBOr -> "|"
OpLAnd -> "&&"
OpLOr -> "||"
instance Pretty AssignOp where
prettyPrint op = text $ case op of
OpAssign -> "="
OpAssignAdd -> "+="
OpAssignSub -> "-="
OpAssignMul -> "*="
OpAssignDiv -> "/="
OpAssignMod -> "%="
OpAssignLShift -> "<<="
OpAssignSpRShift -> ">>="
OpAssignZfRShift -> ">>>="
OpAssignBAnd -> "&="
OpAssignBXor -> "^="
OpAssignBOr -> "|="
instance Pretty PrefixOp where
prettyPrint op = text $ case op of
PrefixLNot -> "!"
PrefixBNot -> "~"
PrefixPlus -> "+"
PrefixMinus -> "-"
PrefixTypeof -> "typeof"
PrefixVoid -> "void"
PrefixDelete -> "delete"
instance Pretty (Prop a) where
prettyPrint p = case p of
PropId _ id -> prettyPrint id
PropString _ str -> dquotes $ text $ jsEscape str
PropNum _ n -> text (show n)
instance Pretty (Id a) where
prettyPrint (Id _ str) = printIdentifierName str
class PP a where
pp :: a -> Doc
instance Pretty a => PP a where
pp = prettyPrint
javaScript :: JavaScript a -> Doc
javaScript = prettyPrint
renderStatements :: [Statement a] -> String
renderStatements = show . prettyPrint
renderExpression :: Expression a -> String
renderExpression = show . prettyPrint
indented :: Int -> Statement a -> Doc
indented _ stmt@BlockStmt {} = prettyPrint stmt
indented width stmt = indent width (prettyPrint stmt)
inBlock:: Statement a -> Doc
inBlock s@(BlockStmt _ _) = prettyPrint s
inBlock s = asBlock [s]
asBlock :: [Statement a] -> Doc
asBlock [] = lbrace <$$> rbrace
asBlock ss = ppBlock 3 (prettyPrint ss)
ppBlock :: Int -> Doc -> Doc
ppBlock width doc = lbrace <> nest width (line <> doc) <$$> rbrace
ppVarDecl :: Bool -> VarDecl a -> Doc
ppVarDecl hasIn vd = case vd of
VarDecl _ id Nothing -> prettyPrint id
VarDecl _ id (Just e) ->
prettyPrint id <+> equals
</> maybeAlign (ppAssignmentExpression hasIn e)
where
maybeAlign =
case e of
FuncExpr {} -> Prelude.id
_ -> align
printIdentifierName :: String -> Doc
printIdentifierName = text
jsEscape:: String -> String
jsEscape "" = ""
jsEscape (ch:chs) = sel ch ++ jsEscape chs where
sel '\b' = "\\b"
sel '\f' = "\\f"
sel '\n' = "\\n"
sel '\r' = "\\r"
sel '\t' = "\\t"
sel '\v' = "\\v"
sel '\'' = "\\'"
sel '\"' = "\\\""
sel '\\' = "\\\\"
sel x = [x]
regexpEscape :: String -> String
regexpEscape = regexpEscapeChar True
where regexpEscapeChar :: Bool
-> String -> String
regexpEscapeChar first s =
case (s, first) of
("", True) -> "(?:)"
("", False)-> ""
("\\", _) -> "\\\\"
('\\':c:rest, _) -> '\\':c:(regexpEscapeChar False rest)
('/':rest, _) -> '\\':'/':regexpEscapeChar False rest
('*':rest, True) -> ('\\':'*':regexpEscapeChar False rest)
(c:rest, _) -> c:regexpEscapeChar False rest
ppPrimaryExpression :: Expression a -> Doc
ppPrimaryExpression e = case e of
ThisRef _ -> text "this"
VarRef _ id -> prettyPrint id
NullLit _ -> text "null"
BoolLit _ True -> text "true"
BoolLit _ False -> text "false"
NumLit _ n -> double n
IntLit _ n -> int n
StringLit _ str -> dquotes $ text $ jsEscape str
RegexpLit _ reg g ci -> text "/" <> (text (regexpEscape reg)) <> text "/" <>
(if g then text "g" else empty) <>
(if ci then text "i" else empty)
ArrayLit _ es -> list $ map (ppAssignmentExpression True) es
ObjectLit _ xs -> encloseSep lbrace rbrace comma $ map ppField xs
where ppField (f,v)= prettyPrint f <> colon <+> ppAssignmentExpression True v
_ -> parens $ ppExpression True e
ppMemberExpression :: Expression a -> Doc
ppMemberExpression e = case e of
FuncExpr _ name params body ->
text "function" <+> maybe name (\n -> prettyPrint n <> space) <>
parenList prettyPrint params <+>
asBlock body
DotRef _ obj id -> ppObjInDotRef obj ppMemberExpression <> text "." <> prettyPrint id
BracketRef _ obj key ->
ppMemberExpression obj <> brackets (ppExpression True key)
NewExpr _ ctor args ->
text "new" <+> ppMemberExpression ctor <> ppArguments args
_ -> ppPrimaryExpression e
ppCallExpression :: Expression a -> Doc
ppCallExpression e = case e of
CallExpr _ f args -> ppCallExpression f <> ppArguments args
DotRef _ obj id -> ppObjInDotRef obj ppCallExpression <> text "." <> prettyPrint id
BracketRef _ obj key -> ppCallExpression obj
<> brackets (ppExpression True key)
_ -> ppMemberExpression e
ppObjInDotRef :: Expression a -> (Expression a -> Doc) -> Doc
ppObjInDotRef i@(IntLit _ _) _ = parens (ppPrimaryExpression i)
ppObjInDotRef e p = p e
ppArguments :: [Expression a] -> Doc
ppArguments = parenList (ppAssignmentExpression True)
ppLHSExpression :: Expression a -> Doc
ppLHSExpression = ppCallExpression
ppPostfixExpression :: Expression a -> Doc
ppPostfixExpression e = case e of
UnaryAssignExpr _ PostfixInc e' -> prettyPrint e' <> text "++"
UnaryAssignExpr _ PostfixDec e' -> prettyPrint e' <> text "--"
_ -> ppLHSExpression e
ppUnaryExpression :: Expression a -> Doc
ppUnaryExpression e = case e of
PrefixExpr _ op e' -> prettyPrint op <> prefixSpace op <> ppUnaryExpression e'
UnaryAssignExpr _ PrefixInc e' -> text "++" <> prettyPrint e'
UnaryAssignExpr _ PrefixDec e' -> text "--" <> prettyPrint e'
_ -> ppPostfixExpression e
prefixSpace :: PrefixOp -> Doc
prefixSpace op = case op of
PrefixLNot -> empty
PrefixBNot -> empty
PrefixPlus -> empty
PrefixMinus -> empty
PrefixTypeof -> space
PrefixVoid -> space
PrefixDelete -> space
ppMultiplicativeExpression :: Expression a -> Doc
ppMultiplicativeExpression e = case e of
InfixExpr _ op e1 e2 | op `elem` [OpMul, OpDiv, OpMod] ->
ppMultiplicativeExpression e1 </> prettyPrint op </> ppUnaryExpression e2
_ -> ppUnaryExpression e
ppAdditiveExpression :: Expression a -> Doc
ppAdditiveExpression e = case e of
InfixExpr _ op e1 e2 | op `elem` [OpAdd, OpSub] ->
ppAdditiveExpression e1 </> prettyPrint op
</> ppMultiplicativeExpression e2
_ -> ppMultiplicativeExpression e
ppShiftExpression :: Expression a -> Doc
ppShiftExpression e = case e of
InfixExpr _ op e1 e2 | op `elem` [OpLShift, OpSpRShift, OpZfRShift] ->
ppShiftExpression e1 </> prettyPrint op </> ppAdditiveExpression e2
_ -> ppAdditiveExpression e
ppRelationalExpression :: Bool -> Expression a -> Doc
ppRelationalExpression hasIn e =
let opsNoIn = [OpLT, OpGT, OpLEq, OpGEq, OpInstanceof]
ops = if hasIn then OpIn:opsNoIn else opsNoIn
in case e of
InfixExpr _ op e1 e2 | op `elem` ops ->
ppRelationalExpression hasIn e1 </> prettyPrint op
</> ppShiftExpression e2
_ -> ppShiftExpression e
ppEqualityExpression :: Bool -> Expression a -> Doc
ppEqualityExpression hasIn e = case e of
InfixExpr _ op e1 e2 | op `elem` [OpEq, OpNEq, OpStrictEq, OpStrictNEq] ->
ppEqualityExpression hasIn e1 </> prettyPrint op </>
ppRelationalExpression hasIn e2
_ -> ppRelationalExpression hasIn e
ppBitwiseANDExpression :: Bool -> Expression a -> Doc
ppBitwiseANDExpression hasIn e = case e of
InfixExpr _ op@OpBAnd e1 e2 -> ppBitwiseANDExpression hasIn e1 </>
prettyPrint op </>
ppEqualityExpression hasIn e2
_ -> ppEqualityExpression hasIn e
ppBitwiseXORExpression :: Bool -> Expression a -> Doc
ppBitwiseXORExpression hasIn e = case e of
InfixExpr _ op@OpBXor e1 e2 -> ppBitwiseXORExpression hasIn e1 </>
prettyPrint op </>
ppBitwiseANDExpression hasIn e2
_ -> ppBitwiseANDExpression hasIn e
ppBitwiseORExpression :: Bool -> Expression a -> Doc
ppBitwiseORExpression hasIn e = case e of
InfixExpr _ op@OpBOr e1 e2 -> ppBitwiseORExpression hasIn e1 </>
prettyPrint op </>
ppBitwiseXORExpression hasIn e2
_ -> ppBitwiseXORExpression hasIn e
ppLogicalANDExpression :: Bool -> Expression a -> Doc
ppLogicalANDExpression hasIn e = case e of
InfixExpr _ op@OpLAnd e1 e2 -> ppLogicalANDExpression hasIn e1 </>
prettyPrint op </>
ppBitwiseORExpression hasIn e2
_ -> ppBitwiseORExpression hasIn e
ppLogicalORExpression :: Bool -> Expression a -> Doc
ppLogicalORExpression hasIn e = case e of
InfixExpr _ op@OpLOr e1 e2 -> ppLogicalORExpression hasIn e1 </>
prettyPrint op </>
ppLogicalANDExpression hasIn e2
_ -> ppLogicalANDExpression hasIn e
ppConditionalExpression :: Bool -> Expression a -> Doc
ppConditionalExpression hasIn e = case e of
CondExpr _ c et ee -> ppLogicalORExpression hasIn c </> text "?" <+>
ppAssignmentExpression hasIn et </> colon <+>
ppAssignmentExpression hasIn ee
_ -> ppLogicalORExpression hasIn e
ppAssignmentExpression :: Bool -> Expression a -> Doc
ppAssignmentExpression hasIn e = case e of
AssignExpr _ op l r -> prettyPrint l </> prettyPrint op </>
ppAssignmentExpression hasIn r
_ -> ppConditionalExpression hasIn e
ppExpression :: Bool -> Expression a -> Doc
ppExpression hasIn e = case e of
ListExpr _ es -> parenList (ppExpression hasIn) es
_ -> ppAssignmentExpression hasIn e
maybe :: Maybe a -> (a -> Doc) -> Doc
maybe Nothing _ = empty
maybe (Just a) f = f a