1module Generate.JavaScript (jsModule) where
3import Control.Arrow (first,second)
4import Control.Monad (liftM,(<=<),join,ap)
5import Data.Char (isAlpha,isDigit)
6import qualified Data.List as List
7import qualified Data.Map as Map
8import qualified Data.Set as Set
9import Data.Either (partitionEithers)
10import qualified Text.Pandoc as Pan
14import SourceSyntax.Everything
15import SourceSyntax.Location
16import qualified Transform.SortDefinitions as SD
18deprime :: String -> String
19deprime = map (\c -> if c == '\'' then '$' else c)
26 [ ("N" , "Elm.Native"),
27 ("_N", "N.Utils(elm)"),
28 ("_L", "N.List(elm)"),
29 ("_E", "N.Error(elm)"),
30 ("_J", "N.JavaScript(elm)"),
31 ("_str", "_J.toString"),
32 ("$moduleName", quoted name)
35parens s = "(" ++ s ++ ")"
36brackets s = "{" ++ s ++ "}"
37commaSep = List.intercalate ", "
38dotSep = List.intercalate "."
39jsObj = brackets . commaSep
40jsList ss = "["++ List.intercalate "," ss ++"]"
41jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
42assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
43ret e = "\nreturn "++ e ++";"
44quoted s = "'" ++ concatMap f s ++ "'"
52globalAssign n e = "\n" ++ assign' n e ++ ";"
53assign' n e = n ++ " = " ++ e
55jsModule :: MetadataModule t v -> String
58 body <- toJS . fst . SD.flattenLets [] $ program modul
59 foreignImport <- mapM importEvent (foreignImports modul)
60 return $ concat [ setup ("Elm": names modul)
61 , globalAssign ("Elm." ++ modName)
62 (jsFunc "elm" $ makeProgram body foreignImport) ]
64 modName = dotSep (names modul)
65 makeProgram body foreignImport =
66 concat [ "\nvar " ++ usefulFuncs ++ ";"
67 , concatMap jsImport (imports modul)
68 , concat foreignImport
71 , concatMap exportEvent $ foreignExports modul
74 setup names = concatMap (\n -> globalAssign n $ n ++ " || {}") .
75 map dotSep . drop 2 . List.inits $ init names
76 usefulFuncs = commaSep (map (uncurry assign') (internalImports modName))
78 jsExports = setup ("elm" : names modul) ++
79 ret (assign' ("elm." ++ modName) (brackets exs))
81 exs = indent . commaSep . concatMap (pair . deprime) $ "_op" : exports modul
83 | otherwise = ["\n" ++ x ++ " : " ++ x]
86 importEvent (js,base,elm,_) =
88 return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
89 , "\ndocument.addEventListener('", js
90 , "_' + elm.id, function(e) { elm.notify(", elm
91 , ".id, e.value); });" ]
93 exportEvent (js,elm,_) =
94 concat [ "\nA2( Signal.lift, function(v) { "
95 , "var e = document.createEvent('Event');"
96 , "e.initEvent('", js, "_' + elm.id, true, true);"
98 , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
100jsImport (modul, method) =
101 concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
103 starters = "\nvar " : repeat "\n"
104 values = map (\name -> name ++ " || {}") (init subnames) ++
105 ["Elm." ++ modul ++ parens "elm"]
106 subnames = map dotSep . tail . List.inits $ split modul
108 split names = case go [] names of
110 (name, ns) -> name : split ns
111 go name str = case str of
112 '.':rest -> (reverse name, rest)
113 c:rest -> go (c:name) rest
114 [] -> (reverse name, [])
118 toJS :: a -> Unique String
121instance ToJS (Def t v) where
123 -- TODO: Make this handle patterns besides plain variables
124 toJS (Def (PVar x) e)
125 | isOp x = globalAssign ("_op['" ++ x ++ "']") `liftM` toJS' e
126 | otherwise = assign (deprime x) `liftM` toJS' e
128 toJS (Def pattern e@(L s _)) =
130 let x = "_" ++ show n
132 toDef y' = let y = deprime y' in
133 Def (PVar y) (L s $ Case (var x) [(pattern, var y)])
134 stmt <- assign x `liftM` toJS' e
135 vars <- toJS . map toDef . Set.toList $ SD.boundVars pattern
136 return (stmt ++ vars)
138 toJS (TypeAnnotation _ _) = return ""
141instance ToJS a => ToJS [a] where
142 toJS xs = concat `liftM` mapM toJS xs
144toJS' :: LExpr t v -> Unique String
147 MultiIf ps -> multiIfToJS span ps
148 Case e cases -> caseToJS span e cases
151remove x e = "_N.remove('" ++ deprime x ++ "', " ++ e ++ ")"
152addField x v e = "_N.insert('" ++ deprime x ++ "', " ++ v ++ ", " ++ e ++ ")"
153setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
154 where f (x,v) = "['" ++ deprime x ++ "'," ++ v ++ "]"
155access x e = e ++ "." ++ deprime x
156makeRecord kvs = record `liftM` collect kvs
158 combine r (k,v) = Map.insertWith (++) k v r
159 collect = liftM (List.foldl' combine Map.empty) . mapM prep
162 return (deprime k, [v])
164 brackets ("\n "++List.intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
165 hidden = fields . map (second jsList) .
166 filter (not . null . snd) . Map.toList . Map.map tail
167 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
170instance ToJS Literal where
173 Chr c -> return $ quoted [c]
174 Str s -> return $ "_str" ++ parens (quoted s)
175 IntNum n -> return $ show n
176 FloatNum n -> return $ show n
177 Boolean b -> return $ if b then "true" else "false"
180instance ToJS (Expr t v) where
183 Var x -> return (deprime x)
184 Literal lit -> toJS lit
185 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
186 Access e x -> access x `liftM` toJS' e
187 Remove e x -> remove x `liftM` toJS' e
188 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
189 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
190 setField fs' `liftM` toJS' e
191 Record fs -> makeRecord fs
192 Binop op e1 e2 -> binop op e1 e2
194 Lambda p e@(L s _) -> liftM fastFunc (toJS' body)
197 | length args < 2 || length args > 9 =
198 foldr (\arg bod -> jsFunc arg (ret bod)) body args
200 "F" ++ show (length args) ++ parens (jsFunc (commaSep args) (ret body))
202 (args, body) = first reverse $ foldr depattern ([], innerBody) (zip patterns [1..])
204 depattern (pattern,n) (args, body) =
206 PVar x -> (deprime x : args, body)
207 _ -> let arg = "arg" ++ show n
208 in (arg:args, L s (Case (L s (Var arg)) [(pattern, body)]))
210 (patterns, innerBody) = collect [p] e
212 collect patterns lexpr@(L _ expr) =
214 Lambda p e -> collect (p:patterns) e
215 _ -> (patterns, lexpr)
217 App e1 e2 -> jsApp e1 e2
218 Let defs e -> jsLet $ SD.flattenLets defs e
221 do es' <- mapM toJS' es
222 return $ "_J.toList" ++ parens (jsList es')
225 do fs <- mapM toJS' es
226 return $ case name of
228 "::" -> jsCons (head fs) ((head . tail) fs)
229 _ -> jsObj $ ("ctor:" ++ show name) : fields
230 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
232 Markdown doc -> return $ "Text.text('" ++ pad ++ md ++ pad ++ "')"
233 where pad = "<div style=\"height:0;width:0;\"> </div>"
234 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
238 as <- mapM toJS' args
241 _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
243 (func, args) = go [e2] e1
246 (L _ (App e1 e2)) -> go (e2 : args) e1
249formatMarkdown = concatMap f
257 (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
258 _ -> format ("_E.If" ++ parens ("$moduleName," ++ quoted (show span))) ps
261 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
262 f (b,e) = do b' <- toJS' b
264 return (b' ++ " ? " ++ e')
266jsLet (defs,e') = do ds <- mapM toJS defs
268 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
270caseToJS span e ps = do
271 (tempVar,match) <- caseToMatch ps
273 let (match',stmt) = case e of
274 L _ (Var x) -> (matchSubst [(tempVar,deprime x)] match, "")
275 _ -> (match, assign tempVar e')
276 matches <- matchToJS span match'
277 return $ jsFunc "" (stmt ++ matches) ++ "()"
279matchToJS span match =
281 Match name clauses def ->
282 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
283 finally <- matchToJS span def
284 let isLiteral p = case p of
285 Clause (Right _) _ _ -> True
287 access = if any isLiteral clauses then "" else ".ctor"
288 return $ concat [ "\nswitch (", name, access, ") {"
289 , indent cases, "\n}", finally ]
290 Fail -> return ("_E.Case" ++ parens ("$moduleName," ++ quoted (show span)))
291 Break -> return "break;"
292 Other e -> ret `liftM` toJS' e
293 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
298 Other _ -> acc ++ [m]
299 _ -> dropEnd (acc ++ [m]) ms
301clauseToJS span var (Clause value vars e) = do
302 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
303 s <- matchToJS span $ matchSubst (zip vars vars') e
304 return $ concat [ "\ncase ", case value of
305 Right (Boolean True) -> "true"
306 Right (Boolean False) -> "false"
307 Right lit -> show lit
308 Left name -> quoted $ case List.elemIndices '.' name of
310 is -> drop (last is + 1) name
314jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
315jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
316jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
321 do ss <- mapM toJS' (e1 : collect [] e2)
322 return . jsFunc "$" . ret $ apply "$" ss
323 | op == "Basics.<|" =
325 funcs <- mapM toJS' (collect [] e1)
326 return (apply arg funcs)
328 binopNormal op `liftM` toJS' e1 `ap` toJS' e2
330 apply = foldr (\f arg-> f ++ parens arg)
334 L _ (Binop op e1 e2) | op == "Basics.." -> collect (es ++ [e1]) e2
337binopNormal op s1 s2 =
338 case Map.lookup op opDict of
340 Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, s1, s2 ])
342 func | isOp op' = "_op['" ++ op' ++ "']"
344 (prefix, op') = case List.elemIndices '.' op of
346 xs -> splitAt (last xs + 1) op
348 opDict = Map.fromList $ basics ++
349 [ ("::", jsCons s1 s2)
350 , ("List.++", "_L.append" ++ parens (s1 ++ "," ++ s2)) ]
352 ops = pow : map (\op -> (op, parens (s1 ++ op ++ s2))) ["+","-","*","/","&&","||"]
353 where pow = ("^" , "Math.pow(" ++ s1 ++ "," ++ s2 ++ ")")
355 basics = ops ++ map (\(op,e) -> ("Basics." ++ op, e))
356 (ops ++ [ ("<|", s1 ++ parens s2)
357 , ("|>", s2 ++ parens s1)
358 , ("==", "_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
359 , ("/=", "!_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
360 , ("<" , jsCompare s1 s2 "<0")
361 , (">" , jsCompare s1 s2 ">0")
362 , ("<=", jsCompare s1 s2 "<1")
363 , (">=", jsCompare s1 s2 ">-1")
364 , ("div", parens (s1 ++ "/" ++ s2 ++ "|0"))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list