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 [ "\nvar " ++ usefulFuncs ++ ";"
64 , concatMap jsImport (imports modul)
65 , concat foreignImport
68 , concatMap exportEvent $ foreignExports modul
71 setup names = concatMap (\n -> globalAssign n $ n ++ " || {}") .
72 map dotSep . drop 2 . List.inits $ init names
73 usefulFuncs = commaSep (map (uncurry assign') (internalImports modName))
75 jsExports = setup ("elm" : names modul) ++
76 ret (assign' ("elm." ++ modName) (brackets exs))
78 exs = indent . commaSep . concatMap pair $ "_op" : exports modul
80 | otherwise = ["\n" ++ x ++ " : " ++ x]
83 importEvent (js,base,elm,_) =
85 return $ concat [ "\nvar " ++ elm ++ "=Signal.constant(" ++ v ++ ");"
86 , "\ndocument.addEventListener('", js
87 , "_' + elm.id, function(e) { elm.notify(", elm
88 , ".id, e.value); });" ]
90 exportEvent (js,elm,_) =
91 concat [ "\nA2( Signal.lift, function(v) { "
92 , "var e = document.createEvent('Event');"
93 , "e.initEvent('", js, "_' + elm.id, true, true);"
95 , "document.dispatchEvent(e); return v; }, ", elm, ");" ]
97jsImport (modul, method) =
98 concat $ zipWith3 (\s n v -> s ++ assign' n v ++ ";") starters subnames values
100 starters = "\nvar " : repeat "\n"
101 values = map (\name -> name ++ " || {}") (init subnames) ++
102 ["Elm." ++ modul ++ parens "elm"]
103 subnames = map dotSep . tail . List.inits $ split modul
105 split names = case go [] names of
107 (name, ns) -> name : split ns
108 go name str = case str of
109 '.':rest -> (reverse name, rest)
110 c:rest -> go (c:name) rest
111 [] -> (reverse name, [])
115 toJS :: a -> Unique String
118instance ToJS (Def t v) where
120 -- TODO: Make this handle patterns besides plain variables
121 toJS (Def (PVar x) e)
122 | isOp x = globalAssign ("_op['" ++ x ++ "']") `liftM` toJS' e
123 | otherwise = assign x `liftM` toJS' e
125 toJS (Def pattern e@(L s _)) =
127 let x = "_" ++ show n
129 toDef y = Def (PVar y) (L s $ Case (var x) [(pattern, var y)])
130 stmt <- assign x `liftM` toJS' e
131 vars <- toJS . map toDef . Set.toList $ SD.boundVars pattern
132 return (stmt ++ vars)
134 toJS (TypeAnnotation _ _) = return ""
137instance ToJS a => ToJS [a] where
138 toJS xs = concat `liftM` mapM toJS xs
140toJS' :: LExpr t v -> Unique String
143 MultiIf ps -> multiIfToJS span ps
144 Case e cases -> caseToJS span e cases
147remove x e = "_N.remove('" ++ x ++ "', " ++ e ++ ")"
148addField x v e = "_N.insert('" ++ x ++ "', " ++ v ++ ", " ++ e ++ ")"
149setField fs e = "_N.replace(" ++ jsList (map f fs) ++ ", " ++ e ++ ")"
150 where f (x,v) = "['" ++ x ++ "'," ++ v ++ "]"
151access x e = e ++ "." ++ x
152makeRecord kvs = record `liftM` collect kvs
154 combine r (k,v) = Map.insertWith (++) k v r
155 collect = liftM (List.foldl' combine Map.empty) . mapM prep
160 brackets ("\n "++List.intercalate ",\n " (map (\(k,v) -> k++":"++v) fs))
161 hidden = fields . map (second jsList) .
162 filter (not . null . snd) . Map.toList . Map.map tail
163 record kvs = fields . (("_", hidden kvs) :) . Map.toList . Map.map head $ kvs
166instance ToJS Literal where
169 Chr c -> return $ quoted [c]
170 Str s -> return $ "_str" ++ parens (quoted s)
171 IntNum n -> return $ show n
172 FloatNum n -> return $ show n
173 Boolean b -> return $ if b then "true" else "false"
176instance ToJS (Expr t v) where
180 Literal lit -> toJS lit
181 Range lo hi -> jsRange `liftM` toJS' lo `ap` toJS' hi
182 Access e x -> access x `liftM` toJS' e
183 Remove e x -> remove x `liftM` toJS' e
184 Insert e x v -> addField x `liftM` toJS' v `ap` toJS' e
185 Modify e fs -> do fs' <- (mapM (\(x,v) -> (,) x `liftM` toJS' v) fs)
186 setField fs' `liftM` toJS' e
187 Record fs -> makeRecord fs
188 Binop op e1 e2 -> binop op e1 e2
190 Lambda p e@(L s _) -> liftM fastFunc (toJS' body)
193 | length args < 2 || length args > 9 =
194 foldr (\arg bod -> jsFunc arg (ret bod)) body args
196 "F" ++ show (length args) ++ parens (jsFunc (commaSep args) (ret body))
198 (args, body) = first reverse $ foldr depattern ([], innerBody) (zip patterns [1..])
200 depattern (pattern,n) (args, body) =
202 PVar x -> (x : args, body)
203 _ -> let arg = "arg" ++ show n
204 in (arg:args, L s (Case (L s (Var arg)) [(pattern, body)]))
206 (patterns, innerBody) = collect [p] e
208 collect patterns lexpr@(L _ expr) =
210 Lambda p e -> collect (p:patterns) e
211 _ -> (patterns, lexpr)
213 App e1 e2 -> jsApp e1 e2
214 Let defs e -> jsLet $ SD.flattenLets defs e
217 do es' <- mapM toJS' es
218 return $ "_J.toList" ++ parens (jsList es')
221 do fs <- mapM toJS' es
222 return $ case name of
224 "::" -> jsCons (head fs) ((head . tail) fs)
225 _ -> jsObj $ ("ctor:" ++ show name) : fields
226 where fields = zipWith (\n e -> "_" ++ show n ++ ":" ++ e) [0..] fs
228 Markdown doc -> return $ "Text.text('" ++ pad ++ md ++ pad ++ "')"
229 where pad = "<div style=\"height:0;width:0;\"> </div>"
230 md = formatMarkdown $ Pan.writeHtmlString Pan.def doc
234 as <- mapM toJS' args
237 _ -> "A" ++ show (length as) ++ parens (commaSep (f:as))
239 (func, args) = go [e2] e1
242 (L _ (App e1 e2)) -> go (e2 : args) e1
245formatMarkdown = concatMap f
253 (L _ (Var "otherwise"), e) -> toJS' e >>= \b -> format b (init ps)
254 _ -> format ("_E.If" ++ parens ("$moduleName," ++ quoted (show span))) ps
257 foldr (\c e -> parens $ c ++ " : " ++ e) base `liftM` mapM f ps
258 f (b,e) = do b' <- toJS' b
260 return (b' ++ " ? " ++ e')
262jsLet (defs,e') = do ds <- mapM toJS defs
264 return $ jsFunc "" (concat ds ++ ret e) ++ "()"
266caseToJS span e ps = do
267 (tempVar,match) <- caseToMatch ps
269 let (match',stmt) = case e of
270 L _ (Var x) -> (matchSubst [(tempVar,x)] match, "")
271 _ -> (match, assign tempVar e')
272 matches <- matchToJS span match'
273 return $ jsFunc "" (stmt ++ matches) ++ "()"
275matchToJS span match =
277 Match name clauses def ->
278 do cases <- concat `liftM` mapM (clauseToJS span name) clauses
279 finally <- matchToJS span def
280 let isLiteral p = case p of
281 Clause (Right _) _ _ -> True
283 access = if any isLiteral clauses then "" else ".ctor"
284 return $ concat [ "\nswitch (", name, access, ") {"
285 , indent cases, "\n}", finally ]
286 Fail -> return ("_E.Case" ++ parens ("$moduleName," ++ quoted (show span)))
287 Break -> return "break;"
288 Other e -> ret `liftM` toJS' e
289 Seq ms -> concat `liftM` mapM (matchToJS span) (dropEnd [] ms)
294 Other _ -> acc ++ [m]
295 _ -> dropEnd (acc ++ [m]) ms
297clauseToJS span var (Clause value vars e) = do
298 let vars' = map (\n -> var ++ "._" ++ show n) [0..]
299 s <- matchToJS span $ matchSubst (zip vars vars') e
300 pattern <- case value of
301 Right (Boolean True) -> return "true"
302 Right (Boolean False) -> return "false"
303 Right lit -> toJS lit
304 Left name -> return . quoted $ case List.elemIndices '.' name of
306 is -> drop (last is + 1) name
307 return $ concat [ "\ncase ", pattern, ":", indent s ]
310jsCons e1 e2 = "_L.Cons(" ++ e1 ++ "," ++ e2 ++ ")"
311jsRange e1 e2 = "_L.range" ++ parens (e1 ++ "," ++ e2)
312jsCompare e1 e2 op = parens ("_N.cmp(" ++ e1 ++ "," ++ e2 ++ ")" ++ op)
317 do ss <- mapM toJS' (e1 : collect [] e2)
318 return . jsFunc "$" . ret $ apply "$" ss
319 | op == "Basics.<|" =
321 funcs <- mapM toJS' (collect [] e1)
322 return (apply arg funcs)
324 binopNormal op `liftM` toJS' e1 `ap` toJS' e2
326 apply = foldr (\f arg-> f ++ parens arg)
330 L _ (Binop op e1 e2) | op == "Basics.." -> collect (es ++ [e1]) e2
333binopNormal op s1 s2 =
334 case Map.lookup op opDict of
336 Nothing -> "A2" ++ parens (commaSep [ prefix ++ func, s1, s2 ])
338 func | isOp op' = "_op['" ++ op' ++ "']"
340 (prefix, op') = case List.elemIndices '.' op of
342 xs -> splitAt (last xs + 1) op
344 opDict = Map.fromList $ basics ++
345 [ ("::", jsCons s1 s2)
346 , ("List.++", "_L.append" ++ parens (s1 ++ "," ++ s2)) ]
348 ops = pow : map (\op -> (op, parens (s1 ++ op ++ s2))) ["+","-","*","/","&&","||"]
349 where pow = ("^" , "Math.pow(" ++ s1 ++ "," ++ s2 ++ ")")
351 basics = ops ++ map (\(op,e) -> ("Basics." ++ op, e))
352 (ops ++ [ ("<|", s1 ++ parens s2)
353 , ("|>", s2 ++ parens s1)
354 , ("==", "_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
355 , ("/=", "!_N.eq(" ++ s1 ++ "," ++ s2 ++ ")")
356 , ("<" , jsCompare s1 s2 "<0")
357 , (">" , jsCompare s1 s2 ">0")
358 , ("<=", jsCompare s1 s2 "<1")
359 , (">=", jsCompare s1 s2 ">-1")
360 , ("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