1module Generate.JavaScript (showErr, 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)
21showErr :: String -> String
22showErr 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 ++ ")) };"
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)
41parens s = "(" ++ s ++ ")"
42brackets s = "{" ++ s ++ "}"
43commaSep = List.intercalate ", "
44dotSep = List.intercalate "."
45jsObj = brackets . commaSep
46jsList ss = "["++ List.intercalate "," ss ++"]"
47jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
48assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
49ret e = "\nreturn "++ e ++";"
50quoted s = "'" ++ concatMap f s ++ "'"
58globalAssign n e = "\n" ++ assign' n e ++ ";"
59assign' n e = n ++ " = " ++ e
61jsModule :: MetadataModule t v -> String
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) ]
70 modName = dotSep (names modul)
71 makeProgram body foreignImport =
72 concat [ "\nvar " ++ usefulFuncs ++ ";"
73 , concatMap jsImport (imports modul)
74 , concat foreignImport
77 , concatMap exportEvent $ foreignExports modul
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))
84 jsExports = setup ("elm" : names modul) ++
85 ret (assign' ("elm." ++ modName) (brackets exs))
87 exs = indent . commaSep . concatMap (pair . deprime) $ "_op" : exports modul
89 | otherwise = ["\n" ++ x ++ " : " ++ x]
92 importEvent (js,base,elm,_) =
94 return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
95 , "\ndocument.addEventListener('", js
96 , "_' + elm.id, function(e) { elm.notify(", elm
97 , ".id, e.value); });" ]
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);"
104 , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
106jsImport (modul, method) =
107 concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
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
114 split names = case go [] names of
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, [])
124 toJS :: a -> Unique String
127instance ToJS (Def t v) where
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
134 toJS (Def pattern e@(L s _)) =
136 let x = "_" ++ show n
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)
144 toJS (TypeAnnotation _ _) = return ""
147instance ToJS a => ToJS [a] where
148 toJS xs = concat `liftM` mapM toJS xs
150toJS' :: LExpr t v -> Unique String
153 MultiIf ps -> multiIfToJS span ps
154 Case e cases -> caseToJS span e cases
157remove x e = "_N.remove('" ++ deprime x ++ "', " ++ e ++ ")"
158addField x v e = "_N.insert('" ++ deprime x ++ "', " ++ v ++ ", " ++ e ++ ")"
159setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
160 where f (x,v) = "['" ++ deprime x ++ "'," ++ v ++ "]"
161access x e = e ++ "." ++ deprime x
162makeRecord kvs = record `liftM` collect kvs
164 combine r (k,v) = Map.insertWith (++) k v r
165 collect = liftM (List.foldl' combine Map.empty) . mapM prep
168 return (deprime k, [v])
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
176instance ToJS Literal where
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"
186instance ToJS (Expr t v) where
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
200 Lambda p e@(L s _) -> liftM (fastFunc . ret) (toJS' 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..])
207 depattern (pattern,n) (args, body) =
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)]))
213 (patterns, innerBody) = collect [p] e
215 collect patterns lexpr@(L _ expr) =
217 Lambda p e -> collect (p:patterns) e
218 _ -> (patterns, lexpr)
220 App e1 e2 -> jsApp e1 e2
221 Let defs e -> jsLet $ SD.flattenLets defs e
224 do es' <- mapM toJS' es
225 return $ "_J.toList" ++ parens (jsList es')
228 do fs <- mapM toJS' es
229 return $ case name of
231 "::" -> jsCons (head fs) ((head . tail) fs)
232 _ -> jsObj $ ("ctor:" ++ show name) : fields
233 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
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
241 as <- mapM toJS' args
244 _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
246 (func, args) = go [e2] e1
249 (L _ (App e1 e2)) -> go (e2 : args) e1
252formatMarkdown = concatMap f
260 (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
261 _ -> format ("_E.If" ++ parens ("$moduleName," ++ quoted (show span))) ps
264 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
265 f (b,e) = do b' <- toJS' b
267 return (b' ++ " ? " ++ e')
269jsLet (defs,e') = do ds <- mapM toJS defs
271 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
273caseToJS span e ps = do
274 (tempVar,match) <- caseToMatch ps
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) ++ "()"
282matchToJS span match =
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
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)
301 Other _ -> acc ++ [m]
302 _ -> dropEnd (acc ++ [m]) ms
304clauseToJS 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
313 is -> drop (last is + 1) name
317jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
318jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
319jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
324 do ss <- mapM toJS' (e1 : collect [] e2)
325 return . jsFunc "$" . ret $ apply "$" ss
326 | op == "Basics.<|" =
328 funcs <- mapM toJS' (collect [] e1)
329 return (apply arg funcs)
331 binopNormal op `liftM` toJS' e1 `ap` toJS' e2
333 apply = foldr (\f arg-> f ++ parens arg)
337 L _ (Binop op e1 e2) | op == "Basics.." -> collect (es ++ [e1]) e2
340binopNormal op s1 s2 =
341 case Map.lookup op opDict of
343 Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, s1, s2 ])
345 func | isOp op' = "_op['" ++ op' ++ "']"
347 (prefix, op') = case List.elemIndices '.' op of
349 xs -> splitAt (last xs + 1) op
351 opDict = Map.fromList $ basics ++
352 [ ("::", jsCons s1 s2)
353 , ("List.++", "_L.append" ++ parens (s1 ++ "," ++ s2)) ]
355 ops = pow : map (\op -> (op, parens (s1 ++ op ++ s2))) ["+","-","*","/","&&","||"]
356 where pow = ("^" , "Math.pow(" ++ s1 ++ "," ++ s2 ++ ")")
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"))
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