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
23 [ ("N" , "Elm.Native"),
24 ("_N", "N.Utils(elm)"),
25 ("_L", "N.List(elm)"),
26 ("_E", "N.Error(elm)"),
27 ("_J", "N.JavaScript(elm)"),
28 ("_str", "_J.toString"),
29 ("$moduleName", quoted name)
32parens s = "(" ++ s ++ ")"
33brackets s = "{" ++ s ++ "}"
34commaSep = List.intercalate ", "
35dotSep = List.intercalate "."
36jsObj = brackets . commaSep
37jsList ss = "["++ List.intercalate "," ss ++"]"
38jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
39assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
40ret e = "\nreturn "++ e ++";"
41quoted s = "'" ++ concatMap f s ++ "'"
49globalAssign n e = "\n" ++ assign' n e ++ ";"
50assign' n e = n ++ " = " ++ e
52jsModule :: MetadataModule t v -> String
55 body <- toJS . fst . SD.flattenLets [] $ program modul
56 foreignImport <- mapM importEvent (foreignImports modul)
57 return $ concat [ setup ("Elm" : names modul)
58 , globalAssign ("Elm." ++ modName)
59 (jsFunc "elm" $ makeProgram body foreignImport) ]
61 modName = dotSep (names modul)
62 makeProgram body foreignImport =
63 concat [ setup ("elm" : names modul)
64 , "\nif (elm." ++ modName ++ ") return elm." ++ modName ++ ";"
65 , "\nvar " ++ usefulFuncs ++ ";"
66 , concatMap jsImport (imports modul)
67 , concat foreignImport
70 , concatMap exportEvent $ foreignExports modul
73 setup names = concatMap (\n -> globalAssign n $ n ++ " || {}") .
74 map dotSep . drop 2 . List.inits $ init names
75 usefulFuncs = commaSep (map (uncurry assign') (internalImports modName))
77 jsExports = ret (assign' ("elm." ++ modName) (brackets exs))
79 exs = indent . commaSep . concatMap pair $ "_op" : exports modul
81 | otherwise = ["\n" ++ x ++ " : " ++ x]
84 importEvent (js,base,elm,_) =
86 return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
87 , "\ndocument.addEventListener('", js
88 , "_' + elm.id, function(e) { elm.notify(", elm
89 , ".id, e.value); });" ]
91 exportEvent (js,elm,_) =
92 concat [ "\nA2( Signal.lift, function(v) { "
93 , "var e = document.createEvent('Event');"
94 , "e.initEvent('", js, "_' + elm.id, true, true);"
96 , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
98jsImport (modul, method) =
99 concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
101 starters = "\nvar " : repeat "\n"
102 values = map (\name -> name ++ " || {}") (init subnames) ++
103 ["Elm." ++ modul ++ parens "elm"]
104 subnames = map dotSep . tail . List.inits $ split modul
106 split names = case go [] names of
108 (name, ns) -> name : split ns
109 go name str = case str of
110 '.':rest -> (reverse name, rest)
111 c:rest -> go (c:name) rest
112 [] -> (reverse name, [])
116 toJS :: a -> Unique String
119instance ToJS (Def t v) where
121 -- TODO: Make this handle patterns besides plain variables
122 toJS (Def (PVar x) e)
123 | isOp x = globalAssign ("_op['" ++ x ++ "']") `liftM` toJS' e
124 | otherwise = assign x `liftM` toJS' e
126 toJS (Def pattern e@(L s _)) =
128 let x = "_" ++ show n
130 toDef y = Def (PVar y) (L s $ Case (var x) [(pattern, var y)])
131 stmt <- assign x `liftM` toJS' e
132 vars <- toJS . map toDef . Set.toList $ SD.boundVars pattern
133 return (stmt ++ vars)
135 toJS (TypeAnnotation _ _) = return ""
138instance ToJS a => ToJS [a] where
139 toJS xs = concat `liftM` mapM toJS xs
141toJS' :: LExpr t v -> Unique String
144 MultiIf ps -> multiIfToJS span ps
145 Case e cases -> caseToJS span e cases
148remove x e = "_N.remove('" ++ x ++ "', " ++ e ++ ")"
149addField x v e = "_N.insert('" ++ x ++ "', " ++ v ++ ", " ++ e ++ ")"
150setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
151 where f (x,v) = "['" ++ x ++ "'," ++ v ++ "]"
152access x e = e ++ "." ++ x
153makeRecord kvs = record `liftM` collect kvs
155 combine r (k,v) = Map.insertWith (++) k v r
156 collect = liftM (List.foldl' combine Map.empty) . mapM prep
161 brackets ("\n "++List.intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
162 hidden = fields . map (second jsList) .
163 filter (not . null . snd) . Map.toList . Map.map tail
164 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
167instance ToJS Literal where
170 Chr c -> return $ quoted [c]
171 Str s -> return $ "_str" ++ parens (quoted s)
172 IntNum n -> return $ show n
173 FloatNum n -> return $ show n
174 Boolean b -> return $ if b then "true" else "false"
177instance ToJS (Expr t v) where
181 Literal lit -> toJS lit
182 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
183 Access e x -> access x `liftM` toJS' e
184 Remove e x -> remove x `liftM` toJS' e
185 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
186 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
187 setField fs' `liftM` toJS' e
188 Record fs -> makeRecord fs
189 Binop op e1 e2 -> binop op e1 e2
191 Lambda p e@(L s _) -> liftM fastFunc (toJS' body)
194 | length args < 2 || length args > 9 =
195 foldr (\arg bod -> jsFunc arg (ret bod)) body args
197 "F" ++ show (length args) ++ parens (jsFunc (commaSep args) (ret body))
199 (args, body) = first reverse $ foldr depattern ([], innerBody) (zip patterns [1..])
201 depattern (pattern,n) (args, body) =
203 PVar x -> (x : args, body)
204 _ -> let arg = "arg" ++ show n
205 in (arg:args, L s (Case (L s (Var arg)) [(pattern, body)]))
207 (patterns, innerBody) = collect [p] e
209 collect patterns lexpr@(L _ expr) =
211 Lambda p e -> collect (p:patterns) e
212 _ -> (patterns, lexpr)
214 App e1 e2 -> jsApp e1 e2
215 Let defs e -> jsLet $ SD.flattenLets defs e
218 do es' <- mapM toJS' es
219 return $ "_J.toList" ++ parens (jsList es')
222 do fs <- mapM toJS' es
223 return $ case name of
225 "::" -> jsCons (head fs) ((head . tail) fs)
226 _ -> jsObj $ ("ctor:" ++ show name) : fields
227 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
229 Markdown doc -> return $ "Text.text('" ++ pad ++ md ++ pad ++ "')"
230 where pad = "<div style=\"height:0;width:0;\"> </div>"
231 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
235 as <- mapM toJS' args
238 _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
240 (func, args) = go [e2] e1
243 (L _ (App e1 e2)) -> go (e2 : args) e1
246formatMarkdown = concatMap f
254 (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
255 _ -> format ("_E.If" ++ parens ("$moduleName," ++ quoted (show span))) ps
258 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
259 f (b,e) = do b' <- toJS' b
261 return (b' ++ " ? " ++ e')
263jsLet (defs,e') = do ds <- mapM toJS defs
265 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
267caseToJS span e ps = do
268 (tempVar,match) <- caseToMatch ps
270 let (match',stmt) = case e of
271 L _ (Var x) -> (matchSubst [(tempVar,x)] match, "")
272 _ -> (match, assign tempVar e')
273 matches <- matchToJS span match'
274 return $ jsFunc "" (stmt ++ matches) ++ "()"
276matchToJS span match =
278 Match name clauses def ->
279 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
280 finally <- matchToJS span def
281 let isLiteral p = case p of
282 Clause (Right _) _ _ -> True
284 access = if any isLiteral clauses then "" else ".ctor"
285 return $ concat [ "\nswitch (", name, access, ") {"
286 , indent cases, "\n}", finally ]
287 Fail -> return ("_E.Case" ++ parens ("$moduleName," ++ quoted (show span)))
288 Break -> return "break;"
289 Other e -> ret `liftM` toJS' e
290 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
295 Other _ -> acc ++ [m]
296 _ -> dropEnd (acc ++ [m]) ms
298clauseToJS span var (Clause value vars e) = do
299 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
300 s <- matchToJS span $ matchSubst (zip vars vars') e
301 pattern <- case value of
302 Right (Boolean True) -> return "true"
303 Right (Boolean False) -> return "false"
304 Right lit -> toJS lit
305 Left name -> return . quoted $ case List.elemIndices '.' name of
307 is -> drop (last is + 1) name
308 return $ concat [ "\ncase ", pattern, ":", indent s ]
311jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
312jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
313jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
318 do ss <- mapM toJS' (e1 : collect [] e2)
319 return . jsFunc "$" . ret $ apply "$" ss
320 | op == "Basics.<|" =
322 funcs <- mapM toJS' (collect [] e1)
323 return (apply arg funcs)
325 binopNormal op `liftM` toJS' e1 `ap` toJS' e2
327 apply = foldr (\f arg-> f ++ parens arg)
331 L _ (Binop op e1 e2) | op == "Basics.." -> collect (es ++ [e1]) e2
334binopNormal op s1 s2 =
335 case Map.lookup op opDict of
337 Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, s1, s2 ])
339 func | isOp op' = "_op['" ++ op' ++ "']"
341 (prefix, op') = case List.elemIndices '.' op of
343 xs -> splitAt (last xs + 1) op
345 opDict = Map.fromList $ basics ++
346 [ ("::", jsCons s1 s2)
347 , ("List.++", "_L.append" ++ parens (s1 ++ "," ++ s2)) ]
349 ops = pow : map (\op -> (op, parens (s1 ++ op ++ s2))) ["+","-","*","/","&&","||"]
350 where pow = ("^" , "Math.pow(" ++ s1 ++ "," ++ s2 ++ ")")
352 basics = ops ++ map (\(op,e) -> ("Basics." ++ op, e))
353 (ops ++ [ ("<|", s1 ++ parens s2)
354 , ("|>", s2 ++ parens s1)
355 , ("==", "_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
356 , ("/=", "!_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
357 , ("<" , jsCompare s1 s2 "<0")
358 , (">" , jsCompare s1 s2 ">0")
359 , ("<=", jsCompare s1 s2 "<1")
360 , (">=", jsCompare s1 s2 ">-1")
361 , ("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