changeset 2226:e10881cd92da

Merge.
author Ziv Scully <ziv@mit.edu>
date Fri, 27 Mar 2015 11:26:06 -0400
parents 6262dabc08d6 e0843b2a636d
children adb49db02af4
files src/c/urweb.c src/cjr_print.sml src/compiler.sml src/jscomp.sml src/mono_opt.sml src/monoize.sml src/settings.sml src/urweb.lex
diffstat 34 files changed, 551 insertions(+), 172 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Fri Mar 27 11:19:15 2015 -0400
+++ b/.hgignore	Fri Mar 27 11:26:06 2015 -0400
@@ -62,6 +62,7 @@
 config.*
 configure
 depcomp
+compile
 install-sh
 ltmain.sh
 missing
--- a/CHANGELOG	Fri Mar 27 11:19:15 2015 -0400
+++ b/CHANGELOG	Fri Mar 27 11:26:06 2015 -0400
@@ -1,3 +1,17 @@
+========
+20150214
+========
+
+- Bug fixes and improvements to type inference and optimization
+
+========
+20150103
+========
+
+- New antiquote syntax for 'ORDER BY' clauses
+- New standard library function: List.mem
+- Bug fixes and improvements to type inference
+
 ========
 20141206
 ========
--- a/configure.ac	Fri Mar 27 11:19:15 2015 -0400
+++ b/configure.ac	Fri Mar 27 11:26:06 2015 -0400
@@ -1,4 +1,4 @@
-AC_INIT([urweb], [20141206])
+AC_INIT([urweb], [20150214])
 WORKING_VERSION=1
 AC_USE_SYSTEM_EXTENSIONS
 
--- a/doc/manual.tex	Fri Mar 27 11:19:15 2015 -0400
+++ b/doc/manual.tex	Fri Mar 27 11:26:06 2015 -0400
@@ -6,8 +6,8 @@
 \newcommand{\mt}[1]{\mathsf{#1}}
 
 \newcommand{\rc}{+ \hspace{-.075in} + \;}
-\newcommand{\rcut}{\; \texttt{--} \;}
-\newcommand{\rcutM}{\; \texttt{---} \;}
+\newcommand{\rcut}{\; \texttt{-{}-} \;}
+\newcommand{\rcutM}{\; \texttt{-{}-{}-} \;}
 
 \begin{document}
 
@@ -632,6 +632,10 @@
 
 It is possible to write a $\mt{let}$ expression with its constituents in reverse order, along the lines of Haskell's \cd{where}.  An expression $\mt{let} \; e \; \mt{where} \; ed^* \; \mt{end}$ desugars to $\mt{let} \; ed^* \; \mt{in} \; e \; \mt{end}$.
 
+Ur/Web also includes a few more infix operators: $f \; \texttt{<|} \; x$ desugars to $f \; x$, $x \; \texttt{|>} \; f$ to $f \; x$, $f \; \texttt{<{}<{}<} \; g$ to $\mt{Top}.\mt{compose} \; f \; g$, and $g \; \texttt{>{}>{}>} \; f$ to $\mt{Top}.\mt{compose} \; f \; g$.  (The latter two are doing function composition in the usual way.)  Furthermore, any identifier may be changed into an infix operator by placing it between backticks, e.g. a silly way to do addition is $x \; \texttt{`}\mt{plus}\texttt{`} \; y$ instead of $x + y$.
+
+Hexadecimal integer literals are supported like \texttt{0xDEADBEEF}.  Only capital letters are allowed.
+
 
 \section{Static Semantics}
 
@@ -2263,7 +2267,7 @@
   \textrm{Pre-queries} & q &::=& \mt{SELECT} \; [\mt{DISTINCT}] \; P \; \mt{FROM} \; F,^+ \; [\mt{WHERE} \; E] \; [\mt{GROUP} \; \mt{BY} \; p,^+] \; [\mt{HAVING} \; E] \\
   &&& \mid q \; R \; q \mid \{\{\{e\}\}\} \\
   \textrm{Relational operators} & R &::=& \mt{UNION} \mid \mt{INTERSECT} \mid \mt{EXCEPT} \\
-  \textrm{$\mt{ORDER \; BY}$ items} & O &::=& \mt{RANDOM} [()] \mid \hat{E} \; [o] \mid \hat{E} \; [o], O
+  \textrm{$\mt{ORDER \; BY}$ items} & O &::=& \mt{RANDOM} [()] \mid \hat{E} \; [o] \mid \hat{E} \; [o], O \mid \{\{\{e\}\}\}
 \end{array}$$
 
 $$\begin{array}{rrcll}
--- a/include/urweb/request.h	Fri Mar 27 11:19:15 2015 -0400
+++ b/include/urweb/request.h	Fri Mar 27 11:26:06 2015 -0400
@@ -2,6 +2,7 @@
 #define REQUEST_H
 
 #include <sys/types.h>
+#include <pthread.h>
 
 #include "types.h"
 
--- a/lib/js/urweb.js	Fri Mar 27 11:19:15 2015 -0400
+++ b/lib/js/urweb.js	Fri Mar 27 11:26:06 2015 -0400
@@ -112,6 +112,10 @@
     return Math.round(n);
 }
 
+function pow(n, m) {
+    return Math.pow(n, m);
+}
+
 
 // Time, represented as counts of microseconds since the epoch
 
@@ -632,21 +636,25 @@
     return closures[n];
 }
 
-function flattenAcc(a, cls, tr) {
-    if (tr.cat1 != null) {
-        flattenAcc(a, cls, tr.cat1);
-        flattenAcc(a, cls, tr.cat2);
-    } else if (tr.closure != null) {
-        var cl = newClosure(tr.closure);
-        cls.v = cons(cl, cls.v);
-        a.push("cr(", cl.toString(), ")");
-    } else
-        a.push(tr);
+function flattenAcc(a, cls, trs) {
+    while (trs) {
+        var tr = trs.data;
+        trs = trs.next;
+
+        if (tr.cat1 != null) {
+            trs = cons(tr.cat1, cons(tr.cat2, trs));
+        } else if (tr.closure != null) {
+            var cl = newClosure(tr.closure);
+            cls.v = cons(cl, cls.v);
+            a.push("cr(", cl.toString(), ")");
+        } else
+            a.push(tr);
+    }
 }
 
 function flatten(cls, tr) {
     var a = [];
-    flattenAcc(a, cls, tr);
+    flattenAcc(a, cls, cons(tr, null));
     return a.join("");
 }
 
@@ -1233,6 +1241,56 @@
     }
 }
 
+function bodyDynClass(s_class, s_style) {
+    if (suspendScripts)
+        return;
+
+    var htmlCls = null;
+
+    if (s_class) {
+        var x = document.createElement("script");
+        x.dead = false;
+        x.signal = s_class;
+        x.sources = null;
+        x.closures = htmlCls;
+
+        x.recreate = function(v) {
+            for (var ls = x.closures; ls != htmlCls; ls = ls.next)
+                freeClosure(ls.data);
+
+            var cls = {v : null};
+            document.body.className = flatten(cls, v);
+            console.log("className to + " + document.body.className);
+	    x.closures = concat(cls.v, htmlCls);
+        }
+
+        document.body.appendChild(x);
+        populate(x);
+    }
+
+    if (s_style) {
+        var htmlCls2 = s_class ? null : htmlCls;
+        var y = document.createElement("script");
+        y.dead = false;
+        y.signal = s_style;
+        y.sources = null;
+        y.closures = htmlCls2;
+
+        y.recreate = function(v) {
+            for (var ls = y.closures; ls != htmlCls2; ls = ls.next)
+                freeClosure(ls.data);
+
+            var cls = {v : null};
+            document.body.style.cssText = flatten(cls, v);
+            console.log("style to + " + document.body.style.cssText);
+	    y.closures = concat(cls.v, htmlCls2);
+        }
+
+        document.body.appendChild(y);
+        populate(y);
+    }
+}
+
 function addOnChange(x, f) {
     var old = x.onchange;
     if (old == null)
@@ -1261,6 +1319,8 @@
 
 function ts(x) { return x.toString() }
 function bs(b) { return (b ? "True" : "False") }
+function s2b(s) { return s == "True" ? true : s == "False" ? false : null; }
+function s2be(s) { return s == "True" ? true : s == "False" ? false : er("Illegal Boolean " ^ s); }
 
 function id(x) { return x; }
 function sub(s, i) { return s.charAt(i); }
--- a/lib/ur/basis.urs	Fri Mar 27 11:19:15 2015 -0400
+++ b/lib/ur/basis.urs	Fri Mar 27 11:26:06 2015 -0400
@@ -914,7 +914,7 @@
 val wbr : bodyTag boxAttrs
 val bdi : bodyTag boxAttrs
 
-val a : bodyTag ([Link = transaction page, Href = url, Target = string, Rel = string] ++ boxAttrs)
+val a : bodyTag ([Link = transaction page, Href = url, Target = string, Rel = string, Download = string] ++ boxAttrs)
 
 val img : bodyTag ([Alt = string, Src = url, Width = int, Height = int,
                     Onabort = transaction unit, Onerror = transaction unit,
--- a/lib/ur/list.ur	Fri Mar 27 11:19:15 2015 -0400
+++ b/lib/ur/list.ur	Fri Mar 27 11:26:06 2015 -0400
@@ -216,6 +216,16 @@
         fold []
     end
 
+fun mem [a] (_ : eq a) (x : a) =
+    let
+        fun mm ls =
+            case ls of
+                [] => False
+              | y :: ls => y = x || mm ls
+    in
+        mm
+    end
+
 fun find [a] f =
     let
         fun find' ls =
--- a/lib/ur/list.urs	Fri Mar 27 11:19:15 2015 -0400
+++ b/lib/ur/list.urs	Fri Mar 27 11:26:06 2015 -0400
@@ -54,6 +54,8 @@
 val foldlMap : a ::: Type -> b ::: Type -> c ::: Type
                -> (a -> b -> c * b) -> b -> t a -> t c * b
 
+val mem : a ::: Type -> eq a -> a -> t a -> bool
+
 val find : a ::: Type -> (a -> bool) -> t a -> option a
 
 val search : a ::: Type -> b ::: Type -> (a -> option b) -> t a -> option b
--- a/src/c/fastcgi.c	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/c/fastcgi.c	Fri Mar 27 11:26:06 2015 -0400
@@ -333,7 +333,7 @@
   size_t path_size = 0;
   char *path_buf = malloc(0);
 
-  hs.uppercased = malloc(0);
+  hs.uppercased = malloc(6);
   hs.uppercased_len = 0;
   hs.nvps = malloc(sizeof(nvp));
   hs.n_nvps = 1;
--- a/src/c/openssl.c	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/c/openssl.c	Fri Mar 27 11:26:06 2015 -0400
@@ -9,6 +9,7 @@
 #include <string.h>
 
 #include <openssl/sha.h>
+#include <openssl/rand.h>
 
 #define PASSSIZE 4
 
@@ -19,10 +20,11 @@
 char *uw_sig_file = NULL;
 
 static void random_password() {
-  int i;
-
-  for (i = 0; i < PASSSIZE; ++i)
-    password[i] = rand();
+  if (!RAND_bytes((unsigned char *)password, sizeof password)) {
+    fprintf(stderr, "Error generating random password\n");
+    perror("RAND_bytes");
+    exit(1);
+  }
 }
 
 void uw_init_crypto() {
--- a/src/c/urweb.c	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/c/urweb.c	Fri Mar 27 11:26:06 2015 -0400
@@ -167,6 +167,19 @@
 void uw_free_client_data(void *);
 void uw_copy_client_data(void *dst, void *src);
 
+static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+static uw_Basis_int my_rand() {
+  pthread_mutex_lock(&rand_mutex);
+  int ret, r = RAND_bytes((unsigned char *)&ret, sizeof ret);
+  pthread_mutex_unlock(&rand_mutex);
+
+  if (r)
+    return abs(ret);
+  else
+    return -1;
+}
+
 static client *new_client() {
   client *c;
 
@@ -192,7 +205,7 @@
 
   pthread_mutex_lock(&c->lock);
   c->mode = USED;
-  c->pass = rand();
+  c->pass = my_rand();
   c->sock = -1;
   c->last_contact = time(NULL);
   uw_buffer_reset(&c->msgs);
@@ -349,8 +362,6 @@
 extern void uw_init_crypto();
 
 void uw_global_init() {
-  srand(time(NULL) ^ getpid());
-
   clients = malloc(0);
 
   uw_global_custom();
@@ -4234,16 +4245,11 @@
   return uw_unit_v;
 }
 
-static pthread_mutex_t rand_mutex = PTHREAD_MUTEX_INITIALIZER;
-
 uw_Basis_int uw_Basis_rand(uw_context ctx) {
-  uw_Basis_int ret;
-  pthread_mutex_lock(&rand_mutex);
-  int r = RAND_bytes((unsigned char *)&ret, sizeof ret);
-  pthread_mutex_unlock(&rand_mutex);
-
-  if (r)
-    return abs(ret);
+  int r = my_rand();
+
+  if (r >= 0)
+    return r;
   else
     uw_error(ctx, FATAL, "Random number generation failed");
 }
--- a/src/cjr_print.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/cjr_print.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -3260,6 +3260,16 @@
                                                               string "))"]))
                          NONE cookies
 
+        val cookieCode = foldl (fn (evar, acc) =>
+                                   SOME (case acc of
+                                             NONE => string ("uw_unnull(uw_Basis_getenv(ctx, \""
+                                                             ^ Prim.toCString evar ^ "\"))")
+                                           | SOME acc => box [string ("uw_Basis_strcat(ctx, uw_unnull(uw_Basis_getenv(ctx, \""
+                                                                      ^ Prim.toCString evar ^ "\")), uw_Basis_strcat(ctx, \"/\", "),
+                                                              acc,
+                                                              string "))"]))
+                         cookieCode (SideCheck.readEnvVars ())
+
         fun makeChecker (name, rules : Settings.rule list) =
             box [string "static int ",
                  string name,
--- a/src/compiler.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/compiler.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -461,14 +461,13 @@
          end
      else
          let
-             val thisPath = OS.Path.dir fname
-
              val pathmap = ref (!pathmap)
              val bigLibs = ref []
 
              fun pu filename =
                  let
                      val filename = OS.Path.mkAbsolute {path = filename, relativeTo = OS.FileSys.getDir ()}
+                     val thisPath = OS.Path.dir filename
 
                      val dir = OS.Path.dir filename
                      fun opener () = TextIO.openIn (OS.Path.joinBaseExt {base = filename, ext = SOME "urp"})
@@ -693,8 +692,8 @@
                            | _ => (ErrorMsg.error "Bad path kind spec";
                                    Settings.Any)
 
-                     fun parseFrom s =
-                         if size s > 1 andalso String.sub (s, size s - 2) = #"/" andalso String.sub (s, size s - 1) = #"*" then
+                     fun parsePattern s =
+                         if size s > 0 andalso String.sub (s, size s - 1) = #"*" then
                              (Settings.Prefix, String.substring (s, 0, size s - 1))
                          else
                              (Settings.Exact, s)
@@ -709,12 +708,6 @@
                            | _ => (ErrorMsg.error "Bad filter kind";
                                    url)
 
-                     fun parsePattern s =
-                         if size s > 0 andalso String.sub (s, size s - 1) = #"*" then
-                             (Settings.Prefix, String.substring (s, 0, size s - 1))
-                         else
-                             (Settings.Exact, s)
-
                      fun read () =
                          case inputCommentableLine inf of
                              EndOfFile => finish []
@@ -801,7 +794,7 @@
                                          fun doit (pkind, from, to, hyph) =
                                              let
                                                  val pkind = parsePkind pkind
-                                                 val (kind, from) = parseFrom from
+                                                 val (kind, from) = parsePattern from
                                              in
                                                  rewrites := {pkind = pkind, kind = kind, from = from, to = to, hyphenate = hyph} :: !rewrites
                                              end
--- a/src/effectize.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/effectize.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -79,6 +79,8 @@
         fun exp evs e =
             case e of
                 EFfi ("Basis", "getCookie") => true
+              | EFfiApp ("Basis", "getHeader", _) => true
+              | EFfiApp ("Basis", "getenv", _) => true
               | ENamed n => IM.inDomain (evs, n)
               | EServerCall (n, _, _, _) => IM.inDomain (evs, n)
               | _ => false
--- a/src/elaborate.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/elaborate.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -2015,6 +2015,45 @@
         L'.CUnif (_, _, _, _, ref (L'.Known c)) => chaseUnifs c
       | _ => c
 
+val consEqSimple =
+    let
+        fun ces env (c1 : L'.con, c2 : L'.con) =
+            let
+                val c1 = hnormCon env c1
+                val c2 = hnormCon env c2
+            in
+                case (#1 c1, #1 c2) of
+                    (L'.CRel n1, L'.CRel n2) => n1 = n2
+                  | (L'.CNamed n1, L'.CNamed n2) =>
+                    n1 = n2 orelse
+                    (case #3 (E.lookupCNamed env n1) of
+                         SOME (L'.CNamed n2', _) => n2' = n1
+                       | _ => false)
+                  | (L'.CModProj n1, L'.CModProj n2) => n1 = n2
+                  | (L'.CApp (f1, x1), L'.CApp (f2, x2)) => ces env (f1, f2) andalso ces env (x1, x2)
+                  | (L'.CAbs (x1, k1, c1), L'.CAbs (_, _, c2)) => ces (E.pushCRel env x1 k1) (c1, c2)
+                  | (L'.CName x1, L'.CName x2) => x1 = x2
+                  | (L'.CRecord (_, xts1), L'.CRecord (_, xts2)) =>
+                    ListPair.all (fn ((x1, t1), (x2, t2)) =>
+                                     ces env (x1, x2) andalso ces env (t2, t2)) (xts1, xts2)
+                  | (L'.CConcat (x1, y1), L'.CConcat (x2, y2)) =>
+                    ces env (x1, x2) andalso ces env (y1, y2)
+                  | (L'.CMap _, L'.CMap _) => true
+                  | (L'.CUnit, L'.CUnit) => true
+                  | (L'.CTuple cs1, L'.CTuple cs2) => ListPair.all (ces env) (cs1, cs2)
+                  | (L'.CProj (c1, n1), L'.CProj (c2, n2)) => ces env (c1, c2) andalso n1 = n2
+                  | (L'.CUnif (_, _, _, _, r1), L'.CUnif (_, _, _, _, r2)) => r1 = r2
+
+                  | (L'.TFun (d1, r1), L'.TFun (d2, r2)) => ces env (d1, d2) andalso ces env (r1, r2)
+                  | (L'.TRecord c1, L'.TRecord c2) => ces env (c1, c2)
+                  
+                  | _ => false
+            end
+    in
+        ces
+    end
+    
+
 fun elabExp (env, denv) (eAll as (e, loc)) =
     let
         (*val () = eprefaces "elabExp" [("eAll", SourcePrint.p_exp eAll)]*)
@@ -3020,26 +3059,7 @@
 
       | (L'.SgnConst sgis1, L'.SgnConst sgis2) =>
         let
-            (* This reshuffling was added to avoid some unfortunate unification behavior.
-             * In particular, in sub-signature checking, constraints might be unified,
-             * even when we don't expect them to be unifiable, deciding on bad values
-             * for unification variables and dooming later unification.
-             * By putting all the constraints _last_, we allow all the other unifications
-             * to happen first, hoping that no unification variables survive to confuse
-             * constraint unification. *)
-
-            val sgis2 =
-                let
-                    val (constraints, others) = List.partition
-                                                   (fn (L'.SgiConstraint _, _) => true
-                                                   | _ => false) sgis2
-                in
-                    case constraints of
-                        [] => sgis2
-                      | _ => others @ constraints
-                end
-
-            (*val () = prefaces "subSgn" [("sgn1", p_sgn env sgn1),
+           (*val () = prefaces "subSgn" [("sgn1", p_sgn env sgn1),
                                         ("sgn2", p_sgn env sgn2),
                                         ("sgis1", p_sgn env (L'.SgnConst sgis1, loc2)),
                                         ("sgis2", p_sgn env (L'.SgnConst sgis2, loc2))]*)
@@ -3329,7 +3349,12 @@
                                      L'.SgiStr (x', n1, sgn1) =>
                                      if x = x' then
                                          let
+                                             (* Don't forget to save & restore the
+                                              * counterparts map around recursive calls!
+                                              * Otherwise, all sorts of mayhem may result. *)
+                                             val saved = !counterparts
                                              val () = subSgn' counterparts env loc sgn1 sgn2
+                                             val () = counterparts := saved
                                              val env = E.pushStrNamedAs env x n1 sgn1
                                              val env = if n1 = n2 then
                                                            env
@@ -3370,8 +3395,11 @@
                         seek (fn (env, sgi1All as (sgi1, loc)) =>
                                  case sgi1 of
                                      L'.SgiConstraint (c1, d1) =>
-                                     if consEq env loc (c1, c2)
-					andalso consEq env loc (d1, d2) then
+                                     (* It's important to do only simple equality checking here,
+                                      * with no unification, because constraints are unnamed.
+                                      * It's too easy to pick the wrong pair to unify! *)
+                                     if consEqSimple env (c1, c2)
+					andalso consEqSimple env (d1, d2) then
                                          SOME env
                                      else
                                          NONE
@@ -3669,6 +3697,21 @@
 
                        | c => ((*Print.preface ("WTF?", p_con env (c, loc));*) NONE)
 
+                 fun isClassOrFolder' env (c : L'.con) =
+                     case #1 c of
+                         L'.CAbs (x, k, c) =>
+                         let
+                             val env = E.pushCRel env x k
+
+                             fun toHead (c : L'.con) =
+                                 case #1 c of
+                                     L'.CApp (c, _) => toHead c
+                                   | _ => isClassOrFolder env c
+                         in
+                             toHead (hnormCon env c)
+                         end
+                       | _ => isClassOrFolder env c
+
                  fun buildNeeded env sgis =
                      #1 (foldl (fn ((sgi, loc), (nd, env')) =>
                                    (case sgi of
@@ -3680,19 +3723,23 @@
                                             fun should t =
                                                 let
                                                     val t = normClassConstraint env' t
+
+                                                    fun shouldR c =
+                                                        case hnormCon env' c of
+                                                            (L'.CApp (f, _), _) =>
+                                                            (case hnormCon env' f of
+                                                                 (L'.CApp (f, cl), loc) =>
+                                                                 (case hnormCon env' f of
+                                                                      (L'.CMap _, _) => isClassOrFolder' env' cl
+                                                                    | _ => false)
+                                                               | _ => false)
+                                                            | (L'.CConcat (c1, c2), _) =>
+                                                              shouldR c1 orelse shouldR c2
+                                                            | c => false
                                                 in
                                                     case #1 t of
                                                         L'.CApp (f, _) => isClassOrFolder env' f
-                                                      | L'.TRecord t =>
-                                                        (case hnormCon env' t of
-                                                             (L'.CApp (f, _), _) =>
-                                                             (case hnormCon env' f of
-                                                                  (L'.CApp (f, cl), loc) =>
-                                                                  (case hnormCon env' f of
-                                                                       (L'.CMap _, _) => isClassOrFolder env' cl
-                                                                     | _ => false)
-                                                                | _ => false)
-                                                           | _ => false)
+                                                      | L'.TRecord t => shouldR t
                                                       | _ => false
                                                 end
                                          in
--- a/src/elisp/urweb-mode.el	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/elisp/urweb-mode.el	Fri Mar 27 11:26:06 2015 -0400
@@ -171,42 +171,47 @@
           (depth 0)
           (finished nil)
           (answer nil)
+          (bound (max 0 (- (point) 1024)))
           )
-      (while (and (not finished) (re-search-backward "[-<{}]" nil t))
-        (cond
-         ((looking-at "{")
-          (if (> depth 0)
-              (decf depth)
-            (setq finished t)))
-         ((looking-at "}")
-          (incf depth))
-         ((looking-at "<xml>")
-          (if (> depth 0)
-              (decf depth)
-            (progn
-              (setq answer t)
-              (setq finished t))))
-         ((looking-at "</xml>")
-          (incf depth))
+      (while (and (not finished)
+                  (re-search-backward "\\(\\([-{}]\\)\\|<\\(/?xml\\)?\\)"
+                                      bound t))
+        (let ((xml-tag (length (or (match-string 3) "")))
+              (ch (match-string 2)))
+         (cond
+          ((equal ch ?\{)
+           (if (> depth 0)
+               (decf depth)
+             (setq finished t)))
+          ((equal ch ?\})
+           (incf depth))
+          ((= xml-tag 3)
+           (if (> depth 0)
+               (decf depth)
+             (progn
+               (setq answer t)
+               (setq finished t))))
+          ((= xml-tag 4)
+           (incf depth))
 
-         ((looking-at "-")
-          (if (looking-at "->")
-            (setq finished (= depth 0))))
+          ((equal ch ?-)
+           (if (looking-at "->")
+               (setq finished (= depth 0))))
 
-         ((and (= depth 0)
-               (not (looking-at "<xml")) ;; ignore <xml/>
-               (eq font-lock-tag-face
-                   (get-text-property (point) 'face)))
-          ;; previous code was highlighted as tag, seems we are in xml
-          (progn
-            (setq answer t)
-            (setq finished t)))
+          ((and (= depth 0)
+                (not (looking-at "<xml")) ;; ignore <xml/>
+                (eq font-lock-tag-face
+                    (get-text-property (point) 'face)))
+           ;; previous code was highlighted as tag, seems we are in xml
+           (progn
+             (setq answer t)
+             (setq finished t)))
 
-         ((= depth 0)
-          ;; previous thing was a tag like, but not tag
-          ;; seems we are in usual code or comment
-          (setq finished t))
-         ))
+          ((= depth 0)
+           ;; previous thing was a tag like, but not tag
+           ;; seems we are in usual code or comment
+           (setq finished t))
+          )))
       answer)))
 
 (defun amAttribute (face)
--- a/src/jscomp.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/jscomp.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -724,6 +724,8 @@
                                              | "<" => "lt"
                                              | "<=" => "le"
                                              | "strcmp" => "strcmp"
+                                             | "powl" => "pow"
+                                             | "powf" => "pow"
                                              | _ => raise Fail ("Jscomp: Unknown binary operator " ^ s)
 
                                 val (e1, st) = jsE inner (e1, st)
--- a/src/mono_opt.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/mono_opt.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -633,6 +633,8 @@
         EFfiApp ("Basis", "writec", [e])
 
       | EBinop (_, "+", (EPrim (Prim.Int n1), _), (EPrim (Prim.Int n2), _)) => EPrim (Prim.Int (Int64.+ (n1, n2)))
+      | EBinop (_, "-", (EPrim (Prim.Int n1), _), (EPrim (Prim.Int n2), _)) => EPrim (Prim.Int (Int64.- (n1, n2)))
+      | EBinop (_, "*", (EPrim (Prim.Int n1), _), (EPrim (Prim.Int n2), _)) => EPrim (Prim.Int (Int64.* (n1, n2)))
 
       | _ => e
 
--- a/src/mono_reduce.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/mono_reduce.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -330,7 +330,9 @@
                                              U.Exp.RelE _ => n + 1
                                            | _ => n} 0
 
-fun reduce (file : file) =
+val yankedCase = ref false
+
+fun reduce' (file : file) =
     let
         val (timpures, impures, absCounts) =
             foldl (fn ((d, _), (timpures, impures, absCounts)) =>
@@ -770,17 +772,18 @@
                                                                       Print.PD.string "}"]
                                     in
                                         if List.all (safe o #2) pes then
-                                            EAbs ("y", dom, result,
-                                                  (ECase (liftExpInExp 0 e',
-                                                          map (fn (p, (EAbs (_, _, _, e), _)) =>
-                                                                  (p, swapExpVarsPat (0, patBinds p) e)
-                                                                | (p, (EError (e, (TFun (_, t), _)), loc)) =>
-                                                                  (p, (EError (liftExpInExp (patBinds p) e, t), loc))
-                                                                | (p, e) =>
-                                                                  (p, (EApp (liftExpInExp (patBinds p) e,
-                                                                             (ERel (patBinds p), loc)), loc)))
-                                                              pes,
-                                                          {disc = disc, result = result}), loc))
+                                            (yankedCase := true;
+                                             EAbs ("y", dom, result,
+                                                   (ECase (liftExpInExp 0 e',
+                                                           map (fn (p, (EAbs (_, _, _, e), _)) =>
+                                                                   (p, swapExpVarsPat (0, patBinds p) e)
+                                                               | (p, (EError (e, (TFun (_, t), _)), loc)) =>
+                                                                 (p, (EError (liftExpInExp (patBinds p) e, t), loc))
+                                                               | (p, e) =>
+                                                                 (p, (EApp (liftExpInExp (patBinds p) e,
+                                                                            (ERel (patBinds p), loc)), loc)))
+                                                               pes,
+                                                           {disc = disc, result = result}), loc)))
                                         else
                                             e
                                     end
@@ -818,10 +821,19 @@
                                 search pes
                         end
 
-                      | EField ((ERecord xes, _), x) =>
-                        (case List.find (fn (x', _, _) => x' = x) xes of
-                             SOME (_, e, _) => #1 e
-                           | NONE => e)
+                      | EField (e1, x) =>
+                        let
+                            fun yankLets (e : exp) =
+                                case #1 e of
+                                    ELet (x, t, e1, e2) => (ELet (x, t, e1, yankLets e2), #2 e)
+                                  | ERecord xes =>
+                                    (case List.find (fn (x', _, _) => x' = x) xes of
+                                         SOME (_, e, _) => e
+                                       | NONE => (EField (e, x), #2 e))
+                                  | _ => (EField (e, x), #2 e)
+                        in
+                            #1 (yankLets e1)
+                        end
 
                       | ELet (x1, t1, (ELet (x2, t2, e1, b1), loc), b2) =>
                         let
@@ -885,4 +897,16 @@
         U.File.mapB {typ = typ, exp = exp, decl = decl, bind = bind} E.empty file
     end
 
+fun reduce file =
+    let
+        val () = yankedCase := false
+        val file' = reduce' file
+    in
+        if !yankedCase then
+            reduce file'
+        else
+            file'
+    end
+
+
 end
--- a/src/monoize.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/monoize.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -89,7 +89,6 @@
                               "p",
                               "hr",
                               "input",
-                              "button",
                               "img",
                               "base",
                               "meta",
@@ -3279,6 +3278,11 @@
                     else
                         (NONE, NONE, attrs)
 
+                val (class, fm) = monoExp (env, st, fm) class
+                val (dynClass, fm) = monoExp (env, st, fm) dynClass
+                val (style, fm) = monoExp (env, st, fm) style
+                val (dynStyle, fm) = monoExp (env, st, fm) dynStyle
+
                 (* Special case for <button value=""> *)
                 val (attrs, extraString) = case tag of
                                                "button" =>
@@ -3286,14 +3290,31 @@
                                                     ([(_, value, _)], rest) =>
                                                     (rest, SOME value)
                                                   | _ => (attrs, NONE))
+                                             | "body" =>
+                                               (attrs,
+                                                if (case (#1 dynClass, #1 dynStyle) of
+                                                        (L'.ESome _, _) => true
+                                                      | (_, L'.ESome _) => true
+                                                      | _ => false) then
+                                                    let
+                                                        fun jsify (e : L'.exp) =
+                                                            case #1 e of
+                                                                L'.ESome (_, ds) => strcat [str "execD(",
+                                                                                            (L'.EJavaScript (L'.Script, ds), loc),
+                                                                                            str ")"]
+                                                              | _ => str "null"
+                                                    in
+                                                        SOME (strcat [str "<script type=\"text/javascript\">bodyDynClass(",
+                                                                      jsify dynClass,
+                                                                      str ",",
+                                                                      jsify dynStyle,
+				                                      str ")</script>"])
+                                                    end
+                                                else
+                                                    NONE)
                                              | _ => (attrs, NONE)
 
 
-                val (class, fm) = monoExp (env, st, fm) class
-                val (dynClass, fm) = monoExp (env, st, fm) dynClass
-                val (style, fm) = monoExp (env, st, fm) style
-                val (dynStyle, fm) = monoExp (env, st, fm) dynStyle
-
                 val dynamics = ["dyn", "ctextbox", "cpassword", "ccheckbox", "cselect", "coption", "ctextarea", "active", "script", "cemail", "csearch", "curl", "ctel", "ccolor"]
 
                 fun isSome (e, _) =
@@ -3458,6 +3479,8 @@
                                            NONE => tagStart
                                          | SOME extra => (L'.EStrcat (tagStart, extra), loc)
 
+                        val firstWord = Substring.string o #1 o Substring.splitl (fn ch => not (Char.isSpace ch)) o Substring.full
+
                         fun normal () =
                             let
                                 val (xml, fm) = monoExp (env, st, fm) xml
@@ -3468,7 +3491,7 @@
                             in
                                 ((L'.EStrcat ((L'.EStrcat (tagStart, strH ">"), loc),
                                               (L'.EStrcat (xml,
-                                                           strH (String.concat ["</", tag, ">"])), loc)),
+                                                           strH (String.concat ["</", firstWord tag, ">"])), loc)),
                                   loc),
                                  fm)
                             end
@@ -3835,10 +3858,16 @@
 
                       | "tabl" => normal ("table", NONE)
                       | _ => normal (tag, NONE)
+
+                val (dynClass', dynStyle') =
+                    case tag of
+                        "body" => ((L'.ENone dummyTyp, ErrorMsg.dummySpan),
+                                   (L'.ENone dummyTyp, ErrorMsg.dummySpan))
+                      | _ => (dynClass, dynStyle)
 	    in
-		case #1 dynClass of
+		case #1 dynClass' of
 		    L'.ENone _ =>
-		    (case #1 dynStyle of
+		    (case #1 dynStyle' of
 		         L'.ENone _ => baseAll
 		       | L'.ESome (_, ds) => (strcat [str "<script type=\"text/javascript\">dynClass(\"",
                                                       str (pnode ()),
@@ -3852,7 +3881,7 @@
                                baseAll))
 		  | L'.ESome (_, dc) =>
                     let
-                        val e = case #1 dynStyle of
+                        val e = case #1 dynStyle' of
                                     L'.ENone _ => str "null"
                                   | L'.ESome (_, ds) => strcat [str "execD(",
                                                                 (L'.EJavaScript (L'.Script, ds), loc),
--- a/src/mysql.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/mysql.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -446,7 +446,7 @@
                       newline,
                       newline,
 
-                      p_list_sepi newline (fn i => fn (s, n) =>
+                      p_list_sepi newline (fn i => fn (s, _) =>
                                                       let
                                                           fun uhoh this s args =
                                                               box [p_list_sepi (box [])
--- a/src/postgres.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/postgres.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -340,14 +340,12 @@
                   newline,
                   newline,
 
-                  p_list_sepi newline (fn i => fn (s, n) =>
+                  p_list_sepi newline (fn i => fn (s, _) =>
                                                   box [string "res = PQprepare(conn, \"uw",
                                                        string (Int.toString i),
                                                        string "\", \"",
                                                        string (Prim.toCString s),
-                                                       string "\", ",
-                                                       string (Int.toString n),
-                                                       string ", NULL);",
+                                                       string "\", 0, NULL);",
                                                        newline,
                                                        string "if (PQresultStatus(res) != PGRES_COMMAND_OK) {",
                                                        newline,
--- a/src/settings.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/settings.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -297,6 +297,8 @@
                           ("mouseEvent", "uw_mouseEvent"),
                           ("keyEvent", "uw_keyEvent"),
                           ("minTime", "0"),
+                          ("stringToBool_error", "s2be"),
+                          ("stringToBool", "s2b"),
 
                           ("islower", "isLower"),
                           ("isupper", "isUpper"),
@@ -378,6 +380,22 @@
 datatype path_kind = Any | Url | Table | Sequence | View | Relation | Cookie | Style
 type rewrite = { pkind : path_kind, kind : pattern_kind, from : string, to : string, hyphenate : bool }
 
+fun pak2s pak =
+    case pak of
+        Exact => "Exact"
+      | Prefix => "Prefix"
+fun pk2s pk =
+    case pk of
+        Any => "Any"
+      | Url => "Url"
+      | Table => "Table"
+      | Sequence => "Sequence"
+      | View => "View"
+      | Relation => "Relation"
+      | Cookie => "Cookie"
+      | Style => "Style"
+fun r2s (r : rewrite) = pak2s (#kind r) ^ " " ^ pk2s (#pkind r) ^ ", from<" ^ #from r ^ ">, to<" ^ #to r ^ ">"
+
 val rewrites = ref ([] : rewrite list)
 
 fun subsume (pk1, pk2) =
@@ -726,15 +744,46 @@
         "" => ""
       | _ => str (Char.toUpper (String.sub (s, 0))) ^ String.extract (s, 1, NONE)
 
+val allLower = CharVector.map Char.toLower
+
 val mangle = ref true
 fun setMangleSql x = mangle := x
-fun mangleSqlTable s = if !mangle then "uw_" ^ capitalize s
-                       else if #name (currentDbms ()) = "mysql" then capitalize s
-                       else lowercase s
-fun mangleSql s = if !mangle then "uw_" ^ s
-                  else if #name (currentDbms ()) = "mysql" then lowercase s
-                  else lowercase s
-fun mangleSqlCatalog s = if !mangle then "uw_" ^ s else lowercase s
+
+fun mangleSqlTable s =
+    if #name (currentDbms ()) = "mysql" then
+        if !mangle then
+            "uw_" ^ allLower s
+        else
+            allLower s
+    else
+        if !mangle then
+            "uw_" ^ capitalize s
+        else
+            lowercase s
+
+fun mangleSql s =
+    if #name (currentDbms ()) = "mysql" then
+        if !mangle then
+            "uw_" ^ allLower s 
+        else
+            allLower s
+    else
+        if !mangle then
+            "uw_" ^ s
+        else
+            lowercase s
+
+fun mangleSqlCatalog s =
+    if #name (currentDbms ()) = "mysql" then
+        if !mangle then
+            "uw_" ^ allLower s
+        else
+            allLower s
+    else
+        if !mangle then
+            "uw_" ^ s
+        else
+            lowercase s
 
 val html5 = ref false
 fun setIsHtml5 b = html5 := b
@@ -822,7 +871,7 @@
 
 fun addFile {Uri, LoadFromFilename} =
     let
-        val path = OS.Path.joinDirFile {dir = !filePath, file = LoadFromFilename}
+        val path = OS.Path.mkAbsolute {relativeTo = !filePath, path = LoadFromFilename}
     in
         case SM.find (!files, Uri) of
             SOME (path', _) =>
--- a/src/sidecheck.sig	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/sidecheck.sig	Fri Mar 27 11:26:06 2015 -0400
@@ -29,4 +29,9 @@
 
     val check : Mono.file -> Mono.file
 
+    (* While we're checking, we'll do some other signature-related work, recording
+     * which environment variables are read.  This function conveys the list,
+     * coming from the most recent call to [check]. *)
+    val readEnvVars : unit -> string list
+
 end
--- a/src/sidecheck.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/sidecheck.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -31,29 +31,54 @@
 
 structure E = ErrorMsg
 
+structure SK = struct
+type ord_key = string
+val compare = String.compare
+end
+
+structure SS = BinarySetFn(SK)
+
+val envVars = ref SS.empty
+
 fun check ds =
-    (MonoUtil.File.appLoc (fn (e, loc) =>
-                              let
-                                  fun error (k as (k1, k2)) =
-                                      if Settings.isClientOnly k then
-                                          let
-                                              val k2 = case k1 of
-                                                           "Basis" =>
-                                                           (case k2 of
-                                                                "get_client_source" => "get"
-                                                              | _ => k2)
-                                                         | _ => k2
-                                          in
-                                              E.errorAt loc ("Server-side code uses client-side-only identifier \"" ^ k1 ^ "." ^ k2 ^ "\"")
-                                          end
-                                      else
-                                          ()
-                              in
-                                  case e of
-                                      EFfi k => error k
-                                    | EFfiApp (k1, k2, _) => error (k1, k2)
-                                    | _ => ()
-                              end) ds;
-     ds)
+    let
+        val alreadyWarned = ref false
+    in
+        envVars := SS.empty;
+        MonoUtil.File.appLoc (fn (e, loc) =>
+                                 let
+                                     fun error (k as (k1, k2)) =
+                                         if Settings.isClientOnly k then
+                                             let
+                                                 val k2 = case k1 of
+                                                              "Basis" =>
+                                                              (case k2 of
+                                                                   "get_client_source" => "get"
+                                                                 | _ => k2)
+                                                            | _ => k2
+                                             in
+                                                 E.errorAt loc ("Server-side code uses client-side-only identifier \"" ^ k1 ^ "." ^ k2 ^ "\"")
+                                             end
+                                         else
+                                             ()
+                                 in
+                                     case e of
+                                         EFfi k => error k
+                                       | EFfiApp ("Basis", "getenv", [(e, _)]) =>
+                                         (case #1 e of
+                                              EPrim (Prim.String (_, s)) =>
+                                              envVars := SS.add (!envVars, s)
+                                            | _ => if !alreadyWarned then
+                                                       ()
+                                                   else
+                                                       (alreadyWarned := true;
+                                                        TextIO.output (TextIO.stdErr, "WARNING: " ^ ErrorMsg.spanToString loc ^ ": reading from an environment variable not determined at compile time, which can confuse CSRF protection")))
+                                       | EFfiApp (k1, k2, _) => error (k1, k2)
+                                       | _ => ()
+                                 end) ds;
+        ds
+    end
+
+fun readEnvVars () = SS.listItems (!envVars)
 
 end
--- a/src/sqlite.sml	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/sqlite.sml	Fri Mar 27 11:26:06 2015 -0400
@@ -202,7 +202,7 @@
                       newline,
                       newline,
 
-                      p_list_sepi newline (fn i => fn (s, n) =>
+                      p_list_sepi newline (fn i => fn (s, _) =>
                                                       let
                                                           fun uhoh this s args =
                                                               box [p_list_sepi (box [])
--- a/src/urweb.grm	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/urweb.grm	Fri Mar 27 11:26:06 2015 -0400
@@ -216,6 +216,14 @@
         (EApp (e, e2), loc)
     end
 
+fun top_binop (oper, e1, e2, loc) =
+    let
+        val e = (EVar (["Top"], oper, Infer), loc)
+        val e = (EApp (e, e1), loc)
+    in
+        (EApp (e, e2), loc)
+    end
+
 val inDml = ref false
 
 fun tagIn bt =
@@ -395,6 +403,8 @@
  | CCONSTRAINT | UNIQUE | CHECK | PRIMARY | FOREIGN | KEY | ON | NO | ACTION | RESTRICT | CASCADE | REFERENCES
  | JOIN | INNER | CROSS | OUTER | LEFT | RIGHT | FULL
  | CIF | CTHEN | CELSE
+ | FWDAPP | REVAPP | COMPOSE | ANDTHEN
+ | BACKTICK_PATH of string
 
 %nonterm
    file of decl list
@@ -565,6 +575,12 @@
 %right CAND
 %nonassoc EQ NE LT LE GT GE IS
 %right ARROW
+
+%left REVAPP
+%right FWDAPP
+%left BACKTICK_PATH
+%right COMPOSE ANDTHEN
+
 %right CARET PLUSPLUS
 %left MINUSMINUS MINUSMINUSMINUS
 %left PLUS MINUS
@@ -1202,6 +1218,22 @@
        | eexp GT eexp                   (native_op ("gt", eexp1, eexp2, s (eexp1left, eexp2right)))
        | eexp GE eexp                   (native_op ("ge", eexp1, eexp2, s (eexp1left, eexp2right)))
 
+       | eexp FWDAPP eexp               (EApp (eexp1, eexp2), s (eexp1left, eexp2right))
+       | eexp REVAPP eexp               (EApp (eexp2, eexp1), s (eexp1left, eexp2right))
+       | eexp COMPOSE eexp              (top_binop ("compose", eexp1, eexp2, s (eexp1left, eexp2right)))
+       | eexp ANDTHEN eexp              (top_binop ("compose", eexp2, eexp1, s (eexp1left, eexp2right)))
+       | eexp BACKTICK_PATH eexp        (let
+                                                val path = String.tokens (fn ch => ch = #".") BACKTICK_PATH
+                                                val pathModules = List.take (path, (length path -1))
+                                                val pathOp = List.last path
+
+                                                val e = (EVar (pathModules, pathOp, Infer)
+                                                        , s (BACKTICK_PATHleft, BACKTICK_PATHright))
+                                                val e = (EApp (e, eexp1), s (eexp1left, BACKTICK_PATHright))
+                                        in
+                                                (EApp (e, eexp2), s (eexp1left, eexp2right))
+                                        end)
+
        | eexp ANDALSO eexp              (let
                                              val loc = s (eexp1left, eexp2right)
                                          in
@@ -2235,6 +2267,7 @@
                                                 (CWild (KRecord (KType, dummy), dummy), dummy)),
                                          dummy)
        | ORDER BY obexps                (obexps)
+       | ORDER BY LBRACE LBRACE LBRACE eexp RBRACE RBRACE RBRACE (eexp)
 
 obitem : sqlexp diropt                  (sqlexp, diropt)
 
--- a/src/urweb.lex	Fri Mar 27 11:19:15 2015 -0400
+++ b/src/urweb.lex	Fri Mar 27 11:26:06 2015 -0400
@@ -182,6 +182,7 @@
 ws = [\ \t\012\r];
 intconst = [0-9]+;
 realconst = [0-9]+\.[0-9]*;
+hexconst = 0x[0-9A-F]{1,8};
 notags = ([^<{\n(]|(\([^\*<{\n]))+;
 xcom = ([^\-]|(-[^\-]))+;
 oint = [0-9][0-9][0-9];
@@ -376,6 +377,15 @@
 <INITIAL> "&&"        => (Tokens.ANDALSO (pos yypos, pos yypos + size yytext));
 <INITIAL> "||"        => (Tokens.ORELSE (pos yypos, pos yypos + size yytext));
 
+<INITIAL> "<<<"       => (Tokens.COMPOSE (pos yypos, pos yypos + size yytext));
+<INITIAL> ">>>"       => (Tokens.ANDTHEN (pos yypos, pos yypos + size yytext));
+<INITIAL> "<|"        => (Tokens.FWDAPP (pos yypos, pos yypos + size yytext));
+<INITIAL> "|>"        => (Tokens.REVAPP (pos yypos, pos yypos + size yytext));
+
+<INITIAL> "`" ({cid} ".")* {id} "`"  => (Tokens.BACKTICK_PATH ( (* strip backticks *)
+                                                              substring (yytext,1,size yytext -2),
+                                                              pos yypos, pos yypos + size yytext));
+
 <INITIAL> "="         => (Tokens.EQ (pos yypos, pos yypos + size yytext));
 <INITIAL> "<>"        => (Tokens.NE (pos yypos, pos yypos + size yytext));
 <INITIAL> "<"         => (Tokens.LT (pos yypos, pos yypos + size yytext));
@@ -532,6 +542,12 @@
 <INITIAL> {id}        => (Tokens.SYMBOL (yytext, pos yypos, pos yypos + size yytext));
 <INITIAL> {cid}       => (Tokens.CSYMBOL (yytext, pos yypos, pos yypos + size yytext));
 
+<INITIAL> {hexconst}  => (case StringCvt.scanString (Int64.scan StringCvt.HEX) (String.extract (yytext, 2, NONE)) of
+                              SOME x => Tokens.INT (x, pos yypos, pos yypos + size yytext)
+                            | NONE   => (ErrorMsg.errorAt' (pos yypos, pos yypos)
+                                                           ("Expected hexInt, received: " ^ yytext);
+                                         continue ()));
+
 <INITIAL> {intconst}  => (case Int64.fromString yytext of
                               SOME x => Tokens.INT (x, pos yypos, pos yypos + size yytext)
                             | NONE   => (ErrorMsg.errorAt' (pos yypos, pos yypos)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dynClassB.ur	Fri Mar 27 11:26:06 2015 -0400
@@ -0,0 +1,17 @@
+style style1
+style style2
+
+fun main () : transaction page =
+    toggle <- source False;
+    return <xml>
+      <head>
+	<link rel="stylesheet" type="text/css" href="/style.css"/>
+      </head>
+      <body dynClass={b <- signal toggle;
+                      return (if b then style1 else style2)}
+            dynStyle={b <- signal toggle;
+                      return (if b then STYLE "margin: 100px" else STYLE "")}>
+        Body
+	<button onclick={fn _ => b <- get toggle; set toggle (not b)}>TOGGLE</button>
+      </body>
+    </xml>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dynClassB.urp	Fri Mar 27 11:26:06 2015 -0400
@@ -0,0 +1,5 @@
+rewrite all DynClassB/*
+file /style.css style.css
+allow url /style.css
+
+dynClassB
--- a/tests/files.urp	Fri Mar 27 11:19:15 2015 -0400
+++ b/tests/files.urp	Fri Mar 27 11:26:06 2015 -0400
@@ -1,6 +1,6 @@
 rewrite all Files/*
 file /hello_world.txt hello.txt
 file /img/web.png web.png
-file /files.urp files.urp
+file /files.urp ./files.urp
 
 files
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/nestedInput.ur	Fri Mar 27 11:26:06 2015 -0400
@@ -0,0 +1,10 @@
+fun main () : transaction page =
+    let
+        fun handler _ = return <xml/>
+    in
+        return <xml><body>
+          <form>
+            <submit action={handler}>Uh oh!</submit>
+          </form>
+        </body></xml>
+    end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/style.css	Fri Mar 27 11:26:06 2015 -0400
@@ -0,0 +1,7 @@
+body.style1 {
+    background-color: blue;
+}
+
+body.style2 {
+     background-color: green;
+}