module Text.Jasmine.Pretty
(
renderJS
) where
import Data.Char
import Data.List
import Data.Monoid
import Language.JavaScript.Parser
import qualified Blaze.ByteString.Builder as BB
import qualified Blaze.ByteString.Builder.Char.Utf8 as BS
import qualified Data.ByteString.Lazy as LB
(<>) :: BB.Builder -> BB.Builder -> BB.Builder
(<>) a b = mappend a b
(<+>) :: BB.Builder -> BB.Builder -> BB.Builder
(<+>) a b = mconcat [a, (text " "), b]
hcat :: (Monoid a) => [a] -> a
hcat xs = mconcat xs
empty :: BB.Builder
empty = mempty
text :: String -> BB.Builder
text s = BS.fromString s
char :: Char -> BB.Builder
char c = BS.fromChar c
comma :: BB.Builder
comma = BS.fromChar ','
punctuate :: a -> [a] -> [a]
punctuate p xs = intersperse p xs
renderJS :: JSNode -> BB.Builder
renderJS (JSEmpty l) = (renderJS l)
renderJS (JSIdentifier s) = text s
renderJS (JSDecimal i) = text i
renderJS (JSOperator s) = text s
renderJS (JSExpression xs) = rJS xs
renderJS (JSSourceElements xs) = rJS (map fixBlock $ fixSourceElements xs)
renderJS (JSSourceElementsTop xs)= rJS (fixTop $ map fixBlock $ fixSourceElements xs)
renderJS (JSFunction s p xs) = (text "function") <+> (renderJS s) <> (text "(") <> (commaList p) <> (text ")") <> (renderJS xs)
renderJS (JSFunctionBody xs) = (text "{") <> (rJS xs) <> (text "}")
renderJS (JSFunctionExpression [] p xs) = (text "function") <> (text "(") <> (commaList p) <> (text ")") <> (renderJS xs)
renderJS (JSFunctionExpression s p xs) = (text "function") <+> (rJS s) <> (text "(") <> (commaList p) <> (text ")") <> (renderJS xs)
renderJS (JSArguments xs) = (text "(") <> (commaListList $ map fixLiterals xs) <> (text ")")
renderJS (JSBlock x) = (text "{") <> (renderJS x) <> (text "}")
renderJS (JSIf c (JSLiteral ";"))= (text "if") <> (text "(") <> (renderJS c) <> (text ")")
renderJS (JSIf c t) = (text "if") <> (text "(") <> (renderJS c) <> (text ")") <> (renderJS $ fixBlock t)
renderJS (JSIfElse c t (JSLiteral ";")) = (text "if") <> (text "(") <> (renderJS c) <> (text ")") <> (renderJS t)
<> (text "else")
renderJS (JSIfElse c t e) = (text "if") <> (text "(") <> (renderJS c) <> (text ")") <> (renderJS t)
<> (text "else") <> (spaceOrBlock $ fixBlock e)
renderJS (JSMemberDot xs y) = (rJS xs) <> (text ".") <> (renderJS y)
renderJS (JSMemberSquare xs x) = (rJS xs) <> (text "[") <> (renderJS x) <> (text "]")
renderJS (JSLiteral l) = (text l)
renderJS (JSStringLiteral s l) = empty <> (char s) <> (text l) <> (char s)
renderJS (JSUnary l ) = text l
renderJS (JSArrayLiteral xs) = (text "[") <> (rJS xs) <> (text "]")
renderJS (JSBreak [] []) = (text "break")
renderJS (JSBreak [] _xs) = (text "break")
renderJS (JSBreak is _xs) = (text "break") <+> (rJS is)
renderJS (JSCallExpression "()" xs) = (rJS xs)
renderJS (JSCallExpression t xs) = (char $ head t) <> (rJS xs) <> (if ((length t) > 1) then (char $ last t) else empty)
renderJS (JSCase (JSExpression [JSStringLiteral sepa s]) xs) = (text "case") <> (renderJS (JSStringLiteral sepa s))
<> (char ':') <> (renderJS xs)
renderJS (JSCase e xs) = (text "case") <+> (renderJS e) <> (char ':') <> (renderJS xs)
renderJS (JSCatch i [] s) = (text "catch") <> (char '(') <> (renderJS i) <> (char ')') <> (renderJS s)
renderJS (JSCatch i c s) = (text "catch") <> (char '(') <> (renderJS i) <>
(text " if ") <> (rJS c) <> (char ')') <> (renderJS s)
renderJS (JSContinue is) = (text "continue") <> (rJS is)
renderJS (JSDefault xs) = (text "default") <> (char ':') <> (renderJS xs)
renderJS (JSDoWhile s e _ms) = (text "do") <> (renderJS s) <> (text "while") <> (char '(') <> (renderJS e) <> (char ')')
renderJS (JSElision xs) = (char ',') <> (rJS xs)
renderJS (JSExpressionBinary o e1 e2) = (rJS e1) <> (text o) <> (rJS e2)
renderJS (JSExpressionParen e) = (char '(') <> (renderJS e) <> (char ')')
renderJS (JSExpressionPostfix o e) = (rJS e) <> (text o)
renderJS (JSExpressionTernary c v1 v2) = (rJS c) <> (char '?') <> (rJS v1) <> (char ':') <> (rJS v2)
renderJS (JSFinally b) = (text "finally") <> (renderJS b)
renderJS (JSFor e1 e2 e3 s) = (text "for") <> (char '(') <> (commaList e1) <> (char ';')
<> (rJS e2) <> (char ';') <> (rJS e3) <> (char ')') <> (renderJS $ fixBlock s)
renderJS (JSForIn e1 e2 s) = (text "for") <> (char '(') <> (rJS e1) <+> (text "in")
<+> (renderJS e2) <> (char ')') <> (renderJS $ fixBlock s)
renderJS (JSForVar e1 e2 e3 s) = (text "for") <> (char '(') <> (text "var") <+> (commaList e1) <> (char ';')
<> (rJS e2) <> (char ';') <> (rJS e3) <> (char ')') <> (renderJS $ fixBlock s)
renderJS (JSForVarIn e1 e2 s) = (text "for") <> (char '(') <> (text "var") <+> (renderJS e1) <+> (text "in")
<+> (renderJS e2) <> (char ')') <> (renderJS $ fixBlock s)
renderJS (JSHexInteger i) = (text $ show i)
renderJS (JSLabelled l v) = (renderJS l) <> (text ":") <> (renderJS v)
renderJS (JSObjectLiteral xs) = (text "{") <> (commaList xs) <> (text "}")
renderJS (JSPropertyNameandValue n vs) = (renderJS n) <> (text ":") <> (rJS vs)
renderJS (JSRegEx s) = (text s)
renderJS (JSReturn []) = (text "return")
renderJS (JSReturn [JSLiteral ";"]) = (text "return;")
renderJS (JSReturn xs) = (text "return") <> (if (spaceNeeded xs) then (text " ") else (empty)) <> (rJS $ fixSourceElements xs)
renderJS (JSThrow e) = (text "throw") <+> (renderJS e)
renderJS (JSStatementList xs) = rJS (fixSourceElements $ map fixBlock xs)
renderJS (JSSwitch e xs) = (text "switch") <> (char '(') <> (renderJS e) <> (char ')') <>
(char '{') <> (rJS $ fixSemis xs) <> (char '}')
renderJS (JSTry e xs) = (text "try") <> (renderJS e) <> (rJS xs)
renderJS (JSVarDecl i []) = (renderJS i)
renderJS (JSVarDecl i xs) = (renderJS i) <> (text "=") <> (rJS xs)
renderJS (JSVariables kw xs) = (text kw) <+> (commaList xs)
renderJS (JSWhile e (JSLiteral ";")) = (text "while") <> (char '(') <> (renderJS e) <> (char ')')
renderJS (JSWhile e s) = (text "while") <> (char '(') <> (renderJS e) <> (char ')') <> (renderJS s)
renderJS (JSWith e s) = (text "with") <> (char '(') <> (renderJS e) <> (char ')') <> (rJS s)
rJS :: [JSNode] -> BB.Builder
rJS xs = hcat $ map renderJS xs
commaList :: [JSNode] -> BB.Builder
commaList [] = empty
commaList xs = (hcat $ (punctuate comma (toDoc xs') ++ trail))
where
(xs', trail) = if (last xs == JSLiteral ",") then (init xs, [comma]) else (xs,[])
commaListList :: [[JSNode]] -> BB.Builder
commaListList xs = (hcat $ punctuate comma $ map rJS xs)
toDoc :: [JSNode] -> [BB.Builder]
toDoc xs = map renderJS xs
spaceOrBlock :: JSNode -> BB.Builder
spaceOrBlock (JSBlock xs) = renderJS (JSBlock xs)
spaceOrBlock x = (text " ") <> (renderJS x)
fixTop :: [JSNode] -> [JSNode]
fixTop [] = []
fixTop xs = if (last xs == (JSLiteral ";")) then (init xs) else (xs)
fixSourceElements :: [JSNode] -> [JSNode]
fixSourceElements xs = fixSemis $ myFix xs
myFix :: [JSNode] -> [JSNode]
myFix [] = []
myFix [x] = [x]
myFix (x:(JSFunction v1 v2 v3):xs) = x : (JSLiteral "\n") : myFix ((JSFunction v1 v2 v3) : xs)
myFix ((JSExpression x):(JSExpression y):xs) = (JSExpression x):(JSLiteral ";"):myFix ((JSExpression y):xs)
myFix ((JSExpression x):(JSBlock y):xs) = (JSExpression x):(JSLiteral ";"):myFix ((JSBlock y):xs)
myFix ((JSBlock x) :(JSBlock y):xs) = (JSBlock x) :(JSLiteral ";"):myFix ((JSBlock y):xs)
myFix ((JSBlock x) :(JSExpression y):xs) = (JSBlock x) :(JSLiteral ";"):myFix ((JSExpression y):xs)
myFix ((JSVariables t1 x1s):(JSLiteral l):(JSVariables t2 x2s):xs)
| t1 == t2 = myFix ((JSVariables t1 (x1s++x2s)):xs)
| otherwise = (JSVariables t1 x1s):myFix ((JSLiteral l):(JSVariables t2 x2s):xs)
myFix ((JSVariables t1 x1s):(JSVariables t2 x2s):xs)
| t1 == t2 = myFix ((JSVariables t1 (x1s++x2s)):xs)
| otherwise = (JSVariables t1 x1s):myFix ((JSVariables t2 x2s):xs)
myFix ((JSLiteral ";"):(JSLiteral ";"):xs) = myFix ((JSLiteral ";"):xs)
myFix ((JSLiteral ";"):(JSLiteral "" ):xs) = myFix ((JSLiteral ""):xs)
myFix (x:xs) = x : myFix xs
fixLiterals :: [JSNode] -> [JSNode]
fixLiterals [] = []
fixLiterals ((JSStringLiteral d1 s1):(JSExpressionBinary "+" [JSStringLiteral d2 s2] r):xs)
| d1 == d2 = fixLiterals ((JSStringLiteral d1 (s1++s2)):(r++xs))
| otherwise = (JSStringLiteral d1 s1):fixLiterals ((JSExpressionBinary "+" [JSStringLiteral d2 s2] r):xs)
fixLiterals ((JSExpressionBinary "+" [JSStringLiteral d1 s1] [JSStringLiteral d2 s2]):xs)
| d1 == d2 = fixLiterals ((JSStringLiteral d1 (s1++s2)):xs)
| otherwise = (JSExpressionBinary "+" [JSStringLiteral d1 s1] [JSStringLiteral d2 s2]):fixLiterals xs
fixLiterals (x:xs) = x:fixLiterals xs
fixSemis :: [JSNode] -> [JSNode]
fixSemis xs = fixSemis' $ filter (\x -> x /= JSLiteral ";" && x /= JSLiteral "") xs
fixSemis' :: [JSNode] -> [JSNode]
fixSemis' [] = []
fixSemis' [JSContinue [JSLiteral ";"]] = [JSContinue []]
fixSemis' [x] = [x]
fixSemis' ((JSIf c (JSReturn [JSLiteral ";"])):xs) = (JSIf c (JSReturn [JSLiteral ";"])):(fixSemis' xs)
fixSemis' ((JSIf c (JSContinue [JSLiteral ";"])):xs) = (JSIf c (JSContinue [JSLiteral ";"])):(fixSemis' xs)
fixSemis' (x:(JSLiteral "\n"):xs) = x:(JSLiteral "\n"):(fixSemis' xs)
fixSemis' ((JSCase e1 ((JSStatementList []))):(JSCase e2 x):xs) = (JSCase e1 ((JSStatementList []))):fixSemis' ((JSCase e2 x):xs)
fixSemis' (x:xs) = x:(JSLiteral ";"):fixSemis' xs
fixBlock :: JSNode -> JSNode
fixBlock (JSBlock (JSStatementList [x])) = x
fixBlock (JSBlock (JSStatementList xs)) = fixBlock' (JSBlock (JSStatementList (fixSourceElements xs)))
fixBlock x = x
fixBlock' :: JSNode -> JSNode
fixBlock' (JSBlock (JSStatementList [x])) = x
fixBlock' (JSBlock (JSStatementList [x,JSLiteral ""])) = x
fixBlock' x = x
spaceNeeded :: [JSNode] -> Bool
spaceNeeded xs =
let
str = LB.unpack $ BB.toLazyByteString $ rJS xs
in
head str /= (fromIntegral $ ord '(')
_case0 :: JSNode
_case0 = JSSourceElements
[
JSExpression [JSIdentifier "x",JSOperator "=",JSDecimal "1"],
JSEmpty (JSLiteral ";")
]
_case1 :: [JSNode]
_case1 = [JSExpression
[JSIdentifier "a",JSOperator "=",JSDecimal "1"]
,JSEmpty (JSLiteral ";")
]
_case4 :: JSNode
_case4 = JSExpression
[
JSIdentifier "opTypeNames",
JSOperator "=",
JSObjectLiteral
[JSPropertyNameandValue (JSStringLiteral '\'' "\\n") [JSStringLiteral '"' "NEWLINE"],
JSPropertyNameandValue (JSStringLiteral '\'' ";") [JSStringLiteral '"' "SEMICOLON"],
JSPropertyNameandValue (JSStringLiteral '\'' ",") [JSStringLiteral '"' "COMMA"]
]
]
_case5 :: JSNode
_case5 = JSSourceElements
[JSFunction (JSIdentifier "load")
[JSIdentifier "s"]
(JSFunctionBody
[
JSSourceElements
[
JSIf
(JSExpression [JSUnary "typeof ",JSIdentifier "s",
JSExpressionBinary "!=" [JSStringLiteral '"' "string"] []])
(JSReturn [JSExpression [JSIdentifier "s"],JSLiteral ";"])
,JSLiteral ";"
,JSExpression [JSIdentifier "a",JSOperator "=",JSDecimal "1"]
]
]
)
]
_case6 :: JSNode
_case6 = JSSourceElements []
_case7 :: JSNode
_case7 = JSSourceElements
[
JSFunction (JSIdentifier "load") [JSIdentifier "s"]
(JSFunctionBody
[JSSourceElements
[JSIf
(JSExpression [JSUnary "typeof ",JSIdentifier "s",JSExpressionBinary "!=" [JSStringLiteral '"' "string"] []])
(JSReturn [JSExpression [JSIdentifier "s"],JSLiteral ";"])
,
JSExpression [JSIdentifier "evaluate",JSArguments [[JSDecimal "1"]]]
]
]
)
]
_case8 :: JSNode
_case8 = undefined
_case9 :: JSNode
_case9 = undefined
_case10 :: JSNode
_case10 = JSSourceElements
[
JSBlock (JSStatementList [JSExpression [JSIdentifier "zero"]]),
JSExpression [JSIdentifier "one"],
JSLiteral ";",
JSExpression [JSIdentifier "two"],
JSBlock (JSStatementList
[JSExpression [JSIdentifier "three"],
JSExpression [JSIdentifier "four"],
JSLiteral ";",
JSExpression [JSIdentifier "five"],
JSLiteral ";",
JSBlock (JSStatementList
[JSExpression [JSIdentifier "six"],
JSLiteral ";",
JSBlock (JSStatementList
[
JSExpression [JSIdentifier "seven"],
JSLiteral ""
]
)
]
)
]
)
]
_case11 :: JSNode
_case11 = JSSourceElements
[
JSExpression [JSIdentifier "a",JSExpressionBinary "+" [JSDecimal "1"] []],
JSLiteral ";",
JSLiteral ";"
]
_case12 :: JSNode
_case12 = undefined
_case13 :: JSNode
_case13 = JSSourceElements
[
JSFor [JSExpression [JSIdentifier "i",JSOperator "=",JSDecimal "0"]]
[]
[]
(JSBlock (
JSStatementList [JSVariables "var" [JSVarDecl (JSIdentifier "t") [JSDecimal "1"]]]
)
),
JSLiteral ";",
JSForVar [JSVarDecl (JSIdentifier "i") [JSDecimal "0"],JSVarDecl (JSIdentifier "j") [JSDecimal "1"]]
[]
[]
(JSBlock (
JSStatementList [JSExpression [JSIdentifier "x",JSOperator "=",JSDecimal "1"]]
)
)
]
_case14 :: JSNode
_case14 = undefined
_case15 :: JSNode
_case15 = JSSourceElements
[
JSExpression [JSIdentifier "a",JSExpressionBinary "+" [JSDecimal "1"] []],
JSLiteral ";",
JSLiteral ";"
]
_case16 :: JSNode
_case16 = JSSourceElementsTop
[
JSFor [JSExpression [JSIdentifier "i",JSOperator "=",JSDecimal "0"]] [] []
(JSBlock
(JSStatementList
[
JSVariables "var" [JSVarDecl (JSIdentifier "t") [JSDecimal "1"]],
JSLiteral ";",
JSLiteral ""
]
)
),
JSExpression [JSIdentifier "x",JSOperator "=",JSDecimal "1"],
JSLiteral ";"
]
_case17 :: JSNode
_case17 = undefined
_case18 :: JSNode
_case18 = JSSourceElementsTop
[
JSIf
(JSExpression [JSUnary "typeof ",JSIdentifier "s",JSExpressionBinary "!=" [JSStringLiteral '"' "string"] []])
(JSReturn [JSLiteral ";"]),
JSWhile (JSExpression [JSUnary "--",JSIdentifier "n",JSExpressionBinary ">=" [JSDecimal "0"] []])
(JSExpression
[JSIdentifier "s",JSOperator "+=",JSIdentifier "t"]
),
JSLiteral ";"
]
_case19 :: JSNode
_case19 = JSSourceElementsTop
[
JSFunction (JSIdentifier "f") []
(JSFunctionBody
[
JSSourceElements
[
JSReturn [JSExpression [JSIdentifier "n"],JSLiteral ";"],
JSExpression
[
JSIdentifier "x",JSOperator "=",JSDecimal "1"
]
]
]
)
]
_case20 :: [JSNode]
_case20 = [
JSReturn [JSExpression [JSIdentifier "n"],JSLiteral ";"],
JSExpression
[JSIdentifier "x",JSOperator "=",JSDecimal "1"]
]
_case21 :: JSNode
_case21 = JSSourceElementsTop
[
JSIf (JSExpression [JSUnary "!",JSIdentifier "u"])
(JSContinue [JSLiteral ";"]),
JSExpression [JSIdentifier "t",JSOperator "=",JSIdentifier "n"]
]
_case22 :: JSNode
_case22 = undefined
_case23 :: JSNode
_case23 = JSSourceElementsTop
[
JSBlock
(
JSStatementList
[
JSThrow
(JSExpression
[
JSLiteral "new ",
JSIdentifier "TypeError",
JSArguments
[
[
JSStringLiteral '"' "Function.prototype.apply called on",
JSExpressionBinary "+"
[
JSStringLiteral '"' " uncallable object"
]
[]
]
]
]
),
JSLiteral ""
]
)
]
_case24 :: JSNode
_case24 = JSSourceElementsTop
[
JSExpression
[JSIdentifier "x",
JSOperator "=",
JSStringLiteral '"' "hello ",
JSExpressionBinary "+"
[JSStringLiteral '"' "world"] []
]
,
JSLiteral ";"
]
_case25 :: JSNode
_case25 = JSSourceElementsTop
[
JSTry
(JSBlock (JSStatementList []))
[
JSCatch
(JSIdentifier "e")
[]
(JSBlock
(JSStatementList
[
JSContinue [JSLiteral ";"]
]
)
)
]
]
_case26 :: JSNode
_case26 = JSSourceElementsTop
[
JSVariables "var"
[
JSVarDecl (JSIdentifier "newlines")
[JSMemberDot [JSIdentifier "spaces"] (JSIdentifier "match"),JSArguments [[JSRegEx "/\\n/g"]]]
],
JSVariables "var"
[
JSVarDecl (JSIdentifier "newlines") [JSMemberDot [JSIdentifier "spaces"] (JSIdentifier "match"),
JSArguments [[JSRegEx "/\\n/g"]]]
]
]
_case27 :: JSNode
_case27 = JSSourceElementsTop
[
JSExpression [JSExpressionBinary "+" [JSStringLiteral '"' "mary"] [JSStringLiteral '"' "had"]]
]
_case28 :: JSNode
_case28 = JSSourceElementsTop
[
JSThrow
(
JSExpression
[
JSLiteral "new ",
JSIdentifier "TypeError",
JSArguments
[
[
JSExpressionBinary "+"
[JSStringLiteral '"' "Function.prototype.apply called on"]
[JSStringLiteral '"' " uncallable object"]
]
]
]
)
]