1module Generate.JavaScript (showErr, jsModule) where
3import Control.Arrow (first,second)
4import Control.Monad (liftM,(<=<),join,ap)
5import Data.Char (isAlpha,isDigit)
6import Data.List (intercalate,sortBy,inits,foldl')
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 hiding (parens)
15import SourceSyntax.Location as Loc
16import Transform.SortDefinitions
17import Types.Types ( Type(RecordT) )
19showErr :: String -> String
20showErr err = globalAssign "Elm.Main" (jsFunc "elm" body)
21 where msg = show . concatMap (++"<br>") . lines $ err
22 body = "var T = Elm.Text(elm);\n\
23 \return { main : T.text(T.monospace(" ++ msg ++ ")) };"
30 [ ("N" , "Elm.Native"),
31 ("_N", "N.Utils(elm)"),
32 ("_L", "N.List(elm)"),
33 ("_E", "N.Error(elm)"),
34 ("_J", "N.JavaScript(elm)"),
35 ("_str", "_J.toString")
38parens s = "(" ++ s ++ ")"
39brackets s = "{" ++ s ++ "}"
40jsObj = brackets . intercalate ", "
41jsList ss = "["++ intercalate "," ss ++"]"
42jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
43assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
44ret e = "\nreturn "++ e ++";"
45quoted s = "'" ++ concatMap f s ++ "'"
53globalAssign n e = "\n" ++ assign' n e ++ ";"
54assign' n e = n ++ " = " ++ e
56jsModule (Module names exports imports stmts) =
57 setup ("Elm":modNames) ++ globalAssign ("Elm." ++ modName) (jsFunc "elm" program)
59 modNames = if null names then ["Main"] else names
60 modName = intercalate "." modNames
61 includes = concatMap jsImport imports
62 body = stmtsToJS stmts
63 defs = assign "$op" "{}"
64 program = "\nvar " ++ usefulFuncs ++ ";" ++ defs ++ includes ++ body ++
65 setup ("elm":"Native":modNames) ++
66 assign "_" ("elm.Native." ++ modName ++ "||{}") ++
67 getExports exports stmts ++ setup ("elm":modNames) ++
68 ret (assign' ("elm." ++ modName) "_") ++ "\n"
69 setup modNames = concatMap (\n -> globalAssign n $ n ++ "||{}") .
70 map (intercalate ".") . drop 2 . inits $ init modNames
71 usefulFuncs = intercalate ", " (map (uncurry assign') internalImports)
73getExports names stmts = "\n"++ intercalate ";\n" (op : map fnPair fns)
74 where exNames n = either derename id n `elem` names
75 exports | null names = concatMap get stmts
76 | otherwise = filter exNames (concatMap get stmts)
78 (fns,ops) = partitionEithers exports
80 opPair op = "'" ++ op ++ "' : $op['" ++ op ++ "']"
81 fnPair fn = let fn' = derename fn in "_." ++ fn' ++ " = " ++ fn
83 op = ("_.$op = "++) . jsObj $ map opPair ops
87 TypeAnnotation _ _ -> []
89 case Set.toList (boundVars pattern) of
90 [var@(c:_)] -> [if isOp c then Right var else Left var]
93 get s = case s of Definition def -> get' def
94 Datatype _ _ tcs -> map (Left . fst) tcs
95 ImportEvent _ _ x _ -> [ Left x ]
96 ExportEvent _ _ _ -> []
100jsImport (modul, how) =
102 As name -> assign name ("Elm." ++ modul ++ parens "elm")
103 Hiding vs -> include ++ " var hiding=" ++ (jsObj $ map (++":1") vs) ++
104 "; for(var k in _){if(k in hiding)continue;" ++
105 "eval('var '+k+'=_[\"'+k+'\"]')}"
106 Importing vs -> include ++ named
108 imprt v = assign' v ("_." ++ v)
109 def (o:p) = imprt (if isOp o then "$op['" ++ o:p ++ "']" else deprime (o:p))
110 named = if null vs then "" else "\nvar " ++ intercalate ", " (map def vs) ++ ";"
112 include = "\nvar _ = Elm." ++ modul ++ parens "elm" ++ ";" ++ setup modul
113 setup moduleName = " var " ++ concatMap (++";") (defs ++ [assign' moduleName "_"])
115 defs = map (\n -> assign' n (n ++ "||{}")) (subnames moduleName)
116 subnames = map (intercalate ".") . tail . inits . init . split
117 split names = case go [] names of
119 (name, ns) -> name : split ns
120 go name str = case str of
121 '.':rest -> (reverse name, rest)
122 c:rest -> go (c:name) rest
123 [] -> (reverse name, [])
127stmtsToJS :: [Declaration t v] -> String
128stmtsToJS stmts = run $ do program <- mapM toJS (sortBy cmpStmt stmts)
129 return (concat program)
131 cmpStmt s1 s2 = compare (valueOf s1) (valueOf s2)
132 valueOf s = case s of
134 ImportEvent _ _ _ _ -> 2
135 Definition (Def f (L _ _ (Lambda _ _))) -> 3
136 Definition (Def (PVar name) _) ->
137 if derename name == "main" then 5 else 4
138 Definition (Def _ _) -> 4
139 Definition (TypeAnnotation _ _) -> 0
140 ExportEvent _ _ _ -> 6
144 toJS :: a -> Unique String
146instance ToJS (Def t v) where
148 -- TODO: Make this handle patterns besides plain variables
150 toJS (Def (PVar x) e) = assign x `liftM` toJS' e
152 toJS (Def f e) = (assign f . wrapper . func) `liftM` toJS' e
154 func body = jsFunc (intercalate ", " as) (ret body)
155 wrapper e | length as == 1 = e
156 | otherwise = 'F' : show (length as) ++ parens e
157 toJS (OpDef op a1 a2 e) =
159 let func = "F2" ++ parens (jsFunc (a1 ++ ", " ++ a2) (ret body))
160 return (globalAssign ("$op['" ++ op ++ "']") func)
162 toJS (TypeAnnotation _ _) = return ""
165instance ToJS (Declaration t v) where
168 Definition d -> toJS d
169 Datatype _ _ tcs -> concat `liftM` mapM (toJS . toDef) tcs
170 where toDef (name,args) =
171 let vars = map (('a':) . show) [1..length args]
172 body = Loc.none (Data (derename name) (map (Loc.none . Var) vars))
173 func = foldr (\x e -> Loc.none (Lambda (PVar x) e)) body vars
174 in Definition $ Def (PVar name) func
176 ImportEvent js base elm _ ->
178 return $ concat [ "\nvar " ++ elm ++ "=Elm.Signal(elm).constant(" ++ v ++ ");"
179 , "\ndocument.addEventListener('", js
180 , "_' + elm.id, function(e) { elm.notify(", elm
181 , ".id, e.value); });" ]
182 ExportEvent js elm _ ->
183 return $ concat [ "\nlift(function(v) { "
184 , "var e = document.createEvent('Event');"
185 , "e.initEvent('", js, "_' + elm.id, true, true);"
187 , "document.dispatchEvent(e); return v; })(", elm, ");" ]
188 TypeAlias n _ t -> return ""
190toJS' :: LExpr t v -> Unique String
191toJS' (L txt span expr) =
193 MultiIf ps -> multiIfToJS span ps
194 Case e cases -> caseToJS span e cases
197remove x e = "_N.remove('" ++ x ++ "', " ++ e ++ ")"
198addField x v e = "_N.insert('" ++ x ++ "', " ++ v ++ ", " ++ e ++ ")"
199setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
200 where f (x,v) = "['" ++ x ++ "'," ++ v ++ "]"
201access x e = e ++ "." ++ x
202makeRecord kvs = record `liftM` collect kvs
204 combine r (k,v) = Map.insertWith (++) k v r
205 collect = liftM (foldl' combine Map.empty) . mapM prep
210 brackets ("\n "++intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
211 hidden = fields . map (second jsList) .
212 filter (not . null . snd) . Map.toList . Map.map tail
213 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
216instance ToJS Literal where
219 Chr c -> return $ quoted [c]
220 Str s -> return $ "_str" ++ parens (quoted s)
221 IntNum n -> return $ show n
222 FloatNum n -> return $ show n
223 Boolean b -> return $ if b then "true" else "false"
226instance ToJS (Expr t v) where
230 Literal lit -> toJS lit
231 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
232 Access e x -> access x `liftM` toJS' e
233 Remove e x -> remove x `liftM` toJS' e
234 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
235 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
236 setField fs' `liftM` toJS' e
237 Record fs -> makeRecord fs
238 Binop op e1 e2 -> binop op `liftM` toJS' e1 `ap` toJS' e2
240 Lambda p e -> liftM (jsFunc (intercalate ", " args) . ret) (toJS' body)
242 (args, body) = foldr depattern ([], innerBody) (zip patterns [1..])
244 depattern (pattern,n) (args, body) =
246 PVar x -> (x:args, body)
247 _ -> let arg = "arg" ++ show n
248 in (arg:args, Loc.none (Case (Loc.none (Var arg)) [(pattern, body)]))
250 (patterns, innerBody) = collect [p] e
252 collect patterns lexpr@(L a b expr) =
254 Lambda p e -> collect (p:patterns) e
255 _ -> (patterns, lexpr)
257 App e1 e2 -> jsApp e1 e2
258 Let defs e -> jsLet defs e
261 do es' <- mapM toJS' es
262 return $ "_J.toList" ++ parens (jsList es')
265 do fs <- mapM toJS' es
266 return $ case name of
268 "::" -> jsCons (head fs) ((head . tail) fs)
269 _ -> jsObj $ ("ctor:" ++ show name) : fields
270 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
272 Markdown doc -> return $ "text('" ++ pad ++ md ++ pad ++ "')"
273 where pad = "<div style=\"height:0;width:0;\"> </div>"
274 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
278 as <- mapM toJS' args
281 _ -> "A" ++ show (length as) ++ parens (intercalate ", " (f:as))
283 (func, args) = go [e2] e1
286 (L _ _ (App e1 e2)) -> go (e2 : args) e1
289formatMarkdown = concatMap f
297 (L _ _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
298 _ -> format ("_E.If" ++ parens (quoted (show span))) ps
301 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
302 f (b,e) = do b' <- toJS' b
304 return (b' ++ " ? " ++ e')
306jsLet defs e' = do ds <- jsDefs defs
308 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
310 jsDefs defs = mapM toJS (sortBy f defs)
311 f a b = compare (valueOf a) (valueOf b)
312 valueOf (Def _ (L _ _ (Lambda _ _))) = 1
313 valueOf (Def _ _) = 2
314 valueOf (TypeAnnotation _ _) = 0
316caseToJS span e ps = do
317 match <- caseToMatch ps
319 let (match',stmt) = case (match,e) of
320 (Match name _ _, L _ _ (Var x)) -> (matchSubst [(name,x)] match, "")
321 (Match name _ _, _) -> (match, assign name e')
323 matches <- matchToJS span match'
324 return $ "function(){ " ++ stmt ++ matches ++ " }()"
326matchToJS span match =
328 Match name clauses def ->
329 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
330 finally <- matchToJS span def
331 let isLiteral p = case p of
332 Clause (Right _) _ _ -> True
334 access = if any isLiteral clauses then "" else ".ctor"
335 return $ concat [ "\nswitch (", name, access, ") {"
336 , indent cases, "\n}", finally ]
337 Fail -> return ("_E.Case" ++ parens (quoted (show span)))
338 Break -> return "break;"
339 Other e -> ret `liftM` toJS' e
340 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
345 Other _ -> acc ++ [m]
346 _ -> dropEnd (acc ++ [m]) ms
348clauseToJS span var (Clause value vars e) = do
349 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
350 s <- matchToJS span $ matchSubst (zip vars vars') e
351 return $ concat [ "\ncase ", case value of
352 Left name -> quoted name
353 Right (Boolean True) -> "true"
354 Right (Boolean False) -> "false"
355 Right lit -> show lit
359jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
360jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
361jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ").ctor" ++ op)
364 | isAlpha o || '_' == o = (o:p) ++ parens e1 ++ parens e2
366 let ops = ["+","-","*","/","&&","||"] in
369 "++" -> "_L.append" ++ parens (e1 ++ "," ++ e2)
370 "<|" -> e1 ++ parens e2
371 "|>" -> e2 ++ parens e1
372 "." -> jsFunc "x" . ret $ e1 ++ parens (e2 ++ parens "x")
373 "^" -> "Math.pow(" ++ e1 ++ "," ++ e2 ++ ")"
374 "==" -> "_N.eq(" ++ e1 ++ "," ++ e2 ++ ")"
375 "/=" -> "!_N.eq(" ++ e1 ++ "," ++ e2 ++ ")"
376 "<" -> jsCompare e1 e2 "==='LT'"
377 ">" -> jsCompare e1 e2 "==='GT'"
378 "<=" -> jsCompare e1 e2 "!=='GT'"
379 ">=" -> jsCompare e1 e2 "!=='LT'"
380 "<~" -> "A2(lift," ++ e1 ++ "," ++ e2 ++ ")"
381 "~" -> "A3(lift2,F2(function(f,x){return f(x)}),"++e1++","++e2++")"
382 _ | elem (o:p) ops -> parens (e1 ++ (o:p) ++ e2)
383 | otherwise -> concat [ "$op['", o:p, "']"
384 , parens e1, parens e2 ]
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