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 Data.Either (partitionEithers)
9import qualified Text.Pandoc as Pan
10import Data.Maybe (maybeToList)
14import SourceSyntax.Everything hiding (parens)
15import Types.Types ( Type(RecordT) )
17showErr :: String -> String
18showErr err = globalAssign "Elm.Main" (jsFunc "elm" body)
19 where msg = show . concatMap (++"<br>") . lines $ err
20 body = "var T = Elm.Text(elm);\n\
21 \return { main : T.text(T.monospace(" ++ msg ++ ")) };"
28 [ ("N" , "Elm.Native"),
29 ("_N", "N.Utils(elm)"),
30 ("_L", "N.List(elm)"),
31 ("_E", "N.Error(elm)"),
32 ("_J", "N.JavaScript(elm)"),
33 ("_str", "_J.toString")
36parens s = "(" ++ s ++ ")"
37brackets s = "{" ++ s ++ "}"
38jsObj = brackets . intercalate ", "
39jsList ss = "["++ intercalate "," ss ++"]"
40jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
41assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
42ret e = "\nreturn "++ e ++";"
43quoted s = "'" ++ concatMap f s ++ "'"
51globalAssign n e = "\n" ++ assign' n e ++ ";"
52assign' n e = n ++ " = " ++ e
54jsModule (Module names exports imports stmts) =
55 setup ("Elm":modNames) ++ globalAssign ("Elm." ++ modName) (jsFunc "elm" program)
57 modNames = if null names then ["Main"] else names
58 modName = intercalate "." modNames
59 includes = concatMap jsImport imports
60 body = stmtsToJS stmts
61 defs = assign "$op" "{}"
62 program = "\nvar " ++ usefulFuncs ++ ";" ++ defs ++ includes ++ body ++
63 setup ("elm":"Native":modNames) ++
64 assign "_" ("elm.Native." ++ modName ++ "||{}") ++
65 getExports exports stmts ++ setup ("elm":modNames) ++
66 ret (assign' ("elm." ++ modName) "_") ++ "\n"
67 setup modNames = concatMap (\n -> globalAssign n $ n ++ "||{}") .
68 map (intercalate ".") . drop 2 . inits $ init modNames
69 usefulFuncs = intercalate ", " (map (uncurry assign') internalImports)
71getExports names stmts = "\n"++ intercalate ";\n" (op : map fnPair fns)
72 where exNames n = either derename id n `elem` names
73 exports | null names = concatMap get stmts
74 | otherwise = filter exNames (concatMap get stmts)
76 (fns,ops) = partitionEithers exports
78 opPair op = "'" ++ op ++ "' : $op['" ++ op ++ "']"
79 fnPair fn = let fn' = derename fn in "_." ++ fn' ++ " = " ++ fn
81 op = ("_.$op = "++) . jsObj $ map opPair ops
83 get' (FnDef x _ _) = Just (Left x)
84 get' (OpDef op _ _ _) = Just (Right op)
85 get' (TypeAnnotation _ _) = Nothing
86 get s = case s of Definition d -> maybeToList (get' d)
87 Datatype _ _ tcs -> map (Left . fst) tcs
88 ImportEvent _ _ x _ -> [ Left x ]
89 ExportEvent _ _ _ -> []
93jsImport (modul, how) =
95 As name -> assign name ("Elm." ++ modul ++ parens "elm")
96 Hiding vs -> include ++ " var hiding=" ++ (jsObj $ map (++":1") vs) ++
97 "; for(var k in _){if(k in hiding)continue;" ++
98 "eval('var '+k+'=_[\"'+k+'\"]')}"
99 Importing vs -> include ++ named
101 imprt v = assign' v ("_." ++ v)
102 def (o:p) = imprt (if isOp o then "$op['" ++ o:p ++ "']" else deprime (o:p))
103 named = if null vs then "" else "\nvar " ++ intercalate ", " (map def vs) ++ ";"
105 include = "\nvar _ = Elm." ++ modul ++ parens "elm" ++ ";" ++ setup modul
106 setup moduleName = " var " ++ concatMap (++";") (defs ++ [assign' moduleName "_"])
108 defs = map (\n -> assign' n (n ++ "||{}")) (subnames moduleName)
109 subnames = map (intercalate ".") . tail . inits . init . split
110 split names = case go [] names of
112 (name, ns) -> name : split ns
113 go name str = case str of
114 '.':rest -> (reverse name, rest)
115 c:rest -> go (c:name) rest
116 [] -> (reverse name, [])
120stmtsToJS :: [Declaration t v] -> String
121stmtsToJS stmts = run $ do program <- mapM toJS (sortBy cmpStmt stmts)
122 return (concat program)
124 cmpStmt s1 s2 = compare (valueOf s1) (valueOf s2)
125 valueOf s = case s of
127 ImportEvent _ _ _ _ -> 2
128 Definition (FnDef f [] _) ->
129 if derename f == "main" then 5 else 4
130 Definition (TypeAnnotation _ _) -> 0
132 ExportEvent _ _ _ -> 6
136 toJS :: a -> Unique String
138instance ToJS (Def t v) where
139 toJS (FnDef x [] e) = assign x `liftM` toJS' e
140 toJS (FnDef f as e) = (assign f . wrapper . func) `liftM` toJS' e
142 func body = jsFunc (intercalate ", " as) (ret body)
143 wrapper e | length as == 1 = e
144 | otherwise = 'F' : show (length as) ++ parens e
145 toJS (OpDef op a1 a2 e) =
147 let func = "F2" ++ parens (jsFunc (a1 ++ ", " ++ a2) (ret body))
148 return (globalAssign ("$op['" ++ op ++ "']") func)
149 toJS (TypeAnnotation _ _) = return ""
152instance ToJS (Declaration t v) where
155 Definition d -> toJS d
156 Datatype _ _ tcs -> concat `liftM` mapM (toJS . toDef) tcs
157 where toDef (name,args) =
158 let vars = map (('a':) . show) [1..length args] in
159 Definition . FnDef name vars . none $
160 Data (derename name) (map (none . Var) vars)
161 ImportEvent js base elm _ ->
163 return $ concat [ "\nvar " ++ elm ++ "=Elm.Signal(elm).constant(" ++ v ++ ");"
164 , "\ndocument.addEventListener('", js
165 , "_' + elm.id, function(e) { elm.notify(", elm
166 , ".id, e.value); });" ]
167 ExportEvent js elm _ ->
168 return $ concat [ "\nlift(function(v) { "
169 , "var e = document.createEvent('Event');"
170 , "e.initEvent('", js, "_' + elm.id, true, true);"
172 , "document.dispatchEvent(e); return v; })(", elm, ");" ]
173 TypeAlias n _ t -> return ""
175toJS' :: LExpr t v -> Unique String
176toJS' (L txt span expr) =
178 MultiIf ps -> multiIfToJS span ps
179 Case e cases -> caseToJS span e cases
182remove x e = "_N.remove('" ++ x ++ "', " ++ e ++ ")"
183addField x v e = "_N.insert('" ++ x ++ "', " ++ v ++ ", " ++ e ++ ")"
184setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
185 where f (x,v) = "['" ++ x ++ "'," ++ v ++ "]"
186access x e = e ++ "." ++ x
187makeRecord kvs = record `liftM` collect kvs
189 combine r (k,v) = Map.insertWith (++) k v r
190 collect = liftM (foldl' combine Map.empty) . mapM prep
191 prep (k, as, e@(L t s _)) =
192 do v <- toJS' (foldr (\x e -> L t s $ Lambda x e) e as)
195 brackets ("\n "++intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
196 hidden = fields . map (second jsList) .
197 filter (not . null . snd) . Map.toList . Map.map tail
198 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
201instance ToJS Literal where
204 Chr c -> return $ quoted [c]
205 Str s -> return $ "_str" ++ parens (quoted s)
206 IntNum n -> return $ show n
207 FloatNum n -> return $ show n
208 Boolean b -> return $ if b then "true" else "false"
211instance ToJS (Expr t v) where
215 Literal lit -> toJS lit
216 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
217 Access e x -> access x `liftM` toJS' e
218 Remove e x -> remove x `liftM` toJS' e
219 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
220 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
221 setField fs' `liftM` toJS' e
222 Record fs -> makeRecord fs
223 Binop op e1 e2 -> binop op `liftM` toJS' e1 `ap` toJS' e2
225 Lambda v e -> liftM (jsFunc v . ret) (toJS' e)
227 App e1 e2 -> jsApp e1 e2
228 Let defs e -> jsLet defs e
231 do es' <- mapM toJS' es
232 return $ "_J.toList" ++ parens (jsList es')
235 do fs <- mapM toJS' es
236 return $ case name of
238 "::" -> jsCons (head fs) ((head . tail) fs)
239 _ -> jsObj $ ("ctor:" ++ show name) : fields
240 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
242 Markdown doc -> return $ "text('" ++ pad ++ md ++ pad ++ "')"
243 where pad = "<div style=\"height:0;width:0;\"> </div>"
244 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
248 as <- mapM toJS' args
251 _ -> "A" ++ show (length as) ++ parens (intercalate ", " (f:as))
253 (func, args) = go [e2] e1
256 (L _ _ (App e1 e2)) -> go (e2 : args) e1
259formatMarkdown = concatMap f
267 (L _ _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
268 _ -> format ("_E.If" ++ parens (quoted (show span))) ps
271 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
272 f (b,e) = do b' <- toJS' b
274 return (b' ++ " ? " ++ e')
276jsLet defs e' = do ds <- jsDefs defs
278 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
280 jsDefs defs = mapM toJS (sortBy f defs)
281 f a b = compare (valueOf a) (valueOf b)
282 valueOf (FnDef _ [] _) = 2
283 valueOf (FnDef _ _ _) = 1
284 valueOf (OpDef _ _ _ _) = 1
285 valueOf (TypeAnnotation _ _) = 0
287caseToJS span e ps = do
288 match <- caseToMatch ps
290 let (match',stmt) = case (match,e) of
291 (Match name _ _, L _ _ (Var x)) -> (matchSubst [(name,x)] match, "")
292 (Match name _ _, _) -> (match, assign name e')
294 matches <- matchToJS span match'
295 return $ "function(){ " ++ stmt ++ matches ++ " }()"
297matchToJS span match =
299 Match name clauses def ->
300 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
301 finally <- matchToJS span def
302 let isLiteral p = case p of
303 Clause (Right _) _ _ -> True
305 access = if any isLiteral clauses then "" else ".ctor"
306 return $ concat [ "\nswitch (", name, access, ") {"
307 , indent cases, "\n}", finally ]
308 Fail -> return ("_E.Case" ++ parens (quoted (show span)))
309 Break -> return "break;"
310 Other e -> ret `liftM` toJS' e
311 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
316 Other _ -> acc ++ [m]
317 _ -> dropEnd (acc ++ [m]) ms
319clauseToJS span var (Clause value vars e) = do
320 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
321 s <- matchToJS span $ matchSubst (zip vars vars') e
322 return $ concat [ "\ncase ", case value of
323 Left name -> quoted name
324 Right (Boolean True) -> "true"
325 Right (Boolean False) -> "false"
326 Right lit -> show lit
330jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
331jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
332jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ").ctor" ++ op)
335 | isAlpha o || '_' == o = (o:p) ++ parens e1 ++ parens e2
337 let ops = ["+","-","*","/","&&","||"] in
340 "++" -> "_L.append" ++ parens (e1 ++ "," ++ e2)
341 "<|" -> e1 ++ parens e2
342 "|>" -> e2 ++ parens e1
343 "." -> jsFunc "x" . ret $ e1 ++ parens (e2 ++ parens "x")
344 "^" -> "Math.pow(" ++ e1 ++ "," ++ e2 ++ ")"
345 "==" -> "_N.eq(" ++ e1 ++ "," ++ e2 ++ ")"
346 "/=" -> "!_N.eq(" ++ e1 ++ "," ++ e2 ++ ")"
347 "<" -> jsCompare e1 e2 "==='LT'"
348 ">" -> jsCompare e1 e2 "==='GT'"
349 "<=" -> jsCompare e1 e2 "!=='GT'"
350 ">=" -> jsCompare e1 e2 "!=='LT'"
351 "<~" -> "A2(lift," ++ e1 ++ "," ++ e2 ++ ")"
352 "~" -> "A3(lift2,F2(function(f,x){return f(x)}),"++e1++","++e2++")"
353 _ | elem (o:p) ops -> parens (e1 ++ (o:p) ++ e2)
354 | otherwise -> concat [ "$op['", o:p, "']"
355 , 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