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 hiding (parens)
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")
40parens s = "(" ++ s ++ ")"
41brackets s = "{" ++ s ++ "}"
42commaSep = List.intercalate ", "
43dotSep = List.intercalate "."
44jsObj = brackets . commaSep
45jsList ss = "["++ List.intercalate "," ss ++"]"
46jsFunc args body = "function(" ++ args ++ "){" ++ indent body ++ "}"
47assign x e = "\nvar " ++ x ++ " = " ++ e ++ ";"
48ret e = "\nreturn "++ e ++";"
49quoted s = "'" ++ concatMap f s ++ "'"
57globalAssign n e = "\n" ++ assign' n e ++ ";"
58assign' n e = n ++ " = " ++ e
60jsModule :: MetadataModule t v -> String
63 body <- toJS . fst . SD.flattenLets [] $ program modul
64 foreignImport <- mapM importEvent (foreignImports modul)
65 return $ concat [ setup ("Elm": names modul)
66 , globalAssign ("Elm." ++ modName)
67 (jsFunc "elm" $ makeProgram body foreignImport) ]
69 modName = dotSep (names modul)
70 makeProgram body foreignImport =
71 concat [ "\nvar " ++ usefulFuncs ++ ";"
72 , concatMap jsImport (imports modul)
73 , concat foreignImport
76 , concatMap exportEvent $ foreignExports modul
79 setup names = concatMap (\n -> globalAssign n $ n ++ " || {}") .
80 map dotSep . drop 2 . List.inits $ init names
81 usefulFuncs = commaSep (map (uncurry assign') internalImports)
83 jsExports = setup ("elm" : names modul) ++
84 ret (assign' ("elm." ++ modName) (brackets exs))
86 exs = indent . commaSep . concatMap (pair . deprime) $ "_op" : exports modul
88 | otherwise = ["\n" ++ x ++ " : " ++ x]
91 importEvent (js,base,elm,_) =
93 return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
94 , "\ndocument.addEventListener('", js
95 , "_' + elm.id, function(e) { elm.notify(", elm
96 , ".id, e.value); });" ]
98 exportEvent (js,elm,_) =
99 concat [ "\nA2( Signal.lift, function(v) { "
100 , "var e = document.createEvent('Event');"
101 , "e.initEvent('", js, "_' + elm.id, true, true);"
103 , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
105jsImport (modul, method) =
106 concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
108 starters = "\nvar " : repeat "\n"
109 values = map (\name -> name ++ " || {}") (init subnames) ++
110 ["Elm." ++ modul ++ parens "elm"]
111 subnames = map dotSep . tail . List.inits $ split modul
113 split names = case go [] names of
115 (name, ns) -> name : split ns
116 go name str = case str of
117 '.':rest -> (reverse name, rest)
118 c:rest -> go (c:name) rest
119 [] -> (reverse name, [])
123 toJS :: a -> Unique String
126instance ToJS (Def t v) where
128 -- TODO: Make this handle patterns besides plain variables
129 toJS (Def (PVar x) e)
130 | isOp x = globalAssign ("_op['" ++ x ++ "']") `liftM` toJS' e
131 | otherwise = assign (deprime x) `liftM` toJS' e
133 toJS (Def pattern e@(L s _)) =
135 let x = "_" ++ show n
137 toDef y' = let y = deprime y' in
138 Def (PVar y) (L s $ Case (var x) [(pattern, var y)])
139 stmt <- assign x `liftM` toJS' e
140 vars <- toJS . map toDef . Set.toList $ SD.boundVars pattern
141 return (stmt ++ vars)
143 toJS (TypeAnnotation _ _) = return ""
146instance ToJS a => ToJS [a] where
147 toJS xs = concat `liftM` mapM toJS xs
149toJS' :: LExpr t v -> Unique String
152 MultiIf ps -> multiIfToJS span ps
153 Case e cases -> caseToJS span e cases
156remove x e = "_N.remove('" ++ deprime x ++ "', " ++ e ++ ")"
157addField x v e = "_N.insert('" ++ deprime x ++ "', " ++ v ++ ", " ++ e ++ ")"
158setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
159 where f (x,v) = "['" ++ deprime x ++ "'," ++ v ++ "]"
160access x e = e ++ "." ++ deprime x
161makeRecord kvs = record `liftM` collect kvs
163 combine r (k,v) = Map.insertWith (++) k v r
164 collect = liftM (List.foldl' combine Map.empty) . mapM prep
167 return (deprime k, [v])
169 brackets ("\n "++List.intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
170 hidden = fields . map (second jsList) .
171 filter (not . null . snd) . Map.toList . Map.map tail
172 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
175instance ToJS Literal where
178 Chr c -> return $ quoted [c]
179 Str s -> return $ "_str" ++ parens (quoted s)
180 IntNum n -> return $ show n
181 FloatNum n -> return $ show n
182 Boolean b -> return $ if b then "true" else "false"
185instance ToJS (Expr t v) where
188 Var x -> return (deprime x)
189 Literal lit -> toJS lit
190 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
191 Access e x -> access x `liftM` toJS' e
192 Remove e x -> remove x `liftM` toJS' e
193 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
194 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
195 setField fs' `liftM` toJS' e
196 Record fs -> makeRecord fs
197 Binop op e1 e2 -> binop op `liftM` toJS' e1 `ap` toJS' e2
199 Lambda p e@(L s _) -> liftM (fastFunc . ret) (toJS' body)
202 | length args < 2 || length args > 9 = foldr jsFunc body args
203 | otherwise = "F" ++ show (length args) ++ parens (jsFunc (commaSep args) body)
204 (args, body) = first reverse $ foldr depattern ([], innerBody) (zip patterns [1..])
206 depattern (pattern,n) (args, body) =
208 PVar x -> (deprime x : args, body)
209 _ -> let arg = "arg" ++ show n
210 in (arg:args, L s (Case (L s (Var arg)) [(pattern, body)]))
212 (patterns, innerBody) = collect [p] e
214 collect patterns lexpr@(L _ expr) =
216 Lambda p e -> collect (p:patterns) e
217 _ -> (patterns, lexpr)
219 App e1 e2 -> jsApp e1 e2
220 Let defs e -> jsLet $ SD.flattenLets defs e
223 do es' <- mapM toJS' es
224 return $ "_J.toList" ++ parens (jsList es')
227 do fs <- mapM toJS' es
228 return $ case name of
230 "::" -> jsCons (head fs) ((head . tail) fs)
231 _ -> jsObj $ ("ctor:" ++ show name) : fields
232 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
234 Markdown doc -> return $ "Text.text('" ++ pad ++ md ++ pad ++ "')"
235 where pad = "<div style=\"height:0;width:0;\"> </div>"
236 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
240 as <- mapM toJS' args
243 _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
245 (func, args) = go [e2] e1
248 (L _ (App e1 e2)) -> go (e2 : args) e1
251formatMarkdown = concatMap f
259 (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
260 _ -> format ("_E.If" ++ parens (quoted (show span))) ps
263 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
264 f (b,e) = do b' <- toJS' b
266 return (b' ++ " ? " ++ e')
268jsLet (defs,e') = do ds <- mapM toJS defs
270 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
272caseToJS span e ps = do
273 (tempVar,match) <- caseToMatch ps
275 let (match',stmt) = case e of
276 L _ (Var x) -> (matchSubst [(tempVar,deprime x)] match, "")
277 _ -> (match, assign tempVar e')
278 matches <- matchToJS span match'
279 return $ jsFunc "" (stmt ++ matches) ++ "()"
281matchToJS span match =
283 Match name clauses def ->
284 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
285 finally <- matchToJS span def
286 let isLiteral p = case p of
287 Clause (Right _) _ _ -> True
289 access = if any isLiteral clauses then "" else ".ctor"
290 return $ concat [ "\nswitch (", name, access, ") {"
291 , indent cases, "\n}", finally ]
292 Fail -> return ("_E.Case" ++ parens (quoted (show span)))
293 Break -> return "break;"
294 Other e -> ret `liftM` toJS' e
295 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
300 Other _ -> acc ++ [m]
301 _ -> dropEnd (acc ++ [m]) ms
303clauseToJS span var (Clause value vars e) = do
304 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
305 s <- matchToJS span $ matchSubst (zip vars vars') e
306 return $ concat [ "\ncase ", case value of
307 Right (Boolean True) -> "true"
308 Right (Boolean False) -> "false"
309 Right lit -> show lit
310 Left name -> quoted $ case List.elemIndices '.' name of
312 is -> drop (last is + 1) name
316jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
317jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
318jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
322 case Map.lookup op opDict of
324 Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, e1, e2 ])
326 func | isOp op' = "_op['" ++ op' ++ "']"
328 (prefix, op') = case List.elemIndices '.' op of
330 xs -> splitAt (last xs + 1) op
332 opDict = Map.fromList $ basics ++
333 [ ("::", jsCons e1 e2)
334 , ("List.++", "_L.append" ++ parens (e1 ++ "," ++ e2)) ]
336 ops = pow : map (\op -> (op, parens (e1 ++ op ++ e2))) ["+","-","*","/","&&","||"]
337 where pow = ("^" , "Math.pow(" ++ e1 ++ "," ++ e2 ++ ")")
339 basics = ops ++ map (\(op,e) -> ("Basics." ++ op, e))
340 (ops ++ [ ("<|", e1 ++ parens e2)
341 , ("|>", e2 ++ parens e1)
342 , ("." , jsFunc "$" . ret $ e1 ++ parens (e2 ++ parens "$"))
343 , ("==", "_N.eq(" ++ e1 ++ "," ++ e2 ++ ")")
344 , ("/=", "!_N.eq(" ++ e1 ++ "," ++ e2 ++ ")")
345 , ("<" , jsCompare e1 e2 "<0")
346 , (">" , jsCompare e1 e2 ">0")
347 , ("<=", jsCompare e1 e2 "<1")
348 , (">=", jsCompare e1 e2 ">-1")
349 , ("div", parens (e1 ++ "/" ++ e2 ++ "|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