Mercurial > urweb
view src/jscomp.sml @ 1739:c414850f206f
Add support for -boot flag, which allows in-tree execution of Ur/Web
The boot flag rewrites most hardcoded paths to point to the build
directory, and also forces static compilation. This is convenient
for developing Ur/Web, or if you cannot 'sudo make install' Ur/Web.
The following changes were made:
* Header files were moved to include/urweb instead of include;
this lets FFI users point their C_INCLUDE_PATH at this directory
at write <urweb/urweb.h>. For internal Ur/Web executables,
we simply pass -I$PATH/include/urweb as normal.
* Differentiate between LIB and SRCLIB; SRCLIB is Ur and JavaScript
source files, while LIB is compiled products from libtool. For
in-tree compilation these live in different places.
* No longer reference Config for paths; instead use Settings; these
settings can be changed dynamically by Compiler.enableBoot ()
(TODO: add a disableBoot function.)
* config.h is now generated directly in include/urweb/config.h,
for consistency's sake (especially since it gets installed
along with the rest of the headers!)
* All of the autotools build products got updated.
* The linkStatic field in protocols now only contains the name of the
build product, and not the absolute path.
Future users have to be careful not to reference the Settings files
to early, lest they get an old version (this was the source of two
bugs during development of this patch.)
author | Edward Z. Yang <ezyang@mit.edu> |
---|---|
date | Wed, 02 May 2012 17:17:57 -0400 |
parents | 0577be31a435 |
children | 10a2cb93d175 |
line wrap: on
line source
(* Copyright (c) 2008-2011, Adam Chlipala * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * - Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * - Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * - The names of contributors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. *) structure JsComp :> JSCOMP = struct open Mono structure EM = ErrorMsg structure E = MonoEnv structure U = MonoUtil structure IS = IntBinarySet structure IM = IntBinaryMap structure TM = BinaryMapFn(struct type ord_key = typ val compare = U.Typ.compare end) type state = { decls : (string * int * (string * int * typ option) list) list, script : string list, included : IS.set, injectors : int IM.map, listInjectors : int TM.map, decoders : int IM.map, maxName : int } fun strcat loc es = case es of [] => (EPrim (Prim.String ""), loc) | [x] => x | x :: es' => (EStrcat (x, strcat loc es'), loc) exception CantEmbed of typ fun inString {needle, haystack} = String.isSubstring needle haystack fun process file = let val (someTs, nameds) = foldl (fn ((DVal (_, n, t, e, _), _), (someTs, nameds)) => (someTs, IM.insert (nameds, n, e)) | ((DValRec vis, _), (someTs, nameds)) => (someTs, foldl (fn ((_, n, _, e, _), nameds) => IM.insert (nameds, n, e)) nameds vis) | ((DDatatype dts, _), state as (someTs, nameds)) => (foldl (fn ((_, _, cs), someTs) => if ElabUtil.classifyDatatype cs = Option then foldl (fn ((_, n, SOME t), someTs) => IM.insert (someTs, n, t) | (_, someTs) => someTs) someTs cs else someTs) someTs dts, nameds) | (_, state) => state) (IM.empty, IM.empty) file fun str loc s = (EPrim (Prim.String s), loc) fun isNullable (t, _) = case t of TOption _ => true | TList _ => true | TDatatype (_, ref (Option, _)) => true | TRecord [] => true | _ => false fun quoteExp loc (t : typ) (e, st) = case #1 t of TSource => ((EFfiApp ("Basis", "htmlifySource", [(e, t)]), loc), st) | TRecord [] => (str loc "null", st) | TRecord [(x, t)] => let val (e, st) = quoteExp loc t ((EField (e, x), loc), st) in (strcat loc [str loc ("{_" ^ x ^ ":"), e, str loc "}"], st) end | TRecord ((x, t) :: xts) => let val (e', st) = quoteExp loc t ((EField (e, x), loc), st) val (es, st) = ListUtil.foldlMap (fn ((x, t), st) => let val (e, st) = quoteExp loc t ((EField (e, x), loc), st) in (strcat loc [str loc (",_" ^ x ^ ":"), e], st) end) st xts in (strcat loc (str loc ("{_" ^ x ^ ":") :: e' :: es @ [str loc "}"]), st) end | TFfi ("Basis", "string") => ((EFfiApp ("Basis", "jsifyString", [(e, t)]), loc), st) | TFfi ("Basis", "char") => ((EFfiApp ("Basis", "jsifyChar", [(e, t)]), loc), st) | TFfi ("Basis", "int") => ((EFfiApp ("Basis", "htmlifyInt", [(e, t)]), loc), st) | TFfi ("Basis", "float") => ((EFfiApp ("Basis", "htmlifyFloat", [(e, t)]), loc), st) | TFfi ("Basis", "channel") => ((EFfiApp ("Basis", "jsifyChannel", [(e, t)]), loc), st) | TFfi ("Basis", "time") => ((EFfiApp ("Basis", "jsifyTime", [(e, t)]), loc), st) | TFfi ("Basis", "bool") => ((ECase (e, [((PCon (Enum, PConFfi {mod = "Basis", datatyp = "bool", con = "True", arg = NONE}, NONE), loc), str loc "true"), ((PCon (Enum, PConFfi {mod = "Basis", datatyp = "bool", con = "False", arg = NONE}, NONE), loc), str loc "false")], {disc = (TFfi ("Basis", "bool"), loc), result = (TFfi ("Basis", "string"), loc)}), loc), st) | TOption t => let val (e', st) = quoteExp loc t ((ERel 0, loc), st) in (case #1 e' of EPrim (Prim.String "ERROR") => raise Fail "UHOH" | _ => (ECase (e, [((PNone t, loc), str loc "null"), ((PSome (t, (PVar ("x", t), loc)), loc), if isNullable t then strcat loc [str loc "{v:", e', str loc "}"] else e')], {disc = (TOption t, loc), result = (TFfi ("Basis", "string"), loc)}), loc), st) end | TList t' => (case TM.find (#listInjectors st, t') of SOME n' => ((EApp ((ENamed n', loc), e), loc), st) | NONE => let val rt = (TRecord [("1", t'), ("2", t)], loc) val n' = #maxName st val st = {decls = #decls st, script = #script st, included = #included st, injectors = #injectors st, listInjectors = TM.insert (#listInjectors st, t', n'), decoders = #decoders st, maxName = n' + 1} val s = (TFfi ("Basis", "string"), loc) val (e', st) = quoteExp loc t' ((EField ((ERel 0, loc), "1"), loc), st) val body = (ECase ((ERel 0, loc), [((PNone rt, loc), str loc "null"), ((PSome (rt, (PVar ("x", rt), loc)), loc), strcat loc [str loc "{_1:", e', str loc ",_2:", (EApp ((ENamed n', loc), (EField ((ERel 0, loc), "2"), loc)), loc), str loc "}"])], {disc = t, result = s}), loc) val body = (EAbs ("x", t, s, body), loc) val st = {decls = ("jsify", n', (TFun (t, s), loc), body, "jsify") :: #decls st, script = #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders= #decoders st, maxName = #maxName st} in ((EApp ((ENamed n', loc), e), loc), st) end) | TDatatype (n, ref (dk, cs)) => (case IM.find (#injectors st, n) of SOME n' => ((EApp ((ENamed n', loc), e), loc), st) | NONE => let val n' = #maxName st val st = {decls = #decls st, script = #script st, included = #included st, injectors = IM.insert (#injectors st, n, n'), listInjectors = #listInjectors st, decoders = #decoders st, maxName = n' + 1} val (pes, st) = ListUtil.foldlMap (fn ((_, cn, NONE), st) => (((PCon (dk, PConVar cn, NONE), loc), case dk of Option => str loc "null" | _ => str loc (Int.toString cn)), st) | ((_, cn, SOME t), st) => let val (e, st) = quoteExp loc t ((ERel 0, loc), st) in (((PCon (dk, PConVar cn, SOME (PVar ("x", t), loc)), loc), case dk of Option => if isNullable t then strcat loc [str loc "{v:", e, str loc "}"] else e | _ => strcat loc [str loc ("{n:" ^ Int.toString cn ^ ",v:"), e, str loc "}"]), st) end) st cs val s = (TFfi ("Basis", "string"), loc) val body = (ECase ((ERel 0, loc), pes, {disc = t, result = s}), loc) val body = (EAbs ("x", t, s, body), loc) val st = {decls = ("jsify", n', (TFun (t, s), loc), body, "jsify") :: #decls st, script = #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders= #decoders st, maxName = #maxName st} in ((EApp ((ENamed n', loc), e), loc), st) end) | _ => ((*Print.prefaces "Can't embed" [("t", MonoPrint.p_typ MonoEnv.empty t)];*) raise CantEmbed t) fun unurlifyExp loc (t : typ, st) = case #1 t of TRecord [] => ("(i++,null)", st) | TFfi ("Basis", "unit") => ("(i++,null)", st) | TRecord [(x, t)] => let val (e, st) = unurlifyExp loc (t, st) in ("{_" ^ x ^ ":" ^ e ^ "}", st) end | TRecord ((x, t) :: xts) => let val (e', st) = unurlifyExp loc (t, st) val (es, st) = ListUtil.foldlMap (fn ((x, t), st) => let val (e, st) = unurlifyExp loc (t, st) in (",_" ^ x ^ ":" ^ e, st) end) st xts in (String.concat ("{_" :: x :: ":" :: e' :: es @ ["}"]), st) end | TFfi ("Basis", "string") => ("uu(t[i++])", st) | TFfi ("Basis", "char") => ("uu(t[i++])", st) | TFfi ("Basis", "int") => ("parseInt(t[i++])", st) | TFfi ("Basis", "time") => ("parseInt(t[i++])", st) | TFfi ("Basis", "float") => ("parseFloat(t[i++])", st) | TFfi ("Basis", "channel") => ("(t[i++].length > 0 ? parseInt(t[i-1]) : null)", st) | TFfi ("Basis", "bool") => ("t[i++] == \"1\"", st) | TSource => ("parseSource(t[i++], t[i++])", st) | TOption t => let val (e, st) = unurlifyExp loc (t, st) val e = if isNullable t then "{v:" ^ e ^ "}" else e in ("(t[i++]==\"Some\"?" ^ e ^ ":null)", st) end | TList t => let val (e, st) = unurlifyExp loc (t, st) in ("uul(function(){return t[i++];},function(){return " ^ e ^ "})", st) end | TDatatype (n, ref (dk, cs)) => (case IM.find (#decoders st, n) of SOME n' => ("(tmp=_n" ^ Int.toString n' ^ "(t,i),i=tmp._1,tmp._2)", st) | NONE => let val n' = #maxName st val st = {decls = #decls st, script = #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders = IM.insert (#decoders st, n, n'), maxName = n' + 1} val (e, st) = foldl (fn ((x, cn, NONE), (e, st)) => ("x==\"" ^ x ^ "\"?" ^ (case dk of Option => "null" | _ => Int.toString cn) ^ ":" ^ e, st) | ((x, cn, SOME t), (e, st)) => let val (e', st) = unurlifyExp loc (t, st) in ("x==\"" ^ x ^ "\"?" ^ (case dk of Option => if isNullable t then "{v:" ^ e' ^ "}" else e' | _ => "{n:" ^ Int.toString cn ^ ",v:" ^ e' ^ "}") ^ ":" ^ e, st) end) ("pf(\"" ^ ErrorMsg.spanToString loc ^ "\")", st) cs val body = "function _n" ^ Int.toString n' ^ "(t,i){var x=t[i++];var r=" ^ e ^ ";return {_1:i,_2:r}}\n\n" val st = {decls = #decls st, script = body :: #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders = #decoders st, maxName = #maxName st} in ("(tmp=_n" ^ Int.toString n' ^ "(t,i),i=tmp._1,tmp._2)", st) end) | _ => (EM.errorAt loc "Don't know how to unurlify type in JavaScript"; Print.prefaces "Can't unurlify" [("t", MonoPrint.p_typ MonoEnv.empty t)]; ("ERROR", st)) fun padWith (ch, s, len) = if size s < len then padWith (ch, String.str ch ^ s, len - 1) else s val foundJavaScript = ref false fun jsExp mode outer = let val len = length outer fun jsE inner (e as (_, loc), st) = let val str = str loc fun patCon pc = case pc of PConVar n => str (Int.toString n) | PConFfi {mod = "Basis", con = "True", ...} => str "true" | PConFfi {mod = "Basis", con = "False", ...} => str "false" | PConFfi {con, ...} => str ("\"" ^ con ^ "\"") fun unsupported s = (EM.errorAt loc (s ^ " in code to be compiled to JavaScript[2]"); Print.preface ("Code", MonoPrint.p_exp MonoEnv.empty e); (str "ERROR", st)) val strcat = strcat loc fun jsPrim p = let fun jsChar ch = case ch of #"'" => if mode = Attribute then "\\047" else "'" | #"\"" => "\\\"" | #"<" => "\\074" | #"\\" => "\\\\" | #"\n" => "\\n" | #"\r" => "\\r" | #"\t" => "\\t" | ch => if Char.isPrint ch orelse ord ch >= 128 then String.str ch else "\\" ^ padWith (#"0", Int.fmt StringCvt.OCT (ord ch), 3) in case p of Prim.String s => str ("\"" ^ String.translate jsChar s ^ "\"") | Prim.Char ch => str ("\"" ^ jsChar ch ^ "\"") | _ => str (Prim.toString p) end fun jsPat (p, _) = case p of PWild => str "{c:\"w\"}" | PVar _ => str "{c:\"v\"}" | PPrim p => strcat [str "{c:\"c\",v:", jsPrim p, str "}"] | PCon (_, PConFfi {mod = "Basis", con = "True", ...}, NONE) => str "{c:\"c\",v:true}" | PCon (_, PConFfi {mod = "Basis", con = "False", ...}, NONE) => str "{c:\"c\",v:false}" | PCon (Option, _, NONE) => str "{c:\"c\",v:null}" | PCon (Option, PConVar n, SOME p) => (case IM.find (someTs, n) of NONE => raise Fail "Jscomp: Not in someTs" | SOME t => strcat [str ("{c:\"s\",n:" ^ (if isNullable t then "true" else "false") ^ ",p:"), jsPat p, str "}"]) | PCon (_, pc, NONE) => strcat [str "{c:\"c\",v:", patCon pc, str "}"] | PCon (_, pc, SOME p) => strcat [str "{c:\"1\",n:", patCon pc, str ",p:", jsPat p, str "}"] | PRecord xps => strcat [str "{c:\"r\",l:", foldr (fn ((x, p, _), e) => strcat [str ("cons({n:\"" ^ x ^ "\",p:"), jsPat p, str "},", e, str ")"]) (str "null") xps, str "}"] | PNone _ => str "{c:\"c\",v:null}" | PSome (t, p) => strcat [str ("{c:\"s\",n:" ^ (if isNullable t then "true" else "false") ^ ",p:"), jsPat p, str "}"] val jsifyString = String.translate (fn #"\"" => "\\\"" | #"\\" => "\\\\" | ch => String.str ch) fun jsifyStringMulti (n, s) = case n of 0 => s | _ => jsifyStringMulti (n - 1, jsifyString s) fun deStrcat level (all as (e, _)) = case e of EPrim (Prim.String s) => jsifyStringMulti (level, s) | EStrcat (e1, e2) => deStrcat level e1 ^ deStrcat level e2 | EFfiApp ("Basis", "jsifyString", [(e, _)]) => "\"" ^ deStrcat (level + 1) e ^ "\"" | _ => (Print.prefaces "deStrcat" [("e", MonoPrint.p_exp MonoEnv.empty all)]; raise Fail "Jscomp: deStrcat") val quoteExp = quoteExp loc in (*Print.prefaces "jsE" [("e", MonoPrint.p_exp MonoEnv.empty e), ("inner", Print.PD.string (Int.toString inner))];*) case #1 e of EPrim p => (strcat [str "{c:\"c\",v:", jsPrim p, str "}"], st) | ERel n => if n < inner then (str ("{c:\"v\",n:" ^ Int.toString n ^ "}"), st) else let val n = n - inner (*val () = Print.prefaces "quote" [("t", MonoPrint.p_typ MonoEnv.empty (List.nth (outer, n)))]*) val (e, st) = quoteExp (List.nth (outer, n)) ((ERel n, loc), st) in (strcat [str "{c:\"c\",v:", e, str "}"], st) end | ENamed n => let val st = if IS.member (#included st, n) then st else case IM.find (nameds, n) of NONE => raise Fail "Jscomp: Unbound ENamed" | SOME e => let val st = {decls = #decls st, script = #script st, included = IS.add (#included st, n), injectors = #injectors st, listInjectors = #listInjectors st, decoders = #decoders st, maxName = #maxName st} val old = e val (e, st) = jsExp mode [] (e, st) val e = deStrcat 0 e val e = String.translate (fn #"'" => "\\'" | #"\\" => "\\\\" | ch => String.str ch) e val sc = "urfuncs[" ^ Int.toString n ^ "] = {c:\"t\",f:'" ^ e ^ "'};\n" in (*Print.prefaces "jsify'" [("old", MonoPrint.p_exp MonoEnv.empty old), ("new", MonoPrint.p_exp MonoEnv.empty new)];*) {decls = #decls st, script = sc :: #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders= #decoders st, maxName = #maxName st} end in (str ("{c:\"n\",n:" ^ Int.toString n ^ "}"), st) end | ECon (Option, _, NONE) => (str "{c:\"c\",v:null}", st) | ECon (Option, PConVar n, SOME e) => let val (e, st) = jsE inner (e, st) in case IM.find (someTs, n) of NONE => raise Fail "Jscomp: Not in someTs [2]" | SOME t => (if isNullable t then strcat [str "{c:\"s\",v:", e, str "}"] else e, st) end | ECon (_, pc, NONE) => (strcat [str "{c:\"c\",v:", patCon pc, str "}"], st) | ECon (_, pc, SOME e) => let val (s, st) = jsE inner (e, st) in (strcat [str "{c:\"1\",n:", patCon pc, str ",v:", s, str "}"], st) end | ENone _ => (str "{c:\"c\",v:null}", st) | ESome (t, e) => let val (e, st) = jsE inner (e, st) in (if isNullable t then strcat [str "{c:\"s\",v:", e, str "}"] else e, st) end | EFfi k => let val name = case Settings.jsFunc k of NONE => (EM.errorAt loc ("Unsupported FFI identifier " ^ #2 k ^ " in JavaScript"); "ERROR") | SOME s => s in (str ("{c:\"c\",v:" ^ name ^ "}"), st) end | EFfiApp ("Basis", "sigString", [_]) => (strcat [str "{c:\"c\",v:\"", e, str "\"}"], st) | EFfiApp (m, x, args) => let val name = case Settings.jsFunc (m, x) of NONE => (EM.errorAt loc ("Unsupported FFI function " ^ m ^ "." ^ x ^ " in JavaScript"); "ERROR") | SOME s => s val (e, st) = foldr (fn ((e, _), (acc, st)) => let val (e, st) = jsE inner (e, st) in (strcat [str "cons(", e, str ",", acc, str ")"], st) end) (str "null", st) args in (strcat [str ("{c:\"f\",f:\"" ^ name ^ "\",a:"), e, str "}"], st) end | EApp (e1, e2) => let val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE inner (e2, st) in (strcat [str "{c:\"a\",f:", e1, str ",x:", e2, str "}"], st) end | EAbs (_, _, _, e) => let val (e, st) = jsE (inner + 1) (e, st) in (strcat [str "{c:\"l\",b:", e, str "}"], st) end | EUnop (s, e) => let val name = case s of "!" => "not" | "-" => "neg" | _ => raise Fail ("Jscomp: Unknown unary operator " ^ s) val (e, st) = jsE inner (e, st) in (strcat [str ("{c:\"f\",f:\"" ^ name ^ "\",a:cons("), e, str ",null)}"], st) end | EBinop (bi, s, e1, e2) => let val name = case s of "==" => "eq" | "!strcmp" => "eq" | "+" => "plus" | "-" => "minus" | "*" => "times" | "/" => (case bi of Int => "divInt" | NotInt => "div") | "%" => (case bi of Int => "modInt" | NotInt => "mod") | "fdiv" => "div" | "fmod" => "mod" | "<" => "lt" | "<=" => "le" | "strcmp" => "strcmp" | _ => raise Fail ("Jscomp: Unknown binary operator " ^ s) val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE inner (e2, st) in (strcat [str ("{c:\"f\",f:\"" ^ name ^ "\",a:cons("), e1, str ",cons(", e2, str ",null))}"], st) end | ERecord [] => (str "{c:\"c\",v:null}", st) | ERecord xes => let val (es, st) = foldr (fn ((x, e, _), (es, st)) => let val (e, st) = jsE inner (e, st) in (strcat [str ("cons({n:\"" ^ x ^ "\",v:"), e, str "},", es, str ")"], st) end) (str "null", st) xes in (strcat [str "{c:\"r\",l:", es, str "}"], st) end | EField (e', x) => let fun default () = let val (e', st) = jsE inner (e', st) in (strcat [str "{c:\".\",r:", e', str (",f:\"" ^ x ^ "\"}")], st) end fun seek (e, xs) = case #1 e of ERel n => if n < inner then default () else let val n = n - inner val t = List.nth (outer, n) val t = foldl (fn (x, (TRecord xts, _)) => (case List.find (fn (x', _) => x' = x) xts of NONE => raise Fail "Jscomp: Bad seek [1]" | SOME (_, t) => t) | _ => raise Fail "Jscomp: Bad seek [2]") t xs val e = (ERel n, loc) val e = foldl (fn (x, e) => (EField (e, x), loc)) e xs val (e, st) = quoteExp t (e, st) in (strcat [str "{c:\"c\",v:", e, str "}"], st) end | EField (e', x) => seek (e', x :: xs) | _ => default () in seek (e', [x]) end | ECase (e', pes, _) => let val (e', st) = jsE inner (e', st) val (ps, st) = foldr (fn ((p, e), (ps, st)) => let val (e, st) = jsE (inner + E.patBindsN p) (e, st) in (strcat [str "cons({p:", jsPat p, str ",b:", e, str "},", ps, str ")"], st) end) (str "null", st) pes in (strcat [str "{c:\"m\",e:", e', str ",p:", ps, str "}"], st) end | EStrcat (e1, e2) => let val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE inner (e2, st) in (strcat [str "{c:\"f\",f:\"cat\",a:cons(", e1, str ",cons(", e2, str ",null))}"], st) end | EError (e, _) => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"er\",a:cons(", e, str ",null)}"], st) end | ESeq (e1, e2) => let val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE inner (e2, st) in (strcat [str "{c:\";\",e1:", e1, str ",e2:", e2, str "}"], st) end | ELet (_, _, e1, e2) => let val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE (inner + 1) (e2, st) in (strcat [str "{c:\"=\",e1:", e1, str ",e2:", e2, str "}"], st) end | EJavaScript (Source _, e) => (foundJavaScript := true; jsE inner (e, st)) | EJavaScript (_, e) => let val (e, st) = jsE inner (e, st) in foundJavaScript := true; (strcat [str "{c:\"e\",e:", e, str "}"], st) end | EWrite _ => unsupported "EWrite" | EClosure _ => unsupported "EClosure" | EQuery _ => unsupported "Query" | EDml _ => unsupported "DML" | ENextval _ => unsupported "Nextval" | ESetval _ => unsupported "Nextval" | EReturnBlob _ => unsupported "EReturnBlob" | ERedirect (e, _) => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"redirect\",a:cons(", e, str ",null)}"], st) end | EUnurlify (_, _, true) => unsupported "EUnurlify" | EUnurlify (e, t, false) => let val (e, st) = jsE inner (e, st) val (e', st) = unurlifyExp loc (t, st) in (strcat [str ("{c:\"f\",f:\"unurlify\",a:cons({c:\"c\",v:function(s){var t=s.split(\"/\");var i=0;return " ^ e' ^ "}},cons("), e, str ",null))}"], st) end | ESignalReturn e => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"sr\",a:cons(", e, str ",null)}"], st) end | ESignalBind (e1, e2) => let val (e1, st) = jsE inner (e1, st) val (e2, st) = jsE inner (e2, st) in (strcat [str "{c:\"f\",f:\"sb\",a:cons(", e1, str ",cons(", e2, str ",null))}"], st) end | ESignalSource e => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"ss\",a:cons(", e, str ",null)}"], st) end | EServerCall (e, t, eff) => let val (e, st) = jsE inner (e, st) val (unurl, st) = unurlifyExp loc (t, st) in (strcat [str ("{c:\"f\",f:\"rc\",a:cons({c:\"c\",v:\"" ^ Settings.getUrlPrefix () ^ "\"},cons("), e, str (",cons({c:\"c\",v:function(s){var t=s.split(\"/\");var i=0;return " ^ unurl ^ "}},cons({c:\"K\"},cons({c:\"c\",v:" ^ (case eff of ReadCookieWrite => "true" | _ => "false") ^ "},null)))))}")], st) end | ERecv (e, t) => let val (e, st) = jsE inner (e, st) val (unurl, st) = unurlifyExp loc (t, st) in (strcat [str ("{c:\"f\",f:\"rv\",a:cons("), e, str (",cons({c:\"c\",v:function(s){var t=s.split(\"/\");var i=0;return " ^ unurl ^ "}},cons({c:\"K\"},null)))}")], st) end | ESleep e => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"sl\",a:cons(", e, str ",cons({c:\"K\"},null))}"], st) end | ESpawn e => let val (e, st) = jsE inner (e, st) in (strcat [str "{c:\"f\",f:\"sp\",a:cons(", e, str ",null)}"], st) end end in jsE 0 end fun patBinds ((p, _), env) = case p of PWild => env | PVar (_, t) => t :: env | PPrim _ => env | PCon (_, _, NONE) => env | PCon (_, _, SOME p) => patBinds (p, env) | PRecord xpts => foldl (fn ((_, p, _), env) => patBinds (p, env)) env xpts | PNone _ => env | PSome (_, p) => patBinds (p, env) fun exp outer (e as (_, loc), st) = ((*Print.preface ("exp", MonoPrint.p_exp MonoEnv.empty e);*) case #1 e of EPrim p => (case p of Prim.String s => if inString {needle = "<script", haystack = s} then foundJavaScript := true else () | _ => (); (e, st)) | ERel _ => (e, st) | ENamed _ => (e, st) | ECon (_, _, NONE) => (e, st) | ECon (dk, pc, SOME e) => let val (e, st) = exp outer (e, st) in ((ECon (dk, pc, SOME e), loc), st) end | ENone _ => (e, st) | ESome (t, e) => let val (e, st) = exp outer (e, st) in ((ESome (t, e), loc), st) end | EFfi _ => (e, st) | EFfiApp (m, x, es) => let val (es, st) = ListUtil.foldlMap (fn ((e, t), st) => let val (e, st) = exp outer (e, st) in ((e, t), st) end) st es in ((EFfiApp (m, x, es), loc), st) end | EApp (e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((EApp (e1, e2), loc), st) end | EAbs (x, dom, ran, e) => let val (e, st) = exp (dom :: outer) (e, st) in ((EAbs (x, dom, ran, e), loc), st) end | EUnop (s, e) => let val (e, st) = exp outer (e, st) in ((EUnop (s, e), loc), st) end | EBinop (bi, s, e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((EBinop (bi, s, e1, e2), loc), st) end | ERecord xets => let val (xets, st) = ListUtil.foldlMap (fn ((x, e, t), st) => let val (e, st) = exp outer (e, st) in ((x, e, t), st) end) st xets in ((ERecord xets, loc), st) end | EField (e, s) => let val (e, st) = exp outer (e, st) in ((EField (e, s), loc), st) end | ECase (e, pes, ts) => let val (e, st) = exp outer (e, st) val (pes, st) = ListUtil.foldlMap (fn ((p, e), st) => let val (e, st) = exp (patBinds (p, outer)) (e, st) in ((p, e), st) end) st pes in ((ECase (e, pes, ts), loc), st) end | EStrcat (e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((EStrcat (e1, e2), loc), st) end | EError (e, t) => let val (e, st) = exp outer (e, st) in ((EError (e, t), loc), st) end | EReturnBlob {blob, mimeType, t} => let val (blob, st) = exp outer (blob, st) val (mimeType, st) = exp outer (mimeType, st) in ((EReturnBlob {blob = blob, mimeType = mimeType, t = t}, loc), st) end | ERedirect (e, t) => let val (e, st) = exp outer (e, st) in ((ERedirect (e, t), loc), st) end | EWrite e => let val (e, st) = exp outer (e, st) in ((EWrite e, loc), st) end | ESeq (e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((ESeq (e1, e2), loc), st) end | ELet (x, t, e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp (t :: outer) (e2, st) in ((ELet (x, t, e1, e2), loc), st) end | EClosure (n, es) => let val (es, st) = ListUtil.foldlMap (exp outer) st es in ((EClosure (n, es), loc), st) end | EQuery {exps, tables, state, query, body, initial} => let val row = exps @ map (fn (x, xts) => (x, (TRecord xts, loc))) tables val row = ListMergeSort.sort (fn ((x, _), (y, _)) => String.compare (x, y) = GREATER) row val row = (TRecord row, loc) val (query, st) = exp outer (query, st) val (body, st) = exp (state :: row :: outer) (body, st) val (initial, st) = exp outer (initial, st) in ((EQuery {exps = exps, tables = tables, state = state, query = query, body = body, initial = initial}, loc), st) end | EDml (e, mode) => let val (e, st) = exp outer (e, st) in ((EDml (e, mode), loc), st) end | ENextval e => let val (e, st) = exp outer (e, st) in ((ENextval e, loc), st) end | ESetval (e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((ESetval (e1, e2), loc), st) end | EUnurlify (e, t, b) => let val (e, st) = exp outer (e, st) in ((EUnurlify (e, t, b), loc), st) end | EJavaScript (m as Source t, e') => (foundJavaScript := true; let val (x', st) = jsExp m (t :: outer) ((ERel 0, loc), st) in ((ELet ("x", t, e', x'), loc), st) end handle CantEmbed _ => (jsExp m outer (e', st) handle CantEmbed t => ((*ErrorMsg.errorAt loc "Unable to embed type in JavaScript"; Print.preface ("Type", MonoPrint.p_typ MonoEnv.empty t);*) (e, st)))) | EJavaScript (m, e') => (foundJavaScript := true; jsExp m outer (e', st) handle CantEmbed t => ((*ErrorMsg.errorAt loc "Unable to embed type in JavaScript"; Print.preface ("Type", MonoPrint.p_typ MonoEnv.empty t);*) (e, st))) | ESignalReturn e => let val (e, st) = exp outer (e, st) in ((ESignalReturn e, loc), st) end | ESignalBind (e1, e2) => let val (e1, st) = exp outer (e1, st) val (e2, st) = exp outer (e2, st) in ((ESignalBind (e1, e2), loc), st) end | ESignalSource e => let val (e, st) = exp outer (e, st) in ((ESignalSource e, loc), st) end | EServerCall (e1, t, ef) => let val (e1, st) = exp outer (e1, st) in ((EServerCall (e1, t, ef), loc), st) end | ERecv (e1, t) => let val (e1, st) = exp outer (e1, st) in ((ERecv (e1, t), loc), st) end | ESleep e1 => let val (e1, st) = exp outer (e1, st) in ((ESleep e1, loc), st) end | ESpawn e1 => let val (e1, st) = exp outer (e1, st) in ((ESpawn e1, loc), st) end) fun decl (d as (_, loc), st) = case #1 d of DVal (x, n, t, e, s) => let val (e, st) = exp [] (e, st) in ((DVal (x, n, t, e, s), loc), st) end | DValRec vis => let val (vis, st) = ListUtil.foldlMap (fn ((x, n, t, e, s), st) => let val (e, st) = exp [] (e, st) in ((x, n, t, e, s), st) end) st vis in ((DValRec vis, loc), st) end | _ => (d, st) fun doDecl (d, st) = let (*val () = Print.preface ("doDecl", MonoPrint.p_decl MonoEnv.empty d)*) val (d, st) = decl (d, st) val ds = case #decls st of [] => [d] | vis => [(DValRec vis, #2 d), d] in (ds, {decls = [], script = #script st, included = #included st, injectors = #injectors st, listInjectors = #listInjectors st, decoders = #decoders st, maxName = #maxName st}) end val (ds, st) = ListUtil.foldlMapConcat doDecl {decls = [], script = [], included = IS.empty, injectors = IM.empty, listInjectors = TM.empty, decoders = IM.empty, maxName = U.File.maxName file + 1} file val inf = TextIO.openIn (OS.Path.joinDirFile {dir = Settings.libJs (), file = "urweb.js"}) fun lines acc = case TextIO.inputLine inf of NONE => String.concat (rev acc) | SOME line => lines (line :: acc) val lines = lines [] val urlRules = foldr (fn (r, s) => "cons({allow:" ^ (if #action r = Settings.Allow then "true" else "false") ^ ",prefix:" ^ (if #kind r = Settings.Prefix then "true" else "false") ^ ",pattern:\"" ^ #pattern r ^ "\"}," ^ s ^ ")") "null" (Settings.getUrlRules ()) val urlRules = "urlRules = " ^ urlRules ^ ";\n\n" val script = if !foundJavaScript then lines ^ urlRules ^ String.concat (rev (#script st)) ^ "\ntime_format = \"" ^ Prim.toCString (Settings.getTimeFormat ()) ^ "\";\n" else "" in TextIO.closeIn inf; (DJavaScript script, ErrorMsg.dummySpan) :: ds end end