@@ -0,0 +1,368 @@
1+module Generate.JavaScript (showErr, jsModule) where
2+
3+import Control.Arrow (first,second)
4+import Control.Monad (liftM,(<=<),join,ap)
5+import Data.Char (isAlpha,isDigit)
6+import qualified Data.List as List
7+import qualified Data.Map as Map
8+import qualified Data.Set as Set
9+import Data.Either (partitionEithers)
10+import qualified Text.Pandoc as Pan
11+
12+import Unique
13+import Generate.Cases
14+import SourceSyntax.Everything
15+import SourceSyntax.Location
16+import qualified Transform.SortDefinitions as SD
17+
18+deprime :: String -> String
19+deprime = map (\c -> if c == '\'' then '$' else c)
20+
21+showErr :: String -> String
22+showErr err = globalAssign "Elm.Main" (jsFunc "elm" body)
23+ where msg = show . concatMap (++"<br>") . lines $ err
24+ body = "var T = Elm.Text(elm);\n\
25+ \return { main : T.text(T.monospace(" ++ msg ++ ")) };"
26+
27+indent = concatMap f
28+ where f '\n' = "\n "
29+ f c = [c]
30+
31+internalImports name =
32+ [ ("N" , "Elm.Native"),
33+ ("_N", "N.Utils(elm)"),
34+ ("_L", "N.List(elm)"),
35+ ("_E", "N.Error(elm)"),
36+ ("_J", "N.JavaScript(elm)"),
37+ ("_str", "_J.toString"),
38+ ("$moduleName", quoted name)
39+ ]
40+
41+parens s = "(" ++ s ++ ")"
42+brackets s = "{" ++ s ++ "}"
43+commaSep = List.intercalate ", "
44+dotSep = List.intercalate "."
45+jsObj = brackets . commaSep
46+jsList ss = "["++ List.intercalate "," ss ++"]"
47+jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
48+assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
49+ret e = "\nreturn "++ e ++";"
50+quoted s = "'" ++ concatMap f s ++ "'"
51+ where f '\n' = "\\n"
52+ f '\'' = "\\'"
53+ f '\t' = "\\t"
54+ f '\"' = "\\\""
55+ f '\\' = "\\\\"
56+ f c = [c]
57+
58+globalAssign n e = "\n" ++ assign' n e ++ ";"
59+assign' n e = n ++ " = " ++ e
60+
61+jsModule :: MetadataModule t v -> String
62+jsModule modul =
63+ run $ do
64+ body <- toJS . fst . SD.flattenLets [] $ program modul
65+ foreignImport <- mapM importEvent (foreignImports modul)
66+ return $ concat [ setup ("Elm": names modul)
67+ , globalAssign ("Elm." ++ modName)
68+ (jsFunc "elm" $ makeProgram body foreignImport) ]
69+ where
70+ modName = dotSep (names modul)
71+ makeProgram body foreignImport =
72+ concat [ "\nvar " ++ usefulFuncs ++ ";"
73+ , concatMap jsImport (imports modul)
74+ , concat foreignImport
75+ , assign "_op" "{}"
76+ , body
77+ , concatMap exportEvent $ foreignExports modul
78+ , jsExports
79+ ]
80+ setup names = concatMap (\n -> globalAssign n $ n ++ " || {}") .
81+ map dotSep . drop 2 . List.inits $ init names
82+ usefulFuncs = commaSep (map (uncurry assign') (internalImports modName))
83+
84+ jsExports = setup ("elm" : names modul) ++
85+ ret (assign' ("elm." ++ modName) (brackets exs))
86+ where
87+ exs = indent . commaSep . concatMap (pair . deprime) $ "_op" : exports modul
88+ pair x | isOp x = []
89+ | otherwise = ["\n" ++ x ++ " : " ++ x]
90+
91+
92+ importEvent (js,base,elm,_) =
93+ do v <- toJS' base
94+ return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
95+ , "\ndocument.addEventListener('", js
96+ , "_' + elm.id, function(e) { elm.notify(", elm
97+ , ".id, e.value); });" ]
98+
99+ exportEvent (js,elm,_) =
100+ concat [ "\nA2( Signal.lift, function(v) { "
101+ , "var e = document.createEvent('Event');"
102+ , "e.initEvent('", js, "_' + elm.id, true, true);"
103+ , "e.value = v;"
104+ , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
105+
106+jsImport (modul, method) =
107+ concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
108+ where
109+ starters = "\nvar " : repeat "\n"
110+ values = map (\name -> name ++ " || {}") (init subnames) ++
111+ ["Elm." ++ modul ++ parens "elm"]
112+ subnames = map dotSep . tail . List.inits $ split modul
113+
114+ split names = case go [] names of
115+ (name, []) -> [name]
116+ (name, ns) -> name : split ns
117+ go name str = case str of
118+ '.':rest -> (reverse name, rest)
119+ c:rest -> go (c:name) rest
120+ [] -> (reverse name, [])
121+
122+
123+class ToJS a where
124+ toJS :: a -> Unique String
125+
126+
127+instance ToJS (Def t v) where
128+
129+ -- TODO: Make this handle patterns besides plain variables
130+ toJS (Def (PVar x) e)
131+ | isOp x = globalAssign ("_op['" ++ x ++ "']") `liftM` toJS' e
132+ | otherwise = assign (deprime x) `liftM` toJS' e
133+
134+ toJS (Def pattern e@(L s _)) =
135+ do n <- guid
136+ let x = "_" ++ show n
137+ var = L s . Var
138+ toDef y' = let y = deprime y' in
139+ Def (PVar y) (L s $ Case (var x) [(pattern, var y)])
140+ stmt <- assign x `liftM` toJS' e
141+ vars <- toJS . map toDef . Set.toList $ SD.boundVars pattern
142+ return (stmt ++ vars)
143+
144+ toJS (TypeAnnotation _ _) = return ""
145+
146+
147+instance ToJS a => ToJS [a] where
148+ toJS xs = concat `liftM` mapM toJS xs
149+
150+toJS' :: LExpr t v -> Unique String
151+toJS' (L span expr) =
152+ case expr of
153+ MultiIf ps -> multiIfToJS span ps
154+ Case e cases -> caseToJS span e cases
155+ _ -> toJS expr
156+
157+remove x e = "_N.remove('" ++ deprime x ++ "', " ++ e ++ ")"
158+addField x v e = "_N.insert('" ++ deprime x ++ "', " ++ v ++ ", " ++ e ++ ")"
159+setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
160+ where f (x,v) = "['" ++ deprime x ++ "'," ++ v ++ "]"
161+access x e = e ++ "." ++ deprime x
162+makeRecord kvs = record `liftM` collect kvs
163+ where
164+ combine r (k,v) = Map.insertWith (++) k v r
165+ collect = liftM (List.foldl' combine Map.empty) . mapM prep
166+ prep (k, e) =
167+ do v <- toJS' e
168+ return (deprime k, [v])
169+ fields fs =
170+ brackets ("\n "++List.intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
171+ hidden = fields . map (second jsList) .
172+ filter (not . null . snd) . Map.toList . Map.map tail
173+ record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
174+
175+
176+instance ToJS Literal where
177+ toJS lit =
178+ case lit of
179+ Chr c -> return $ quoted [c]
180+ Str s -> return $ "_str" ++ parens (quoted s)
181+ IntNum n -> return $ show n
182+ FloatNum n -> return $ show n
183+ Boolean b -> return $ if b then "true" else "false"
184+
185+
186+instance ToJS (Expr t v) where
187+ toJS expr =
188+ case expr of
189+ Var x -> return (deprime x)
190+ Literal lit -> toJS lit
191+ Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
192+ Access e x -> access x `liftM` toJS' e
193+ Remove e x -> remove x `liftM` toJS' e
194+ Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
195+ Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
196+ setField fs' `liftM` toJS' e
197+ Record fs -> makeRecord fs
198+ Binop op e1 e2 -> binop op e1 e2
199+
200+ Lambda p e@(L s _) -> liftM (fastFunc . ret) (toJS' body)
201+ where
202+ fastFunc body
203+ | length args < 2 || length args > 9 = foldr jsFunc body args
204+ | otherwise = "F" ++ show (length args) ++ parens (jsFunc (commaSep args) body)
205+ (args, body) = first reverse $ foldr depattern ([], innerBody) (zip patterns [1..])
206+
207+ depattern (pattern,n) (args, body) =
208+ case pattern of
209+ PVar x -> (deprime x : args, body)
210+ _ -> let arg = "arg" ++ show n
211+ in (arg:args, L s (Case (L s (Var arg)) [(pattern, body)]))
212+
213+ (patterns, innerBody) = collect [p] e
214+
215+ collect patterns lexpr@(L _ expr) =
216+ case expr of
217+ Lambda p e -> collect (p:patterns) e
218+ _ -> (patterns, lexpr)
219+
220+ App e1 e2 -> jsApp e1 e2
221+ Let defs e -> jsLet $ SD.flattenLets defs e
222+
223+ ExplicitList es ->
224+ do es' <- mapM toJS' es
225+ return $ "_J.toList" ++ parens (jsList es')
226+
227+ Data name es ->
228+ do fs <- mapM toJS' es
229+ return $ case name of
230+ "[]" -> jsNil
231+ "::" -> jsCons (head fs) ((head . tail) fs)
232+ _ -> jsObj $ ("ctor:" ++ show name) : fields
233+ where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
234+
235+ Markdown doc -> return $ "Text.text('" ++ pad ++ md ++ pad ++ "')"
236+ where pad = "<div style=\"height:0;width:0;\"> </div>"
237+ md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
238+
239+jsApp e1 e2 =
240+ do f <- toJS' func
241+ as <- mapM toJS' args
242+ return $ case as of
243+ [a] -> f ++ parens a
244+ _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
245+ where
246+ (func, args) = go [e2] e1
247+ go args e =
248+ case e of
249+ (L _ (App e1 e2)) -> go (e2 : args) e1
250+ _ -> (e, args)
251+
252+formatMarkdown = concatMap f
253+ where f '\'' = "\\'"
254+ f '\n' = "\\n"
255+ f '"' = "\""
256+ f c = [c]
257+
258+multiIfToJS span ps =
259+ case last ps of
260+ (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
261+ _ -> format ("_E.If" ++ parens ("$moduleName," ++ quoted (show span))) ps
262+ where
263+ format base ps =
264+ foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
265+ f (b,e) = do b' <- toJS' b
266+ e' <- toJS' e
267+ return (b' ++ " ? " ++ e')
268+
269+jsLet (defs,e') = do ds <- mapM toJS defs
270+ e <- toJS' e'
271+ return $ jsFunc "" (concat ds ++ ret e) ++ "()"
272+
273+caseToJS span e ps = do
274+ (tempVar,match) <- caseToMatch ps
275+ e' <- toJS' e
276+ let (match',stmt) = case e of
277+ L _ (Var x) -> (matchSubst [(tempVar,deprime x)] match, "")
278+ _ -> (match, assign tempVar e')
279+ matches <- matchToJS span match'
280+ return $ jsFunc "" (stmt ++ matches) ++ "()"
281+
282+matchToJS span match =
283+ case match of
284+ Match name clauses def ->
285+ do cases <- concat `liftM` mapM (clauseToJS span name) clauses
286+ finally <- matchToJS span def
287+ let isLiteral p = case p of
288+ Clause (Right _) _ _ -> True
289+ _ -> False
290+ access = if any isLiteral clauses then "" else ".ctor"
291+ return $ concat [ "\nswitch (", name, access, ") {"
292+ , indent cases, "\n}", finally ]
293+ Fail -> return ("_E.Case" ++ parens ("$moduleName," ++ quoted (show span)))
294+ Break -> return "break;"
295+ Other e -> ret `liftM` toJS' e
296+ Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
297+ where
298+ dropEnd acc [] = acc
299+ dropEnd acc (m:ms) =
300+ case m of
301+ Other _ -> acc ++ [m]
302+ _ -> dropEnd (acc ++ [m]) ms
303+
304+clauseToJS span var (Clause value vars e) = do
305+ let vars' = map (\n -> var ++ "._" ++ show n) [0..]
306+ s <- matchToJS span $ matchSubst (zip vars vars') e
307+ return $ concat [ "\ncase ", case value of
308+ Right (Boolean True) -> "true"
309+ Right (Boolean False) -> "false"
310+ Right lit -> show lit
311+ Left name -> quoted $ case List.elemIndices '.' name of
312+ [] -> name
313+ is -> drop (last is + 1) name
314+ , ":", indent s ]
315+
316+jsNil = "_L.Nil"
317+jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
318+jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
319+jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
320+
321+
322+binop op e1 e2
323+ | op == "Basics.." =
324+ do ss <- mapM toJS' (e1 : collect [] e2)
325+ return . jsFunc "$" . ret $ apply "$" ss
326+ | op == "Basics.<|" =
327+ do arg <- toJS' e2
328+ funcs <- mapM toJS' (collect [] e1)
329+ return (apply arg funcs)
330+ | otherwise =
331+ binopNormal op `liftM` toJS' e1 `ap` toJS' e2
332+ where
333+ apply = foldr (\f arg-> f ++ parens arg)
334+
335+ collect es e =
336+ case e of
337+ L _ (Binop op e1 e2) | op == "Basics.." -> collect (es ++ [e1]) e2
338+ _ -> es ++ [e]
339+
340+binopNormal op s1 s2 =
341+ case Map.lookup op opDict of
342+ Just e -> e
343+ Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, s1, s2 ])
344+ where
345+ func | isOp op' = "_op['" ++ op' ++ "']"
346+ | otherwise = op'
347+ (prefix, op') = case List.elemIndices '.' op of
348+ [] -> ("", op)
349+ xs -> splitAt (last xs + 1) op
350+
351+ opDict = Map.fromList $ basics ++
352+ [ ("::", jsCons s1 s2)
353+ , ("List.++", "_L.append" ++ parens (s1 ++ "," ++ s2)) ]
354+
355+ ops = pow : map (\op -> (op, parens (s1 ++ op ++ s2))) ["+","-","*","/","&&","||"]
356+ where pow = ("^" , "Math.pow(" ++ s1 ++ "," ++ s2 ++ ")")
357+
358+ basics = ops ++ map (\(op,e) -> ("Basics." ++ op, e))
359+ (ops ++ [ ("<|", s1 ++ parens s2)
360+ , ("|>", s2 ++ parens s1)
361+ , ("==", "_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
362+ , ("/=", "!_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
363+ , ("<" , jsCompare s1 s2 "<0")
364+ , (">" , jsCompare s1 s2 ">0")
365+ , ("<=", jsCompare s1 s2 "<1")
366+ , (">=", jsCompare s1 s2 ">-1")
367+ , ("div", parens (s1 ++ "/" ++ s2 ++ "|0"))
368+ ])