2012-04-19 06:32:10 +00:00
|
|
|
|
2012-06-14 08:43:04 +00:00
|
|
|
module CompileToJS (showErr, jsModule) where
|
2012-04-19 06:32:10 +00:00
|
|
|
|
|
|
|
import Ast
|
2012-08-01 23:37:37 +00:00
|
|
|
import Control.Arrow (first)
|
2012-04-19 06:32:10 +00:00
|
|
|
import Control.Monad (liftM,(<=<),join)
|
2012-06-12 06:28:45 +00:00
|
|
|
import Data.Char (isAlpha,isDigit)
|
|
|
|
import Data.List (intercalate,sortBy,inits)
|
2012-04-19 06:32:10 +00:00
|
|
|
import Data.Map (toList)
|
2012-06-12 06:28:45 +00:00
|
|
|
import Data.Maybe (mapMaybe)
|
2012-04-19 06:32:10 +00:00
|
|
|
|
2012-05-29 18:25:43 +00:00
|
|
|
import Initialize
|
2012-08-09 14:38:18 +00:00
|
|
|
import Rename (derename)
|
2012-04-19 06:32:10 +00:00
|
|
|
|
2012-06-09 16:13:10 +00:00
|
|
|
showErr :: String -> String
|
2012-06-12 06:28:45 +00:00
|
|
|
showErr err = mainEquals $ "text(monospace(" ++ msg ++ "))"
|
2012-06-09 16:13:10 +00:00
|
|
|
where msg = show . concatMap (++"<br>") . lines $ err
|
2012-04-19 06:32:10 +00:00
|
|
|
|
2012-06-28 08:52:47 +00:00
|
|
|
parens s = "(" ++ s ++ ")"
|
|
|
|
braces s = "{" ++ s ++ "}"
|
|
|
|
jsList ss = "["++ intercalate "," ss ++"]"
|
2012-04-19 06:32:10 +00:00
|
|
|
jsFunc args body = "function(" ++ args ++ "){" ++ body ++ "}"
|
2012-06-12 06:28:45 +00:00
|
|
|
assign x e = "\nvar " ++ x ++ "=" ++ e ++ ";"
|
|
|
|
ret e = "\nreturn "++ e ++";"
|
2012-04-19 06:32:10 +00:00
|
|
|
iff a b c = a ++ "?" ++ b ++ ":" ++ c
|
|
|
|
|
2012-06-12 06:28:45 +00:00
|
|
|
mainEquals s = globalAssign "ElmCode.main" (jsFunc "" (ret s))
|
|
|
|
globalAssign m s = "\n" ++ m ++ "=" ++ s ++ ";"
|
|
|
|
|
2012-08-11 10:44:09 +00:00
|
|
|
tryBlock escapees names e =
|
2012-06-19 06:59:46 +00:00
|
|
|
concat [ "\ntry{\n" ++ e ++ "\n\n} catch (e) {"
|
|
|
|
, "ElmCode.main=function() {"
|
2012-08-11 10:44:09 +00:00
|
|
|
, "var msg = ('<br/><h2>Your browser may not be supported. " ++
|
2012-06-19 06:59:46 +00:00
|
|
|
"Are you using a modern browser?</h2>' +" ++
|
2012-08-11 10:44:09 +00:00
|
|
|
" '<br/><span style=\"color:grey\">Runtime Error in " ++
|
|
|
|
intercalate "." names ++ " module:<br/>' + e + '" ++ msg ++ "</span>');"
|
2012-06-19 06:59:46 +00:00
|
|
|
, "document.body.innerHTML = Text.monospace(msg);"
|
|
|
|
, "throw e;"
|
|
|
|
, "};}"
|
|
|
|
]
|
2012-08-11 10:44:09 +00:00
|
|
|
where msg | escapees /= [] = concat [ "<br/><br/>The problem may stem from an improper usage of:<br/>"
|
|
|
|
, intercalate ", " escapees ]
|
|
|
|
| otherwise = ""
|
2012-06-12 06:28:45 +00:00
|
|
|
|
2012-08-11 10:44:09 +00:00
|
|
|
jsModule (escapees, Module names exports imports stmts) =
|
|
|
|
tryBlock escapees (tail modNames) $ concat
|
2012-06-25 12:07:52 +00:00
|
|
|
[ concatMap (\n -> globalAssign n $ n ++ " || {}") .
|
|
|
|
map (intercalate ".") . drop 2 . inits $
|
|
|
|
take (length modNames - 1) modNames
|
|
|
|
, "\nif (" ++ modName ++ ") throw \"Module name collision, '" ++
|
|
|
|
intercalate "." (tail modNames) ++ "' is already defined.\"; "
|
2012-08-01 23:37:37 +00:00
|
|
|
, globalAssign modName $ jsFunc "" (includes ++body++ export) ++ "()"
|
2012-06-25 12:07:52 +00:00
|
|
|
, mainEquals $ modName ++ ".main" ]
|
2012-08-01 23:37:37 +00:00
|
|
|
where modNames = if null names then ["ElmCode", "Main"]
|
|
|
|
else "ElmCode" : names
|
2012-06-12 06:28:45 +00:00
|
|
|
modName = intercalate "." modNames
|
2012-08-01 23:37:37 +00:00
|
|
|
includes = concatMap jsImport $ map (first ("ElmCode."++)) imports
|
|
|
|
body = stmtsToJS stmts
|
|
|
|
export = getExports exps stmts
|
2012-06-12 06:28:45 +00:00
|
|
|
exps = if null exports then ["main"] else exports
|
|
|
|
|
2012-08-01 23:37:37 +00:00
|
|
|
getExports names stmts = ret . braces $ intercalate "," pairs
|
|
|
|
where pairs = mapMaybe pair $ concatMap get stmts
|
|
|
|
pair x = if y `elem` names then Just $ y ++ ":" ++ x else Nothing
|
2012-08-09 14:38:18 +00:00
|
|
|
where y = derename x
|
2012-08-01 23:37:37 +00:00
|
|
|
get s = case s of Def x _ _ -> [x]
|
|
|
|
Datatype _ _ tcs -> map fst tcs
|
|
|
|
ImportEvent _ _ x _ -> [x]
|
|
|
|
ExportEvent _ _ _ -> []
|
|
|
|
|
2012-06-25 12:07:52 +00:00
|
|
|
|
2012-06-14 10:02:33 +00:00
|
|
|
jsImport (modul, how) =
|
2012-06-28 08:52:47 +00:00
|
|
|
concat [ "\ntry{" ++ modul ++ " instanceof Object} catch(e) {throw \"Module '"
|
2012-06-19 06:59:46 +00:00
|
|
|
, drop 1 (dropWhile (/='.') modul)
|
|
|
|
, "' is missing. Compile with --make flag or load missing "
|
2012-06-25 12:07:52 +00:00
|
|
|
, "module in a separate JavaScript file.\";}" ] ++
|
|
|
|
jsImport' (modul, how)
|
2012-06-19 06:59:46 +00:00
|
|
|
|
2012-06-14 10:02:33 +00:00
|
|
|
jsImport' (modul, As name) = assign name modul
|
|
|
|
jsImport' (modul, Importing []) = jsImport' (modul, Hiding [])
|
|
|
|
jsImport' (modul, Importing vs) =
|
2012-06-12 06:28:45 +00:00
|
|
|
concatMap (\v -> assign v $ modul ++ "." ++ v) vs
|
2012-06-14 10:02:33 +00:00
|
|
|
jsImport' (modul, Hiding vs) =
|
2012-06-19 06:59:46 +00:00
|
|
|
concat [ "\nfor(var i in " ++ modul ++ "){"
|
|
|
|
, assign "hiddenVars" . jsList $ map (\v -> "'" ++ v ++ "'") vs
|
|
|
|
, "\nif (hiddenVars.indexOf(i) >= 0) continue;"
|
|
|
|
, globalAssign "this[i]" $ modul ++ "[i]"
|
|
|
|
, "}" ]
|
2012-06-12 06:28:45 +00:00
|
|
|
|
2012-08-01 23:37:37 +00:00
|
|
|
stmtsToJS :: [Statement] -> String
|
|
|
|
stmtsToJS stmts = concatMap stmtToJS (sortBy cmpStmt stmts)
|
|
|
|
where cmpStmt s1 s2 = compare (valueOf s1) (valueOf s2)
|
|
|
|
valueOf s = case s of Datatype _ _ _ -> 1
|
|
|
|
ImportEvent _ _ _ _ -> 3
|
|
|
|
Def _ [] _ -> 3
|
|
|
|
Def _ _ _ -> 4
|
|
|
|
ExportEvent _ _ _ -> 5
|
|
|
|
|
|
|
|
stmtToJS :: Statement -> String
|
|
|
|
stmtToJS (Def name [] e) = assign name (toJS e)
|
|
|
|
stmtToJS (Def name (a:as) e) = "\nfunction " ++ name ++ parens a ++
|
|
|
|
braces (ret . toJS $ foldr Lambda e as) ++ ";"
|
|
|
|
stmtToJS (Datatype _ _ tcs) = concatMap (stmtToJS . toDef) tcs
|
2012-08-09 14:38:18 +00:00
|
|
|
where toDef (name,args) = Def name vars $ Data (derename name) (map Var vars)
|
2012-08-01 23:37:37 +00:00
|
|
|
where vars = map (('a':) . show) [1..length args]
|
|
|
|
stmtToJS (ImportEvent js base elm _) =
|
|
|
|
concat [ "\nvar " ++ elm ++ " = Elm.Input(" ++ toJS base ++ ");"
|
|
|
|
, "\nSignal.addListener(document, '" ++ js
|
|
|
|
, "', function(e) { Dispatcher.notify(" ++ elm
|
|
|
|
, ".id, e.value); });" ]
|
|
|
|
stmtToJS (ExportEvent js elm _) =
|
|
|
|
concat [ "\nlift(function(v) { var e = document.createEvent('Event');"
|
|
|
|
, "e.initEvent('" ++ js ++ "', true, true);"
|
|
|
|
, "e.value = v;"
|
|
|
|
, "document.dispatchEvent(e); return v; })(" ++ elm ++ ");"
|
|
|
|
]
|
|
|
|
|
|
|
|
|
2012-06-12 06:28:45 +00:00
|
|
|
|
2012-08-01 23:37:37 +00:00
|
|
|
toJS :: Expr -> String
|
2012-04-19 06:32:10 +00:00
|
|
|
toJS expr =
|
|
|
|
case expr of
|
2012-07-19 11:47:53 +00:00
|
|
|
IntNum n -> show n
|
|
|
|
FloatNum n -> show n
|
2012-04-19 06:32:10 +00:00
|
|
|
Var x -> x
|
|
|
|
Chr c -> show c
|
2012-06-28 08:52:47 +00:00
|
|
|
Str s -> "Value.str" ++ parens (show s)
|
2012-04-19 06:32:10 +00:00
|
|
|
Boolean b -> if b then "true" else "false"
|
|
|
|
Range lo hi -> jsRange (toJS lo) (toJS hi)
|
|
|
|
Access e lbl -> toJS e ++ "." ++ lbl
|
|
|
|
Binop op e1 e2 -> binop op (toJS e1) (toJS e2)
|
|
|
|
If eb et ef -> parens $ iff (toJS eb) (toJS et) (toJS ef)
|
|
|
|
Lambda v e -> jsFunc v $ ret (toJS e)
|
2012-08-14 18:50:03 +00:00
|
|
|
App (Var "toText") (Str s) -> "toText" ++ parens (show s)
|
2012-05-22 22:07:21 +00:00
|
|
|
App (Var "link") (Str s) -> "link(" ++ show s ++ ")"
|
|
|
|
App (Var "plainText") (Str s) -> "plainText(" ++ show s ++ ")"
|
2012-04-19 06:32:10 +00:00
|
|
|
App e1 e2 -> toJS e1 ++ parens (toJS e2)
|
|
|
|
Let defs e -> jsLet defs e
|
|
|
|
Case e cases -> jsCase e cases
|
|
|
|
Data name es -> jsList $ show name : map toJS es
|
|
|
|
|
2012-06-12 06:28:45 +00:00
|
|
|
jsLet defs e' = jsFunc "" (jsDefs defs ++ ret (toJS e')) ++ "()"
|
|
|
|
|
|
|
|
jsDefs defs = concatMap toDef $ sortBy f defs
|
2012-07-21 23:48:51 +00:00
|
|
|
where f a b = compare (valueOf a) (valueOf b)
|
|
|
|
valueOf (Definition _ args _) = min 1 (length args)
|
|
|
|
toDef (Definition x [] e) = assign x (toJS e)
|
|
|
|
toDef (Definition f (a:as) e) =
|
|
|
|
"\nfunction " ++ f ++ parens a ++ braces (ret . toJS $ foldr Lambda e as) ++ ";"
|
2012-04-19 06:32:10 +00:00
|
|
|
|
|
|
|
jsCase e [c] = jsMatch c ++ parens (toJS e)
|
|
|
|
jsCase e cases = "(function(){" ++
|
|
|
|
assign "v" (toJS e) ++
|
|
|
|
assign "c" jsCases ++
|
|
|
|
"for(var i=c.length;i--;){" ++
|
|
|
|
assign "r" "c[i](v)" ++
|
|
|
|
"if(r!==undefined){return r;}}}())"
|
|
|
|
where jsCases = jsList $ map jsMatch (reverse cases)
|
|
|
|
|
|
|
|
jsMatch (p,e) = jsFunc "v" . match p "v" . ret $ toJS e
|
|
|
|
match p v hole =
|
|
|
|
case p of
|
|
|
|
PAnything -> hole
|
|
|
|
PVar x -> assign x v ++ hole
|
|
|
|
PData name ps ->
|
|
|
|
"if(" ++ show name ++ "!==" ++ v ++
|
|
|
|
"[0]){return undefined;}else{"++body++"}"
|
|
|
|
where matches = zipWith match ps vs
|
|
|
|
vs = map (\i -> v++"["++show (i+1)++"]") [0..length ps-1]
|
|
|
|
body = foldr ($) hole matches
|
|
|
|
|
|
|
|
jsNil = "[\"Nil\"]"
|
|
|
|
jsCons e1 e2 = jsList [ show "Cons", e1, e2 ]
|
|
|
|
jsRange e1 e2 = (++"()") . jsFunc "" $
|
|
|
|
assign "lo" e1 ++ assign "hi" e2 ++ assign "lst" jsNil ++
|
2012-08-10 20:15:48 +00:00
|
|
|
"if(lo<=hi){do{" ++ assign "lst" (jsCons "hi" "lst") ++ "}while(hi-->lo)}" ++
|
2012-04-19 06:32:10 +00:00
|
|
|
ret "lst"
|
|
|
|
|
|
|
|
binop (o:p) e1 e2
|
|
|
|
| isAlpha o || '_' == o = (o:p) ++ parens e1 ++ parens e2
|
|
|
|
| otherwise = case o:p of
|
2012-07-28 18:44:34 +00:00
|
|
|
":" -> jsCons e1 e2
|
2012-04-19 06:32:10 +00:00
|
|
|
"++" -> append e1 e2
|
2012-07-28 18:44:34 +00:00
|
|
|
"$" -> e1 ++ parens e2
|
|
|
|
"." -> jsFunc "x" . ret $ e1 ++ parens (e2 ++ parens "x")
|
|
|
|
"^" -> "Math.pow(" ++ e1 ++ "," ++ e2 ++ ")"
|
2012-06-10 12:58:11 +00:00
|
|
|
"==" -> "eq(" ++ e1 ++ "," ++ e2 ++ ")"
|
|
|
|
"/=" -> "not(eq(" ++ e1 ++ "," ++ e2 ++ "))"
|
2012-07-28 18:44:34 +00:00
|
|
|
"<" -> "(compare(" ++ e1 ++ ")(" ++ e2 ++ ")[0] === 'LT')"
|
|
|
|
">" -> "(compare(" ++ e1 ++ ")(" ++ e2 ++ ")[0] === 'GT')"
|
|
|
|
"<=" -> "function() { var ord = compare(" ++ e1 ++ ")(" ++ e2 ++ ")[0]; return ord === 'LT' || ord === 'EQ'; }()"
|
|
|
|
">=" -> "function() { var ord = compare(" ++ e1 ++ ")(" ++ e2 ++ ")[0]; return ord === 'GT' || ord === 'EQ'; }()"
|
|
|
|
_ -> parens (e1 ++ (o:p) ++ e2)
|
2012-04-19 06:32:10 +00:00
|
|
|
|
|
|
|
append e1 e2 = "Value.append" ++ parens (e1 ++ "," ++ e2)
|